Compare: scale the export rate with the hardware overrides - #4897
Compare: scale the export rate with the hardware overrides#4897springfall2008 wants to merge 3 commits into
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
springfall2008
left a comment
There was a problem hiding this comment.
Code review — 10 finder angles, all findings re-verified against the branch
Overall the approach is sound: the export-follows-discharge coupling mirrors both the real hardware derivation (inverter.py:431) and the existing annual tool (annual.py:733), the save/restore coverage is complete (snapshot + two restores, soc_kw clamp included), and the docs correctly leave the grid export_limit alone.
The one real code bug is inline at compare.py:229: a numeric-string override (e.g. a quoted "7.5") passes float() but the {:.2f} log on the raw value raises, so the value is applied while the flag stays False — which silently skips the #4895 coupling for string inputs and publishes a false "not numeric" warning. Parse once, format the float; that fixes both branches.
Findings not anchorable to diff lines:
annual.py:685documentsapply_hardware()as "the sole owner ofbattery_rate_max_export". This PR makes Compare a second writer of the same attribute, andannual.py:733implements the identical export-follows-discharge rule. Worth updating that docstring (and/or cross-linking the two sites) in this PR so the ownership contract and the coupling rule don't drift apart.plan.py:200andplan.py:3055size/threshold export windows frombattery_rate_max_discharge, whileprediction.py:906simulates export windows atbattery_rate_max_export. That seam is pre-existing (real hardware can have export ≠ discharge), but with an explicit export-only override the optimiser and the simulation now deliberately diverge — worth a sentence in the PR description.- Freeze-only export windows (
set_export_freeze_onlyorexport_limit_now >= EXPORT_LIMIT_FREEZE,prediction.py:900) never usebattery_rate_max_export, so the fix reaches force-export slots only. One doc sentence would set expectations for freeze-heavy users.
Raised by finders and refuted (don't act on these): a claimed NameError on inspect in T19 is wrong — import inspect exists as a pre-existing function-local import in the T12 block (test_compare.py:369) which executes before T19. Also verified clean: line lengths within the 256/250 limits; the override reaches both the Python and C++ prediction engines (prediction.py:140, prediction_kernel.py:671); fetch_config_options does not clobber the override; the discharge power curve cannot cap the override (it uses the passed rate as the cap); no stale sensor publish occurs (compare's publish_data emits only compare entities, and the main loop re-derives rates each tick); no DC discharge attribute exists, so the charge-override's DC scaling asymmetry is correct.
…rdware in finally, docs and test updates from review
There was a problem hiding this comment.
🟢 Approval recommended
The changes directly address the reported bug, add explicit override semantics, and include targeted regression tests and documentation updates.
Pull request overview
This PR fixes Compare’s hardware override handling so export windows scale correctly when modelling different inverter/battery hardware, aligning Compare’s behaviour with how the prediction engine uses battery_rate_max_export during force-export slots.
Changes:
- Extend
apply_hardware_overrides()to keepbattery_rate_max_exportin sync withoverride_battery_rate_max_discharge_kwby default, and add a dedicatedoverride_battery_rate_max_export_kwthat takes precedence when provided. - Harden override parsing by rejecting non-finite values (nan/inf) and ensuring bad export overrides fall back to the discharge override when available.
- Ensure
run_all()snapshots/restoresbattery_rate_max_exportper-tariff (and restores hardware/config infinallyblocks) to prevent override bleed between tariffs; add/extend unit tests and update docs.
File summaries
| File | Description |
|---|---|
| docs/compare.md | Documents the new export-rate override key and clarifies how export-rate modelling interacts with grid export limits and inverter limits. |
| apps/predbat/tests/test_compare.py | Adds regression tests covering export-rate override semantics and verifies run_all() snapshots/restores battery_rate_max_export. |
| apps/predbat/compare.py | Implements export-rate override behaviour, rejects non-finite override inputs, and snapshots/restores battery_rate_max_export to prevent bleed. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This is an automated draft PR generated from issue #4895 — a maintainer should review it before merging.
Fixes #4895
Summary
Compare's
apply_hardware_overrides()never touchedbattery_rate_max_export, but the prediction engine uses that variable — notbattery_rate_max_discharge— once an export window is active (prediction.py:906). A tariff modelling a 30 kW inverter therefore discharged at 30 kW everywhere except the force-export slots, which stayed pinned at the real hardware's export rate, materially understating export earnings for upgrade scenarios.override_battery_rate_max_discharge_kwnow carries the export rate with it, so the common "model a bigger inverter" case just works.override_battery_rate_max_export_kwkey for export-limited hardware, which takes precedence. A non-numeric value there falls back to the discharge override rather than silently reinstating the real hardware rate.run_all()now snapshots and restoresbattery_rate_max_exportper tariff, alongside the other four hardware attributes, so an override can't bleed into the next tariff.docs/compare.md, including a note that the gridexport_limitis deliberately not overridden — the property's grid connection doesn't change when you swap the inverter, so a grid-capped user won't see more export from raising these keys.inverter_limit_exportitself is left alone: it is only an input to the derivedbattery_rate_max_export, which is now set directly.Testing
cd coverage && ./run_pre_commit— all hooks Passed (file contents sorter, trailing whitespace, end of files, check yaml/json/json5, cspell dictionary sorter, triage daemon unit tests, ruff, black, markdownlint-fix, cspell), and the bundled quick suite reportedAll tests passed (4 slow tests skipped, total time: 105.02s).tools/triage_test.sh compare— exit 0, T1–T19 all pass. Five new cases cover the fix: export follows the discharge override (T15), explicit export override wins (T16), export override alone leaves discharge untouched (T17), a bad export value falls back to the discharge override (T18), andrun_all()genuinely snapshots/restores the attribute (T19). The existing T6/T7/T11 cases were extended to include the new attribute, and the test stub now starts with an export rate below its discharge rate, as real hardware often does — without the fix T15 fails.