Files
goodwe-addon/goodwe_controller/DECISIONS/SAFETY-86-peak-over-hold.md
glenn schrooyenandClaude Opus 5.5 1c41a1e412
checks / unit (pull_request) Successful in 24m23s
infra-pin / fresh (pull_request) Successful in 1s
SAFETY-86 review: hold the site at the peak floor while a HOLD gives
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>
2026-10-01 02:00:20 +02:00

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