-
-
Notifications
You must be signed in to change notification settings - Fork 6
consumption prediction
This document explains how HSEM predicts house load (consumption) for the planning horizon — the weighted-average model, outlier detection, spike suppression, and reliability weighting.
HSEM predicts house load using a multi-window weighted average of historical consumption data. The prediction feeds the planner's net consumption calculation:
Accurate load prediction is critical: over-prediction leads to unnecessary grid imports; under-prediction leads to insufficient battery charging for peak hours.
HSEM supports two prediction modes, toggled via hsem_ml_consumption_enabled:
- Legacy (default): Four-window weighted average using HSEM custom sensors
- ML (new): Ridge regression on recorder history with DOW + seasonality + temperature
When enabled, the ML predictor queries the HA recorder directly for historical energy data from the configured energy sensor. No custom sensor entities are required.
Weighted ridge regression solves:
where:
-
$X$ is the$n \times k$ design matrix ($n$ observations,$k$ features) -
$W = \operatorname{diag}(w_1, \ldots, w_n)$ weights recent observations higher -
$\alpha = 1.0$ is the L2 regularization strength -
$y$ is the vector of observed per-slot energy (kWh)
Time-decay sample weights:
where
For 15-minute slots (
| Index range | Count | Feature | Description |
|---|---|---|---|
| 672 | one-hot |
Day-of-week × 15-min slot | |
|
|
2 | Day-of-year seasonality | |
| 1 | Outdoor temperature (°C), optional | ||
| 1 | Previous slot energy (sequential mode only) |
Total: 674 (without temperature + sequential), 675 (with temperature), 676 (with both).
For a target slot
$$ \hat{E}{d,s} = \beta{d,s} + \beta_{\sin} \cdot \sin\left(\frac{2\pi\delta}{365}\right)
- \beta_{\cos} \cdot \cos\left(\frac{2\pi\delta}{365}\right)
- \beta_T \cdot T + \beta_{\text{lag}} \cdot E_{t-1} $$
where
When sequential prediction is enabled, slots within a day are predicted in order and each output is fed as the lag input to the next slot:
where
The normal equation is solved via Cholesky decomposition
(numpy.linalg.solve). For
Each slot gets a per-slot safety margin based on the weighted standard deviation of its (DOW, slot) group:
where
| Safety factor | Meaning | |
|---|---|---|
| Prediction is reliable — trust it | ||
| Moderate uncertainty — small buffer | ||
| Sparse or variable data — full buffer |
The MILP receives
For slots that have already passed today, the predictor uses actual meter readings from the energy sensor instead of predictions. This anchors the battery SoC simulation to reality.
- 15-min resolution: matches Nord Pool spot market
- Day-of-week awareness: Monday ≠ Saturday
- Seasonality: winter mornings get higher predictions than summer
- Temperature: cold/hot outdoor temps → higher heating/cooling load
- No custom sensors: reads directly from recorder database
Four overlapping historical windows are maintained per clock-hour (0–23):
| Window | Span | Default weight | Purpose |
|---|---|---|---|
| 1-day | Last 24 hours | 25 % | Captures yesterday's pattern (weather, routine) |
| 3-day | Last 72 hours | 30 % | Short-term trend (weekday pattern) |
| 7-day | Last 168 hours | 30 % | Weekly rhythm (same weekday last week) |
| 14-day | Last 336 hours | 15 % | Long-term baseline (weather-independent) |
Default weights sum to 100 %. Configurable via the options flow.
Each HourlyConsumptionAverage carries per-window averages for one clock-hour:
@dataclass
class HourlyConsumptionAverage:
hour: int # 0-23
avg_1d: float # kWh average over the last 24 h for this hour
avg_3d: float # kWh average over the last 72 h
avg_7d: float # kWh average over the last 168 h
avg_14d: float # kWh average over the last 336 h
day_offset: int # 0 = today, 1 = tomorrow, ...The raw forecast for hour h is:
Before this weighted average, the weights undergo three transformations:
- IQR outlier detection — flag anomalous windows
- Spike detection and redistribution — suppress sudden jumps
- Reliability weighting — down-weight windows that disagree
Replaces the old ratio-based spike detection with the standard Tukey fence.
For each clock-hour, the four window values form a set of four data points. The interquartile range (IQR) is computed, and values outside
are flagged as outliers, where
When a window is flagged as an outlier, its weight is redistributed to the remaining non-outlier windows proportionally. If ALL windows are outliers (degenerate case), no redistribution occurs — all weights are kept unchanged.
Even after IQR filtering, the planner applies additional capping to prevent short-term spikes from dominating the forecast.
| Cap | Value | Meaning |
|---|---|---|
CAP7_DOWN |
0.85 | 7-day avg cannot be < 85 % of 14-day avg |
CAP7_UP |
1.15 | 7-day avg cannot be > 115 % of 14-day avg |
CAP14_DOWN |
0.90 | 14-day avg cannot be < 90 % of 7-day effective avg |
CAP14_UP |
1.10 | 14-day avg cannot be > 110 % of 7-day effective avg |
When a short window is significantly higher than a longer window, it is flagged as a spike:
| Comparison | Ratio range | Max weight reduction | Redistribution |
|---|---|---|---|
| 1d vs 7d | 1.30 – 2.00 | 50 % of 1d weight | 20 % → 3d, 55 % → 7d, 25 % → 14d |
| 3d vs 7d | 1.20 – 1.80 | 30 % of 3d weight | 60 % → 7d, 40 % → 14d |
| 7d vs 14d | 1.20 – 1.60 | 20 % of 7d weight | 100 % → 14d |
| 14d vs 7d | 1.15 – 1.50 | 15 % of 14d weight | 100 % → 7d |
Severity scaling: The fraction of weight actually removed interpolates
between 0 at the _MIN ratio and the maximum at the _MAX ratio:
Short windows (1-day, 3-day) are also capped against a blended baseline:
The 3-day uses slightly looser bounds (0.85 – 1.15) to avoid removing legitimate multi-day trends.
After spike suppression, each window's weight is further scaled by its agreement with the other windows:
Where
The scale strength is configurable via RELIABILITY_SCALE_STRENGTH (default 1.0).
Setting it to 0 disables reliability weighting entirely.
Weights are normalised after scaling so they still sum to the original total.
-
Linear relationship: The model assumes linear relationships between features and consumption. Non-linear effects (e.g. U-shaped heating/cooling curve vs temperature) are approximated but not fully captured.
-
Minimum history: Requires at least 14 days of recorder history for the energy sensor. Falls back to legacy mode below this threshold.
-
Single energy source: The model predicts from one energy accumulator. It cannot combine signals from multiple meters (e.g. import + solar).
-
No external events: Holidays, parties, or unusual appliance usage are not explicitly modeled — they appear as unexplained variance.
-
Stationarity: The model assumes consumption patterns are relatively stable over the 14-day window. Major lifestyle changes (new EV, heat pump, home renovation) require 14 days to be fully reflected.
-
Weather dependence: The model has no weather inputs. Weather-driven consumption (AC, heating) appears as unexplained variance unless correlated with the same-day-previous-week pattern (7-day window).
-
No day-of-week distinction: All windows are rolling and do not distinguish weekdays from weekends. A Monday forecast uses the same weights as a Saturday forecast, relying on the 7-day window to capture the weekly rhythm.
-
Zero-consumption hours: Hours with consistently zero consumption (e.g. night-time) produce zero forecasts, which is correct for most installations.
-
Outlier detection limitations: With only four data points (1d, 3d, 7d, 14d) per hour, the IQR method has limited statistical power. The spike caps act as a second line of defence.
The ML mode (ridge regression) addresses several legacy limitations and is the forward path. Remaining improvements include:
- Prediction-vs-actual diagnostics: Rolling MAE to measure model accuracy
- Multi-modal decomposition: Separate models for weather-driven, EV-driven, and baseline load
- Configurable alpha and decay: Expose regularization and time-decay as user-tunable parameters
- 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