Skip to content

Commit f0496eb

Browse files
chalfontchubbyclaude
authored andcommitted
fix(plan): don't offer Freeze Export for inverters that always charge from PV surplus (#4207)
Alternative to the stale #4425 (which patched the prediction model instead - reworking it here per that PR's own follow-up discussion): on an inverter with inverter_can_charge_during_export=true (the default), PV surplus charges the battery regardless of what mode Predbat has commanded, so Freeze Export (hold SoC flat, export the rest) can never actually be achieved - it collapses to identical behaviour as Idle. Rather than teach the prediction model to simulate a distinction that doesn't exist in reality, stop the optimiser offering/selecting freeze for these inverters in the first place: - optimise_export(): allow_freeze is now also forced off when the inverter can charge during export, alongside the existing set_export_freeze check. - optimise_solar() ("Export more solar"): sets export limits to 99.0 (freeze) directly, bypassing optimise_export()'s search entirely, so it needs the same gate independently or it reintroduces the bug through the back door. No prediction.py or kernel changes needed - this is purely a search-space restriction, not a modelling change, so no parity bump or binary rebuild. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent a4a0cbd commit f0496eb

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

apps/predbat/plan.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,7 +2158,11 @@ def optimise_export(self, window_n, record_charge_windows, try_charge_limit, cha
21582158
export_step = 5
21592159
export_step_large = 15
21602160

2161-
if not self.set_export_freeze:
2161+
if not self.set_export_freeze or self.inverter_can_charge_during_export:
2162+
# An inverter that charges the battery from surplus PV regardless of what it's been told
2163+
# (inverter_can_charge_during_export) can't actually achieve a frozen SoC - it behaves
2164+
# identically to idle in that case, so offering freeze as a distinct option is pointless
2165+
# and would mislabel the plan (#4207/#4425).
21622166
allow_freeze = False
21632167

21642168
# loop on each export option
@@ -3080,8 +3084,10 @@ def optimise_solar(self, best_metric, best_cost, best_keep, best_cycle, best_car
30803084
"""
30813085
curr = self.currency_symbols[1]
30823086

3083-
# Freeze export slots only have an effect when export freeze is enabled
3084-
if not self.calculate_best_export or not self.set_export_freeze or not self.export_window_best:
3087+
# Freeze export slots only have an effect when export freeze is enabled, and only mean anything
3088+
# distinct from idle when the inverter can actually hold SoC flat - see optimise_export()'s
3089+
# matching gate (#4207/#4425) for why inverter_can_charge_during_export rules it out too.
3090+
if not self.calculate_best_export or not self.set_export_freeze or not self.export_window_best or self.inverter_can_charge_during_export:
30853091
return best_metric, best_cost, best_keep, best_cycle, best_carbon, best_import
30863092

30873093
pv_forecast_minute_step = self.prediction.pv_forecast_minute_step

apps/predbat/tests/test_optimise_solar.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def run_optimise_solar(
3232
expect_export_start=None,
3333
car_charging_slots=None,
3434
car_charging_from_battery=False,
35+
inverter_can_charge_during_export=False,
3536
):
3637
print("Starting optimise solar test {}".format(name))
3738
failed = False
@@ -41,6 +42,7 @@ def run_optimise_solar(
4142
my_predbat.calculate_best_export = calculate_best_export
4243
my_predbat.set_export_freeze = set_export_freeze
4344
my_predbat.set_charge_freeze = True
45+
my_predbat.inverter_can_charge_during_export = inverter_can_charge_during_export
4446
my_predbat.export_more_solar_threshold = threshold
4547
my_predbat.soc_max = battery_size
4648
my_predbat.soc_kw = battery_soc
@@ -161,6 +163,20 @@ def run_optimise_solar_tests(my_predbat):
161163
set_export_freeze=False,
162164
)
163165

166+
# An inverter that charges the battery from surplus PV regardless of command can't actually hold
167+
# SoC flat, so freeze is no different from idle for it (#4207/#4425) - feature is a no-op here too,
168+
# same outcome as freeze_disabled above but via the capability gate instead of the switch
169+
failed |= run_optimise_solar(
170+
"freeze_pointless_when_inverter_always_charges",
171+
my_predbat,
172+
export_window_best=export_window_best,
173+
export_limits_best=[100.0, 100.0, 100.0],
174+
expect_export_limit=[100.0, 100.0, 100.0],
175+
pv_amount=3.0,
176+
threshold=100.0,
177+
inverter_can_charge_during_export=True,
178+
)
179+
164180
# An already active export window (limit 0) is left untouched, only idle ones are converted
165181
failed |= run_optimise_solar(
166182
"skip_active_window",

0 commit comments

Comments
 (0)