MAINT-28: the hold is for N=2 and up (SAFETY-87 N=1 pins), N=1 peak digest test
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
8cd10ee6aa
commit
4f9aa5223f
@@ -6,7 +6,7 @@
|
||||
`peakguard.shaping_suspended()` pauses the charge while the forecast is over the aim or the guard is engaged. MAINT-24 moved the aim from 3450 W to the month aim (SAFETY-80), so the pause no longer lasts the whole peak: the guard gives, its projection drops under 5950 W and it idles ("heading for 5 6xx W, under this month's peak"), the pause ends, the floor charges A 150-460 W, the grid goes to 6.3-6.9 kW, the guard trips again. A flipped charge/give 468 times in 45 min (base 02eac3b 11). The quarters stayed at the aim; the cost was a battery working against itself and a grid that swung 5.4-6.9 kW.
|
||||
|
||||
## The change
|
||||
`peakguard.shaping_held(held, decision, forecast, aim, house)`: the old conjunction, and once it was true the pause stays until the house (`grid + battery power`, the load without our own charge) is `MAINT_RESUME_UNDER_W` = 300 W under the aim. A guard that is engaged or a forecast over the aim still pauses at once. House unread: the forecast stands in; neither read: no pause (SAFETY-63). `ctl_cycle.py` keeps the flag in `_maint_peak_hold`.
|
||||
`peakguard.shaping_held(held, decision, forecast, aim, house)`: the old conjunction, and once it was true the pause stays until the house (`grid + battery power`, the load without our own charge) is `MAINT_RESUME_UNDER_W` = 300 W under the aim. A guard that is engaged or a forecast over the aim still pauses at once. House unread: the forecast stands in; neither read: no pause (SAFETY-63). `ctl_cycle.py` keeps the flag in `_maint_peak_hold`. The hold is for N=2 and up: at one inverter the old rule stays, because SAFETY-87's N=1 digests (`n1_charge_low`, a real peak at N=1) are pinned byte for byte.
|
||||
|
||||
Why the house and not the forecast: while A gives, the forecast sits just under the aim by design, so no forecast margin is both safe and reachable. The house does not move when A switches.
|
||||
|
||||
|
||||
@@ -565,10 +565,12 @@ class CycleMixin:
|
||||
float(self.o.get("peak_cap_w", 3500)),
|
||||
self.peak_margin_w, _reading)
|
||||
# MAINT-28: stays paused until the house is clear of the aim.
|
||||
# N=2 and up; one inverter keeps the old rule (SAFETY-87 pins).
|
||||
house = (None if self.grid is None or self.site_batt_w is None
|
||||
else float(self.grid) + float(self.site_batt_w))
|
||||
self._maint_peak_hold = peakguard.shaping_held(
|
||||
self._maint_peak_hold, self.peak_guard, self.peak_fc,
|
||||
self._maint_peak_hold and len(self.inverters) > 1,
|
||||
self.peak_guard, self.peak_fc,
|
||||
maint_aim, house)
|
||||
if self._maint_peak_hold:
|
||||
# Edge-logged: level-logging here would write one line per
|
||||
|
||||
@@ -207,17 +207,21 @@ check("MAINT-24 live shape: A charges 1475 W and the pause never comes",
|
||||
d1 = sc.digest(scen(a=CH, n=1, fc=None, peak=None)[0])
|
||||
dd = sc.digest(scen(a=DRAIN_R, soc=(60.0, 30.0), peak=None, fc=None)[0])
|
||||
dm = sc.digest(scen(a=CH, variant="modes_off", peak=None, fc=None)[0])
|
||||
d1p = sc.digest(scen(a=CH, n=1, fc=None, peak=None, house_w=5000.0)[0])
|
||||
AT_BASE = {
|
||||
"n1_peak": "8d266f39eb880f80fad8d236b2fb5100934cb92efd8ed2082e85eb3b71cd6c45",
|
||||
"n1": "44816d132984ecd64d5a2510223debe102f29decd1d5127ae2b255b45dbf8bd4",
|
||||
"drain": "8fd86d0816626ee5ae86bc973cb0121b1b5108e56c3b0fdd4b4be6c1f403f748",
|
||||
"off": "a007f962793611bce15b11f576a76aa52063adcfde82b2a2dc0b0c075b60e60b",
|
||||
}
|
||||
got = {"n1": d1, "drain": dd, "off": dm}
|
||||
got = {"n1_peak": d1p, "n1": d1, "drain": dd, "off": dm}
|
||||
for k in AT_BASE:
|
||||
print(f" {k} {'same' if got[k] == AT_BASE[k] else 'DIFFERS'} "
|
||||
f"{got[k][:16]}")
|
||||
check("N=1 with A in maintenance charge and no forecast entity: equal to "
|
||||
"release/1.0 02eac3b", got["n1"] == AT_BASE["n1"])
|
||||
check("N=1 in a real peak (house 5000 W, a guard that steps in): equal "
|
||||
"to 02eac3b, the hold is for N=2 and up", got["n1_peak"] == AT_BASE["n1_peak"])
|
||||
check("the SAFETY-78 drain beside a charger: equal to 02eac3b",
|
||||
got["drain"] == AT_BASE["drain"])
|
||||
check("battery modes off: equal to 02eac3b", got["off"] == AT_BASE["off"])
|
||||
|
||||
Reference in New Issue
Block a user