You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Split out from #381 after diagnosis. Frank-Leysen (Growatt MOD 5000TL3-XH via solax_modbus_growatt_min, TOU mode) reported small recurring overnight grid imports (0.1-0.2 kWh, a few tens of cents/night) while SOE had 3+ kWh of headroom above min_soe_kwh. Confirmed against his bess-debug-2026-07-21 / bess-debug-2026-07-22 bundles: every import happens in the same 15-minute period the battery is actively discharging under LOAD_SUPPORT, with the discharge-rate register at exactly the ceiling written for that period.
Root cause
_map_intent_to_rates (core/bess/inverter_controller.py:479-486) uses one shared branch for both LOAD_SUPPORT and BATTERY_EXPORT, capping the discharge-rate register at the DP's planned average power for the period:
BATTERY_EXPORT → grid_first: no deficit backstop. An open ceiling directly oversells stored energy beyond the arbitrage plan for the full period, regardless of load. Capping this is correct and should not change.
LOAD_SUPPORT → load_first: self-limiting to the real house deficit (inverter_simulator.py:132-143, delivered_kwh = min(deficit, rate_kw*dt, ...)). A load spike above the written ceiling leaks straight to grid even with ample SOE and physical discharge capacity (max_discharge_power_kw) sitting unused, because the register — not the deficit — is the binding constraint.
Note: LOAD_SUPPORT is deliberately not hardcoded to 100% — that was the bug fixed in #147 (battery drained early on high-consumption days, blowing past reserve planned for a later, pricier period). So the fix is not "remove the cap," it's making the cap economically aware.
Proposed fix
Add a shadow-price-gated discharge ceiling for LOAD_SUPPORT, mirroring the existing SOLAR_EXPORT shadow-price gate (docs/agents/bess-knowledge.md "Governing Economic Law" section, battery_system_manager.pysolar_export_discharge_rate()): allow the discharge ceiling to open above the plan-scaled value specifically when the DP's own shadow price for that period indicates the marginal stored kWh is not earmarked for a later, more valuable use (i.e., genuine surplus headroom exists). Keep BATTERY_EXPORT's ceiling exactly as-is (plan-scaled, no gate) since it lacks the physical backstop that makes this safe for LOAD_SUPPORT.
Acceptance criteria
LOAD_SUPPORT discharge ceiling is no longer a single shared branch with BATTERY_EXPORT.
When shadow price indicates no future need for reserve, LOAD_SUPPORT can open beyond the plan-scaled ceiling to cover a real-time load deficit up to max_discharge_power_kw.
Regression test replaying Frank's bess-debug-2026-07-21/-22 bundles shows the previously-leaked overnight import periods now served from battery instead of grid, with no change to daytime/evening schedule.
Context
Split out from #381 after diagnosis. Frank-Leysen (Growatt MOD 5000TL3-XH via
solax_modbus_growatt_min, TOU mode) reported small recurring overnight grid imports (0.1-0.2 kWh, a few tens of cents/night) while SOE had 3+ kWh of headroom abovemin_soe_kwh. Confirmed against hisbess-debug-2026-07-21/bess-debug-2026-07-22bundles: every import happens in the same 15-minute period the battery is actively discharging underLOAD_SUPPORT, with the discharge-rate register at exactly the ceiling written for that period.Root cause
_map_intent_to_rates(core/bess/inverter_controller.py:479-486) uses one shared branch for bothLOAD_SUPPORTandBATTERY_EXPORT, capping the discharge-rate register at the DP's planned average power for the period:These two intents are not physically equivalent:
BATTERY_EXPORT→grid_first: no deficit backstop. An open ceiling directly oversells stored energy beyond the arbitrage plan for the full period, regardless of load. Capping this is correct and should not change.LOAD_SUPPORT→load_first: self-limiting to the real house deficit (inverter_simulator.py:132-143,delivered_kwh = min(deficit, rate_kw*dt, ...)). A load spike above the written ceiling leaks straight to grid even with ample SOE and physical discharge capacity (max_discharge_power_kw) sitting unused, because the register — not the deficit — is the binding constraint.Note:
LOAD_SUPPORTis deliberately not hardcoded to 100% — that was the bug fixed in #147 (battery drained early on high-consumption days, blowing past reserve planned for a later, pricier period). So the fix is not "remove the cap," it's making the cap economically aware.Proposed fix
Add a shadow-price-gated discharge ceiling for
LOAD_SUPPORT, mirroring the existingSOLAR_EXPORTshadow-price gate (docs/agents/bess-knowledge.md"Governing Economic Law" section,battery_system_manager.pysolar_export_discharge_rate()): allow the discharge ceiling to open above the plan-scaled value specifically when the DP's own shadow price for that period indicates the marginal stored kWh is not earmarked for a later, more valuable use (i.e., genuine surplus headroom exists). KeepBATTERY_EXPORT's ceiling exactly as-is (plan-scaled, no gate) since it lacks the physical backstop that makes this safe forLOAD_SUPPORT.Acceptance criteria
LOAD_SUPPORTdischarge ceiling is no longer a single shared branch withBATTERY_EXPORT.LOAD_SUPPORTcan open beyond the plan-scaled ceiling to cover a real-time load deficit up tomax_discharge_power_kw.bess-debug-2026-07-21/-22bundles shows the previously-leaked overnight import periods now served from battery instead of grid, with no change to daytime/evening schedule.BATTERY_EXPORTbehavior is unchanged.Related
docs/agents/bess-knowledge.mdSOLAR_EXPORT shadow-price gate (design precedent)