-
-
Notifications
You must be signed in to change notification settings - Fork 6
adr 005 forecast confidence
Status: Accepted (partially superseded by ADR-006)
Date: 2026-05-11
Deciders: Project maintainers
Note: The original "diagnostic-only, non-adaptive" decision was amended in June 2026 by ADR-006, which introduces adaptive per-hour PV correction. The deterministic decay factors for future-day PV, the non-adaptive load and price treatment, and the diagnostic tracking infrastructure remain unchanged.
The HSEM planner relies on three categories of forecast data to make decisions:
- PV production — from Solcast, with per-slot estimates extending 48+ hours.
- House load consumption — from weighted historical averages of recent days.
- Electricity prices — from Energi Data Service, firm for today and increasingly uncertain for future days.
These forecasts are never perfect. For multi-day horizons (up to 48 hours), the confidence in future-day predictions decays significantly. Using Day+2 PV and load forecasts with the same weight as current-hour data would lead the planner to make confident decisions based on highly uncertain inputs.
Additionally, the system had no mechanism to track forecast accuracy over time — no way to answer "how wrong was our PV prediction yesterday?" This made it impossible to detect systematic bias (e.g., Solcast consistently over-forecasts by 15 %) or to feed accuracy metrics back into the planning process.
The key questions were:
- How should the planner treat future-day forecasts with lower confidence?
- Should forecast errors affect planning decisions (adaptive confidence)?
- What diagnostics should be exposed to help users understand forecast reliability?
- Should the system persist accuracy data across Home Assistant restarts?
We adopt a diagnostic-only, non-adaptive approach to forecast confidence, with a decayed PV weighting for future days and a separate forecast accuracy tracking system that monitors PV and load errors but never feeds back into the planner.
Instead of a probabilistic or adaptive confidence model, PV estimates for future days are multiplied by a fixed decay factor at slot-population time. This is a deterministic discount, not a statistical confidence interval.
| Day offset | Decay factor | Rationale |
|---|---|---|
| 0 (today) | 1.00 | Solcast nowcast is calibrated hourly |
| 1 (tomorrow) | 0.90 | Day-ahead PV forecasts degrade ~10 % |
| 2 (day after) | 0.80 | 48-hour PV forecasts degrade ~20 % |
The decayed PV value is used in the slot's estimated_net_consumption calculation:
pv_decayed[t] = pv_raw[t] × decay_factor[day_offset]
Prices are NOT decayed. Spot-market prices published by EDS are typically firm by mid-day for the following day, and the user's actual tariff components are known with certainty. Decaying prices would introduce a spurious penalty against future-day planning that is not justified by actual price uncertainty.
Load forecasts are NOT decayed. The weighted-average load model is already a conservative estimate — it averages over multiple days, which inherently dampens volatility. Adding a confidence decay would double-count the smoothing effect.
A separate ForecastTracker system (utils/forecast_tracker.py) stores per-slot forecast and actual values, computes error metrics, and exposes them via a diagnostic HA sensor. Key properties:
- Purely diagnostic — the tracker never modifies planner behaviour, candidate selection, or cost calculation.
- Ring-buffer storage — holds at most 192 records (~48 hours of 15-min slots), automatically pruning older records.
- Accumulation-based actuals — instantaneous power readings from the coordinator are converted to energy (kWh) and accumulated per slot.
- Idempotent finalisation — once a slot's end time passes, the record is frozen and error metrics are computed.
-
Reboot persistence — serialised tracker data is stored in HA entity attributes and restored via
RestoreEntityso long-term accuracy trends survive restarts.
Once a slot is finalised, these aggregates are computed across all finalised records:
| Metric | Formula | Interpretation |
|---|---|---|
| MAE (Mean Absolute Error) | (1/n) × Σ |forecast − actual| |
Average absolute deviation (kWh) |
| Bias (signed error) | (1/n) × Σ (forecast − actual) |
Systematic over/under forecast (kWh) |
| RMSE (Root Mean Squared Error) | √((1/n) × Σ (forecast − actual)²) |
Large-error penalised (kWh) |
| MAPE (Mean Absolute % Error) | (1/n) × Σ (|forecast − actual| / |actual|) × 100 |
Relative error (%, None if all actual=0) |
We deliberately chose not to feed accuracy metrics back into the planner for several reasons:
-
Stability risk — adaptive confidence weighting creates feedback loops. A bad forecast week would cause the planner to discount all future-day PV, which in turn reduces arbitrage value, which may change the user's behaviour, changing the load forecast, etc. Breaking this loop is difficult.
-
Non-stationary errors — forecast bias changes seasonally (summer cloud cover vs winter clear skies). An adaptive system tuned for July would be wrong in December.
-
User transparency — a deterministic decay factor is easy to explain ("tomorrow's PV counts 90 %"). An adaptive factor driven by a moving window of MAE is opaque and surprises users.
-
Minimal benefit — the planner already generates multiple candidates (solar-only, grid-charge, passive, etc.) and picks the cheapest. A small confidence adjustment on future PV would rarely change the winner, because the no-action baseline already handles PV uncertainty conservatively.
- Deterministic and predictable: PV decay factors are fixed and documented. Users understand exactly how future-day forecasts are treated.
- No hidden feedback loops: Forecast errors never influence planning decisions, keeping the system stable and auditable.
- Rich diagnostics: Users can see MAE, bias, RMSE, and MAPE per PV and load, enabling them to identify systematic forecast issues (e.g., "Solcast consistently over-forecasts by 15 %") and take corrective action (e.g., adjust their Solcast configuration).
- Reboot-safe tracking: Accuracy data survives HA restarts without custom file I/O or database schema.
-
Testable in isolation:
ForecastTrackeris pure Python with zero HA imports (aside from the sensor wrapper). Tests run in plainpytest.
- Forecast errors are ignored by the planner: If Solcast consistently over-forecasts by 30 %, the planner will see optimistic PV values that never materialise. The tracker reports this error but does nothing about it. Users must manually adjust their Solcast configuration.
- Fixed decay factors are not adaptive: If a particular season has unusually inaccurate Day+1 forecasts, the 0.90 decay factor is still applied. An adaptive system could theoretically do better — but at the cost of stability (see above).
- Load forecasts are not decayed: If the weighted-average load model is systematically wrong (e.g., holiday week vs normal week), the planner may over- or under-estimate consumption. The tracker reports this but does not compensate.
- Memory overhead: Each slot record stores ~10 floats plus metadata. At 192 records this is ~30 KB total — negligible for HA, but non-zero.
- The tracker's diagnostic data is exposed as sensor attributes, enabling users to create automations that alert on high bias (e.g., "PV bias > 20 % → send notification").
- The fixed decay factors (1.00, 0.90, 0.80) are documented and could be made configurable via options flow if user demand arises.
- The
DataQualityobject inPlannerOutputsurfaces missing-price and missing-PV hours per future day, allowing users to diagnose data gaps separately from accuracy issues.
Assign a probability distribution to every forecast and integrate over the uncertainty during optimisation.
Rejected because:
- Massive complexity increase: the MILP would need to handle stochastic programming or scenario trees.
- The additional computational cost (multiple scenario evaluations × MILP iterations) is not justified by the marginal benefit.
- Hard to explain and debug — a "80 % confidence PV" number is less transparent than a "0.90 multiplier."
Feed the tracker's MAE/bias back into the planner, e.g., subtract bias from PV forecasts, or weigh Day+2 PV by (1 − trailing_mape).
Rejected because:
- Feedback loops (see Stability risk above).
- Non-stationary errors — a moving window tuned for one season fails in another.
- User transparency — "why is Day+2 PV suddenly worth only 50 %?" is hard to answer.
- The
no_actionbaseline already handles PV uncertainty: if PV is over-forecast, the battery simply does not charge as expected, and the no-action candidate's cost converges to the actual outcome.
Run the planner multiple times with different PV scenarios (e.g., p10/p50/p90 from Solcast) and select the plan with the best expected outcome across scenarios.
Rejected because:
- Solcast does not expose probabilistic forecast bands via its HA integration — only point estimates.
- Multiple scenario runs would multiply planner compute time (currently < 100 ms per run) by the scenario count, potentially exceeding HA's coordinator timeout.
- The marginal benefit over the existing candidate set + PV decay is unproven.
- ADR-001: Planner Extraction (forecast tracker is a pure-Python utility)
-
docs/forecast-accuracy-tracking.md— Full technical guide -
docs/planner-spec.md— Multi-day planning horizon (confidence decay section) -
docs/energy-accounting.md— PV confidence decay formula -
utils/forecast_tracker.py— Implementation -
custom_sensors/forecast_accuracy_sensor.py— HA sensor wrapper - Issue #373 — Forecast accuracy tracking (origin)
- Home — User-facing overview: features, FAQ, working modes, battery schedules, excess export, consumption sensors
- Battery Charging Economics — How to calculate the minimum charging price for a battery schedule
- Architecture Overview — System context, layered architecture, module map, planning pipeline
- Planner Specification — Normative — all planner invariants, rules, and constraints
- Planner Technical Guide — How the planner works with worked examples
- Cost Function Math — Complete mathematical formulation of the 8-term cost function
- Energy Accounting — Physical energy flow model, SoC simulation, efficiency math
- Candidate Generation — How candidates are generated, assumptions, partial-SoC
- MILP Optimization — Full LP formulation, variable layout, constraints, and solver pipeline
- Consumption Prediction — Weighted-average model, IQR outlier detection, spike suppression
- Safety Modes — Degraded mode, read-only gate, write-verify applier, runtime resolver
- Price Scaling — EDS price scaling, eds_share conversion factor
- Services Reference — All 4 HSEM services with examples
- Sensors Reference — Complete entity reference: all sensor, select, switch, number, and time entities
- Dashboard Setup — Step-by-step ApexCharts dashboard with full YAML, layout reference, and troubleshooting
- Config Flow Reference — Every config/options flow step and field
- EV Charge Plan Setup — EV planned load configuration guide
- EV Surplus Charging Automation — Wire your physical EV charger (go-e, Easee, Zaptec) to follow HSEM surplus recommendations
- EV Optimal Charging Template — Legacy Home Assistant template sensor for cost-optimal EV charging
- Forecast Accuracy Tracking — Forecast vs actual tracking system
- Huawei Entities — Canonical HA entity ID reference
- Troubleshooting Guide — Diagnose and fix common problems: missing data, wrong prices, write failures, battery behaviour
- Quality Checks — Static quality tools and CI configuration