Three runtime tiers, three external services
The web service reads cached results. Scoring and training run in separate scheduled jobs so they do not delay page requests.
- A stateless web service serves the dashboard and the public JSON API. It reads from Redis and nothing else.
- An hourly scoring job fetches demand and weather, loads the current models from cloud storage, and writes forecasts, alerts and diagnostics into Redis.
- A daily training job retrains all three model families, validates each candidate, and persists the accepted ones.
Data comes from three outside services: the EIA API for demand, generation and interchange; Open-Meteo for 17 weather variables; and NOAA’s National Weather Service for severe-weather alerts. Forecast-hour weather is a composite of NOAA’s National Blend of Models over the base forecast — that arrives through Open-Meteo as a model option, not as a fourth feed of its own.
Why the web tier never does heavy work in the request path
The web container has no trained models on disk, no model metadata, and no path to the model store. That is deliberate, and it is enforced rather than merely intended: any component that reaches for model output must read it from Redis, because the scoring job is the only writer.
The rule exists because breaking it fails quietly. A function that reads a model from local disk does not crash in production when the disk is empty — it falls back to a simulated path and returns a plausible-looking series. Two real bugs shipped that way in a single day: an overview chart rendering noisy historical actuals at forward timestamps on every page load, and a model badge reporting “simulated” while the forecasts in Redis were real. Both looked fine.
So the fallbacks are now gated: when the web tier is configured for Redis-only operation and no real data is available, it returns no forecast and the interface reports that published data is unavailable. A missing result does not establish its cause or when it will recover.
What goes into a forecast: 49 features from 17 weather variables
Each region’s feature matrix carries 49 features — 17 raw weather variables and 32 derived weather and demand statistics. The derived set includes cooling and heating degree days against a 65°F baseline, wind power density, solar capacity factor, and a family of lag and rolling-window statistics over recent demand.
Demand lags and rolling statistics describe prior reported demand. Weather inputs include forecasts for future hours. The evaluation below distinguishes historical holdout results from observed forecast error.
Where the weather is sampled
A balancing authority responds to weather across its whole territory, but the fetch originally sampled one representative point — for MISO, a single spot in rural Illinois standing in for a territory running from Minnesota to Louisiana. Each footprint is now sampled at up to 12 fixed cells and averaged. A published sampling study reported an error reduction of 1.14 sMAPE points overall (sMAPE is a symmetric variant of the average-error percentage; MISO +1.77, PJM +1.41) and approximately nothing for compact single-metro authorities, which keep their single point.
Two details matter more than they look. Wind direction uses a circular mean, because averaging 350° and 10° arithmetically points the wind due south. And the aggregation is unweighted, because population weighting was measured and added nothing — so production carries coordinates and needs no census data at runtime.
Three base models and one ensemble
XGBoost learns the non-linear weather-to-demand relationship from the full engineered feature set and was the best base model for 42 of 51 balancing authorities in the published 2026-08-07 evaluation. Prophet fits additive seasonality with weather regressors. SARIMAX captures stationary structure, with its order selected automatically.
Why inverse-MAPE weighting instead of stacking
The ensemble gives each model a share of the vote in proportion to
how accurate it has been: weight ∝ (1/MAPE)³,
normalised to sum to one. MAPE is mean absolute percentage error
— the average miss, as a share of actual demand — so a
model with lower error carries more weight. The recorded weights show how much each
model contributes. This makes the blend inspectable; it does not
establish that a blend is more accurate.
Cubing inverse MAPE concentrates weight on lower-error models. Models with similar measured error receive more similar weights.
Ensemble accuracy and tradeoffs
GridPulse used to say the ensemble bought variance reduction in the tail. That claim is withdrawn. It rested on one balancing authority improving from 38.63% to 13.61%. In the published 2026-08-07 evaluation that same authority is 13.68% under XGBoost alone and 14.72% under the ensemble — the blend is worse there.
In that evaluation, the ensemble trails best-base in aggregate: 4.35% median per-BA error against 3.69%, and a 90th percentile of 14.27% against XGBoost-alone’s 9.87%. It wins on 21 of 51 balancing authorities.
Blending improves accuracy on some balancing authorities and worsens it on others. The per-authority results show the difference.
One further correction worth stating plainly: ensemble figures published before August 2026 were optimistically biased. The weights were fitted on the same window the blend was scored against. Re-scoring strictly out-of-sample moved the median from 3.89% to 4.35% and made it worse on 33 of 51 authorities. Base-model numbers were never affected.
How accuracy is measured: recursive, 168 hours, per authority
Training holdout and observed error answer different questions
In a recursive evaluation, the model’s predictions feed forward as demand-lag inputs instead of receiving the true demand for each next step. The training reference uses a 168-hour holdout: historical hours withheld from model fitting. It is not expected accuracy for the next seven-day forecast.
Observed error instead compares previously issued forecasts with reported demand at a stated lead and measurement window. Neither score applies automatically to another model, a baseline, or a different forecast lead.
Why there is no single “across-51” number
The published 2026-08-07 holdout reports error per balancing authority, with medians used to summarize the group. On the best-base-model-per-authority view, the lowest error was 1.72% and the highest was 23.26%. (Those two bracket the best base model per authority. The 4.35% median quoted elsewhere on this site is a different figure — the evaluated ensemble, across all 51 — and the gap between the two is the subject of the section above.) A group median does not establish the accuracy for an individual authority or the next forecast.
The live benchmark scores model series against the authority’s earliest-observed published day-ahead forecast where sufficient paired observations exist. It uses the same hours and settled actuals for both, but their forecast leads are not fully matched. It does not score the published fallback baseline.
The gates a model passes before it serves
The serve-path acceptance gate
Retraining daily means drawing a new model daily, and the draws are not all good. A historical replay of persisted model versions for one authority found that about 27% produced recursive forecasts that collapsed overnight demand — a failure that authority’s training holdout missed. The holdout scores a fresh model on sliced historical rows rather than running the deployed candidate through the recursion path production actually uses.
So before the training job promotes a candidate, it replays that candidate through the real serve path from three anchors. A candidate that fails is still kept for forensics, but the pointer stays on yesterday’s accepted model. Serving a slightly older model that behaves is better than serving a new one that does not, which is the same rule the data layer follows when an upstream API degrades.
The visibility gate, and why it asks the most generous question
A second gate decides which authorities appear in the product at all. It is judged on the training holdout against the seven-day band — deliberately the most generous question available, because hiding a balancing authority is a heavy act. As measured on 2026-07-28, it hides none of the 51.
The cost of that generosity is real, and worth stating: one authority sat at 6.96% on the holdout, well inside the bar, while every served model graded a rollback at 24 hours on the live path. Both numbers were right about their own question, and nothing compared them. A second opinion is now computed every hour, published beside the verdict, and logged whenever the two disagree.
Where the weather inputs change
The scoring job produces a 30-day hourly demand forecast, but real weather forecast data only covers the first 16 days. Past that boundary the weather inputs are driven from a per-authority seasonal normal — a trailing ten-year “normal weather year” — with the current weather regime decaying into that normal over about five days.
The Forecast tab draws a visible divider at day 16 and labels both sides. For trained-model curves, demand beyond that line uses seasonal weather assumptions rather than a current weather forecast. The repeated-day baseline does not use forecast weather. A 30-day curve does not have a matching 30-day live accuracy score.
What this does not do
- Monitoring is not proof of future accuracy. Live error tracking reports completed forecasts against reported demand between training runs. It can flag errors, but it does not establish that the next forecast is fit for an operational decision.
- It does not price energy or recommend trades. There is a merit-order cost model for context, not for decisions.
- It does not cover Alaska or Hawaii, and it covers 51 of the 63 balancing authorities that report to EIA — which is roughly 100% of lower-48 demand, because the missing ones are small.
- Its per-authority champion is not stable. Which base model wins for a given authority changes between retrains, so a specific claim about one is only true on the run that produced it.
Every number on this page traces to CANONICAL_FACTS.md, and the architecture decisions behind it — including the ones that turned out to be wrong — are in PRD.md.