2727# 3. Consider Octopus API key to access current tariff info and switch links
2828
2929
30+ # What Compare.run_all() saves before comparing tariffs and restores afterwards: every tariff fetches, plans and
31+ # predicts on the live instance. The plan state is what calculate_plan() writes that outlives the run - leaving
32+ # plan_preclip as the last tariff's plan once made the first re-plan after midnight score the wrong plan as the
33+ # plan in force. Add any new plan state calculate_plan() keeps across runs here.
34+ COMPARE_PLAN_STATE = (
35+ "charge_window_best" ,
36+ "charge_limit_best" ,
37+ "export_window_best" ,
38+ "export_limits_best" ,
39+ "plan_preclip" ,
40+ "end_record" ,
41+ "plan_valid" ,
42+ "plan_last_updated" ,
43+ "plan_last_updated_minutes" ,
44+ "rate_best_cost_threshold_charge" ,
45+ "rate_best_cost_threshold_export" ,
46+ )
47+ # Battery and inverter values a tariff can override, reset after each tariff as well as at the end
48+ COMPARE_HARDWARE_STATE = ("soc_max" , "battery_rate_max_charge" , "battery_rate_max_charge_dc" , "battery_rate_max_discharge" , "battery_rate_max_export" , "inverter_limit" )
49+ COMPARE_RESTORED_STATE = (
50+ COMPARE_PLAN_STATE
51+ + COMPARE_HARDWARE_STATE
52+ + (
53+ "forecast_plan_hours" ,
54+ "forecast_minutes" ,
55+ "forecast_days" ,
56+ "manual_charge_times" ,
57+ "manual_export_times" ,
58+ "manual_freeze_charge_times" ,
59+ "manual_freeze_export_times" ,
60+ "manual_demand_times" ,
61+ "manual_all_times" ,
62+ "cost_today_sofar" ,
63+ "carbon_today_sofar" ,
64+ "iboost_today" ,
65+ "iboost_plan" ,
66+ "import_today_now" ,
67+ "export_today_now" ,
68+ "octopus_intelligent_charging" ,
69+ "soc_kw" ,
70+ )
71+ )
72+ # Car state the comparison rebuilds per tariff, deep-copied as the lists are changed in place
73+ COMPARE_RESTORED_COPIES = (
74+ "car_charging_plan_smart" ,
75+ "car_charging_limit" ,
76+ "car_charging_limit_model" ,
77+ "car_charging_soc" ,
78+ "car_charging_battery_size" ,
79+ "car_charging_slots" ,
80+ "car_charging_now_slots" ,
81+ )
82+
83+
3084class Compare :
3185 """Tariff comparison engine.
3286
@@ -605,43 +659,9 @@ def run_all(self, debug=False, fetch_sensor=True):
605659
606660 my_predbat = self .pb
607661
608- save_forecast_plan_hours = my_predbat .forecast_plan_hours
609- save_forecast_minutes = my_predbat .forecast_minutes
610- save_forecast_days = my_predbat .forecast_days
611- save_manual_charge_times = my_predbat .manual_charge_times
612- save_manual_export_times = my_predbat .manual_export_times
613- save_manual_freeze_charge_times = my_predbat .manual_freeze_charge_times
614- save_manual_freeze_export_times = my_predbat .manual_freeze_export_times
615- save_manual_demand_times = my_predbat .manual_demand_times
616- save_manual_all_times = my_predbat .manual_all_times
617- save_charge_window_best = my_predbat .charge_window_best
618- save_export_window_best = my_predbat .export_window_best
619- save_export_limits_best = my_predbat .export_limits_best
620- save_charge_limit_best = my_predbat .charge_limit_best
621- # The pre-clip copy of the plan in force, which the next re-plan scores as the previous plan; each tariff's
622- # calculate_plan() replaces it with its own plan
623- save_plan_preclip = my_predbat .plan_preclip
624- save_cost_today_sofar = my_predbat .cost_today_sofar
625- save_carbon_today_sofar = my_predbat .carbon_today_sofar
626- save_iboost_today = my_predbat .iboost_today
627- save_iboost_plan = my_predbat .iboost_plan
628- save_import_today_now = my_predbat .import_today_now
629- save_export_today_now = my_predbat .export_today_now
630- save_octopus_intelligent_charging = my_predbat .octopus_intelligent_charging
631- save_car_charging_plan_smart = copy .deepcopy (my_predbat .car_charging_plan_smart )
632- save_car_charging_limit = copy .deepcopy (my_predbat .car_charging_limit )
633- save_car_charging_limit_model = copy .deepcopy (my_predbat .car_charging_limit_model )
634- save_car_charging_soc = copy .deepcopy (my_predbat .car_charging_soc )
635- save_car_charging_battery_size = copy .deepcopy (my_predbat .car_charging_battery_size )
636- save_car_charging_slots = copy .deepcopy (my_predbat .car_charging_slots )
637- save_car_charging_now_slots = copy .deepcopy (my_predbat .car_charging_now_slots )
638- save_soc_kw = my_predbat .soc_kw
639- save_soc_max = my_predbat .soc_max
640- save_battery_rate_max_charge = my_predbat .battery_rate_max_charge
641- save_battery_rate_max_charge_dc = my_predbat .battery_rate_max_charge_dc
642- save_battery_rate_max_discharge = my_predbat .battery_rate_max_discharge
643- save_battery_rate_max_export = my_predbat .battery_rate_max_export
644- save_inverter_limit = my_predbat .inverter_limit
662+ # Everything the comparison changes on the live instance, saved here and put back in the finally below
663+ saved = {name : getattr (my_predbat , name ) for name in COMPARE_RESTORED_STATE }
664+ saved .update ({name : copy .deepcopy (getattr (my_predbat , name )) for name in COMPARE_RESTORED_COPIES })
645665
646666 # Final reports, cut end_record back to 24 hours to ignore the dump at end of day
647667 end_record = int ((my_predbat .minutes_now + 24 * 60 + 29 ) / 30 ) * 30 - my_predbat .minutes_now
@@ -688,19 +708,18 @@ def run_all(self, debug=False, fetch_sensor=True):
688708 # First ever run for this tariff: start from actual midnight SoC
689709 start_soc = soc_midnight_fallback
690710 self .log ("Compare tariff {} starting SoC: {:.2f} kWh" .format (tariff .get ("id" , "" ), start_soc ))
711+ # Each tariff plans from scratch: scored against the previous tariff's plan, the keep-or-switch threshold
712+ # in calculate_plan() could leave one tariff's result as another tariff's plan
713+ my_predbat .plan_valid = False
691714 try :
692- result_data = self .run_single (tariff , rate_import_base , rate_export_base , end_record , debug = debug , fetch_sensor = fetch_sensor , car_charging_slots = save_car_charging_slots , start_soc = start_soc , io_adjusted_base = io_adjusted_base )
715+ result_data = self .run_single (tariff , rate_import_base , rate_export_base , end_record , debug = debug , fetch_sensor = fetch_sensor , car_charging_slots = saved [ "car_charging_slots" ] , start_soc = start_soc , io_adjusted_base = io_adjusted_base )
693716 if result_data is not None :
694717 results [tariff ["id" ]] = result_data
695718 finally :
696719 # Restore hardware settings after each tariff so overrides don't bleed into the next tariff;
697720 # in a finally so a raised run_single() can't leave compare values applied to the live instance
698- my_predbat .soc_max = save_soc_max
699- my_predbat .battery_rate_max_charge = save_battery_rate_max_charge
700- my_predbat .battery_rate_max_charge_dc = save_battery_rate_max_charge_dc
701- my_predbat .battery_rate_max_discharge = save_battery_rate_max_discharge
702- my_predbat .battery_rate_max_export = save_battery_rate_max_export
703- my_predbat .inverter_limit = save_inverter_limit
721+ for name in COMPARE_HARDWARE_STATE :
722+ setattr (my_predbat , name , saved [name ])
704723 # Restore config: overrides after each tariff too, for the same reason - otherwise a
705724 # tariff's config override stays applied to config_index for every subsequent tariff,
706725 # even ones with no config block of their own (issue #4156)
@@ -725,39 +744,6 @@ def run_all(self, debug=False, fetch_sensor=True):
725744 my_predbat .fetch_config_options ()
726745
727746 # Restore original settings
728- my_predbat .forecast_plan_hours = save_forecast_plan_hours
729- my_predbat .forecast_minutes = save_forecast_minutes
730- my_predbat .forecast_days = save_forecast_days
731- my_predbat .manual_charge_times = save_manual_charge_times
732- my_predbat .manual_export_times = save_manual_export_times
733- my_predbat .manual_freeze_charge_times = save_manual_freeze_charge_times
734- my_predbat .manual_freeze_export_times = save_manual_freeze_export_times
735- my_predbat .manual_demand_times = save_manual_demand_times
736- my_predbat .manual_all_times = save_manual_all_times
737- my_predbat .charge_window_best = save_charge_window_best
738- my_predbat .export_window_best = save_export_window_best
739- my_predbat .export_limits_best = save_export_limits_best
740- my_predbat .charge_limit_best = save_charge_limit_best
741- my_predbat .plan_preclip = save_plan_preclip
742- my_predbat .cost_today_sofar = save_cost_today_sofar
743- my_predbat .carbon_today_sofar = save_carbon_today_sofar
744- my_predbat .iboost_today = save_iboost_today
745- my_predbat .iboost_plan = save_iboost_plan
746- my_predbat .import_today_now = save_import_today_now
747- my_predbat .export_today_now = save_export_today_now
748- my_predbat .octopus_intelligent_charging = save_octopus_intelligent_charging
749- my_predbat .car_charging_limit = save_car_charging_limit
750- my_predbat .car_charging_limit_model = save_car_charging_limit_model
751- my_predbat .car_charging_soc = save_car_charging_soc
752- my_predbat .car_charging_battery_size = save_car_charging_battery_size
753- my_predbat .car_charging_slots = save_car_charging_slots
754- my_predbat .car_charging_now_slots = save_car_charging_now_slots
755- my_predbat .car_charging_plan_smart = save_car_charging_plan_smart
756- my_predbat .soc_kw = save_soc_kw
757- my_predbat .soc_max = save_soc_max
758- my_predbat .battery_rate_max_charge = save_battery_rate_max_charge
759- my_predbat .battery_rate_max_charge_dc = save_battery_rate_max_charge_dc
760- my_predbat .battery_rate_max_discharge = save_battery_rate_max_discharge
761- my_predbat .battery_rate_max_export = save_battery_rate_max_export
762- my_predbat .inverter_limit = save_inverter_limit
747+ for name in COMPARE_RESTORED_STATE + COMPARE_RESTORED_COPIES :
748+ setattr (my_predbat , name , saved [name ])
763749 my_predbat .io_adjusted = io_adjusted_base
0 commit comments