Skip to content

feat(prediction): model PV recapture beyond the export limit during Freeze Export - #4582

Open
chalfontchubby wants to merge 2 commits into
mainfrom
feat/freeze-export-recapture-modelling
Open

feat(prediction): model PV recapture beyond the export limit during Freeze Export#4582
chalfontchubby wants to merge 2 commits into
mainfrom
feat/freeze-export-recapture-modelling

Conversation

@chalfontchubby

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #4538 (merged) - reworks the fix properly instead of disabling Freeze Export for FoxESS/FoxCloud outright.

Root cause, not FoxESS-specific: Predbat's Freeze Export model forced charge_rate_now = 0 unconditionally and fell through to the general export-limit clip, so any inverter with PV genuinely exceeding load + the hardware export limit had that surplus modelled as clipped/lost - regardless of whether the real hardware actually routes it into the battery instead. inverter_can_charge_during_export already modelled exactly this recapture behaviour, but only for active Force Export windows (export_limit_now < 99.0) - it was never wired into the freeze path (export_limit_now == 99.0).

Confirmed on real hardware two ways:

Fix: Freeze Export now gets its own branch in prediction.py (mirrored in prediction_kernel.cpp) - the battery is never actively discharged (still a genuine freeze), but once PV alone exceeds what load + the export limit can absorb, the genuine spillover charges the battery (capped at the overflow amount and the battery's own charge rate) instead of being clipped, when inverter_can_charge_during_export allows it. SoC still holds flat whenever the export limit alone can cover the surplus.

FoxESS/FoxCloud's support_discharge_freeze reverts to True (same hardware via two connection methods) now that this is correctly modelled instead of disabled - Freeze Export is offered again and correctly predicted for both.

Kernel

  • KERNEL_PARITY_REVISION/PK_PARITY_REVISION bumped 6 → 7.
  • All six platform binaries cross-rebuilt via zig (build_kernel_cross.sh).
  • ./run_all --test kernel_parity passes - the random/clipping sweeps randomise set_export_freeze and include a dedicated export_freeze scenario, so the new branch is genuinely exercised, bit-identical between Python and C++.

Baselines regenerated

Freeze Export now recaptures more energy than before in scenarios where PV exceeds the export limit, so some plans shift to use it more (strictly an improvement in the underlying model's accuracy, never a regression in capability - freeze could previously never do worse than clip everything, now it can do better).

  • coverage/cases/random_results.json regenerated via ./run_random - self-consistent (0 diff on re-compare).
  • coverage/cases/predbat_debug_pre_saving1.yaml.expected.json regenerated (one of the two debug-case regression fixtures picked a different, cheaper plan).

Docs

inverter_can_charge_during_export's apps-yaml.md entry updated to cover Freeze Export, not just Force Export. Also added it to APPS_SCHEMA (config.py) - it was previously read via get_arg with no schema entry at all.

Test plan

  • New test_freeze_export_recapture_beyond_limit in test_optimise_solar.py - PV well above load+export limit charges the battery when inverter_can_charge_during_export=True, stays flat (old behaviour) when False. Directly exercises Prediction.run_prediction(), isolated from the optimiser's window search.
  • test_foxess_support_discharge_freeze_matches_foxcloud (test_inverter.py) updated: both now True.
  • ./run_all --test kernel_parity passes.
  • ./run_all --quick passes (full suite, including regenerated baselines).
  • ./run_pre_commit passes.

🤖 Generated with Claude Code

chalfontchubby and others added 2 commits August 18, 2026 22:58
…reeze Export, enable it for FoxESS/FoxCloud (#4207)

Freeze Export previously forced charge_rate_now to 0 unconditionally and fell
through into the general export-limit clip, so any PV genuinely exceeding
load + the hardware export limit was modelled as clipped/lost - regardless of
whether the real inverter actually routes it into the battery instead
(confirmed on FoxESS's "Feed-in First" mode and independently on Sigenergy
under active Force Export). inverter_can_charge_during_export already modelled
this exact recapture for active Force Export windows; it was just never wired
into the freeze path.

Freeze Export now gets its own branch in prediction.py (and its C++ kernel
mirror): the battery is never actively discharged, but once PV alone exceeds
what load + the export limit can absorb, the genuine spillover charges the
battery (capped at the overflow amount and the battery's own charge rate, not
the full rate) rather than being clipped, when inverter_can_charge_during_export
allows it. SoC still holds flat whenever the export limit alone can cover the
surplus - only genuine overflow moves it.

FoxESS and FoxCloud's support_discharge_freeze reverts to True (both are the
same hardware via different connection methods) now that this is correctly
modelled instead of disabled outright - supersedes the earlier fix that just
turned Freeze Export off for them.

Kernel parity revision bumped (6 -> 7) and all six platform binaries
cross-rebuilt via zig. coverage/cases/random_results.json and
predbat_debug_pre_saving1.yaml.expected.json regenerated - Freeze Export now
recaptures more energy than before in scenarios where PV exceeds the export
limit, so some plans shift to use it more.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chalfontchubby
chalfontchubby marked this pull request as draft August 19, 2026 07:34
@chalfontchubby
chalfontchubby marked this pull request as ready for review August 19, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant