- 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>
64 lines
3.4 KiB
Markdown
64 lines
3.4 KiB
Markdown
# 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.** `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. 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.
|