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>
100 lines
5.6 KiB
Markdown
100 lines
5.6 KiB
Markdown
# SAFETY-119 - no "not keeping up" when the loop takes the wire back
|
|
|
|
**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
|
|
owner's command, and `compute()` adds `gain * error` from a meter that does
|
|
not show that command yet. On the TEST-23 rig (SAFETY-118 I-2) the guard
|
|
stepped from -2000 W to +429 W with 5 s left. A cycle later the loop's ask
|
|
crossed the +2500 W rail and beat the guard's floor, so SAFETY-85 rule 1
|
|
counted it as the loop's own bound and raised `goodwe_integrator_bound`. The
|
|
battery gave what it was told, and the cap held.
|
|
|
|
**Decision.** After a hand-back, the loop's integrator bound is not counted
|
|
until the plant's settle has passed (`SHORT_CYCLES` cycles and
|
|
`SHORT_SPAN_S`, the window SAFETY-85 grades a battery on). After that it
|
|
counts as before. An over-rating (`clamped`, a bug) on a cycle the loop
|
|
decided always counts. No command changes, and no text was added or changed.
|
|
|
|
**A rolling budget (round 3).** The settle above is per hand-back, and
|
|
losing the wire restarts it. Across all hand-backs the bound is hidden on at
|
|
most a budget of cycles in any 15 minutes (`ROLLING_BUDGET`, `ROLLING_S`); a
|
|
cycle over the budget counts. Round 1 restarted the settle at every
|
|
hand-back, so a guard and a charge taking turns hid the bound for good (0
|
|
warnings in 2 h where 28 were raised before). Round 2 gave each run of
|
|
hand-backs its own budget; at 10 s and 15 s meters each quarter then started
|
|
a fresh one and the loop's bound was never counted. A quarter-edge kick is a
|
|
few bound cycles in one quarter, a real bound that stays is many: a budget
|
|
per 15 minutes tells them apart.
|
|
|
|
**The budget follows the cycle length.** 10 cycles up to a 3.5 s cycle, 8 up
|
|
to 11 s, 6 up to 16 s, 4 beyond. The cycle length is the time between two
|
|
decided cycles (a stop, or a gap over two minutes, is not one); until it is
|
|
known, and without a monotonic clock, the budget is 8. Without a monotonic
|
|
clock the budget never refills.
|
|
|
|
**Why not one number.** Measured over 2 h (`goodwe-addon-checks:1`), the
|
|
quarter-edge case (house 800 W, cap 2500 W, no forecast, 30 seeds) and the
|
|
case below. No single budget held both at 1 to 15 s: 3 to 6 let the false
|
|
alarm back at 2 s; 7 on 10 of 30 seeds at 2 s; 8 on 1 of 30 (seed 25) at
|
|
2 s, and warned in only 1 to 3 of 7 quarters at 15 s; 9 and 10 miss quarters
|
|
at 10 s. A slow cycle needs a small budget to warn, a fast one a large budget
|
|
to stay quiet. Per band, the largest budget that still warns: up to 3.5 s,
|
|
10 (9 and 10 had no false alarm at 1 and 2 s; above 10 was not swept); up to
|
|
11 s, 8 (9 misses quarters at 10 s); up to 16 s, 6 (7 leaves two quiet
|
|
quarters in a row at 15 s, 4 false-alarms at 12 s on 7 of 30 seeds); beyond,
|
|
4 (3 false-alarms at 20 and 30 s on 14 of 30, 5 leaves five quiet quarters at
|
|
20 s).
|
|
|
|
**What holds.** Quarter-edge case, 30 seeds each: 0 notifications at 1, 2,
|
|
3, 4, 5, 10, 12, 15, 20 and 30 s. Repeated hand-backs (2 x 2500 W,
|
|
cheap-window charge, no usable forecast, cap 5000 W, house 5050 and 5100 W,
|
|
constant and with noise, 8 runs per meter):
|
|
|
|
| meter | warnings in 2 h | quarters warned (of 7 after the first) | without the fix |
|
|
|---|---|---|---|
|
|
| 1 s | 56 to 63 | 7 | 56 to 63 |
|
|
| 2 s | 49 to 56 | 7 | 54 to 56 |
|
|
| 5 s | 19 to 23 | 7 | 28 |
|
|
| 10 s | 7 to 10 | 7 | 14 |
|
|
| 15 s | 4 to 10 | 4 to 7, never two quiet in a row | 14 |
|
|
| 30 s | 3 to 7 | 3 to 5, up to two quiet in a row | 7 to 11 |
|
|
|
|
At 30 s no budget does better: 4 leaves two quiet quarters, 3 brings back
|
|
the false alarm. Single hand-back, the loop at its rail, first warning after
|
|
the hand-back compared with no fix: 10 s at 1 s, 20 s at 2 s, 40 s at 5 s
|
|
(default), 80 s at 10 s, 90 s at 15 s, 80 s at 20 s, 120 s at 30 s, 240 s at
|
|
60 s. At 60 s the rig is a different regime (the house is not over the cap
|
|
in the case above), so nothing more is claimed there. The cap held at 1 to
|
|
10 s in the quarter-edge case and at 1 to 5 s in the case above; at slower
|
|
meters the meter's lag puts quarters over it, the same with and without the
|
|
fix (commands are identical).
|
|
|
|
**Limits.** These runs have one cycle per meter reading, at a steady
|
|
interval. A 20 or 30 s cycle needs `cycle_floor_s` raised; at the default
|
|
15 s, a slower meter adds cycles on an old reading. Measured that way (11
|
|
seeds): a 15 s meter that varies by up to 30 % false-alarmed on 5 seeds, a
|
|
30 s meter on 10 (0 to 4 in 2 h, 8 to 15 without the fix); a 20 s meter on
|
|
none. The case above still warned every quarter there.
|
|
|
|
**Kept.** The loop in charge at its rail warns on the same cycle as before.
|
|
The start and a restart after a stop are not hand-backs. The quarter-edge
|
|
kick of `test_safety119.py` part 1 stays hidden (see above).
|
|
|
|
**Re-pinned.** test_safety84 (all six) and test_safety87 (`n1_crossing`,
|
|
`n1_above`, `n1_charge_low`): only OUTPUT_CLAMP log rows differ; commands and
|
|
claims are the same (`test_safety119.py` part 6).
|
|
|
|
**Not hidden.** A battery that gives less than another owner told it still
|
|
alarms through SAFETY-85 rule 2. "Always counts" for an over-rating holds on
|
|
cycles the loop decided; on others SAFETY-85 rule 1 already dropped it.
|
|
|
|
**Not changed.** The kick itself is control behaviour: for a few seconds at
|
|
a quarter's edge the loop asks up to its rail. In `test_safety119.py` part 1
|
|
that is more discharge than the guard asked at a quarter's end, and a few
|
|
seconds of up to 500 W more charge at the next quarter's start.
|