Files
goodwe-addon/goodwe_controller/DECISIONS/SAFETY-119-bound-after-hand-back.md
T
glenn schrooyenandClaude b0c2e5977d
checks / unit (pull_request) Successful in 41m47s
infra-pin / fresh (pull_request) Successful in 1s
SAFETY-119: re-pin test_safety84/87 digests (log only); prove it
The re-pinned digests differ only in OUTPUT_CLAMP log rows; every command
and claim is the same as without the fix (test_safety119.py part 5).
DECISIONS: the delay at a 5 s meter and the open swap case.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-19 15:30:18 +02:00

2.0 KiB

SAFETY-119 - no "not keeping up" when the loop takes the wire back

Status: for review. 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) always counts. No command changes, and no text was added or changed.

Kept. The loop in charge at its rail warns on the same cycle as before. A loop still at its rail after a hand-back warns once the window has passed: 22 s at a 2 s meter, 55 s at 5 s. Delay, not loss. The start and a restart after a stop are not hand-backs.

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 5).

Open. If another owner and the loop swapped the wire every few cycles, each swap restarts the window and the loop's bound would not be counted. A loop at its rail beats a floor or ceiling on its own side, so it keeps the wire; a swap needs a claim that keeps outranking 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.