- The alarm must hold for peakguard.ALARM_HOLD_S (10 s) on the projection's
clock. A load that tops cap + battery for ~5 s every 30 s alarmed at every
quarter start while the quarter landed under the aim (sine4500_fast).
- A cycle with no battery reading leaves the hold alone.
- Notified once per quarter, but the ERROR line is written per episode.
- New sim scenarios (sine4500_fast, twice7000, step6500_gap) with 5a4cdc2
digests; control still byte-identical. Unit checks for export and the
hold across a missing reading. test_peakguard's saturated fixture moves
the hold start back instead of waiting it out.
- DECISIONS and DOCS updated: costs of the hold, tolerance reasoning, the
double count in control left to a follow-up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.4 KiB
SAFETY-81 — no false "Load too high for battery" alarms
Status: 2026-09-29, owner go for the card. Review kept the 10 W tolerance as a constant and added the 10 s hold.
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.
saturatedis 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. It must hold for ALARM_HOLD_S (10 s). At most one notification per
quarter, because near the threshold the test flickers with the meter; the
ERROR log line is written for every episode.
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; at a capacity
tariff of the order of EUR 50 per kW a year, 10 W in one month is cents.
Review measured +13 W on a 10 kW two-unit site at margin 0, not saturated, so
no alarm: the tolerance only matters when the battery is at its rating. A
constant, not an option: peak_margin_w is the owner's knob.
Why the hold (review): the test assumes the load now lasts the rest of
the quarter. A load over cap + battery for ~5 s every 30 s (sine4500_fast,
a cycling hob or heater) alarmed at each quarter start while the quarter
landed under the aim. The hold stops that.
Cost: a real alarm comes 10-12 s later than on 5a4cdc2 (step6500 at
1211 s, not 1201 s); a slow ramp 20 s later (ramp6500), as it waits for the
real cap. While the battery refuses or its reading is lost, the alarm waits
until the quarter is lost even at full battery (27-60 s later in review runs);
those faults have their own alarms. A load over cap + battery for more than
10 s still alarms even if the quarter later lands under. ALARM_HOLD_S = 0
removes the hold.
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).
The double count also moves control, not only the alarm: on a late sample the guard asks for the full rating for one cycle (edge4600 at 902 s: 2000 W where 1150 W holds the aim). Left to a follow-up card.