Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 78 additions & 85 deletions apps/predbat/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,60 @@
# 3. Consider Octopus API key to access current tariff info and switch links


# What Compare.run_all() saves before comparing tariffs and restores afterwards: every tariff fetches, plans and
# predicts on the live instance. The plan state is what calculate_plan() writes that outlives the run - leaving
# plan_preclip as the last tariff's plan once made the first re-plan after midnight score the wrong plan as the
# plan in force. Add any new plan state calculate_plan() keeps across runs here.
COMPARE_PLAN_STATE = (
"charge_window_best",
"charge_limit_best",
"export_window_best",
"export_limits_best",
"plan_preclip",
"end_record",
"plan_valid",
"plan_last_updated",
"plan_last_updated_minutes",
"rate_best_cost_threshold_charge",
"rate_best_cost_threshold_export",
)
# Battery and inverter values a tariff can override, reset after each tariff as well as at the end
COMPARE_HARDWARE_STATE = ("soc_max", "battery_rate_max_charge", "battery_rate_max_charge_dc", "battery_rate_max_discharge", "battery_rate_max_export", "inverter_limit")
COMPARE_RESTORED_STATE = (
COMPARE_PLAN_STATE
+ COMPARE_HARDWARE_STATE
+ (
"forecast_plan_hours",
"forecast_minutes",
"forecast_days",
"manual_charge_times",
"manual_export_times",
"manual_freeze_charge_times",
"manual_freeze_export_times",
"manual_demand_times",
"manual_all_times",
"cost_today_sofar",
"carbon_today_sofar",
"iboost_today",
"iboost_plan",
"import_today_now",
"export_today_now",
"octopus_intelligent_charging",
"soc_kw",
)
)
# Car state the comparison rebuilds per tariff, deep-copied as the lists are changed in place
COMPARE_RESTORED_COPIES = (
"car_charging_plan_smart",
"car_charging_limit",
"car_charging_limit_model",
"car_charging_soc",
"car_charging_battery_size",
"car_charging_slots",
"car_charging_now_slots",
)


