UX-54: a Forecast tab with the sun, the house and each battery's level, per hour
checks / unit (pull_request) Successful in 48m29s
infra-pin / fresh (pull_request) Successful in 1s

A new tab beside Home and Batteries for today and tomorrow: the solar
forecast, the solar measured so far, the house's expected use and each
battery's expected level, with the plan's own charge and give blocks under it.

- Blocks are Home's: today's are the plan's `runs`, tomorrow's come from the
  same builders (`homeview.runs_for()`), in the plan lines' words.
- The sun is what solarfc reads; a daily total is spread over the bell and the
  tab says so. The house is HouseProfile.estimator(), as the plan reads it.
- The level is new: `daycurve.curve()` gives each battery the role
  `modetrade.decide()` gives it and moves it hour by hour inside the plan's
  floor and ceiling. It is an estimate and counts on `solar_trust_pct`.
- Built on request (`GET forecast`), no Home Assistant call. Measured solar is
  kept per hour in memory from the reading control already takes.
- A part not known yet is a note: modes off, no forecast, learning, no
  forecast for tomorrow, a monthly cycle, no level, stopped, shadow.

No decision, command, timing or option key changes. Found, not changed:
`dayplan.plan()` over-counts a state-only forecast (DECISIONS/UX-54).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
glenn schrooyen
2026-10-26 19:38:59 +01:00
co-authored by Claude
parent a1d07030f8
commit b6b2d1b184
16 changed files with 1912 additions and 11 deletions
+7
View File
@@ -150,6 +150,12 @@ def build_app(controller, selfcfg=None) -> web.Application:
return web.json_response(controller.status_payload())
return web.json_response(controller.status_payload(lang=lang))
async def forecast(request):
"""UX-54: the Forecast tab's data, built when the tab asks. Reads only."""
build = getattr(controller, "forecast_payload", None)
lang = uitext.lang_of(request.query.get("lang"))
return web.json_response(build(lang=lang) if build else {"ok": False})
async def action(request):
try:
body = await request.json()
@@ -471,6 +477,7 @@ def build_app(controller, selfcfg=None) -> web.Application:
app.add_routes([
web.get("/", index),
web.get("/status", status),
web.get("/forecast", forecast),
web.post("/action", action),
web.get("/config", config_index),
web.get("/config/model", config_model),