diff --git a/apps/predbat/output.py b/apps/predbat/output.py index 2f195d63..c6fcb49d 100644 --- a/apps/predbat/output.py +++ b/apps/predbat/output.py @@ -584,11 +584,15 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, for try_minute in range(minute_start, minute_end, PREDICT_STEP): charge_window_n = self.in_charge_window(self.charge_window_best, try_minute) + if charge_window_n >= 0 and self.charge_limit_best[charge_window_n] == 0: + charge_window_n = -1 if charge_window_n >= 0: break for try_minute in range(minute_start, minute_end, PREDICT_STEP): export_window_n = self.in_charge_window(self.export_window_best, try_minute) + if export_window_n >= 0 and self.export_limits_best[export_window_n] == 100: + export_window_n = -1 if export_window_n >= 0: break @@ -599,7 +603,18 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, in_span = False if charge_window_n >= 0 and not in_span: - rowspan = int((self.charge_window_best[charge_window_n]["end"] - minute) / 30) + charge_end_minute = self.charge_window_best[charge_window_n]["end"] + discharge_intersect = -1 + for try_minute in range(minute_start, charge_end_minute, PREDICT_STEP): + discharge_intersect = self.in_charge_window(self.export_window_best, try_minute) + if discharge_intersect >= 0 and self.export_limits_best[discharge_intersect] == 100: + discharge_intersect = -1 + if discharge_intersect >= 0: + break + if discharge_intersect >= 0: + charge_end_minute = min(charge_end_minute, self.export_window_best[discharge_intersect]["start"]) + + rowspan = int((charge_end_minute - minute) / 30) if rowspan > 1 and (export_window_n < 0): in_span = True start_span = True @@ -608,7 +623,18 @@ def publish_html_plan(self, pv_forecast_minute_step, pv_forecast_minute_step10, rowspan = 0 if export_window_n >= 0 and not in_span: - rowspan = int((self.export_window_best[export_window_n]["end"] - minute) / 30) + export_end_minute = self.export_window_best[export_window_n]["end"] + charge_intersect = -1 + for try_minute in range(minute_start, export_end_minute, PREDICT_STEP): + charge_intersect = self.in_charge_window(self.charge_window_best, try_minute) + if charge_intersect >= 0 and self.charge_limit_best[charge_intersect] == 0: + charge_intersect = -1 + if charge_intersect >= 0: + break + if charge_intersect >= 0: + export_end_minute = min(export_end_minute, self.charge_window_best[charge_intersect]["start"]) + + rowspan = int((export_end_minute - minute) / 30) start = self.export_window_best[export_window_n]["start"] if start <= minute and rowspan > 1 and (charge_window_n < 0): in_span = True diff --git a/apps/predbat/plan.py b/apps/predbat/plan.py index 477b06d6..8e4d23f3 100644 --- a/apps/predbat/plan.py +++ b/apps/predbat/plan.py @@ -418,12 +418,16 @@ def scenario_summary_state(self, record_time): charge_window_n = -1 for try_minute in range(this_minute_absolute, minute_absolute + 30, 5): charge_window_n = self.in_charge_window(self.charge_window_best, try_minute) + if charge_window_n >= 0 and self.charge_limit_best[charge_window_n] == 0: + charge_window_n = -1 if charge_window_n >= 0: break export_window_n = -1 for try_minute in range(this_minute_absolute, minute_absolute + 30, 5): export_window_n = self.in_charge_window(self.export_window_best, try_minute) + if export_window_n >= 0 and self.export_limits_best[export_window_n] == 100: + export_window_n = -1 if export_window_n >= 0: break @@ -433,7 +437,7 @@ def scenario_summary_state(self, record_time): soc_percent_min = min(soc_percent, soc_percent_end) if charge_window_n >= 0 and export_window_n >= 0: - value = "Chrg/Dis" + value = "Chrg/Exp" elif charge_window_n >= 0: charge_target = self.charge_limit_best[charge_window_n] if charge_target == self.reserve: @@ -1371,7 +1375,7 @@ def optimise_export(self, window_n, record_charge_windows, try_charge_limit, cha window_size = try_export_window[window_n]["end"] - start window_key = str(int(this_export_limit)) + "_" + str(window_size) - window_results[window_key] = metric + window_results[window_key] = [metric, cost] if all_n: min_improvement_scaled = self.metric_min_improvement_export diff --git a/apps/predbat/prediction.py b/apps/predbat/prediction.py index d1c0d07a..2eaeb607 100644 --- a/apps/predbat/prediction.py +++ b/apps/predbat/prediction.py @@ -583,9 +583,8 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi export_limit = self.export_limit * step discharge_min = self.reserve - use_keep = self.best_soc_keep if four_hour_rule else self.reserve if export_window_n >= 0: - discharge_min = max(self.soc_max * export_limits[export_window_n] / 100.0, self.reserve, use_keep, self.best_soc_min) + discharge_min = max(self.soc_max * export_limits[export_window_n] / 100.0, self.reserve, self.best_soc_min) if not self.set_export_freeze_only and (export_window_n >= 0) and export_limits[export_window_n] < 100.0 and (soc - step * self.battery_rate_max_discharge_scaled) > discharge_min: # Discharge enable diff --git a/apps/predbat/unit_test.py b/apps/predbat/unit_test.py index e8028d2d..4b1e3357 100644 --- a/apps/predbat/unit_test.py +++ b/apps/predbat/unit_test.py @@ -896,6 +896,7 @@ def run_execute_test( def run_single_debug(my_predbat, debug_file): print("**** Running debug test {} ****\n".format(debug_file)) + re_do_rates = True reset_inverter(my_predbat) my_predbat.read_debug_yaml(debug_file) @@ -903,6 +904,23 @@ def run_single_debug(my_predbat, debug_file): my_predbat.save_restore_dir = "./" my_predbat.fetch_config_options() + if re_do_rates: + my_predbat.combine_export_slots = False # XXX: This is a hack to make the test pass + # Find discharging windows + if my_predbat.rate_export: + my_predbat.high_export_rates, lowest, highest = my_predbat.rate_scan_window(my_predbat.rate_export, 5, my_predbat.rate_export_cost_threshold, True) + # Update threshold automatically + if my_predbat.rate_high_threshold == 0 and lowest <= my_predbat.rate_export_max: + my_predbat.rate_export_cost_threshold = lowest + + # Find charging windows + if my_predbat.rate_import: + # Find charging window + my_predbat.low_rates, lowest, highest = my_predbat.rate_scan_window(my_predbat.rate_import, 5, my_predbat.rate_import_cost_threshold, False) + # Update threshold automatically + if my_predbat.rate_low_threshold == 0 and highest >= my_predbat.rate_min: + my_predbat.rate_import_cost_threshold = highest + end_record = my_predbat.end_record print("minutes_now {}".format(my_predbat.minutes_now)) failed = False @@ -915,18 +933,14 @@ def run_single_debug(my_predbat, debug_file): my_predbat.prediction = Prediction(my_predbat, pv_step, pv_step, load_step, load_step) my_predbat.debug_enable = True - charge_limit_best = my_predbat.charge_limit_best - charge_window_best = my_predbat.charge_window_best - export_window_best = my_predbat.export_window_best - export_limits_best = my_predbat.export_limits_best - failed = False + my_predbat.log("********ORIGINAL PLAN********") metric, import_kwh_battery, import_kwh_house, export_kwh, soc_min, soc, soc_min_minute, battery_cycle, metric_keep, final_iboost, final_carbon_g = my_predbat.run_prediction( - charge_limit_best, charge_window_best, export_window_best, export_limits_best, False, end_record=end_record, save="best" + my_predbat.charge_limit_best, my_predbat.charge_window_best, my_predbat.export_window_best, my_predbat.export_limits_best, False, end_record=my_predbat.end_record, save="best" ) # Save plan # Pre-optimise all plan - my_predbat.charge_limit_percent_best = calc_percent_limit(charge_limit_best, my_predbat.soc_max) + my_predbat.charge_limit_percent_best = calc_percent_limit(my_predbat.charge_limit_best, my_predbat.soc_max) my_predbat.update_target_values() my_predbat.publish_html_plan(pv_step, pv10_step, load_step, load10_step, end_record) open("plan_orig.html", "w").write(my_predbat.html_plan) @@ -956,18 +970,15 @@ def run_single_debug(my_predbat, debug_file): # Optimise windows best_metric, best_cost, best_keep, best_cycle, best_carbon, best_import = my_predbat.optimise_all_windows(metric, metric_keep, debug_mode=True) - charge_limit_best = my_predbat.charge_limit_best - export_limits_best = my_predbat.export_limits_best - charge_window_best = my_predbat.charge_window_best - export_window_best = my_predbat.export_window_best # Predict + my_predbat.log("********RAW PLAN********") metric, import_kwh_battery, import_kwh_house, export_kwh, soc_min, soc, soc_min_minute, battery_cycle, metric_keep, final_iboost, final_carbon_g = my_predbat.run_prediction( - charge_limit_best, charge_window_best, export_window_best, export_limits_best, False, end_record=end_record, save="best" + my_predbat.charge_limit_best, my_predbat.charge_window_best, my_predbat.export_window_best, my_predbat.export_limits_best, False, end_record=my_predbat.end_record, save="best" ) # Save plan - my_predbat.charge_limit_percent_best = calc_percent_limit(charge_limit_best, my_predbat.soc_max) + my_predbat.charge_limit_percent_best = calc_percent_limit(my_predbat.charge_limit_best, my_predbat.soc_max) my_predbat.update_target_values() my_predbat.publish_html_plan(pv_step, pv10_step, load_step, load10_step, end_record) open("plan_raw.html", "w").write(my_predbat.html_plan) @@ -993,8 +1004,9 @@ def run_single_debug(my_predbat, debug_file): my_predbat.charge_limit_percent_best = calc_percent_limit(my_predbat.charge_limit_best, my_predbat.soc_max) # Predict + my_predbat.log("********FINAL PLAN*******") metric, import_kwh_battery, import_kwh_house, export_kwh, soc_min, soc, soc_min_minute, battery_cycle, metric_keep, final_iboost, final_carbon_g = my_predbat.run_prediction( - charge_limit_best, charge_window_best, export_window_best, export_limits_best, False, end_record=end_record, save="best" + my_predbat.charge_limit_best, my_predbat.charge_window_best, my_predbat.export_window_best, my_predbat.export_limits_best, False, end_record=my_predbat.end_record, save="best" ) my_predbat.publish_html_plan(pv_step, pv10_step, load_step, load10_step, end_record)