class Compare:
"""Tariff comparison engine.

Expand Down Expand Up @@ -605,40 +659,9 @@ def run_all(self, debug=False, fetch_sensor=True):

my_predbat = self.pb

save_forecast_plan_hours = my_predbat.forecast_plan_hours
save_forecast_minutes = my_predbat.forecast_minutes
save_forecast_days = my_predbat.forecast_days
save_manual_charge_times = my_predbat.manual_charge_times
save_manual_export_times = my_predbat.manual_export_times
save_manual_freeze_charge_times = my_predbat.manual_freeze_charge_times
save_manual_freeze_export_times = my_predbat.manual_freeze_export_times
save_manual_demand_times = my_predbat.manual_demand_times
save_manual_all_times = my_predbat.manual_all_times
save_charge_window_best = my_predbat.charge_window_best
save_export_window_best = my_predbat.export_window_best
save_export_limits_best = my_predbat.export_limits_best
save_charge_limit_best = my_predbat.charge_limit_best
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
save_car_charging_plan_smart = copy.deepcopy(my_predbat.car_charging_plan_smart)
save_car_charging_limit = copy.deepcopy(my_predbat.car_charging_limit)
save_car_charging_limit_model = copy.deepcopy(my_predbat.car_charging_limit_model)
save_car_charging_soc = copy.deepcopy(my_predbat.car_charging_soc)
save_car_charging_battery_size = copy.deepcopy(my_predbat.car_charging_battery_size)
save_car_charging_slots = copy.deepcopy(my_predbat.car_charging_slots)
save_car_charging_now_slots = copy.deepcopy(my_predbat.car_charging_now_slots)
save_soc_kw = my_predbat.soc_kw
save_soc_max = my_predbat.soc_max
save_battery_rate_max_charge = my_predbat.battery_rate_max_charge
save_battery_rate_max_charge_dc = my_predbat.battery_rate_max_charge_dc
save_battery_rate_max_discharge = my_predbat.battery_rate_max_discharge
save_battery_rate_max_export = my_predbat.battery_rate_max_export
save_inverter_limit = my_predbat.inverter_limit
# Everything the comparison changes on the live instance, saved here and put back in the finally below
saved = {name: getattr(my_predbat, name) for name in COMPARE_RESTORED_STATE}
saved.update({name: copy.deepcopy(getattr(my_predbat, name)) for name in COMPARE_RESTORED_COPIES})

# Final reports, cut end_record back to 24 hours to ignore the dump at end of day
end_record = int((my_predbat.minutes_now + 24 * 60 + 29) / 30) * 30 - my_predbat.minutes_now
Expand Down Expand Up @@ -685,19 +708,18 @@ def run_all(self, debug=False, fetch_sensor=True):
# First ever run for this tariff: start from actual midnight SoC
start_soc = soc_midnight_fallback
self.log("Compare tariff {} starting SoC: {:.2f} kWh".format(tariff.get("id", ""), start_soc))
# Each tariff plans from scratch: scored against the previous tariff's plan, the keep-or-switch threshold
# in calculate_plan() could leave one tariff's result as another tariff's plan
my_predbat.plan_valid = False
try:
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)
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)
if result_data is not None:
results[tariff["id"]] = result_data
finally:
# Restore hardware settings after each tariff so overrides don't bleed into the next tariff;
# in a finally so a raised run_single() can't leave compare values applied to the live instance
my_predbat.soc_max = save_soc_max
my_predbat.battery_rate_max_charge = save_battery_rate_max_charge
my_predbat.battery_rate_max_charge_dc = save_battery_rate_max_charge_dc
my_predbat.battery_rate_max_discharge = save_battery_rate_max_discharge
my_predbat.battery_rate_max_export = save_battery_rate_max_export
my_predbat.inverter_limit = save_inverter_limit
for name in COMPARE_HARDWARE_STATE:
setattr(my_predbat, name, saved[name])
# Restore config: overrides after each tariff too, for the same reason - otherwise a
# tariff's config override stays applied to config_index for every subsequent tariff,
# even ones with no config block of their own (issue #4156)
Expand All @@ -713,47 +735,18 @@ def run_all(self, debug=False, fetch_sensor=True):
self.save_yaml()
self.publish_data()
finally:
# Restore config values overridden by any tariff's fetch_config() call
if config_snapshot:
for key, orig_value in config_snapshot.items():
item = my_predbat.config_index.get(key)
if item is not None:
item["value"] = orig_value
my_predbat.fetch_config_options()

# Restore original settings
my_predbat.forecast_plan_hours = save_forecast_plan_hours
my_predbat.forecast_minutes = save_forecast_minutes
my_predbat.forecast_days = save_forecast_days
my_predbat.manual_charge_times = save_manual_charge_times
my_predbat.manual_export_times = save_manual_export_times
my_predbat.manual_freeze_charge_times = save_manual_freeze_charge_times
my_predbat.manual_freeze_export_times = save_manual_freeze_export_times
my_predbat.manual_demand_times = save_manual_demand_times
my_predbat.manual_all_times = save_manual_all_times
my_predbat.charge_window_best = save_charge_window_best
my_predbat.export_window_best = save_export_window_best
my_predbat.export_limits_best = save_export_limits_best
my_predbat.charge_limit_best = save_charge_limit_best
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
my_predbat.car_charging_limit = save_car_charging_limit
my_predbat.car_charging_limit_model = save_car_charging_limit_model
my_predbat.car_charging_soc = save_car_charging_soc
my_predbat.car_charging_battery_size = save_car_charging_battery_size
my_predbat.car_charging_slots = save_car_charging_slots
my_predbat.car_charging_now_slots = save_car_charging_now_slots
my_predbat.car_charging_plan_smart = save_car_charging_plan_smart
my_predbat.soc_kw = save_soc_kw
my_predbat.soc_max = save_soc_max
my_predbat.battery_rate_max_charge = save_battery_rate_max_charge
my_predbat.battery_rate_max_charge_dc = save_battery_rate_max_charge_dc
my_predbat.battery_rate_max_discharge = save_battery_rate_max_discharge
my_predbat.battery_rate_max_export = save_battery_rate_max_export
my_predbat.inverter_limit = save_inverter_limit
my_predbat.io_adjusted = io_adjusted_base
try:
# Restore config values overridden by any tariff's fetch_config() call
if config_snapshot:
for key, orig_value in config_snapshot.items():
item = my_predbat.config_index.get(key)
if item is not None:
item["value"] = orig_value
my_predbat.fetch_config_options()
finally:
# Restore original settings, last so they win over fetch_config_options(), and even if it raised
for name in COMPARE_RESTORED_STATE + COMPARE_RESTORED_COPIES:
setattr(my_predbat, name, saved[name])
# A re-plan requested while the comparison ran (a setting or override changed) must still happen
my_predbat.plan_valid = saved["plan_valid"] and not my_predbat.update_pending
my_predbat.io_adjusted = io_adjusted_base
Loading
Loading