At N=1 the drain is the site SET at P_MAINTENANCE (60), so the peak floor
(P_PEAK, 50) was not applied and the house imported what the drain left.
Where the floor asks for more than the drain rate, main.py now refiles the
peak claim at P_MAINTENANCE, so it wins; safety SETs still park everything.
Nothing else changes: safety78_identity --diff against 35cdffe changes only
cycles where the guard wanted more than the drain; N=2 is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
36 lines
2.0 KiB
Markdown
36 lines
2.0 KiB
Markdown
# SAFETY-83 - the peak guard outranks the maintenance drain at N=1
|
|
|
|
**Status: DECIDED 2026-09-29 (owner go, P1).** Follows SAFETY-78's "Not
|
|
changed" list. Tests: `test_safety83.py`.
|
|
|
|
**The defect.** At N=1 the drain is the site SET at `P_MAINTENANCE` (60). The
|
|
peak floor is a limit at `P_PEAK` (50), and clause 2 applies only limits at or
|
|
above the winner, so the floor was ignored and the house imported what the
|
|
drain left. A 5000 W house under a 2500 W limit and a 2000 W drain banked
|
|
3000 W quarters; on the owner's settings (limit 3500, drain 2500, margin 50) a
|
|
breach starts at about 5950 W.
|
|
|
|
**The fix.** While a drain owns the setpoint at N=1 and the peak floor asks
|
|
for more than the drain rate, `main.py` (`_peak_over_drain()`) refiles that
|
|
peak claim at `P_MAINTENANCE`. The arbiter then applies it: the target is the
|
|
floor, still inside the safety limits, and a safety SET (100) still parks
|
|
everything. Where the floor is at or below the drain rate nothing changes.
|
|
|
|
- The order in effect is now the same at N=1 and N=2: safety limits and the
|
|
SAFETY-73 refusal > peak guard (with the SAFETY-79 margin) > maintenance >
|
|
modes > trade > loop (`arbiter.py`, beside the priorities).
|
|
- The drain keeps counting: the battery discharges at least the drain rate on
|
|
every cycle, so the cycle is not reset or stalled; it only ends sooner.
|
|
- Below the drain rate the rest is still exported (owner, 2026-09-29).
|
|
- Identity: `tools/safety78_identity.py --diff` against 35cdffe changes only
|
|
cycles where the guard asked for more than the drain; after such an episode
|
|
the commands are identical again and only the log and the idle loop's
|
|
integrator differ. N=2 is unchanged.
|
|
|
|
**Not changed.**
|
|
- Between the drain rate and the peak aim, the house still imports what the
|
|
drain leaves (N=1 behaviour kept by the identity). At N=2 the loop covers it.
|
|
- A maintenance HOLD (0 W, 120 min, battery full) still outranks the peak
|
|
guard at N=1. Discharging there spends the charge the BMS balances on; it is
|
|
an owner question, not this fix.
|