Skip to content

Commit

Permalink
Day of week filter (#1945)
Browse files Browse the repository at this point in the history
* Day of week filter

#1926

* [pre-commit.ci lite] apply automatic fixes

* Updated

* [pre-commit.ci lite] apply automatic fixes

* Update energy-rates.md

* [pre-commit.ci lite] apply automatic fixes

* Update energy-rates.md

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
  • Loading branch information
springfall2008 and pre-commit-ci-lite[bot] authored Feb 1, 2025
1 parent 26b8970 commit b518c3b
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 16 deletions.
45 changes: 33 additions & 12 deletions apps/predbat/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,7 @@ def basic_rates(self, info, rtype, prev=None, rate_replicate={}):
rates = prev.copy()
else:
# Set to zero
for minute in range(24 * 60):
for minute in range(48 * 60):
rates[minute] = 0

manual_items = self.get_manual_api(rtype)
Expand Down Expand Up @@ -1385,6 +1385,23 @@ def basic_rates(self, info, rtype, prev=None, rate_replicate={}):
self.log("Warn: Bad date {} provided in energy rates".format(this_rate["date"]))
self.record_status("Bad date {} provided in energy rates".format(this_rate["date"]), had_errors=True)
continue
day_of_week = []
if "day_of_week" in this_rate:
day = str(this_rate["day_of_week"])
days = day.split(",")
for day in days:
try:
day = int(day)
except (ValueError, TypeError):
self.log("Warn: Bad day_of_week {} provided in energy rates, should be 0-6".format(day_of_week))
self.record_status("Bad day_of_week {} provided in energy rates, should be 0-6".format(day_of_week), had_errors=True)
continue
if day < 1 or day > 7:
self.log("Warn: Bad day_of_week {} provided in energy rates, should be 0-6".format(day))
self.record_status("Bad day_of_week {} provided in energy rates, should be 0-6".format(day), had_errors=True)
continue
# Store as Python day of week
day_of_week.append(day - 1)

# Support increment to existing rates (for override)
if "rate" in this_rate:
Expand Down Expand Up @@ -1433,7 +1450,9 @@ def basic_rates(self, info, rtype, prev=None, rate_replicate={}):
start_minutes += delta_minutes
end_minutes += delta_minutes

self.log("Adding rate {}: {} => {} to {} @ {} date {} increment {}".format(rtype, this_rate, self.time_abs_str(start_minutes), self.time_abs_str(end_minutes), rate, date, rate_increment))
self.log("Adding rate {}: {} => {} to {} @ {} date {} day_of_week {} increment {}".format(rtype, this_rate, self.time_abs_str(start_minutes), self.time_abs_str(end_minutes), rate, date, day_of_week, rate_increment))

day_of_week_midnight = self.midnight.weekday()

# Store rates against range
if end_minutes >= (-24 * 60) and start_minutes < max_minute:
Expand All @@ -1443,16 +1462,18 @@ def basic_rates(self, info, rtype, prev=None, rate_replicate={}):
minute_index = minute_mod
# For incremental adjustments we have to loop over 24-hour periods
while minute_index < max_minute:
if rate_increment:
rates[minute_index] = rates.get(minute_index, 0.0) + rate
rate_replicate[minute_index] = "increment"
else:
rates[minute_index] = rate
rate_replicate[minute_index] = "user"
if load_scaling is not None:
self.load_scaling_dynamic[minute_index] = load_scaling
if date or not prev:
break
current_day_of_week = (day_of_week_midnight + int(minute_index / (24 * 60))) % 7
if not day_of_week or (current_day_of_week in day_of_week):
if rate_increment:
rates[minute_index] = rates.get(minute_index, 0.0) + rate
rate_replicate[minute_index] = "increment"
else:
rates[minute_index] = rate
rate_replicate[minute_index] = "user"
if load_scaling is not None:
self.load_scaling_dynamic[minute_index] = load_scaling
if date:
break
minute_index += 24 * 60
if not date and not prev:
rates[minute_mod + max_minute] = rate
Expand Down
63 changes: 59 additions & 4 deletions apps/predbat/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,12 +390,14 @@ def run_nordpool_test(my_predbat):
rate_import2, rate_export2 = future.futurerate_analysis(rates_agile, rates_agile_export)
for key in rate_import:
if rate_import[key] != rate_import2.get(key, None):
print("ERROR: Rate import data not the same")
print("ERROR: Rate import data not the same got {} vs {}".format(rate_import[key], rate_import2.get(key, None)))
failed = True
break
for key in rate_export:
if rate_export[key] != rate_export2.get(key, None):
print("ERROR: Rate export data not the same")
print("ERROR: Rate export data not the same got {} vs {}".format(rate_export[key], rate_export2.get(key, None)))
failed = True
break

# Compute the minimum value in the hash, ignoring the keys
min_import = min(rate_import.values())
Expand Down Expand Up @@ -1423,6 +1425,57 @@ def run_load_octopus_slot_test(testname, my_predbat, slots, expected_slots, cons
return failed


def assert_rates(rates, start_minute, end_minute, expect_rate):
"""
Assert rates
"""
end_minute = min(end_minute, len(rates))
for minute in range(start_minute, end_minute):
if rates[minute] != expect_rate:
print("ERROR: Rate at minute {} should be {} got {}".format(minute, expect_rate, rates[minute]))
return 1
return 0


def test_basic_rates(my_predbat):
"""
Test for basic rates function
rates = basic_rates(self, info, rtype, prev=None, rate_replicate={}):
"""
failed = 0

old_midnight = my_predbat.midnight
my_predbat.midnight = datetime.strptime("2025-07-05T00:00:00+00:00", "%Y-%m-%dT%H:%M:%S%z")

print("Simple rate1")
simple_rate = [
{"rate": 5},
{
"rate": 10,
"start": "17:00:00",
"end": "19:00:00",
},
]
results = my_predbat.basic_rates(simple_rate, "import")
results, results_replicated = my_predbat.rate_replicate(results, is_import=True, is_gas=False)

failed |= assert_rates(results, 0, 17 * 60, 5)
failed |= assert_rates(results, 17 * 60, 19 * 60, 10)
failed |= assert_rates(results, 19 * 60, 24 * 60 + 17 * 60, 5)
failed |= assert_rates(results, 24 * 60 + 17 * 60, 24 * 60 + 19 * 60, 10)

simple_rate = [{"rate": 5}, {"rate": 10, "start": "17:00:00", "end": "19:00:00", "day_of_week": "7"}]
results = my_predbat.basic_rates(simple_rate, "import")
results, results_replicated = my_predbat.rate_replicate(results, is_import=True, is_gas=False)

failed |= assert_rates(results, 0, 17 * 60 + 24 * 60, 5)
failed |= assert_rates(results, 24 * 60 + 17 * 60, 24 * 60 + 19 * 60, 10)

my_predbat.midnight = old_midnight
return failed


def run_load_octopus_slots_tests(my_predbat):
"""
Test for load octopus slots
Expand Down Expand Up @@ -7769,8 +7822,12 @@ def main():
run_single_debug(args.debug_file, my_predbat, args.debug_file)
sys.exit(0)

if not failed:
failed |= run_nordpool_test(my_predbat)
if not failed:
failed |= run_load_octopus_slots_tests(my_predbat)
if not failed:
failed |= test_basic_rates(my_predbat)
if not failed:
failed |= test_find_charge_rate(my_predbat)
if not failed:
Expand All @@ -7784,8 +7841,6 @@ def main():
failed = 1
if not failed:
failed |= test_alert_feed(my_predbat)
if not failed:
failed |= run_nordpool_test(my_predbat)
if not failed:
failed |= run_inverter_tests()
if not failed:
Expand Down
13 changes: 13 additions & 0 deletions docs/energy-rates.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,19 @@ rates_gas:
**start** and **end** are in the time format of "HH:MM:SS" e.g. "12:30:00" and should be aligned to 30 minute slots normally.
**rate** is in pence e.g. 4.2
**day_of_week** Can also be used to control rates on specific days. You can specify one day or multiple days split by comma.
Note: Day 1 = Monday, 2 = Tuesday .... 7 = Sunday
e.g:
```yaml
rates_import:
- rate: 15
day_of_week: "1,2,3,4,5"
- rate: 10
day_of_week: "6,7"
```
start and end can be omitted and Predbat will assume that you are on a single flat rate tariff.
If there are any gaps in the 24-hour period then a zero rate will be assumed.
Expand Down

0 comments on commit b518c3b

Please sign in to comment.