From 33cfc733d3892ca4a08ced524c611ae6e654c30f Mon Sep 17 00:00:00 2001 From: glenn schrooyen Date: Thu, 12 Nov 2026 11:08:30 +0100 Subject: [PATCH] MAINT-25: clean test_maint25.py, split its DECISIONS note Co-Authored-By: Claude --- .../DECISIONS/MAINT-24-charge-first.md | 13 ++------ .../DECISIONS/MAINT-25-plan-backoff.md | 10 ++++++ goodwe_controller/test_maint25.py | 33 ------------------- 3 files changed, 12 insertions(+), 44 deletions(-) create mode 100644 goodwe_controller/DECISIONS/MAINT-25-plan-backoff.md diff --git a/goodwe_controller/DECISIONS/MAINT-24-charge-first.md b/goodwe_controller/DECISIONS/MAINT-24-charge-first.md index 2a84b3f..240da11 100644 --- a/goodwe_controller/DECISIONS/MAINT-24-charge-first.md +++ b/goodwe_controller/DECISIONS/MAINT-24-charge-first.md @@ -1,6 +1,6 @@ # MAINT-24 - a started maintenance charge gets the cheap night -**Status: for review.** Also fixes MAINT-25 (last section). Tests: `test_maint24.py` (`test_opt20.py`'s `ch_ch_maint` digest moved with it). No option added; the MAINT-13 +**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 @@ -69,13 +69,4 @@ 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 honours the 7-day back-off - -After a failed cycle `wants_start()` waits 7 days (MAINT-13, `backoff_until()`), -but `homeview.maintenance_runs()` only asked `due()`, so the plan drew a cycle at -the start hour every night of the wait. During the back-off it now draws no run -for that battery and shows one line instead: "Battery A: the monthly cycle waits -until 19 November." (Dutch: "... wacht tot 19 november."). A cycle completed -after the failure ends it, as in `backoff_until()`. The other battery is planned -as before. Tests: `test_maint25.py` (N=1 and N=2, 1 day, 7 days, completion -before and after the failure, Dutch). +MAINT-25 (the Home plan and the back-off) has its own note: `MAINT-25-plan-backoff.md`. diff --git a/goodwe_controller/DECISIONS/MAINT-25-plan-backoff.md b/goodwe_controller/DECISIONS/MAINT-25-plan-backoff.md new file mode 100644 index 0000000..c2e7c6a --- /dev/null +++ b/goodwe_controller/DECISIONS/MAINT-25-plan-backoff.md @@ -0,0 +1,10 @@ +# MAINT-25 - the Home plan honours the 7-day back-off + +**Status: for review.** Added to the MAINT-24 PR by the owner (2026-11-12). +Tests: `test_maint25.py`. After a failed cycle `wants_start()` waits 7 days +(MAINT-13, `backoff_until()`), but `homeview.maintenance_runs()` only asked +`due()`, so the plan drew a cycle at the start hour every night of the wait. +During the back-off it now draws no run for that battery and shows one line +instead: "Battery A: the monthly cycle waits until 19 November." (Dutch: "... +wacht tot 19 november."). A cycle completed after the failure ends it, as in +`backoff_until()`. The other battery is planned as before. diff --git a/goodwe_controller/test_maint25.py b/goodwe_controller/test_maint25.py index 1d8c1fa..715341f 100644 --- a/goodwe_controller/test_maint25.py +++ b/goodwe_controller/test_maint25.py @@ -80,39 +80,6 @@ class _Hass: return [] -ONE = {"setpoint_entity": "number.sp", "meter_entity": "sensor.grid", - "soc_entity": "sensor.soc", "batt_entity": "sensor.batt", - "meter_source": "off", "max_w": 5000, "shadow_mode": "off", - "maintenance_enabled": False} -TWO = dict(ONE, inverters=[ - {"name": "Battery A", "setpoint_entity": "number.a", "soc_entity": - "sensor.soc_a", "batt_entity": "sensor.batt_a", "max_w": 2500, - "capacity_kwh": 9.6}, - {"name": "Battery B", "setpoint_entity": "number.b", "soc_entity": - "sensor.soc_b", "batt_entity": "sensor.batt_b", "max_w": 2500, - "capacity_kwh": 5.4}]) - -# Sunday 27 September 2026, 11:40 in Brussels (09:40 UTC): the mockup's day. -NOW = datetime(2026, 9, 27, 9, 40, tzinfo=timezone.utc) -DAY0 = datetime(2026, 9, 26, 22, 0, tzinfo=timezone.utc) -MOCK = [.24, .22, .21, .20, .21, .22, .24, .27, .26, .22, .18, .16, .15, .14, - .16, .19, .23, .28, .33, .36, .34, .29, .26, .24] - - -def make(opts, *, soc=(62.0, 35.0), batt=(-2500.0, -300.0), grid=10.0, - auto=True, wrote=0.0): - """`wrote` is each inverter's last accepted power request (None: none yet).""" - c = Controller(dict(opts), _Hass(), _Store(), _Pub(), stop=asyncio.Event()) - c.auto = auto - for i, inv in enumerate(c.inverters): - inv.soc, inv.batt = soc[i], batt[i] - inv.last_write = wrote - inv.dev_min, inv.dev_max = -5000.0, 5000.0 - c.grid = grid - return c - - - NOW = datetime(2026, 9, 27, 9, 40, tzinfo=timezone.utc) ONE = {"setpoint_entity": "number.sp", "meter_entity": "sensor.grid", "soc_entity": "sensor.soc", "batt_entity": "sensor.batt",