diff --git a/apps/predbat/compare.py b/apps/predbat/compare.py index 141fad9fa..48f4de16b 100644 --- a/apps/predbat/compare.py +++ b/apps/predbat/compare.py @@ -336,6 +336,7 @@ def run_single(self, tariff, rate_import_base, rate_export_base, end_record, deb my_predbat.manual_all_times = [] my_predbat.octopus_intelligent_charging = False + self.recompute_iboost() self.recompute_car_charging(car_charging_slots) self.log("Running scenario for tariff: {}".format(name)) @@ -448,6 +449,17 @@ def publish_only(self): self.select_best(compare_list, self.comparisons) self.publish_data() + def recompute_iboost(self): + """ + Recompute iBoost plan + """ + my_predbat = self.pb + + if my_predbat.iboost_enable and (((not my_predbat.iboost_solar) and (not my_predbat.iboost_charging)) or my_predbat.iboost_smart): + my_predbat.iboost_plan = my_predbat.plan_iboost_smart() + else: + my_predbat.iboost_plan = [] + def recompute_car_charging(self, car_charging_slots): """ Recompute car charging plan @@ -506,6 +518,7 @@ def run_all(self, debug=False, fetch_sensor=True): save_cost_today_sofar = my_predbat.cost_today_sofar save_carbon_today_sofar = my_predbat.carbon_today_sofar save_iboost_today = my_predbat.iboost_today + save_iboost_plan = my_predbat.iboost_plan save_import_today_now = my_predbat.import_today_now save_export_today_now = my_predbat.export_today_now save_octopus_intelligent_charging = my_predbat.octopus_intelligent_charging @@ -607,6 +620,7 @@ def run_all(self, debug=False, fetch_sensor=True): my_predbat.cost_today_sofar = save_cost_today_sofar my_predbat.carbon_today_sofar = save_carbon_today_sofar my_predbat.iboost_today = save_iboost_today + my_predbat.iboost_plan = save_iboost_plan my_predbat.import_today_now = save_import_today_now my_predbat.export_today_now = save_export_today_now my_predbat.octopus_intelligent_charging = save_octopus_intelligent_charging diff --git a/apps/predbat/tests/test_compare.py b/apps/predbat/tests/test_compare.py index 80c78b11e..ff94174e2 100644 --- a/apps/predbat/tests/test_compare.py +++ b/apps/predbat/tests/test_compare.py @@ -369,6 +369,99 @@ def _mock_fetch_config_options(): else: print("PASS T12: run_all() restores config: overrides inside the per-tariff loop, not just once at the end") + # ------------------------------------------------------------------ + # T13: run_single() re-plans iBoost against the tariff's own rates + # instead of leaving it stuck with the plan computed for the live + # tariff (the compare-plan iBoost timing bug) + # ------------------------------------------------------------------ + cmp, pb = _make_compare() + pb.iboost_enable = True + pb.iboost_solar = False + pb.iboost_charging = False + pb.iboost_smart = True + pb.iboost_today = 3.0 + pb.import_today_now = 0 + pb.export_today_now = 0 + pb.cost_today_sofar = 0 + pb.carbon_today_sofar = 0 + pb.forecast_plan_hours = 48 + pb.manual_charge_times = None + pb.manual_export_times = None + pb.manual_freeze_charge_times = None + pb.manual_freeze_export_times = None + pb.manual_demand_times = None + pb.manual_all_times = None + pb.octopus_intelligent_charging = False + pb.iboost_plan = ["stale_plan_from_live_tariff"] + + tariff_plan_calls = [] + + def _mock_plan_iboost_smart(): + tariff_plan_calls.append(1) + return ["fresh_plan_for_this_tariff"] + + pb.plan_iboost_smart = _mock_plan_iboost_smart + + cmp.fetch_config = lambda tariff: None + cmp.apply_hardware_overrides = lambda tariff, pb: None + cmp.fetch_rates = lambda tariff, base_import, base_export: "existing" + cmp.recompute_car_charging = lambda slots: None + cmp.run_scenario = lambda end_record: {"cost": 0, "metric": 0} + + result = cmp.run_single({"name": "test_tariff", "id": "test"}, {}, {}, 48 * 60, fetch_sensor=False) + + if not tariff_plan_calls: + print("ERROR T13: plan_iboost_smart() was not called during run_single()") + failed += 1 + elif pb.iboost_plan != ["fresh_plan_for_this_tariff"]: + print("ERROR T13: iboost_plan should be replaced with the tariff-specific plan, got {}".format(pb.iboost_plan)) + failed += 1 + else: + print("PASS T13: run_single() re-plans iBoost using the compared tariff's own rates") + + # ------------------------------------------------------------------ + # T14: run_single() clears iboost_plan (rather than reusing a stale one) + # when iBoost smart-rate planning isn't applicable for this tariff run + # ------------------------------------------------------------------ + cmp, pb = _make_compare() + pb.iboost_enable = True + pb.iboost_solar = True + pb.iboost_charging = True + pb.iboost_smart = False + pb.iboost_today = 0 + pb.import_today_now = 0 + pb.export_today_now = 0 + pb.cost_today_sofar = 0 + pb.carbon_today_sofar = 0 + pb.forecast_plan_hours = 48 + pb.manual_charge_times = None + pb.manual_export_times = None + pb.manual_freeze_charge_times = None + pb.manual_freeze_export_times = None + pb.manual_demand_times = None + pb.manual_all_times = None + pb.octopus_intelligent_charging = False + pb.iboost_plan = ["stale_plan_from_live_tariff"] + pb.plan_iboost_smart = lambda: tariff_plan_calls.append(1) or [] + + cmp.fetch_config = lambda tariff: None + cmp.apply_hardware_overrides = lambda tariff, pb: None + cmp.fetch_rates = lambda tariff, base_import, base_export: "existing" + cmp.recompute_car_charging = lambda slots: None + cmp.run_scenario = lambda end_record: {"cost": 0, "metric": 0} + + tariff_plan_calls.clear() + cmp.run_single({"name": "test_tariff2", "id": "test2"}, {}, {}, 48 * 60, fetch_sensor=False) + + if tariff_plan_calls: + print("ERROR T14: plan_iboost_smart() should not be called when iboost_charging/iboost_solar handle boosting and iboost_smart is off") + failed += 1 + elif pb.iboost_plan != []: + print("ERROR T14: stale iboost_plan from the live tariff should be cleared, got {}".format(pb.iboost_plan)) + failed += 1 + else: + print("PASS T14: run_single() clears stale iboost_plan when smart-rate planning isn't applicable") + if failed: print("**** compare tests FAILED: {} errors ****\n".format(failed)) else: