OPT-17 review: title the forecast row on Home; test the hint and blank rows
checks / unit (pull_request) Successful in 36m20s
infra-pin / fresh (pull_request) Successful in 2s

The new status row's id `solar_forecast` had no Home title, so Home showed
it under the fallback "Inverter connection". Add "Solar forecast" /
"Zonneprognose" to ui_text, and checks for the title, the tomorrow-gap hint
and blank list rows (review mutations R7, R9, R14 now killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
2026-10-11 05:12:35 +02:00
co-authored by Claude Opus 5.5
parent 9b1fa758ad
commit 92a80c09b2
4 changed files with 17 additions and 3 deletions
@@ -47,8 +47,9 @@ day can list them.
gather that reads the inverters, so a slow sensor does not delay control. gather that reads the inverters, so a slow sensor does not delay control.
7. **Shown and logged once.** With two or more members, each one not usable 7. **Shown and logged once.** With two or more members, each one not usable
is named on the status page by its place in the list (STYLE.md: no entity is named on the status page by its place in the list (STYLE.md: no entity
ids on the page), as a caution row. The action log names the entity id, ids on the page), as a caution row titled "Solar forecast" on Home. The
once when the set of failing members changes. action log names the entity id, once when the set of failing members
changes.
8. **One member is the single key.** A one-member list reads, plans, shows 8. **One member is the single key.** A one-member list reads, plans, shows
and logs exactly as the single key with that entity. and logs exactly as the single key with that entity.
+1
View File
@@ -449,6 +449,7 @@
"delivery": "An inverter is not delivering", "delivery": "An inverter is not delivering",
"estop": "Emergency stop", "estop": "Emergency stop",
"pv": "Solar reading", "pv": "Solar reading",
"solar_forecast": "Solar forecast",
"none": "Inverter connection" "none": "Inverter connection"
}, },
"months": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] "months": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
+1
View File
@@ -372,6 +372,7 @@
"delivery": "Een omvormer levert niet", "delivery": "Een omvormer levert niet",
"estop": "Noodstop", "estop": "Noodstop",
"pv": "Meting van de zonnepanelen", "pv": "Meting van de zonnepanelen",
"solar_forecast": "Zonneprognose",
"none": "Verbinding met de omvormer" "none": "Verbinding met de omvormer"
}, },
"months": ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"] "months": ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"]
+12 -1
View File
@@ -28,7 +28,8 @@ sys.path.insert(0, os.path.join(HERE, "tools"))
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
import opt11p3_replay as rp # noqa: E402 import opt11p3_replay as rp # noqa: E402
from app import dayplan, messages, solarfc, validate # noqa: E402 from app import (dayplan, homeview, messages, solarfc, uitext, # noqa: E402
validate)
fails = [] fails = []
@@ -240,6 +241,9 @@ for one, many in (solarfc.TODAY_KEYS, solarfc.TOMORROW_KEYS):
not F({many: ["sensor.b", "sensor.c"]}) and not F({one: "sensor.a"}) not F({many: ["sensor.b", "sensor.c"]}) and not F({one: "sensor.a"})
and not F({one: "sensor.a", many: []}) and not F({one: "sensor.a", many: []})
and not F({one: "", many: ["sensor.b"]})) and not F({one: "", many: ["sensor.b"]}))
check(f"...blank rows in {many} are ignored, alone or beside {one}",
not F({many: ["", None, "sensor.b"]})
and not F({one: "sensor.a", many: ["", None]}))
p = F({many: ["sensor.b", "number.x"]}) p = F({many: ["sensor.b", "number.x"]})
check(f"...an entry in {many} that is not a sensor is refused", check(f"...an entry in {many} that is not a sensor is refused",
len(p) == 1 and "only sensors" in p[0] and "number.x" in p[0]) len(p) == 1 and "only sensors" in p[0] and "number.x" in p[0])
@@ -454,6 +458,13 @@ check("a tomorrow member down: today is used, tomorrow is not, and the row "
"Solar forecast for tomorrow: not used, source 2 of 2 has no " "Solar forecast for tomorrow: not used, source 2 of 2 has no "
"reading"] "reading"]
and len(fc_events(c)) == 1 and "sensor.tom_down" in fc_events(c)[0]) and len(fc_events(c)) == 1 and "sensor.tom_down" in fc_events(c)[0])
check("...and the page hint says tomorrow's forecast is not used",
(c.hint or "").endswith(messages.hint("forecast_no_tomorrow")))
check("...and Home titles the row as the solar forecast, in EN and NL",
[a["title"] for a in homeview.attention(fc_rows(c))]
== ["Solar forecast"]
and uitext.words("nl")["home_words"]["attn"]["solar_forecast"]
== "Zonneprognose")
first = CALLS[:8] first = CALLS[:8]
check("every member of both days is read in one gather (all four start " check("every member of both days is read in one gather (all four start "
"before any ends)", "before any ends)",