SAFETY-121: a recurring overload warns every quarter
checks / unit (pull_request) Successful in 44m51s
infra-pin / fresh (pull_request) Successful in 1s

TEST-23 F-1: on a 5 s meter a steady overload warned once in 70 min. The
loop was at its bound 4-5 cycles a quarter, all inside SAFETY-119's settle
of 12 cycles (60 s) and under its budget of 8 per 900 s. The simulation
fed the meter a cycle behind the battery reading, so the guard and the
charge chased each other and gave ~20 bound cycles a quarter.

The settle is now 4 cycles and 22 s (HANDBACK_CYCLES, SHORT_SPAN_S), and
the rolling budget one number, 11 per 900 s, at every cycle length. The
cycle length is no longer tracked. Commands unchanged; the re-pinned
digests of test_safety84/87 differ from bee7431 in OUTPUT_CLAMP rows only.
test_safety119 runs the rig's shape (meter in the same cycle) at 1-15 s
and checks a stop keeps the budget (review F2, X3).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-10-21 10:09:14 +02:00
co-authored by Claude
parent bee7431793
commit 8bf25696cb
6 changed files with 291 additions and 135 deletions
@@ -1,6 +1,9 @@
# SAFETY-119 - no "not keeping up" when the loop takes the wire back
**Status: for review.** Tests: `test_safety119.py`.
**Status: merged (bee7431); the settle and the budget changed in SAFETY-121**
(DECISIONS/SAFETY-121-bound-every-quarter.md: 4 cycles and 22 s, one budget
of 11). The rolling-budget sections below describe bee7431. Tests:
`test_safety119.py`.
**Cause.** When another owner (the peak guard, a maintenance charge) has set
the wire and the loop wins again, `cycle()` re-seeds the integrator from that
@@ -0,0 +1,91 @@
# SAFETY-121 - a recurring overload warns every quarter
**Status: for review.** Tests: `test_safety119.py` (parts 2, 4, 5, 6).
Follows DECISIONS/SAFETY-119-bound-after-hand-back.md.
**The defect (TEST-23, SAFETY-119 rig test F-1).** 5 s meter, 2 x 2500 W,
cap 5000 W, maintenance charge, no forecast, house 5100 W. bee7431 warned
once in 70 min (04:31:01), then not at 04:45, 05:00 or 05:15. Base 75f0804
warned at every quarter start. The loop held the wire once a quarter, at its
start, and was at its bound on 4 or 5 cycles. SAFETY-119's settle (12
cycles, 60 s at 5 s) covered all of them, and 5 cycles a quarter fit in its
budget of 8 per 900 s.
**Why the simulation said "every quarter".** `test_safety119.py` fed the
meter one cycle behind the battery reading (`delay` counts cycles: 5 s at a
5 s meter). Just after the loop leaves its rail, the guard's charge
headroom (house = meter + battery) is then off by up to 5000 W, the charge
pulls 2000 W, the guard steps in again, and the two chase each other: 8 to
10 hand-backs and about 20 bound cycles a quarter at 5 s (rig: 1 and 4-5).
With the meter read in the same cycle (`delay` 0, the rig's shape) the sim
gives the rig's numbers: 3 to 5 bound cycles a quarter at 5 s, all inside
SAFETY-119's settle, and bee7431 warns in 0 to 2 of 7 quarters. The test
now runs both shapes.
**Decision.** After a hand-back the loop's bound is hidden until 4 cycles
(`HANDBACK_CYCLES`) and 22 s (`SHORT_SPAN_S`) have passed; across all
hand-backs on at most 11 cycles (`ROLLING_BOUND_CYCLES`) in any 900 s. The
budget no longer follows the cycle length, and the cycle length is not
tracked. A stop ends the settle and keeps the budget. An over-rating always
counts. Commands are unchanged.
**Why it holds for any number of bound cycles a quarter.** The settle is
fixed: at most 3 cycles or 22 s after a hand-back, whichever is longer. A
bound that lasts past it counts on its first cycle after it. So one
hand-back a quarter, with the loop at its rail past the settle, warns in
that quarter, whether it is 1 bound cycle or 100. A bound that never lasts
past a settle counts once more than 11 are hidden within 900 s. Hidden
are only shapes with at most 11 bound cycles in 900 s, each within 3 cycles
or 22 s of a hand-back. That is the quarter-edge kick: in every steady-meter
run below it is on the 1st to 3rd cycle after its hand-back (up to 30 s at
a 30 s meter). In the rig's shape at 5 s the bound comes 30 to 55 s after
the hand-back.
**Sweep** (`goodwe-addon-checks:1`, 2 h runs, scratch/SAFETY-121/dev/).
Quarter edge = test_safety119 part 1 (house 800 W, cap 2500 W, no
forecast). Overload = part 4 (2 x 2500 W, cap 5000 W, house 5050 or
5100 W, seeds 0-3). "Delay 1" is the old sim, "delay 0" the rig's shape.
| meter | quarter edge, runs with a warning (new / bee7431 / no fix) | overload delay 1, quarters warned of 7 (new / bee7431 / no fix) | overload delay 0, 5100 W (new / bee7431 / no fix) |
|---|---|---|---|
| 1 s | 0/70 / 0/70 / 68/70 | 7 / 7 / 7 | 7 / 7 / 7 |
| 2 s | 0/70 / 0/70 / 39/70 | 7 / 7 / 7 | 7 / 7 / 7 |
| 3 s | 0/70 / 0/70 / 40/70 | 7 / 7 / 7 | 7 / 7 / 7 (5050 W: 7 / 1-7 / 7) |
| 5 s | 0/70 / 0/70 / 40/70 | 7 / 7 / 7 | 7 / 0-2 / 7 |
| 10 s | 0/70 / 0/70 / 40/70 | 7 / 7 / 7 | 7 / 0 / 7 |
| 15 s | 0/70 / 0/70 / 40/70 | 7 / 4-7 / 7 | no fix warns 0-1 (5300 W: 7 / 0 / 7) |
| 20 s | 0/60 / 0/60 / 30/60 | 7 / 5-7 / 7 | no fix warns 0 (5300 W: 7 / 0 / 7) |
| 30 s | 0/60 / 0/60 / 30/60 | 7 / 3-5 / 7 | no fix warns 0 (5300 W: 6-7 / 0 / 6-7) |
Quarter-edge seeds: 1-30 with the meter in the same cycle, and 1-40 (1-30
at 20 and 30 s) with the test's 1-2 cycle delay. 5300 W: seeds 1-5.
Commands identical to no fix and to bee7431 in all 1013 runs (these and
the limits below). Choosing the numbers: the runs above were recorded
without the fix and replayed through candidate rules; with 3, 4 or 5
cycles, budgets 9 to 13 met both cases at 1-30 s. 8 warns at the quarter
edge (2 s, seed 25) and 14 misses quarters in the rig's shape (1 s,
5050 W), both checked in part 4. 11 is the middle, and 4 the middle of 3
to 5; with a jittery meter (below) 3 gave more false alarms, 5 more quiet
quarters (replay).
**First warning after one hand-back** (the loop at its rail; later than
no fix by): 11 s at 1 s, 22 s at 2 s, 24 s at 3 s, 25 s at 5 s (default),
30 s at 10 s, 45 s at 15 s, 60 s at 20 s, 90 s at 30 s, 180 s at 60 s.
bee7431: 10, 20, 30, 40, 80, 90, 80, 120, 240 s. In the overload runs at
1 to 30 s, the first warning was at most 90 s later than without the fix.
**Limits (measured, not fixed).**
- A meter that jitters by 30 % with `cycle_floor_s` 15 (11 seeds): with
the test's 1-2 cycle meter delay the quarter edge warns on 2 of 11 seeds
at 15 s, 2 at 20 s and 5 at 30 s (bee7431: 4, 0, 10; no fix: 11 each);
with the meter in the same cycle on none (no fix: 10, 9, 11). The overload at 5100 W leaves up
to 4 quiet quarters in a row at 15 and 20 s (bee7431: no warning at all
on some seeds); at 5200 W at most 1.
- With cycles over about 40 s, a bound that is never past a settle cannot
use up 11 in 900 s, so it is never counted (part 5 prints it at 60 s).
- Hidden as designed: a real bound with the kick's shape (at most 11 cycles
in 900 s, each within 3 cycles or 22 s of a hand-back).
**Re-pinned.** test_safety84 (all six) and test_safety87 (`n1_crossing`,
`n1_above`, `n1_charge_low`): only OUTPUT_CLAMP log rows differ from
bee7431 and from no fix; commands, claims, guard and phases are the same.