SAFETY-81: no false 'Load too high' alarms
checks / unit (pull_request) Successful in 16m11s
infra-pin / fresh (pull_request) Successful in 1s

The alarm now also needs peakguard.load_too_high(): with the battery at
full power, the quarter still ends more than 10 W over the real limit.
The battery reading is the lower of this cycle's and the last, so a meter
sample from before the battery's last increase is not counted twice.
At most one alarm per quarter. Control outputs unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
goodwe-agent
2026-09-29 19:55:38 +02:00
parent 5a4cdc273e
commit 6eb67c0f9c
6 changed files with 433 additions and 7 deletions
@@ -0,0 +1,47 @@
# SAFETY-81 — no false "Load too high for battery" alarms
**Status: 2026-09-29, owner go for the card. The 10 W tolerance is this PR's choice, for review.**
## Defect
The SAFETY-79 test (SAFETY-79-test-report.md, F1/F2) saw 6 false alarms in 7.
The alarm fired whenever `decide()` clamped its floor to the rating
(`saturated`). Three things set that while the quarter landed on target:
- **Double count.** `decide()` sizes from the held meter reading plus the
battery reading. When the meter sample predates the battery's last increase,
the increase is counted twice. It happens under noise, and at each quarter
start, where the battery is back while the held sample is still the spike.
- **The aim, not the cap.** `saturated` is graded against cap − margin, so at
margin 50 noise needs 50 W less to trip it (F2).
- **Last seconds.** The gain 900 / remaining turns a few W over the aim into
thousands of W wanted, with the battery far from its rating.
## Decision
The alarm needs `saturated` and `peakguard.load_too_high()`: with the battery
at full power from now on, the quarter still ends more than `ALARM_MISS_W`
(10 W) over the real limit (`ceiling_w`). That is the miss itself, without the
gain. The battery reading it uses is the lower of this cycle's and the last
one's. At most one alarm per quarter, because near the threshold the test
flickers with the meter.
**Why 10 W:** at margin 0 the guard itself lands up to +5.7 W over (SAFETY-79
test, check 5), and those misses are real but tiny; the alarm would fire in
the last second of most guarded quarters. The margin (default 50) is the fix
for them. Cost: a real miss of 10 W or less is not alarmed.
**Cost:** a load that climbs slowly through the band between the aim and the
cap alarms when the quarter will end over the cap, not at the aim (10 s later
in `ramp6500`). A step overload alarms in the same cycle as before; a real
overload shows on the meter before the battery moves, so the lower reading
costs it nothing. A quarter already over that the battery cannot win back
still alarms (test_peakguard.py's saturated fixture).
## Not changed: the 1 s spike itself
The cycle after a quarter boundary and before the new quarter's first meter
sample still sees 0 s left, files no claim, and an Off battery drops to 0 W for
one cycle. That is the same edge tick as OPT-15 F1, so it is left to OPT-15.
Control is unchanged here: every command in `tools/safety81_sim.py` is
byte-identical to 5a4cdc2 (test_safety81.py part 1).