Skip to content

Schema — forecast

Tables an experiment of type=forecast publishes. The forecast fact is tidy and keyed on (series, ts) — there is no row_index and no positional join to inputs.

Contents

  • Fact: *__forecast
  • Example queries

Fact — *__forecast (PK (series, ts))

column notes
series series identifier (key)
ts timestamp of the forecast point — a string; cast when range-filtering
yhat point forecast
model the series' winning model (its CV winner)
yhat_lower_<level>? lower bound at <level>% — one pair per level the run produced (yhat_lower_80, yhat_lower_95, …); NULL where the series' winner carries no band at that level
yhat_upper_<level>? upper bound at <level>%

One row per (series, ts), so SUM(yhat) counts each point once. Whether a level is calibrated is on the run's forecast diagnostics (get_insight), not in this table. Never join a forecast on row_index. Aggregate/compare per series and ts.

Example queries

Placeholder <forecast> is the data-kb name.

One series, full horizon with its 80% and 95% bands (read the level columns off the catalog first):

SELECT ts, yhat, yhat_lower_80, yhat_upper_80, yhat_lower_95, yhat_upper_95
FROM "<forecast>" WHERE series = 'store_42' ORDER BY ts;

Total predicted demand per series:

SELECT series, SUM(yhat) AS total_yhat
FROM "<forecast>" GROUP BY series ORDER BY total_yhat DESC;

Widest 80% prediction intervals:

SELECT series, ts, (yhat_upper_80 - yhat_lower_80) AS interval_width
FROM "<forecast>"
ORDER BY interval_width DESC LIMIT 20;

Range filter (cast the string ts):

SELECT series, ts, yhat
FROM "<forecast>"
WHERE CAST(ts AS TIMESTAMP) >= TIMESTAMP '2026-01-01'
ORDER BY series, ts;