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>
47 lines
2.7 KiB
Markdown
47 lines
2.7 KiB
Markdown
# SAFETY-84 - a maintenance charge at two inverters
|
|
|
|
**Status: DECIDED 2026-09-30.** Follows SAFETY-78's "Not changed" list.
|
|
Tests: `test_safety84.py`; identity: `tools/safety84_identity.py`.
|
|
|
|
**The defect.** In a cheap window the charging pack files its own
|
|
`hi = -charge` (P_MAINT_SHAPE). At N>=2 the loop's site target sat above it
|
|
(a house to cover, or its 400 W slew step from 0 W), so the other pack had to
|
|
give while this one charged. The allocator refuses opposite directions
|
|
(SAFETY-31), so the site sat at 0 W whatever the house
|
|
(`tools/safety84_identity.py` on 9d5b7d1: 436 of 480 cycles refused). N=1
|
|
was fine.
|
|
|
|
**The fix.**
|
|
- While a pack's own limits make it charge at N>=2, `main.py` files a site
|
|
limit `charge`, `hi` = what that pack's own `hi` places
|
|
(`allocator.hi_w()`, whole W as the allocator rounds). The site target is
|
|
never above it, so the other pack only adds power in the same direction.
|
|
Opposite splits stay forbidden. Mirror of SAFETY-78's `drain` floor.
|
|
- Peak guard first (owner order, SAFETY-83). The charge is sized to the
|
|
quarter's headroom under the aim (`charge_headroom_w()`, cap or banked peak
|
|
minus `peak_margin_w`), so it charges less instead of stopping: the meter
|
|
sits on the aim and the guard does not need to step in. Where the house
|
|
alone is over the aim the guard engages, the shaping is suspended as before
|
|
(`shaping_suspended()`) and no `charge` is filed, so the guard's `lo` and
|
|
this `hi` never meet in one cycle (no `lo > hi`, no park at 0 W).
|
|
Without the headroom sizing the aim still held (the suspension did it), but
|
|
a battery gave power back on 285 of 540 cycles at a 3000 W house
|
|
(test_safety84.py part 2).
|
|
- A site stop (a safety SET above P_MAINT_SHAPE) sizes the charge to 0 W:
|
|
the unit `hi` is a hard limit, so a charge under a 0 W stop needs the other
|
|
pack to give. It was refused, with an ARBITER line, on most cycles of the
|
|
stop (test_safety84.py part 4). The stop still parked the site at 0 W.
|
|
- A pack that cannot take the charge (a refusal, a full pack, a stale link)
|
|
files no ceiling; the other pack follows the loop.
|
|
- Not filed at N=1: byte-identical (`tools/safety84_identity.py`, which hashes
|
|
every claim, in and out of a cheap window and under a banked peak).
|
|
|
|
**Not changed.**
|
|
- At N=1 the charge is not sized to the headroom: the guard still suspends it
|
|
and the battery gives until the quarter is safe. The same sizing would fit
|
|
there; that is an owner question, not this fix.
|
|
- Outside a cheap window (`hi = 0`, solar only) nothing is filed, at any N.
|
|
- A HOLD (later fixed by [[SAFETY-86]]) and the "Battery is not keeping up"
|
|
alarm (SAFETY-85) are untouched. In the identity runs that alarm's log line
|
|
comes as often at N=2 as at N=1 (2 in 480 cycles).
|