SAFETY-79: peak guard keeps a margin below the limit
checks / unit (pull_request) Failing after 11m34s
infra-pin / fresh (pull_request) Successful in 1s

New option peak_margin_w (default 50 W, 0-500 W). The peak guard, the
forecast checks and the maintenance charge sizing aim at
peak_cap_w - peak_margin_w; the month peak, the bill, headroom, warnings
and every notice keep the real cap. Owner decision 2026-09-28.

peak_margin_w 0 is byte-identical to ed739d4 (tools/safety79_identity.py,
test_peak_margin.py part 1). validate.py rule 12 refuses a value outside
0-500, not whole, or not below the peak limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV
This commit is contained in:
glenn schrooyen
2026-09-28 23:09:39 +02:00
co-authored by Claude Opus 5.5
parent ed739d4cdb
commit 7c4c8f5c66
18 changed files with 549 additions and 53 deletions
+2 -2
View File
@@ -1039,7 +1039,7 @@ def s45_loop(*, sized: bool, cap_w=S45_CAP_W, load_w=S45_LOAD_W,
now = t0 + timedelta(seconds=t)
claims = []
pk = _pg.decide(PeakTracker.project(avg, now), batt_w=batt,
cap_w=cap_w, reading=None, achievable_w=MAX_W,
cap_w=cap_w, margin_w=0.0, reading=None, achievable_w=MAX_W,
soc=50.0, soc_floor=20.0, max_hold_s=900.0,
frozen=False)
pc = _pg.claim(pk)
@@ -1380,7 +1380,7 @@ def _s47_headroom(*, meter_w, batt_w, elapsed_s=450.0):
t += 5.0
pk = _pg.decide(
PeakTracker.project(avg, S45_T0 + timedelta(seconds=elapsed_s)),
batt_w=batt_w, cap_w=S45_CAP_W, reading=None, achievable_w=MAX_W,
batt_w=batt_w, cap_w=S45_CAP_W, margin_w=0.0, reading=None, achievable_w=MAX_W,
soc=50.0, soc_floor=20.0, max_hold_s=900.0, frozen=False)
return _pg.charge_headroom_w(pk)