Skip to content

Commit 6665ac1

Browse files
fix(compare): fix compare tariffs using stale iBoost plan from live tariff
iboost_plan (the low-rate slots iBoost may run in) was only ever computed once against the live tariff's rates. Compare scenarios swapped in each tariff's own rates but reused that stale plan, so iBoost timing in a compare-plan reflected the current live tariff instead of the one being compared. Add recompute_iboost(), matching the existing recompute_car_charging pattern, and call it per tariff in run_single(). Save/restore iboost_plan in run_all() alongside iboost_today. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent cd84fc9 commit 6665ac1

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

apps/predbat/compare.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ def run_single(self, tariff, rate_import_base, rate_export_base, end_record, deb
336336
my_predbat.manual_all_times = []
337337
my_predbat.octopus_intelligent_charging = False
338338

339+
self.recompute_iboost()
339340
self.recompute_car_charging(car_charging_slots)
340341

341342
self.log("Running scenario for tariff: {}".format(name))
@@ -448,6 +449,17 @@ def publish_only(self):
448449
self.select_best(compare_list, self.comparisons)
449450
self.publish_data()
450451

452+
def recompute_iboost(self):
453+
"""
454+
Recompute iBoost plan
455+
"""
456+
my_predbat = self.pb
457+
458+
if my_predbat.iboost_enable and (((not my_predbat.iboost_solar) and (not my_predbat.iboost_charging)) or my_predbat.iboost_smart):
459+
my_predbat.iboost_plan = my_predbat.plan_iboost_smart()
460+
else:
461+
my_predbat.iboost_plan = []
462+
451463
def recompute_car_charging(self, car_charging_slots):
452464
"""
453465
Recompute car charging plan
@@ -506,6 +518,7 @@ def run_all(self, debug=False, fetch_sensor=True):
506518
save_cost_today_sofar = my_predbat.cost_today_sofar
507519
save_carbon_today_sofar = my_predbat.carbon_today_sofar
508520
save_iboost_today = my_predbat.iboost_today
521+
save_iboost_plan = my_predbat.iboost_plan
509522
save_import_today_now = my_predbat.import_today_now
510523
save_export_today_now = my_predbat.export_today_now
511524
save_octopus_intelligent_charging = my_predbat.octopus_intelligent_charging
@@ -607,6 +620,7 @@ def run_all(self, debug=False, fetch_sensor=True):
607620
my_predbat.cost_today_sofar = save_cost_today_sofar
608621
my_predbat.carbon_today_sofar = save_carbon_today_sofar
609622
my_predbat.iboost_today = save_iboost_today
623+
my_predbat.iboost_plan = save_iboost_plan
610624
my_predbat.import_today_now = save_import_today_now
611625
my_predbat.export_today_now = save_export_today_now
612626
my_predbat.octopus_intelligent_charging = save_octopus_intelligent_charging

apps/predbat/tests/test_compare.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,99 @@ def _mock_fetch_config_options():
369369
else:
370370
print("PASS T12: run_all() restores config: overrides inside the per-tariff loop, not just once at the end")
371371

372+
# ------------------------------------------------------------------
373+
# T13: run_single() re-plans iBoost against the tariff's own rates
374+
# instead of leaving it stuck with the plan computed for the live
375+
# tariff (the compare-plan iBoost timing bug)
376+
# ------------------------------------------------------------------
377+
cmp, pb = _make_compare()
378+
pb.iboost_enable = True
379+
pb.iboost_solar = False
380+
pb.iboost_charging = False
381+
pb.iboost_smart = True
382+
pb.iboost_today = 3.0
383+
pb.import_today_now = 0
384+
pb.export_today_now = 0
385+
pb.cost_today_sofar = 0
386+
pb.carbon_today_sofar = 0
387+
pb.forecast_plan_hours = 48
388+
pb.manual_charge_times = None
389+
pb.manual_export_times = None
390+
pb.manual_freeze_charge_times = None
391+
pb.manual_freeze_export_times = None
392+
pb.manual_demand_times = None
393+
pb.manual_all_times = None
394+
pb.octopus_intelligent_charging = False
395+
pb.iboost_plan = ["stale_plan_from_live_tariff"]
396+
397+
tariff_plan_calls = []
398+
399+
def _mock_plan_iboost_smart():
400+
tariff_plan_calls.append(1)
401+
return ["fresh_plan_for_this_tariff"]
402+
403+
pb.plan_iboost_smart = _mock_plan_iboost_smart
404+
405+
cmp.fetch_config = lambda tariff: None
406+
cmp.apply_hardware_overrides = lambda tariff, pb: None
407+
cmp.fetch_rates = lambda tariff, base_import, base_export: "existing"
408+
cmp.recompute_car_charging = lambda slots: None
409+
cmp.run_scenario = lambda end_record: {"cost": 0, "metric": 0}
410+
411+
result = cmp.run_single({"name": "test_tariff", "id": "test"}, {}, {}, 48 * 60, fetch_sensor=False)
412+
413+
if not tariff_plan_calls:
414+
print("ERROR T13: plan_iboost_smart() was not called during run_single()")
415+
failed += 1
416+
elif pb.iboost_plan != ["fresh_plan_for_this_tariff"]:
417+
print("ERROR T13: iboost_plan should be replaced with the tariff-specific plan, got {}".format(pb.iboost_plan))
418+
failed += 1
419+
else:
420+
print("PASS T13: run_single() re-plans iBoost using the compared tariff's own rates")
421+
422+
# ------------------------------------------------------------------
423+
# T14: run_single() clears iboost_plan (rather than reusing a stale one)
424+
# when iBoost smart-rate planning isn't applicable for this tariff run
425+
# ------------------------------------------------------------------
426+
cmp, pb = _make_compare()
427+
pb.iboost_enable = True
428+
pb.iboost_solar = True
429+
pb.iboost_charging = True
430+
pb.iboost_smart = False
431+
pb.iboost_today = 0
432+
pb.import_today_now = 0
433+
pb.export_today_now = 0
434+
pb.cost_today_sofar = 0
435+
pb.carbon_today_sofar = 0
436+
pb.forecast_plan_hours = 48
437+
pb.manual_charge_times = None
438+
pb.manual_export_times = None
439+
pb.manual_freeze_charge_times = None
440+
pb.manual_freeze_export_times = None
441+
pb.manual_demand_times = None
442+
pb.manual_all_times = None
443+
pb.octopus_intelligent_charging = False
444+
pb.iboost_plan = ["stale_plan_from_live_tariff"]
445+
pb.plan_iboost_smart = lambda: tariff_plan_calls.append(1) or []
446+
447+
cmp.fetch_config = lambda tariff: None
448+
cmp.apply_hardware_overrides = lambda tariff, pb: None
449+
cmp.fetch_rates = lambda tariff, base_import, base_export: "existing"
450+
cmp.recompute_car_charging = lambda slots: None
451+
cmp.run_scenario = lambda end_record: {"cost": 0, "metric": 0}
452+
453+
tariff_plan_calls.clear()
454+
cmp.run_single({"name": "test_tariff2", "id": "test2"}, {}, {}, 48 * 60, fetch_sensor=False)
455+
456+
if tariff_plan_calls:
457+
print("ERROR T14: plan_iboost_smart() should not be called when iboost_charging/iboost_solar handle boosting and iboost_smart is off")
458+
failed += 1
459+
elif pb.iboost_plan != []:
460+
print("ERROR T14: stale iboost_plan from the live tariff should be cleared, got {}".format(pb.iboost_plan))
461+
failed += 1
462+
else:
463+
print("PASS T14: run_single() clears stale iboost_plan when smart-rate planning isn't applicable")
464+
372465
if failed:
373466
print("**** compare tests FAILED: {} errors ****\n".format(failed))
374467
else:

0 commit comments

Comments
 (0)