Files
goodwe-addon/goodwe_controller/DECISIONS/MAINT-24-charge-first.md
T
glenn schrooyenandClaude 33cfc733d3
checks / unit (pull_request) Successful in 26m39s
infra-pin / fresh (pull_request) Successful in 1s
MAINT-25: clean test_maint25.py, split its DECISIONS note
Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-12 11:08:30 +01:00

3.8 KiB

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.

MAINT-25 (the Home plan and the back-off) has its own note: MAINT-25-plan-backoff.md.