The mode trade still lets a SAFETY-73 probe (PROBE_W alone) go on while a pack is in its maintenance CHARGE phase; test_opt20.py g7 pins it. ch_ch_maint digest moved (B no longer grid-charges beside A's charge). Co-Authored-By: Claude <noreply@anthropic.com>
71 lines
3.7 KiB
Markdown
71 lines
3.7 KiB
Markdown
# MAINT-24 - a started maintenance charge gets the cheap night
|
|
|
|
**Status: for review.** Tests: `test_maint24.py` (`test_opt20.py`'s `ch_ch_maint` digest moved with it). No option added; the MAINT-13
|
|
timeout numbers are unchanged. Folds in SAFETY-80. SAFETY-111 stays on its card (section 3).
|
|
|
|
**What happened (live 0.21.0, A, 2026-11-08..11).** A sat in its maintenance
|
|
CHARGE phase for more than 75 h and stalled at 49-50 %. At night B's battery-mode
|
|
grid charge took the cheap run: A is not idle, so `modetrade.chargers()` never
|
|
lists it, B is the only charger, and the allocator hands the whole negative
|
|
target to B. A's own shaping then paused or shrank (the check compared the peak
|
|
forecast with `cap - margin` = 3450 W, while B's trade and the guard aim at this
|
|
month's peak, about 4.9 kW), and A gave power back on guard stops against a peak
|
|
that B's own charge made. The timeout (MAINT-13) counted that starved time as
|
|
charging time and ended the cycle. Report: `reports/MAINT-24-investigation-report.md`.
|
|
|
|
## 1. The maintenance charge goes first in the cheap run
|
|
|
|
`modetrade.decide(maint_charging=)`, set in `_mode_roles()` when any pack is in
|
|
its maintenance CHARGE phase. It is the mirror of `draining` (SAFETY-78): no
|
|
grid-charge trade for the other packs, why `cycle`, the same Tick. The other
|
|
packs keep their solar roles, so they may still take spare solar. A refusal probe
|
|
(SAFETY-73, PROBE_W alone, OPT-20) still goes on: it places no charge on the
|
|
pack in maintenance, and `test_opt20.py` g7 pins it. The drain still blocks it. B's night
|
|
charge waits for the nights A needs. At N=1 the only pack is the one in
|
|
maintenance, so nothing changes.
|
|
|
|
## 2. The shaping aims where the guard aims (SAFETY-80)
|
|
|
|
The pause (`shaping_suspended()`) and the floor (`maintenance_charge_floor()`)
|
|
get `peakguard.quarter_target_w(cap, margin, month reading)` instead of
|
|
`peak_cap_w - peak_margin_w`. With no banked peak above the cap that is the same
|
|
number. SAFETY-63 (no forecast: no pause, configured rate) and SAFETY-125 (late
|
|
reading: `charge_room_w`) are untouched. `peak_aim_w()` stays for the day plan.
|
|
|
|
## 3. SAFETY-111 is not folded in
|
|
|
|
`peak_fc` holds the pack's own charge, so the floor `aim - peak_fc` settles at
|
|
(aim - house) / 2, here 1475 W instead of 2000 W. Taking the own charge off the
|
|
forecast was tried: it lifts A to 2000 W, but it changes the N=1 charge that
|
|
`test_safety119.py` pins (a static 800 W forecast then no longer shapes the
|
|
charge), so it is a change of its own. 1475 W fills a 10 kWh pack inside the
|
|
night (sim below). It stays on its card.
|
|
|
|
## 4. A gives nothing while no real peak exists
|
|
|
|
With 1 and 2 no grid charge of B makes a peak against the lower aim, and the pause
|
|
comes only when the forecast is over the month aim, so A is not made the only
|
|
battery that gives while B charges. A guard stop on a real peak
|
|
still lands on A (SAFETY-07 / MAINT-02, unchanged).
|
|
|
|
## Numbers (sim, house 3000 W, cap 3500, margin 50, month peak 6000 W)
|
|
|
|
| | base 02eac3b | MAINT-24 |
|
|
|---|---|---|
|
|
| A charge | 0 W (B charges 2500 W) | 1475 W = (5950 - 3000) / 2 |
|
|
| B grid trade | yes, quarters 5500 W | none, why `cycle` |
|
|
| quarter peak | 5500 W | 4475 W (aim 5950 W) |
|
|
| A at house 3800 W | 0 W | 1075 W, quarters 4876 W |
|
|
|
|
Whole night (sim, `tools`-style replay, N=2, cheap 21:00-06:00, A from 10 %, B
|
|
from 30 %, 9 h): base A takes 1.41 kWh (10 -> 23 %), B takes 6.84 kWh by grid
|
|
trade; MAINT-24 A takes 9.47 kWh and is full (100 %) inside the night, B takes
|
|
no grid charge. Worst 15-minute average 5500 W on base, 4475 W now, A never
|
|
gives.
|
|
|
|
## Not changed
|
|
|
|
The MAINT-13 timeout, the peak aim itself, the month peak, `P_MAINT_SHAPE` and
|
|
`P_ARBITRAGE`. The Home card for B does not say "the other battery is cycling"
|
|
for a CHARGE phase (`other_cycle` reads "emptying"); that is a wording card.
|