feat: model grid connection import capacity in the DP optimizer - #433
Open
johanzander wants to merge 4 commits into
Open
feat: model grid connection import capacity in the DP optimizer#433johanzander wants to merge 4 commits into
johanzander wants to merge 4 commits into
Conversation
The DP had no ceiling on grid import: _ac_flows computed grid_imported = home_consumption - home_served with no cap, so the optimizer could plan to leave the battery idle and import an unbounded load through a fuse-limited connection. HomePowerMonitor only throttles battery charging at runtime, never the household's own load, so a plan computed without this constraint could silently diverge from what the fuse can actually deliver. Derives a per-period grid-import energy cap from the existing HomeSettings (voltage x max_fuse_current x safety_margin -- not multiplied by phase_count, matching HomePowerMonitor's own worst-phase gating for unbalanced 3-phase loads), gated on power_monitoring_enabled. Enforced as a hard constraint on total import (load + battery grid-charging), constraining rather than excluding a period whose load alone exceeds the cap -- the battery is forced to discharge and cover the excess, falling back to the minimum-import action available if even full discharge can't satisfy it. Touches core DP internals (_state_transition, _state_transition_grid, _compute_reward, _compute_reward_grid, _build_period_data, _run_dynamic_programming, _best_action_at_continuous_state) since the cap couples the charge and discharge action spaces in a way the existing AC-output cap and temperature derating (independent per- disposition ceilings) don't need to. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
johanzander
marked this pull request as ready for review
July 30, 2026 19:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
_ac_flowscomputedgrid_imported = home_consumption - home_servedwith no cap, so the optimizer could plan to leave the battery idle and import an unbounded load through a fuse-limited connection — physically undeliverable, sinceHomePowerMonitoronly throttles battery charging at runtime, never the household's own load.HomeSettings(voltage × max_fuse_current × safety_margin— deliberately not multiplied byphase_count, matchingHomePowerMonitor's own worst-phase gating for unbalanced 3-phase loads), gated onpower_monitoring_enabled.Root cause
Independently diagnosed and confirmed via
bess-analystagainst the current source (dp_battery_algorithm.py,power_monitor.py,settings.py) before implementation — see issue #429 for the full write-up and the 4 open design questions it raised.Design decisions (per issue's open questions)
HomeSettings.__post_init__) ifpower_monitoring_enabledis set with implausible fuse/voltage/safety_margin values, never a silent no-op inside the DP.Fix
Touches core DP internals since the cap couples the charge and discharge action spaces in a way the existing per-disposition ceilings (AC cap, temperature derating) don't need to:
_state_transition,_state_transition_grid,_compute_reward(now returns(reward, new_cost_basis, grid_imported)),_compute_reward_grid(now returns(reward, grid_imported)),_build_period_data,_run_dynamic_programming(new feasibility mask: constrain tomax(cap, minimum achievable import this period)), and_best_action_at_continuous_state(candidates gathered then filtered against the same floor logic for the continuous forward-reconstruction path).A code-review pass caught and fixed one real bug before this PR opened: the cap formula initially multiplied by
phase_count, butHomePowerMonitorgates on the single worst-loaded phase, not phase-summed power (a deliberate fix for unbalanced 3-phase loads, commit37201cb9). Fixed, and the test scenario now usesphase_count=3specifically to pin this.docs/agents/bess-knowledge.mdanddocs/SOFTWARE_DESIGN.mdupdated with a new section on the cap's derivation and behavior.Test plan
./scripts/quality-check.shpasses locally (fast suite: 1378 passed, 15 skipped; frontend: 103 passed; lint/format clean).venv/bin/pytest -m slowpasses (394 passed, 3 skipped)core/bess/tests/unit/test_grid_import_cap.py) usingrun_scenario_realized— asserts R==P and that grid import stays within the fuse-derived cap during a load spike that would otherwise be economically preferable to import unconstrained (confirmed the pin discriminates: reverting the fix, or reintroducing thephase_countbug, makes the test fail)PATCH /api/settingsto enablepower_monitoring_enabledwith a tight fuse limit, then ran the realoptimize_battery_scheduleinside the running container using the settings as persisted through the actual API — observed the DP discharge to satisfy the cap and correctly fall back to the physically-unavoidable minimum import once discharge capacity was exhaustedCloses #429