Skip to content

Commit e272a1b

Browse files
fix(octopus): keep the current half hour out of the car-need gate
The surplus started at minutes_now, which usually falls mid-period, so the rest of the current half hour lost its low rate once the car was full - even when the car charged earlier in it, which makes Octopus bill the whole period cheap. Surplus now starts at the next half-hour boundary. Clamping back to the start of the current period instead would have marked elapsed minutes as surplus too, and rate_add_io_slots() applies the skip to past minutes, misreporting today's cost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1c3a836 commit e272a1b

2 files changed

Lines changed: 13 additions & 1 deletion

File tree

‎apps/predbat/octopus.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3819,6 +3819,9 @@ def octopus_surplus_minutes(self):
38193819
return set()
38203820
dispatch_minutes = set()
38213821
needed_minutes = set()
3822+
# The current half hour is never surplus: the car may have charged earlier in it, which makes the whole
3823+
# period cheap, and once the car is full its slots no longer show that
3824+
next_period = ((self.minutes_now + 29) // 30) * 30
38223825
for car_n in range(min(self.num_cars, len(self.octopus_slots), len(self.car_charging_slots))):
38233826
car_slots = self.car_charging_slots[car_n]
38243827
if not any(slot.get("octopus", False) for slot in car_slots):
@@ -3828,7 +3831,7 @@ def octopus_surplus_minutes(self):
38283831
if start_minutes == end_minutes:
38293832
continue
38303833
start_minutes, end_minutes = round_out_to_period(start_minutes, end_minutes)
3831-
dispatch_minutes.update(range(max(start_minutes, self.minutes_now), end_minutes))
3834+
dispatch_minutes.update(range(max(start_minutes, next_period), end_minutes))
38323835
for slot in car_slots:
38333836
# A cancelled car's slots are zeroed with their kWh kept in kwh_cancelled (dynamic_load_car_check());
38343837
# cancellation decides their rate itself, so they still count as needed here

‎apps/predbat/tests/test_iog_car_need_gate.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ def run_surplus_minutes_tests(my_predbat):
9494
surplus = my_predbat.octopus_surplus_minutes()
9595
failed |= _expect("elapsed untouched", surplus == set(range(10 * 60, 11 * 60)), "expected 10:00-11:00 only")
9696

97+
# Mid half hour, with the car already full: the rest of the current half hour stays cheap (the car may have
98+
# charged earlier in it), and nothing elapsed is ever touched
99+
my_predbat.minutes_now = 14 * 60 + 10
100+
my_predbat.octopus_slots = [[_slot(my_predbat, 13.5, 15, 20.0)]]
101+
my_predbat.car_charging_slots = [[{"start": 14 * 60 + 10, "end": 15 * 60, "kwh": 0.0, "octopus": True}]]
102+
surplus = my_predbat.octopus_surplus_minutes()
103+
failed |= _expect("current half hour kept", surplus == set(range(14 * 60 + 30, 15 * 60)), "expected 14:30-15:00 only, got {}".format(sorted(surplus)[:1] + sorted(surplus)[-1:]))
104+
my_predbat.minutes_now = 10 * 60
105+
97106
# Rates are shared: a minute another car still needs stays cheap
98107
my_predbat.num_cars = 2
99108
my_predbat.octopus_slots = [[_slot(my_predbat, 13, 15, 20.0)], [_slot(my_predbat, 14, 14.5, 5.0)]]

0 commit comments

Comments
 (0)