SAFETY-125 round 2: the one-inverter maintenance charge is sized to the room too
Owner 2026-10-29: fix N=1 in this hotfix. _charge_headroom() no longer returns early at one inverter, so the N=1 cheap-window maintenance charge is held to charge_room_w() as at N=2; SAFETY-84's site-stop rule stays N>=2 only (at N=1 such a stop outranks the shaping anyway). The guard no longer steps in and gives power at each quarter's end during that charge. test_safety125.py: N=1 maintenance on 5 s and 10 s meters with late readings, with and without a banked peak; the row in an N=2 maintenance charge and in shadow mode; edge cases (blind past the hold, a start mid-quarter, the summer-time change, the forecast in W, a 10 s meter, the last second). test_safety84/87: three N=1 pins move. test_safety119: its quarter-edge hand-back runs keep the old N=1 sizing as a fixture, plus a check that with the new sizing nothing hands back. CHANGELOG, DECISIONS, DOCS follow. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
6eebd4882b
commit
2929ece7da
@@ -3232,14 +3232,14 @@ class Controller:
|
||||
return floor
|
||||
|
||||
def _charge_headroom(self, floor: float, claims) -> float:
|
||||
"""SAFETY-84: at N>=2 a cheap-window charge pulls no more than the
|
||||
quarter's headroom under the peak aim (`charge_headroom_w()`), so it
|
||||
yields just enough to the peak guard, and nothing under a site stop
|
||||
(its 0 W would need the other pack to give). N=1 is unchanged."""
|
||||
if len(self.inverters) < 2:
|
||||
return floor
|
||||
if any(c.kind == SET and c.actuator == ACTUATOR_SITE
|
||||
and c.priority > P_MAINT_SHAPE for c in claims):
|
||||
"""SAFETY-84: a cheap-window charge pulls no more than the quarter's
|
||||
room under the peak aim (`charge_room_w()`), so it yields just enough
|
||||
to the peak guard; at N>=2 nothing under a site stop (its 0 W would
|
||||
need the other pack to give). SAFETY-125: at N=1 too, owner
|
||||
2026-10-29; the stop rule stays N>=2 only."""
|
||||
if len(self.inverters) > 1 and any(
|
||||
c.kind == SET and c.actuator == ACTUATOR_SITE
|
||||
and c.priority > P_MAINT_SHAPE for c in claims):
|
||||
return 0.0
|
||||
room = peakguard.charge_room_w(self.peak_guard, self._peak_proj)
|
||||
floor = floor if room is None else min(floor, room)
|
||||
|
||||
Reference in New Issue
Block a user