From 0b96e3191cd88f3a713fee502e6d6b6ab1c39167 Mon Sep 17 00:00:00 2001 From: glenn schrooyen Date: Tue, 29 Sep 2026 22:29:00 +0200 Subject: [PATCH] OPT-11 P4 review: records and text - DECISIONS: the Cheap hours change and why; the no-sun replay (the gain is Leftover solar buying the evening's shortfall; the forecast added 0.14 EUR); the reconstruction bias stated plainly. - DOCS: Cheap hours ignores the forecast; a reading from before midnight is stale. - The forecast line: "Solar still to come today: ..." (it is what is left, and it stays within the 12-word limit). Co-Authored-By: Claude Opus 5.5 --- .../DECISIONS/OPT-11-battery-modes.md | 17 ++++++++++++----- goodwe_controller/DOCS.md | 2 +- goodwe_controller/app/ui_text/en.json | 2 +- goodwe_controller/app/ui_text/nl.json | 2 +- goodwe_controller/test_modes_p4.py | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/goodwe_controller/DECISIONS/OPT-11-battery-modes.md b/goodwe_controller/DECISIONS/OPT-11-battery-modes.md index 5a68ede..93897d6 100644 --- a/goodwe_controller/DECISIONS/OPT-11-battery-modes.md +++ b/goodwe_controller/DECISIONS/OPT-11-battery-modes.md @@ -94,7 +94,8 @@ P1 is its section 6 P1, and the pull request lists what was measured. - Leftover solar may then charge from the grid up to min(ceiling, floor + the house's use in the expensive runs / 0.95), less its sun share: only the shortfall. - - Cheap hours only charges up to the ceiling less its sun share. + - Cheap hours only ignores the plan and fills to the ceiling (design + section 1; review of #156, see the replay below). - Solar first never charges from the grid (owner, 2026-09-29). - The level is kept unless it moves by min(1 kWh, 10 %). With no plan (no, stale or failed forecast, no house profile, no capacity) every mode @@ -114,9 +115,15 @@ P1 is its section 6 P1, and the pull request lists what was measured. - the days' real 2026 quarter-hourly prices (held out): P2 32.26, option A 32.01, old trade 31.40, P4 29.19 (perfect 29.21, too high 29.28, missing 32.01). - - Solar first + Cheap hours only: 19.57 on both P3 and P4 (year-earlier - prices); on real 2026 prices P4 29.72 against P3's 29.41. Skipping the - sun's share does not pay for Cheap hours there; Leftover solar is the - mode the forecast pays for. + - Solar first + Cheap hours only, skipping its sun share (the first + build): 19.57 (same as P3) and 29.72 against P3's 29.41. The shorter, + moved runs left B lower, so later midday top-ups rested A while the sun + shone. Cheap hours now ignores the plan, which is P3 exactly. + - A forecast of no sun at all gives 19.14 and 29.33. So nearly all of the + gain is Leftover solar buying the evening's shortfall in the cheap run; + the forecast itself was worth 0.14 EUR in 16 September days. + - Bias: the site's solar is rebuilt from the same net use the replay + plays, and "recorded" keeps its hourly shape and adds only the national + daily error. Both favour P4; "national" (no house leakage) gives 29.25. No threshold was tuned to these days. The 2026-09-24 PV recording could not be replayed: it is only in the live Home Assistant. diff --git a/goodwe_controller/DOCS.md b/goodwe_controller/DOCS.md index 95e7be9..7c1060f 100644 --- a/goodwe_controller/DOCS.md +++ b/goodwe_controller/DOCS.md @@ -541,7 +541,7 @@ With battery modes on, these choices replace the *Charging and selling on price* **Solar forecast** (`pv_forecast_entity`, optional). A Home Assistant sensor with the solar energy still to come today, in kWh. These work: Forecast.Solar (`sensor.energy_production_today_remaining`), Solcast PV Forecast (`sensor.solcast_pv_forecast_forecast_remaining_today`) and Open-Meteo Solar Forecast (`sensor.energy_production_today_remaining`). Set `pv_forecast_tomorrow_entity` to the matching *tomorrow* sensor to plan a cheap run before midnight. - Before each cheap run the add-on asks: will the sun fill the batteries before the expensive hours? It counts on `solar_trust_pct` of the forecast (70 %), less what the house uses. Solar first batteries get that sun first. - *Leftover solar* then charges from the grid only the shortfall: what the house will draw from it in the expensive hours, less its share of the sun. Not up to the top level. -- *Cheap hours only* fills to the top level less its share of the sun. +- *Cheap hours only* ignores the forecast and fills to the top level. - *Solar first* never charges from the grid. - The house use is the add-on's own measurement (grid + battery power) per hour, over the last 7 days. It needs one day of it before the first plan. - The plan is checked every 15 minutes. A planned level moves only by 1 kWh or 10 %, so a wobbling forecast does not flip it. diff --git a/goodwe_controller/app/ui_text/en.json b/goodwe_controller/app/ui_text/en.json index 27d8534..e7f429b 100644 --- a/goodwe_controller/app/ui_text/en.json +++ b/goodwe_controller/app/ui_text/en.json @@ -339,7 +339,7 @@ "tops_up": "Too little sun is forecast, so it charges some from the grid.", "no_forecast": "No solar forecast, so it charges from solar only." }, - "solar": {"line": "Solar forecast for the rest of today: {kwh}, we count on {count}. Updated {at}.", "no_forecast": "No solar forecast now, so Leftover solar charges from solar only.", "learning": "The add-on is learning your house's use. The plan starts after one full day."}, + "solar": {"line": "Solar still to come today: {kwh}, we count on {count}. Updated {at}.", "no_forecast": "No solar forecast now, so Leftover solar charges from solar only.", "learning": "The add-on is learning your house's use. The plan starts after one full day."}, "today": { "not_planned": "Not planned yet.", "no_runs": "No cheap or expensive hours left today.", diff --git a/goodwe_controller/app/ui_text/nl.json b/goodwe_controller/app/ui_text/nl.json index 410e29d..291acb7 100644 --- a/goodwe_controller/app/ui_text/nl.json +++ b/goodwe_controller/app/ui_text/nl.json @@ -281,7 +281,7 @@ "tops_up": "Te weinig zon voorspeld, dus ze laadt wat van het net.", "no_forecast": "Geen zonneprognose, dus ze laadt alleen met zonne-energie." }, - "solar": {"line": "Zonneprognose voor de rest van vandaag: {kwh}, we rekenen op {count}. Bijgewerkt om {at}.", "no_forecast": "Nu geen zonneprognose, dus Overschot zonne-energie laadt alleen met zon.", "learning": "De add-on leert eerst het verbruik van uw huis. Het plan start na één volle dag."}, + "solar": {"line": "Nog verwachte zon vandaag: {kwh}, we rekenen op {count}. Bijgewerkt om {at}.", "no_forecast": "Nu geen zonneprognose, dus Overschot zonne-energie laadt alleen met zon.", "learning": "De add-on leert eerst het verbruik van uw huis. Het plan start na één volle dag."}, "today": { "not_planned": "Nog niet gepland.", "no_runs": "Vandaag geen goedkope of dure uren meer.", diff --git a/goodwe_controller/test_modes_p4.py b/goodwe_controller/test_modes_p4.py index a2c3e42..b0f1234 100644 --- a/goodwe_controller/test_modes_p4.py +++ b/goodwe_controller/test_modes_p4.py @@ -708,7 +708,7 @@ rows, st_, c = run_day(SF_LS, fc=1.0, soc0={"a": 60.0, "b": 30.0}, rp.m.datetime = datetime sb = homeview.solar_block(c, c.solar_fc.at) check("the forecast line names the forecast, what is counted and when", - sb["line"].startswith("Solar forecast for the rest of today:") + sb["line"].startswith("Solar still to come today:") and "we count on" in sb["line"] and sb["state"] == "plan") why = homeview.day_key(c, B) check("B's Why key (plan, dark) is tops_up", why == "tops_up")