At N>=2 the holding pack's pin follows the guard's floor while the loop still asks for +5000 W, so the ALLOCATOR "could not be placed" row was re-logged on every 1 W change and filled the 40-row action log. While the pin is raised, cycle() now files a site `hold` ceiling at the floor (P_MAINTENANCE), as SAFETY-84 does for a charge. Filed only when the holding pack gives; hi == the floor, so it never clashes with it. Tests: a noisy N=2 house logs no ALLOCATOR row and at most one clamp row (20 and 20 without the ceiling); the ceiling is the floor itself; a CHARGE is not refiled at N=1. The DECISIONS entry records the owner's 2026-10-01 decision: the HOLD ends on its clock, no top-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.7 KiB
SAFETY-86 - the peak guard outranks the maintenance HOLD
Status: DECIDED 2026-09-30 (owner). Follows SAFETY-83's "Not changed"
list. Tests: test_safety86.py.
The defect. The HOLD (battery resting full after the maintenance charge)
is a SET of 0 W at P_MAINTENANCE (60), above the peak floor (P_PEAK, 50),
so the floor did not bind. At N=1 a 6500 W house banked 6500 W quarters and
'Load too high' fired (SAFETY-83 rig test, check 4). At N=2 only the other
battery gave: 4500 W against a 3950 W aim (SAFETY-84 rig test, check 6).
The fix (main.py, _peak_over_hold()).
- N=1: during a HOLD a peak floor above 0 W is refiled at
P_MAINTENANCE, as for a drain (SAFETY-83). The battery gives the floor; the Why reads "raised for the peak limit". - N>=2: the holding battery's pin is raised to the floor minus what the other
live batteries may place (their hard
hi, or their pin). The others give first, the holding one only the shortfall. Why this split and not the allocator's shares: the hold is maintenance and outranks the others' use (owner order), and the owner rule for an Off battery is the same, "give way only for the shortfall". A battery Off by its mode (P_MODE, below maintenance) therefore still gives before the holding one. - N>=2, while the holding battery gives: a site
holdceiling at the floor (P_MAINTENANCE), so the loop does not ask for watts the hold keeps back. Without it the moving pin re-logged the ALLOCATOR "could not be placed" row each cycle and filled the action log (review, PR #161). - Below the aim nothing changes: the floor is 0 W, the hold is 0 W, the rows are identical to before (test_safety86.py part 2).
- A site stop (a SET above
P_PEAK) decides alone: nothing is raised at N>=2, and the refiled floor at N=1 stays underP_SAFETY. If another battery is pinned to charge (a SAFETY-73 probe) the pin is not raised, so the site is never split both ways. - The hold resumes by itself: the rule is evaluated each cycle, so when the guard lets go the command is 0 W again.
The level dips below the target: the hold finishes anyway. Owner
decision 2026-10-01: the HOLD ends on its clock (maintenance_hold_min) even
when the peak guard pulls the level down; no top-up. A top-up would re-enter
CHARGE (up to 36 h, CHARGE_TIMEOUT_MIN) and keep the site's one maintenance
slot meanwhile.
Not changed.
- The peak guard still grades the emptiest battery (SAFETY-32): at N=2 with
B under
peak_soc_floorthe floor is limited and A holds even when it is full (SAFETY-87). - "Battery is not keeping up" during maintenance (SAFETY-85), the N=2 charge flapping (SAFETY-88) and the N=1 charge headroom sizing are untouched.