MAINT-24 + MAINT-25: a started maintenance charge gets the cheap night, and aims at the month peak like the guard.
Cause (investigation report reports/MAINT-24-investigation-report.md). While pack A is in its maintenance CHARGE phase, B's battery-mode grid charge took the cheap run, and A's shaping paused or shrank because it compared the peak forecast with cap - margin (3450 W) instead of this month's peak, which the guard and B's trade aim at.
Fix (smallest).
modetrade.decide(maint_charging=): while any pack is in maintenance CHARGE, no grid-charge trade for the other packs, why cycle (mirror of SAFETY-78). Spare solar is still allowed, and a SAFETY-73 refusal probe (PROBE_W alone) still goes on (test_opt20.py g7).
The maintenance pause and charge floor aim at peakguard.quarter_target_w(cap, margin, month reading) (folds in SAFETY-80). SAFETY-63 and SAFETY-125 unchanged.
SAFETY-111 is NOT folded in: taking the pack's own charge off the forecast changes the N=1 charge pinned by test_safety119.py. It stays on its card; A settles at (aim - house) / 2.
No option, no MAINT-13 timeout change.
Tests.test_maint24.py through the real cycle() (N=2 lagged replay): A charges 1475 W, B no trade (why cycle), quarters at most 4475 W against the 5950 W month aim; forecast under the month aim: no pause; real peak still pauses; mirror case; N=1 / drain / modes-off digests equal 02eac3b. 4 mutants red. Each charge test fails on base. Whole 9 h night (sim): A 1.41 -> 9.47 kWh and full, B no grid trade.
test_opt20.py: the ch_ch_maint digest is re-pinned (A in maintenance charge beside a charger B changed by design, same as SAFETY-125 moved two before).
DECISIONS: MAINT-24-charge-first.md.
CHANGELOG line: The charge of a started monthly cycle now gets the cheap night: the other battery no longer grid-charges ahead of it, and the charge aims at this month's peak.
MAINT-25 (same PR, owner 2026-11-12). The Home plan ignored the 7-day back-off after a failed cycle and drew a cycle every night. homeview.maintenance_runs() now draws no run during backoff_until() and shows ": the monthly cycle waits until 19 November." (NL too). test_maint25.py. CHANGELOG line: After a failed monthly cycle the Home plan no longer shows a cycle every night; it says when the next try is.
MAINT-24 + MAINT-25: a started maintenance charge gets the cheap night, and aims at the month peak like the guard.
**Cause (investigation report `reports/MAINT-24-investigation-report.md`).** While pack A is in its maintenance CHARGE phase, B's battery-mode grid charge took the cheap run, and A's shaping paused or shrank because it compared the peak forecast with `cap - margin` (3450 W) instead of this month's peak, which the guard and B's trade aim at.
**Fix (smallest).**
1. `modetrade.decide(maint_charging=)`: while any pack is in maintenance CHARGE, no grid-charge trade for the other packs, why `cycle` (mirror of SAFETY-78). Spare solar is still allowed, and a SAFETY-73 refusal probe (PROBE_W alone) still goes on (`test_opt20.py` g7).
2. The maintenance pause and charge floor aim at `peakguard.quarter_target_w(cap, margin, month reading)` (folds in SAFETY-80). SAFETY-63 and SAFETY-125 unchanged.
- SAFETY-111 is NOT folded in: taking the pack's own charge off the forecast changes the N=1 charge pinned by `test_safety119.py`. It stays on its card; A settles at (aim - house) / 2.
- No option, no MAINT-13 timeout change.
**Tests.** `test_maint24.py` through the real `cycle()` (N=2 lagged replay): A charges 1475 W, B no trade (why cycle), quarters at most 4475 W against the 5950 W month aim; forecast under the month aim: no pause; real peak still pauses; mirror case; N=1 / drain / modes-off digests equal 02eac3b. 4 mutants red. Each charge test fails on base. Whole 9 h night (sim): A 1.41 -> 9.47 kWh and full, B no grid trade.
`test_opt20.py`: the `ch_ch_maint` digest is re-pinned (A in maintenance charge beside a charger B changed by design, same as SAFETY-125 moved two before).
DECISIONS: `MAINT-24-charge-first.md`.
CHANGELOG line: The charge of a started monthly cycle now gets the cheap night: the other battery no longer grid-charges ahead of it, and the charge aims at this month's peak.
Not merged; rig test on TEST-23 is the next step.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
**MAINT-25 (same PR, owner 2026-11-12).** The Home plan ignored the 7-day back-off after a failed cycle and drew a cycle every night. `homeview.maintenance_runs()` now draws no run during `backoff_until()` and shows "<name>: the monthly cycle waits until 19 November." (NL too). `test_maint25.py`. CHANGELOG line: After a failed monthly cycle the Home plan no longer shows a cycle every night; it says when the next try is.
While a pack is in its maintenance CHARGE phase the mode trade files no grid
charge for the other packs (why "cycle", mirror of SAFETY-78). The maintenance
shaping check and charge floor aim at peakguard.quarter_target_w, month peak
included (SAFETY-80). Tests through cycle(): test_maint24.py.
Co-Authored-By: Claude <noreply@anthropic.com>
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>
homeview.maintenance_runs() ignored backoff_until(); it drew a cycle every
night of the wait. Now no run and a plain line (EN, NL). test_maint25.py.
Co-Authored-By: Claude <noreply@anthropic.com>
admin
changed title from MAINT-24: a started maintenance charge gets the cheap night to MAINT-24 + MAINT-25: a started maintenance charge gets the cheap night; the plan honours the back-off2026-11-12 09:58:03 +01:00
Verdict: MERGE-READY if the owner accepts trade-off T1 below. No safety finding. No code finding above Low. Not merged, because the brief allows a merge only with no owner trade-off.
What I checked and the results
Numbers: I re-ran the dev's sims on base and head (real cycle(), lagged N=2 replay). They match the dev's table exactly. Live shape (house 3000 W, banked 6000 W, aim 5950 W): base A 0 W, B 2500 W, trade on, worst quarter 5500 W. Head A 1475 W, B 0 W, no trade, worst quarter 4475 W. 9 h night: base A 1.41 kWh (10 -> 23 %), B 6.84 kWh. Head A 9.47 kWh (10 -> 100 %), B 0. The mirror (B cycling) gives the same numbers with A and B swapped. A never gives back in either.
Safety: I ran these extra sims (head): no forecast (SAFETY-63) A 2000 W, worst quarter 5000 W; month start (no banked peak) A 225 W, worst quarter 3225 W, under 3450 W; house 5800 W A -75 W, worst quarter 5875 W, never gives (base: paused, A gave up to +2500 W); real peak at house 6500 W still pauses (1875/2700 cycles), A gives, worst quarter 5689 W (base 5951 W). N=1, drain and modes-off digests equal base (no banked peak). maint_aim is the same quarter_target_w(cap, margin, reading) the guard gets (ctl_cycle.py:395-402 vs :563-566), and a failed reading keeps the cap. The floor still goes through _charge_headroom (SAFETY-125 charge_room_w). shaping_suspended still trips on decision.engaged. Blocking B's grid charge cannot drain B: nothing in the add-on grid-charges a pack to protect a reserve, B still takes spare solar, and the block ends with A's CHARGE phase (MAINT-13 timeouts).
Mutants (my own copies, fresh trees, python3 -B): all red. Dropping the maint_charging branch: 10 checks fail. Passing maint_charging=False in ctl_trade: 6 fail. Aim reverted on both the pause and the floor: 3 fail. Floor only: 3. Pause only: 6. Probe carve-out removed: 2 in test_maint24 and opt20 g7. MAINT-25 back-off check dropped: 4. On base: test_maint24 has 7 FAILs and a TypeError, test_maint25 has 4 FAILs (the dev said 5, see L3).
CI / suite: run 1613 checks / unit success and 1614 infra-pin / fresh success on 33cfc73. Full local suite (goodwe-addon-checks:1, --network none, real hwsim at infra pin 3b4ff84, GOODWE_REQUIRE_HWSIM=1): 106 files, 0 failed, 1832 s. test_tariff24_weather.py PASS in 226 s, so the router's TIMEOUT came from its environment. test_split_patches.py (MAINT-21 tripwire) PASS.
Owner trade-off
T1 (design, money only). The block leaves half the room unused and costs B its cheap nights for the whole charge phase. The floor settles at (aim - house) / 2 because SAFETY-111 stays out: the forecast includes A's own charge. The other half of the room under the aim is not used by anyone, because B is blocked.
Live shape (banked 6000 W, house 3000 W): A is full within the night. 1475 W of room goes unused, which is fine.
House 4500 W: A 6.53 kWh, 10 -> 72 % in 9 h (base 0). Two nights.
Month start (no month peak above the cap yet), house 3000 W: A gets only 225 W, 2.03 kWh a night (base 0.13). B gets 0 (base 3.81 kWh a night). About 9 kWh at 2 kWh a night is past the 36 h counted timeout, so the cycle can still fail, and B has lost about 4 cheap nights for nothing.
19 Nov is fine (November already has a banked peak). Follow-ups that would remove this: SAFETY-111, or let B take the room A's floor leaves (size B's trade to room - A's floor instead of blocking it).
Findings
L1 Low (docs / wording):homeview.py:1537 shows the day the 7-day wait ends. For the live failure (11-11 22:54 UTC) the line reads "18 November". The next try is 19 Nov 19:00. The line meets the brief ("the day the wait ends"), but three things in the PR say otherwise: the PR body's CHANGELOG line ("it says when the next try is"), the PR body example, and DECISIONS/MAINT-25-plan-backoff.md:8 ("19 November"). Fix the wording, or show the date of the first start slot after until.
L2 Low (UX, follow-up card): during A's maintenance CHARGE, B's Home card shows "Saves its power for higher prices. / Resting now.", and the plan still draws B's grid charge in the cheap run (e.g. "Charges up to 6.5 kWh from the grid"), which will not happen. I checked this on head through homeview.build on the sim controller. other_cycle only fires for DRAIN (homeview.py:902), so DECISIONS/MAINT-24-charge-first.md:70 is wrong when it says B "reads 'emptying'". The plan also draws a grid run for the cycling pack itself, as on base. Display only. Suggested card: "UX: while one battery's monthly cycle charges, the other battery's Home card and plan do not say it waits for the cycle".
L3 Low (test):test_maint25.py:124 says "the run tonight is B's" but checks only len(runs) == 1, so it also passes on base (A gets the run, B "waits its turn"). Check runs[0]["battery"] == 1.
I1 Info:test_peak_margin.py:260 (test_aim_w) still says the forecast sizing uses peak_aim_w(). That is true only with no banked peak.
I2 Info: N=1 and modes-off match base only without a banked peak. With 6000 W banked they change by design (SAFETY-80): N=1 A 225 -> 1475 W, worst quarter 3225 -> 4475 W. The PR body's "digests equal 02eac3b" is true only for the pinned scenarios.
I3 Info: a SAFETY-73 probe (200 W) beside A's floor uses the same measured room. This is the same double count base had with a full charger, and the guard corrects it.
I4 Info: if the cycling pack cannot charge (no SoC, refusing), B stays blocked until the MAINT-13 timeout. This mirrors SAFETY-78. Money only.
Design answers
Is (aim - house) / 2 intended? Not as a goal. It is the SAFETY-111 equilibrium. It is safe and enough for the live shape. Leaving SAFETY-111 out is right for this PR: folding it in changes the N=1 charge pinned by test_safety119, and it belongs on its own card.
Is "cycle" shown plainly? Only as the technical arb reason "battery modes: no trade (cycle)", the same as SAFETY-78. The Home page does not show it (L2).
Comments are one line plus a pointer. DECISIONS notes are short and accurate apart from L1 and L2. The CHANGELOG lines are in the PR body and there is no ## Unreleased. The new EN/NL text is short and plain (UX-08).
## Review: PR #246 (MAINT-24 + MAINT-25) at 33cfc733d3892ca4a08ced524c611ae6e654c30f vs release/1.0 02eac3b
**Verdict: MERGE-READY if the owner accepts trade-off T1 below.** No safety finding. No code finding above Low. Not merged, because the brief allows a merge only with no owner trade-off.
### What I checked and the results
- **Numbers:** I re-ran the dev's sims on base and head (real `cycle()`, lagged N=2 replay). They match the dev's table exactly. Live shape (house 3000 W, banked 6000 W, aim 5950 W): base A 0 W, B 2500 W, trade on, worst quarter 5500 W. Head A 1475 W, B 0 W, no trade, worst quarter 4475 W. 9 h night: base A 1.41 kWh (10 -> 23 %), B 6.84 kWh. Head A 9.47 kWh (10 -> 100 %), B 0. The mirror (B cycling) gives the same numbers with A and B swapped. A never gives back in either.
- **Safety:** I ran these extra sims (head): no forecast (SAFETY-63) A 2000 W, worst quarter 5000 W; month start (no banked peak) A 225 W, worst quarter 3225 W, under 3450 W; house 5800 W A -75 W, worst quarter 5875 W, never gives (base: paused, A gave up to +2500 W); real peak at house 6500 W still pauses (1875/2700 cycles), A gives, worst quarter 5689 W (base 5951 W). N=1, drain and modes-off digests equal base (no banked peak). `maint_aim` is the same `quarter_target_w(cap, margin, reading)` the guard gets (ctl_cycle.py:395-402 vs :563-566), and a failed reading keeps the cap. The floor still goes through `_charge_headroom` (SAFETY-125 `charge_room_w`). `shaping_suspended` still trips on `decision.engaged`. Blocking B's grid charge cannot drain B: nothing in the add-on grid-charges a pack to protect a reserve, B still takes spare solar, and the block ends with A's CHARGE phase (MAINT-13 timeouts).
- **Mutants (my own copies, fresh trees, `python3 -B`):** all red. Dropping the `maint_charging` branch: 10 checks fail. Passing `maint_charging=False` in ctl_trade: 6 fail. Aim reverted on both the pause and the floor: 3 fail. Floor only: 3. Pause only: 6. Probe carve-out removed: 2 in test_maint24 and opt20 g7. MAINT-25 back-off check dropped: 4. On base: test_maint24 has 7 FAILs and a TypeError, test_maint25 has 4 FAILs (the dev said 5, see L3).
- **CI / suite:** run 1613 `checks / unit` success and 1614 `infra-pin / fresh` success on 33cfc73. Full local suite (`goodwe-addon-checks:1`, `--network none`, real hwsim at infra pin 3b4ff84, `GOODWE_REQUIRE_HWSIM=1`): **106 files, 0 failed, 1832 s**. `test_tariff24_weather.py` PASS in 226 s, so the router's TIMEOUT came from its environment. `test_split_patches.py` (MAINT-21 tripwire) PASS.
### Owner trade-off
**T1 (design, money only).** The block leaves half the room unused and costs B its cheap nights for the whole charge phase. The floor settles at `(aim - house) / 2` because SAFETY-111 stays out: the forecast includes A's own charge. The other half of the room under the aim is not used by anyone, because B is blocked.
- Live shape (banked 6000 W, house 3000 W): A is full within the night. 1475 W of room goes unused, which is fine.
- House 4500 W: A 6.53 kWh, 10 -> 72 % in 9 h (base 0). Two nights.
- **Month start (no month peak above the cap yet), house 3000 W:** A gets only 225 W, 2.03 kWh a night (base 0.13). B gets 0 (base 3.81 kWh a night). About 9 kWh at 2 kWh a night is past the 36 h counted timeout, so the cycle can still fail, and B has lost about 4 cheap nights for nothing.
- 19 Nov is fine (November already has a banked peak). Follow-ups that would remove this: SAFETY-111, or let B take the room A's floor leaves (size B's trade to `room - A's floor` instead of blocking it).
### Findings
- **L1 Low (docs / wording):** `homeview.py:1537` shows the day the 7-day wait ends. For the live failure (11-11 22:54 UTC) the line reads "**18** November". The next try is 19 Nov 19:00. The line meets the brief ("the day the wait ends"), but three things in the PR say otherwise: the PR body's CHANGELOG line ("it says when the next try is"), the PR body example, and `DECISIONS/MAINT-25-plan-backoff.md:8` ("19 November"). Fix the wording, or show the date of the first start slot after `until`.
- **L2 Low (UX, follow-up card):** during A's maintenance CHARGE, B's Home card shows "Saves its power for higher prices. / Resting now.", and the plan still draws B's grid charge in the cheap run (e.g. "Charges up to 6.5 kWh from the grid"), which will not happen. I checked this on head through `homeview.build` on the sim controller. `other_cycle` only fires for DRAIN (`homeview.py:902`), so `DECISIONS/MAINT-24-charge-first.md:70` is wrong when it says B "reads 'emptying'". The plan also draws a grid run for the cycling pack itself, as on base. Display only. Suggested card: **"UX: while one battery's monthly cycle charges, the other battery's Home card and plan do not say it waits for the cycle"**.
- **L3 Low (test):** `test_maint25.py:124` says "the run tonight is B's" but checks only `len(runs) == 1`, so it also passes on base (A gets the run, B "waits its turn"). Check `runs[0]["battery"] == 1`.
- **I1 Info:** `test_peak_margin.py:260` (`test_aim_w`) still says the forecast sizing uses `peak_aim_w()`. That is true only with no banked peak.
- **I2 Info:** N=1 and modes-off match base only without a banked peak. With 6000 W banked they change by design (SAFETY-80): N=1 A 225 -> 1475 W, worst quarter 3225 -> 4475 W. The PR body's "digests equal 02eac3b" is true only for the pinned scenarios.
- **I3 Info:** a SAFETY-73 probe (200 W) beside A's floor uses the same measured room. This is the same double count base had with a full charger, and the guard corrects it.
- **I4 Info:** if the cycling pack cannot charge (no SoC, refusing), B stays blocked until the MAINT-13 timeout. This mirrors SAFETY-78. Money only.
### Design answers
- Is `(aim - house) / 2` intended? Not as a goal. It is the SAFETY-111 equilibrium. It is safe and enough for the live shape. Leaving SAFETY-111 out is right for this PR: folding it in changes the N=1 charge pinned by test_safety119, and it belongs on its own card.
- Is "cycle" shown plainly? Only as the technical arb reason "battery modes: no trade (cycle)", the same as SAFETY-78. The Home page does not show it (L2).
- Comments are one line plus a pointer. DECISIONS notes are short and accurate apart from L1 and L2. The CHANGELOG lines are in the PR body and there is no `## Unreleased`. The new EN/NL text is short and plain (UX-08).
admin
merged commit 1b5f7c9ccb into release/1.02026-11-12 16:09:33 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
MAINT-24 + MAINT-25: a started maintenance charge gets the cheap night, and aims at the month peak like the guard.
Cause (investigation report
reports/MAINT-24-investigation-report.md). While pack A is in its maintenance CHARGE phase, B's battery-mode grid charge took the cheap run, and A's shaping paused or shrank because it compared the peak forecast withcap - margin(3450 W) instead of this month's peak, which the guard and B's trade aim at.Fix (smallest).
modetrade.decide(maint_charging=): while any pack is in maintenance CHARGE, no grid-charge trade for the other packs, whycycle(mirror of SAFETY-78). Spare solar is still allowed, and a SAFETY-73 refusal probe (PROBE_W alone) still goes on (test_opt20.pyg7).peakguard.quarter_target_w(cap, margin, month reading)(folds in SAFETY-80). SAFETY-63 and SAFETY-125 unchanged.test_safety119.py. It stays on its card; A settles at (aim - house) / 2.Tests.
test_maint24.pythrough the realcycle()(N=2 lagged replay): A charges 1475 W, B no trade (why cycle), quarters at most 4475 W against the 5950 W month aim; forecast under the month aim: no pause; real peak still pauses; mirror case; N=1 / drain / modes-off digests equal02eac3b. 4 mutants red. Each charge test fails on base. Whole 9 h night (sim): A 1.41 -> 9.47 kWh and full, B no grid trade.test_opt20.py: thech_ch_maintdigest is re-pinned (A in maintenance charge beside a charger B changed by design, same as SAFETY-125 moved two before).DECISIONS:
MAINT-24-charge-first.md.CHANGELOG line: The charge of a started monthly cycle now gets the cheap night: the other battery no longer grid-charges ahead of it, and the charge aims at this month's peak.
Not merged; rig test on TEST-23 is the next step.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
MAINT-25 (same PR, owner 2026-11-12). The Home plan ignored the 7-day back-off after a failed cycle and drew a cycle every night.
homeview.maintenance_runs()now draws no run duringbackoff_until()and shows ": the monthly cycle waits until 19 November." (NL too).test_maint25.py. CHANGELOG line: After a failed monthly cycle the Home plan no longer shows a cycle every night; it says when the next try is.MAINT-24: a started maintenance charge gets the cheap nightto MAINT-24 + MAINT-25: a started maintenance charge gets the cheap night; the plan honours the back-offReview: PR #246 (MAINT-24 + MAINT-25) at
33cfc733d3vs release/1.002eac3bVerdict: MERGE-READY if the owner accepts trade-off T1 below. No safety finding. No code finding above Low. Not merged, because the brief allows a merge only with no owner trade-off.
What I checked and the results
cycle(), lagged N=2 replay). They match the dev's table exactly. Live shape (house 3000 W, banked 6000 W, aim 5950 W): base A 0 W, B 2500 W, trade on, worst quarter 5500 W. Head A 1475 W, B 0 W, no trade, worst quarter 4475 W. 9 h night: base A 1.41 kWh (10 -> 23 %), B 6.84 kWh. Head A 9.47 kWh (10 -> 100 %), B 0. The mirror (B cycling) gives the same numbers with A and B swapped. A never gives back in either.maint_aimis the samequarter_target_w(cap, margin, reading)the guard gets (ctl_cycle.py:395-402 vs :563-566), and a failed reading keeps the cap. The floor still goes through_charge_headroom(SAFETY-125charge_room_w).shaping_suspendedstill trips ondecision.engaged. Blocking B's grid charge cannot drain B: nothing in the add-on grid-charges a pack to protect a reserve, B still takes spare solar, and the block ends with A's CHARGE phase (MAINT-13 timeouts).python3 -B): all red. Dropping themaint_chargingbranch: 10 checks fail. Passingmaint_charging=Falsein ctl_trade: 6 fail. Aim reverted on both the pause and the floor: 3 fail. Floor only: 3. Pause only: 6. Probe carve-out removed: 2 in test_maint24 and opt20 g7. MAINT-25 back-off check dropped: 4. On base: test_maint24 has 7 FAILs and a TypeError, test_maint25 has 4 FAILs (the dev said 5, see L3).checks / unitsuccess and 1614infra-pin / freshsuccess on33cfc73. Full local suite (goodwe-addon-checks:1,--network none, real hwsim at infra pin 3b4ff84,GOODWE_REQUIRE_HWSIM=1): 106 files, 0 failed, 1832 s.test_tariff24_weather.pyPASS in 226 s, so the router's TIMEOUT came from its environment.test_split_patches.py(MAINT-21 tripwire) PASS.Owner trade-off
T1 (design, money only). The block leaves half the room unused and costs B its cheap nights for the whole charge phase. The floor settles at
(aim - house) / 2because SAFETY-111 stays out: the forecast includes A's own charge. The other half of the room under the aim is not used by anyone, because B is blocked.room - A's floorinstead of blocking it).Findings
homeview.py:1537shows the day the 7-day wait ends. For the live failure (11-11 22:54 UTC) the line reads "18 November". The next try is 19 Nov 19:00. The line meets the brief ("the day the wait ends"), but three things in the PR say otherwise: the PR body's CHANGELOG line ("it says when the next try is"), the PR body example, andDECISIONS/MAINT-25-plan-backoff.md:8("19 November"). Fix the wording, or show the date of the first start slot afteruntil.homeview.buildon the sim controller.other_cycleonly fires for DRAIN (homeview.py:902), soDECISIONS/MAINT-24-charge-first.md:70is wrong when it says B "reads 'emptying'". The plan also draws a grid run for the cycling pack itself, as on base. Display only. Suggested card: "UX: while one battery's monthly cycle charges, the other battery's Home card and plan do not say it waits for the cycle".test_maint25.py:124says "the run tonight is B's" but checks onlylen(runs) == 1, so it also passes on base (A gets the run, B "waits its turn"). Checkruns[0]["battery"] == 1.test_peak_margin.py:260(test_aim_w) still says the forecast sizing usespeak_aim_w(). That is true only with no banked peak.Design answers
(aim - house) / 2intended? Not as a goal. It is the SAFETY-111 equilibrium. It is safe and enough for the live shape. Leaving SAFETY-111 out is right for this PR: folding it in changes the N=1 charge pinned by test_safety119, and it belongs on its own card.## Unreleased. The new EN/NL text is short and plain (UX-08).Owner accepted T1 2026-11-12; merged at
1b5f7c9ccb.