Files
goodwe-addon/goodwe_controller/DECISIONS/MAINT-24-charge-first.md
T
glenn schrooyenandClaude 0f5e513f1d
checks / unit (pull_request) Failing after 45m44s
infra-pin / fresh (pull_request) Successful in 2s
MAINT-24: keep the refusal probe beside a maintenance charge
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>
2026-11-12 07:33:04 +01:00

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.