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>
48 lines
2.7 KiB
Markdown
48 lines
2.7 KiB
Markdown
# 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 `hold` ceiling 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 under `P_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_floor` the 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.
|