Files
goodwe-addon/goodwe_controller/DECISIONS/SAFETY-86-peak-over-hold.md
T
glenn schrooyenandClaude Opus 5.5 ba31e7c8b1
checks / unit (pull_request) Successful in 24m17s
infra-pin / fresh (pull_request) Successful in 1s
SAFETY-86: peak guard outranks the maintenance hold
During a HOLD the holding battery gives what keeps the quarter at the peak
aim. N=1: the peak floor is refiled at P_MAINTENANCE, as for a drain
(SAFETY-83). N>=2: the hold's pin is raised to the floor minus what the
other live batteries may place, so they give first. A site stop still
decides alone. The hold ends on its clock even if the level dipped.
Below the aim nothing changes (rows identical).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:56:34 +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.
  • 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. The HOLD does not read the level; it ends on its clock (maintenance_hold_min). Topping up would mean going back to CHARGE, which waits for sun or a cheap window (up to 36 h, CHARGE_TIMEOUT_MIN) while the pack keeps the site's one maintenance slot, so the other battery's cycle waits, and then a new full hold that the guard can interrupt again. The guard only gives while the quarter is at risk, and the next monthly cycle balances again. If the owner wants a top-up instead, it is a change to maintenance.py's HOLD branch, not to this rule.

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.