OPT-17: the solar forecast can come from several entities, summed
checks / unit (pull_request) Successful in 36m13s
infra-pin / fresh (pull_request) Successful in 1s

New list options pv_forecast_entities and pv_forecast_tomorrow_entities
(same shape as pv_entities); the single-entity keys are unchanged.

- Each member is read in its own unit (Wh/kWh/MWh) in one gather and the
  day is summed. Hourly splits are merged when every member has one, else
  the total goes on the bell.
- A member missing, unavailable, stale, non-energy or bad makes that
  day's forecast unusable (no partial sum). A caution row names it by
  position; the action log names the entity once per change.
- Single key and list both set for one day: refused at start.
- A one-member list behaves exactly like the single key (test_opt17.py);
  the single-key path is byte-identical to e36565f over 24 scenarios.
- Supervisor shape measured: tools/supervisor_shape_probe.py section J.

DECISIONS/OPT-17-several-forecast-entities.md has the rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
glenn schrooyen
2026-10-11 02:55:58 +02:00
co-authored by Claude Opus 5.5
parent e36565fe39
commit 9b1fa758ad
13 changed files with 878 additions and 48 deletions
+8 -6
View File
@@ -113,19 +113,21 @@ check("the harness bites: Leftover solar grid-charging with no plan moves "
lambda: setattr(modetrade, "GRID_CHARGE_MODES",
(packmode.LEFTOVER_SOLAR, packmode.CHEAP_HOURS)),
lambda: setattr(modetrade, "GRID_CHARGE_MODES", _gcm)))
_read = solarfc.read
# OPT-17: the controller reads through `read_sum()`, so that is the seam.
_read = solarfc.read_sum
def _as_dark(today, tomorrow, now, **kw):
return _read({"state": "0", "attributes": {"unit_of_measurement":
"kWh"},
"last_updated": now.isoformat()}, None, now)
return _read([("sensor.f", {"state": "0", "attributes": {
"unit_of_measurement": "kWh"}, "last_updated": now.isoformat()})],
[], now)
check("...and an unavailable forecast read as a dark one moves "
"n2_sf_ls_fc_down (Leftover solar would buy its shortfall)",
_moved("n2_sf_ls_fc_down", lambda: setattr(solarfc, "read", _as_dark),
lambda: setattr(solarfc, "read", _read)))
_moved("n2_sf_ls_fc_down",
lambda: setattr(solarfc, "read_sum", _as_dark),
lambda: setattr(solarfc, "read_sum", _read)))
# ============================================================================ #
print("2. the forecast sensors")