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
@@ -0,0 +1,78 @@
# OPT-17 - the solar forecast from several entities, summed
**Status: for review.** Tests: `test_opt17.py`. Two options added.
A site whose forecast comes as several Home Assistant sensors (one per
inverter or roof side) had to build a template sensor to sum them. Now each
day can list them.
## Options
- `pv_forecast_entities` and `pv_forecast_tomorrow_entities`: lists of
sensors, default `[]`, the same shape as `pv_entities`.
- `pv_forecast_entity` and `pv_forecast_tomorrow_entity` are unchanged. An
install that uses them needs no edit.
- **Why new keys, not "string or list" in the old ones.** Measured on
Supervisor 2026.08.0 by `tools/supervisor_shape_probe.py` §J, as TEL-12's
§G did for `pv_entity`. Declared as a list, each forecast key refuses the
string an install already stores (`Invalid list for option
'pv_forecast_entity'`), and a `[]` default does not rescue it. The one
spelling that takes both, `match(.*)?`, also takes the integer 417 and
shows a plain text box, so a second sensor cannot be added in the UI.
- §J also shows: a 0.17.0 blob validates unchanged with the lists at `[]`;
the lists render as repeatable fields; a downgrade drops them and keeps
the single keys.
- `[]` in `options:` is load-bearing, as for `pv_entities`.
## Rules
1. **One key per day.** Setting the single key and the list for the same day
is refused at start, with one message: keep the list, clear the single one.
The list can hold that entity too. (TEL-12 lets `pv_entities` replace
`pv_entity` with a caution row. Here the brief asked for a refusal: a
doubled or dropped forecast changes what a battery buys, and no install
has both keys yet, so the refusal breaks nothing.)
2. **Each member in its own unit.** Every member is read as one forecast
sensor is today (`solarfc._day()`): Wh, kWh or MWh, never guessed. Then the
day's kWh are added up. A repeated id counts once.
3. **One member not usable means the day is not usable.** Missing,
unavailable, unknown, stale, not an energy unit, or a bad number: that
day's sum is not used, exactly as when the single sensor reads that way.
No today means no forecast (P3 option A). No tomorrow means today only.
4. **Hourly split.** If every member has a split, the splits are joined
(overlapping periods add up). If any member has none, the sum is spread
over the bell, as for a state-only sensor.
5. **Time.** The forecast is as old as its oldest member.
6. **One read.** All members of both days are read in one gather, inside the
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
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,
once when the set of failing members changes.
8. **One member is the single key.** A one-member list reads, plans, shows
and logs exactly as the single key with that entity.
## Why
- A partial sum is a smaller number that looks real. Two of three inverters'
forecasts read as a duller day, and Leftover solar would then buy grid
energy the sun was going to give. TEL-12 took the same rule for the same
reason. No forecast is a known state with a tested fallback.
- The bell for a mixed list: joining one member's hours with another's total
has no honest shape, and the bell is the existing state-only rule.
## Cost
- One flaky member costs the whole day's forecast. A site with three
sensors loses the plan more often than one with a template sum that hides
gaps.
- Two more options (85).
## What this does not cover
- No weighting or calibration per member; FCST-01 is still the own forecast.
- Members on different integrations with different refresh times: each must
be fresh (3 h) on its own; there is no wait for a lagging one.
- The day plan itself, the trust and the house profile are unchanged.
- The Batteries screen still says only "no solar forecast now". The member
is named on the status page.