Run Inspection, Diagnostics, Scenarios & Reports¶
review
¶
Monitor a running run and review a finished one — the run-inspection layer, keyed on a run_id.
Two questions, two entry points, both taking only a run_id (the run is its config, so this
layer reads the run's own raw_config back to recover what it planned to do):
monitor_run— how far along is a run in flight? Per-family job state, series done vs. the expected total, remaining, and mean fit time per family on its chosen runner. Progress is coarse: the registry has no live per-series counter — cells land when a family'swrite_cellsruns (often at job end), so done-counts step up per job. The per-job status (fromv_run_jobs) is the primary live signal; landed-cell counts refine it.
A registry row is written by the job, so a job that dies without writing leaves its row
RUNNING forever and the bar simply stops moving. Two things keep that legible. Every family
carries quiet_seconds — how long since its last registry signal — which is derived from rows
already read, costs no runtime call, and is a fact rather than a judgement (a family that
writes its cells at job end is legitimately quiet for its whole run, so a threshold here would
cry wolf). And probe=True escalates the non-terminal families to their runtime via
probes.reconcile.probe_run's reader, attaching a probes.reconcile.ProbeReport that says
whether the job is actually still alive. Registry-first is the default deliberately: a fleet
poll must never fan native calls, so escalation stays the deliberate per-run drill-down.
- review_run — how did a finished run do, in data-science detail? The best model per family and
overall, the full metric panel aggregated across every series (mean + p10/p50/p90), and each
ensemble's lift over the best base model.
- calibration_report — should you believe the numbers review_run just showed you? Whether the
point-forecast correction earned its place on this run's own held-out folds, per model, and
whether the prediction interval achieves the coverage it claims — broken out by horizon step,
because a band that averages to nominal can still be wrong at both ends.
Same pure/I-O seam as sdk: the _assemble_* functions are pure (turn reader dicts into the
result dataclasses, unit-tested offline), while monitor_run / review_run are the thin I/O
callers that read the registry (registry.reads, registry.jobs) and hand off. Plotting
(plot_progress, plot_leaderboard, plot_metric_distribution) is a convenience over the
dataclasses, with matplotlib imported lazily so it never touches the near-instant
import scale_forecasting path. For the wall-clock execution timeline, reuse
sdk.build_trace_frame + sdk.plot_trace.
ArmComparison
dataclass
¶
ArmComparison(
model_type,
compute_engine,
interval_calibration,
n_series,
n_raw_arm,
n_auto_decided,
n_compared,
n_corrected_wins,
mean_margin,
median_margin,
)
One model's point-forecast arm choice on a finished run, and what the choice was worth.
margin is always "how much the corrected arm beat the raw one by", as a fraction of the raw
arm's loss in the run's decision_metric, whichever arm the run actually shipped. Negative
means the correction lost. win_rate is the share of compared series where it won — the
number that matters more than the average, because a correction that helps 51% of series by a
lot and hurts 49% by a lot is a different proposition from one that helps everything a little.
n_raw_arm and n_auto_decided are counts rather than a single label because under
output.point_forecast="auto" the series of one model do not have to agree, and that
disagreement is the feature — reporting one of them as if it spoke for all would hide it.
raw_arm_rate
property
¶
Share of this model's series shipping the raw arm, or None when there are none.
1.0 or 0.0 under a fleetwide setting; anything between means selection actually split the
model's series, which is the thing auto exists to do and the thing worth looking at.
win_rate
property
¶
Share of compared series the corrected arm won, or None when nothing was compared.
BacktestCohort
dataclass
¶
BacktestCohort(
n_series=0,
n_full=0,
n_reduced=0,
n_unscored=0,
n_failed=0,
n_not_requested=0,
fold_histogram=dict(),
refit_modes=dict(),
staleness_gap=None,
)
How much of the panel one model was actually scored on, split by outcome.
The context a leaderboard score is meaningless without. A ragged panel does not give every
series the same backtest: backtest.make_folds drops the folds a short series cannot afford,
so full (every requested fold), reduced (some), unscored (none — too short to
backtest at all) and failed are all ordinary outcomes within a single run, and a model
whose mean error came mostly off reduced series won an easier contest than its neighbour.
n_not_requested counts series whose backtest_status is NULL, which means the run never
asked for a backtest rather than that one was attempted and produced nothing.
fold_histogram maps achieved fold count → series count, so the shape of the raggedness is
visible and not just its worst case. Keyed by the achieved count as an int; series with a
NULL n_folds_achieved are left out of it (they are still counted in n_series).
refit_modes is the other axis of the same question — not how much of the panel was
scored, but on what. It maps backtest_refit → series count: per_fold (a fresh fit at
every origin), recondition (one fit carried forward on the new observations),
extrapolate (one fit, never told what happened next), and unsupported (a frozen scheme
was asked for and this model had no seam for it, so those series refit anyway). A model showing
unsupported on a frozen run is not answering the same question as its neighbours. Ensemble
cohorts inherit their members' mode, or mixed where the members disagreed — see
ensemble_run.ensemble_refit_mode.
staleness_gap is what never refreshing the model cost this panel, in the run's decision
metric, averaged over the series that ran a control arm and weighted by cohort size. Positive
means refitting earns its keep. None on the two refit schemes, which run no control arm.
CalibrationReport
dataclass
¶
What the run's own data says about its point forecast and its prediction interval.
Two questions a forecaster asks of any system that corrects a model's output, answered from the run's held-out folds rather than from a claim in a doc:
- Was the correction worth applying?
arms, per model — win rate and margin. - Does the band mean what it says?
coverageper horizon step againstnominal, which is the width of the run's quantile set (0.8 for the shipped default of 0.1/0.5/0.9).
worst_step is the single largest deviation from nominal across every model and step: a run
whose average coverage looks fine while one end of the horizon is badly wrong should not read
as healthy, and an average will always say it does.
CoveragePoint
dataclass
¶
Achieved interval coverage at one horizon step for one model.
EnsembleLift
dataclass
¶
How much an ensemble improved on the best base model, in the run's decision metric.
lift is best_base_score − score (positive = the ensemble is better, since lower error is
better); lift_pct is that as a fraction of the base score. Compares against the single best
base model overall (best_overall), the bar an ensemble has to clear to be worth keeping.
FamilyProgress
dataclass
¶
FamilyProgress(
family,
runtime,
hardware,
status,
models,
n_expected,
n_done,
fraction,
avg_fit_seconds,
runtime_seconds,
last_signal_at=None,
quiet_seconds=None,
device_verdict=None,
)
One family's live progress on a run: its job state and how many series it has scored.
runtime / hardware name the runner this family resolved to (a Spark family's mean fit
time is only comparable to another family on the same runner). n_expected is
n_series × models-in-family (None when the run's series count isn't known);
n_done counts landed full-fit cells; fraction is their ratio. avg_fit_seconds is the
mean per-cell fit time across this family's landed cells; runtime_seconds is the job's
wall-clock once it finishes.
last_signal_at is the most recent timestamp the job row carries (ended_at →
started_at → created_at, first present wins) and quiet_seconds is its age at read
time — both None for a family with no job row yet, or an unparseable timestamp. They are
reported, never judged: how long a family may legitimately stay quiet depends on the family,
and the escalation threshold that does judge it lives with the probe
(probes.reconcile._DEFAULT_STALE_S), not here.
device_verdict is device_audit's answer to whether the accelerator this family paid for
did any work — None for every CPU family, and for a GPU family until its job finishes,
because the audit runs on the driver once the cells are written. It is the only field here that
is about cost rather than progress, and it is on the progress object because this is where a
reader is already looking at the per-family runtime and hardware.
ModelReview
dataclass
¶
ModelReview(
model_type,
family,
ensemble_id,
is_ensemble,
compute_engine,
n_series,
score,
metric_means=dict(),
metric_p10=dict(),
metric_p50=dict(),
metric_p90=dict(),
mean_fit_seconds=None,
median_fit_seconds=None,
no_artifact_rate=None,
n_predictions=0,
cohort=None,
pooled_wape=None,
n_comparable_series=None,
base_model=None,
scope_capability=None,
training_mode=None,
cov_profile=None,
is_univariate=None,
covariates_used=None,
model_spec_hash=None,
borrowed_from_run_id=None,
n_borrowed=0,
)
One model's (or ensemble pseudo-model's) outcome on a finished run, across all its series.
score is the mean of the run's decision_metric over every series (lower = better;
None when no backtest scored it). metric_means / metric_p10 / metric_p50 /
metric_p90 carry the full metric panel — the cross-series mean and the 10th/50th/90th
percentile of each metric in METRIC_COLUMNS — so distribution shape reads off the aggregates
without pulling per-series rows. ensemble_id is None for a base model; is_ensemble
is its convenience flag. n_predictions is the forecast-row count (0 flags a model that
scored metadata but produced no forecasts — a fully-failed fit).
RunProgress
dataclass
¶
A run's live progress snapshot: header status plus one FamilyProgress per family.
status is None when no run exists for the id yet. n_done / n_expected /
fraction are the run-wide roll-up across families (n_expected and fraction are
None when the series count — hence the denominator — isn't known).
probe carries the reconciled probes.reconcile.ProbeReport when monitor_run was
called with probe=True, and is None for the default registry-only read — so a caller
can always tell "the runtime agreed the job is alive" apart from "we never asked".
RunReview
dataclass
¶
RunReview(
run_id,
status,
decision_metric,
n_series,
models,
best_per_family,
best_overall,
ensembles,
ensemble_lift,
)
A finished run's data-science review: the leaderboard plus derived bests and ensemble lift.
models is every model best-first (lowest score). best_per_family maps each base
family to its champion; best_overall is the single best base model; ensembles are the
ensemble pseudo-models; ensemble_lift scores each against best_overall.
best_overall
¶
The single best base model (lowest score); None if no base model was scored.
Source code in src/scale_forecasting/review.py
best_per_family
¶
Map each base family to its champion (lowest-score scored model in that family).
Source code in src/scale_forecasting/review.py
build_attributions_frame
¶
Unpack Tier 1 (level="global") or Tier 2 (level="local") attribution rows into a
tidy DataFrame (pure, offline).
level="global"unpacksfit_diagnostics["feature_attributions"]into columns["ts_id", "model_type", "compute_engine", "feature", "importance"]sorted byts_id, model_type, importance DESC.level="local"unpacks per-horizon-stepexplanationsJSON into columns["ts_id", "model_type", "compute_engine", "forecast_date", "yhat", "baseline_score", "feature", "attribution"]sorted byts_id, model_type, forecast_date.
Source code in src/scale_forecasting/review.py
2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 | |
build_calibration_frames
¶
Convert a CalibrationReport into (arms_df, coverage_df) pandas DataFrames (pure).
arms_dfsummarizes point-forecast bias-correction arm selection (rawvscorrectedwin rates and relative margins per model).coverage_dfreports empirical prediction-interval coverage and mean interval width at each horizon step ($h = 1 \dots H$) againstreport.nominal_coverage.
Source code in src/scale_forecasting/review.py
1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 | |
build_cohorts_frame
¶
Convert a RunReview's per-model BacktestCohort and comparable holdout metrics into a
tidy DataFrame (pure, offline).
Exposes v_backtest_coverage and v_model_leaderboard_comparable side-by-side so you can
inspect achieved fold counts (fold_histogram), refit modes (per_fold / recondition /
extrapolate / unsupported), staleness_gap (cost of never refitting), and holdout-fold
pooled_wape alongside the fleet mean score.
Source code in src/scale_forecasting/review.py
build_ensemble_weights_frame
¶
Unpack learned ensemble stacking weights from read_best_params rows or a
build_best_params_frame DataFrame into a long-form DataFrame (pure, offline).
Columns: ts_id, ensemble_model, strategy, base_model, weight, wape.
Source code in src/scale_forecasting/review.py
1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 | |
build_hierarchy_frame
¶
Summarize hierarchical predictions by model and level, verifying additive coherence ($\max_t |\hat{y}{\text{total},t} - \sum|$) (pure).}} \hat{y}_{b,t
Source code in src/scale_forecasting/review.py
build_leaderboard_frame
¶
Convert a RunReview into a ranked pandas DataFrame (pure, offline).
Includes core ranking columns (rank, model_type, family, is_ensemble, compute_engine,
n_series, score, pooled_wape, n_comparable_series), key accuracy and interval metrics
(mean_wape, mean_smape, mean_mase, mean_rmsse, mean_mae, mean_rmse,
mean_coverage, mean_interval_score), runtime/reliability (mean_fit_seconds,
median_fit_seconds, no_artifact_rate, n_predictions), and ensemble lift
(lift_vs_best_base, lift_pct). When all_metrics=True, appends mean_<metric> and
p50_<metric> for every metric in METRIC_COLUMNS.
Source code in src/scale_forecasting/review.py
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 | |
build_predictions_frame
¶
Stack historical observations, out-of-fold backtests, and forward predictions into one tidy
DataFrame (pure, offline).
Columns: ts_id, segment ("history" / "oof" / "forecast"), model_type,
ds, y_true, yhat, yhat_lower, yhat_upper, fold_id.
Source code in src/scale_forecasting/review.py
1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 | |
calibration_report
¶
Read a finished run's point-forecast and interval diagnostic.
Reads the config (for the decision metric and the quantile set that defines nominal coverage),
the per-model arm rollup (registry.reads.read_arm_comparison) and the per-step coverage panel
(registry.reads.read_coverage_by_step), then composes via _assemble_calibration.
Source code in src/scale_forecasting/review.py
ensemble_lift
¶
Score each ensemble's improvement over the best base model, best lift first.
Empty when there is no scored base model to compare against, or no scored ensemble.
Source code in src/scale_forecasting/review.py
explain_forecast_frame
¶
explain_forecast_frame(
frame,
*,
ts_id=None,
model_type=None,
seasonal_period=7,
covariate_df=None,
)
Decompose a single series' historical actuals, backtest OOF trajectory, and future forecast into interpretable structural components (pure, offline).
Given a frame from build_predictions_frame (or Forecaster.predictions_df), extracts:
1. trend_baseline & level_shift: Smooth rolling/piecewise trend plus abrupt regime-jump
detection via features.level_shift_step.
2. seasonal_effect: Repeating periodic cycle (period seasonal_period, default 7)
estimated from detrended history and projected across the forecast horizon.
3. covariate_effect (plus cov_<name> columns when covariate_df is provided):
Ridge-stabilized linear attribution of numeric exogenous covariates across history and the
future forecast horizon.
4. oof_residual & interval_width: Out-of-fold error ($y - \hat{y}{\text{OOF}}$) on
backtest dates and prediction-interval width ($\hat{y} -
\hat{y}_{\text{lower}}$) across the future horizon.}
Source code in src/scale_forecasting/review.py
2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 | |
family_of
¶
The family a result row belongs to: "ensemble" if ensemble_id is set, else the
model's registered family ("unknown" for a name this build no longer registers).
Source code in src/scale_forecasting/review.py
monitor_run
¶
Read a run's live progress: header status + per-family job state + series done vs. expected.
Reads the run's header (registry.reads.read_run_summary), its config
(registry.reads.read_run_config, for the expected-work denominator), its jobs
(registry.jobs.read_run_jobs) and its landed-cell counts (registry.reads.read_progress),
then composes them via _assemble_progress. Poll it while a run is in flight; returns a
status-only snapshot when the run id has never run. Every family carries quiet_seconds
either way — the "is this bar frozen or just coarse" signal, free because it comes off rows
already read.
probe=True additionally escalates the run's non-terminal jobs to their runtime and attaches
the reconciled probes.reconcile.ProbeReport as RunProgress.probe — the answer to is this
job still alive, which the registry alone cannot give. It shares one pass of reads with the
registry side (probes.reconcile.read_and_probe), so probing costs the native calls and not a
second set of queries; an already-terminal run short-circuits and touches no runtime at all.
stale_after_s overrides the probe's startup grace (see probes.reconcile.probe_run) and
is ignored when probe is False.
Source code in src/scale_forecasting/review.py
plot_attributions
¶
plot_attributions(
attributions_df,
*,
ts_id=None,
model_type=None,
top_k=12,
ax=None,
title=None,
)
Plot Tier 1 global driver importance or Tier 2 per-horizon-step local feature attributions (pure, offline).
Source code in src/scale_forecasting/review.py
2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 | |
plot_calibration
¶
Plot empirical prediction-interval coverage and mean interval width by horizon step from a
CalibrationReport (pure, offline).
When ax=None, creates a two-panel side-by-side figure:
* Left panel: Empirical coverage by horizon step ($h = 1 \dots H$) vs. nominal target
(dashed horizontal reference line at report.nominal_coverage).
* Right panel: Mean prediction-interval width ($\hat{y}{\text{upper}} -
\hat{y}$) by horizon step.}
Source code in src/scale_forecasting/review.py
1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 | |
plot_ensemble_weights
¶
Plot base-model stacking weights across learned ensemble strategies (nnls, ridge, xgb)
as a horizontal stacked bar chart (pure, offline).
Source code in src/scale_forecasting/review.py
1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 | |
plot_forecast_explanation
¶
Render a 4-panel forecast explainability & decomposition chart from an
explain_forecast_frame DataFrame (pure, offline).
Panels:
1. Trajectory, Trend Baseline & Regime Shift: History, OOF backtest, future forecast + 80%
prediction interval, and structural trend baseline.
2. Periodic Seasonal Cycle: Extracted seasonal wave across history and forecast horizon.
3. Exogenous Covariate Attribution (or Detrended Residual Wave when univariate): Individual
cov_<name> contributions and total covariate effect across history and the future
horizon.
4. Backtest OOF Residuals & Forecast Horizon Uncertainty: Out-of-fold errors ($y -
\hat{y}{\text{OOF}}$) transitioning into future 80% interval width
($\hat{y}$).}} - \hat{y}_{\text{lower}
Source code in src/scale_forecasting/review.py
2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 | |
plot_forecasts_frame
¶
plot_forecasts_frame(
frame,
*,
ts_id=None,
ts_ids=None,
models=None,
max_series=3,
history_tail=None,
ax=None,
title=None,
)
Plot historical actuals, out-of-fold backtests, and forward predictions with 80% intervals
from a build_predictions_frame DataFrame (pure, offline).
Renders up to max_series subplots (one per ts_id) and returns the matplotlib Axes
(or array of Axes when multiple series are plotted).
Source code in src/scale_forecasting/review.py
1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 | |
plot_hierarchy_frame
¶
Plot top-level (__total__) forecast against the sum of bottom-level leaf forecasts (or
a level summary from build_hierarchy_frame) to visually verify hierarchical coherence.
Source code in src/scale_forecasting/review.py
1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 | |
plot_leaderboard
¶
Render a RunReview's scored models as a ranked bar chart; return the matplotlib Axes.
One horizontal bar per model, best (lowest decision-metric error) on top, coloured base vs.
ensemble (_BASE_COLOR/_ENSEMBLE_COLOR) with a legend when both are present and the score
labelled at each bar end. Unscored models (no backtest) are dropped. top caps the bar count.
matplotlib imports lazily; a review with no scored model renders an empty titled axes.
Source code in src/scale_forecasting/review.py
plot_metric_distribution
¶
Render each model's cross-series spread for one metric (p10–p90 range, p50 dot) as Axes.
One row per scored model: a thin line from the 10th to the 90th cross-series percentile with a
marker at the median, coloured base vs. ensemble — the distribution shape (not just the mean) of
a metric across every series, read straight off the server-side aggregates so it holds at scale.
metric defaults to the run's decision metric. matplotlib imports lazily; a review with no
aggregated percentiles renders an empty titled axes.
Source code in src/scale_forecasting/review.py
plot_progress
¶
Render a RunProgress as a per-family progress bar chart and return the matplotlib Axes.
One horizontal bar per family — length is the fraction of expected cells that have landed,
colour is the family's job status (_STATUS_COLORS) — with the done/expected count and
status labelled at the bar end (the label doubles as the status's secondary encoding). Families
keep DAG order, ensemble last. matplotlib imports lazily so it never touches the package import;
an empty run renders an empty titled axes rather than raising.
A non-terminal family also gets its quiet_seconds in the label (quiet 22m): the bar of a
job that died mid-run stops moving and its status stays RUNNING, so without this a dead run
and a slow one are pixel-identical. It is reported as an age, not flagged against a threshold —
a family that writes its cells at job end is legitimately quiet the whole time, and a
cry-wolf marker teaches the reader to ignore it. When a probes.reconcile.ProbeReport is
attached (monitor_run(probe=True)), its verdict replaces the age for the families it
covers, since a live reading beats an inference from silence.
A family that finished with a device verdict gets two more words at the end of its label
(gpu used / gpu idle / no gpu). A GPU family's bar is otherwise indistinguishable
from a CPU family's, which is the whole reason the accelerator went twenty-one jobs without
anyone noticing it was doing nothing.
Source code in src/scale_forecasting/review.py
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 | |
review_run
¶
Read a finished run's data-science review: bests per family/overall + ensemble lift + panel.
Reads the header (registry.reads.read_run_summary), the config (for the decision metric and
series count), the leaderboard (registry.reads.read_leaderboard), the cross-series aggregates
(registry.reads.read_metric_aggregates), per-model prediction counts
(registry.reads.read_prediction_counts), and the two cohort reads that say whether the
ranking is comparable at all — registry.reads.read_backtest_coverage and
registry.reads.read_comparable_leaderboard — then composes via _assemble_review.
Source code in src/scale_forecasting/review.py
diagnostics
¶
Econometric & operational forecast diagnostics — pure DataFrame analytics (zero GCP imports).
Provides the post-backtest and post-run diagnostic suite for evaluating model value, lead-time accuracy decay, directional bias, and temporal/lead-time aggregation:
- Forecast Value Added (FVA) & Statistical Significance (
compute_fva,diebold_mariano_test) — step-by-step accuracy waterfall across modeling tiers (Baseline->Statistical->ML->Deep Learning->AutoML->Ensemble) with Harvey, Leybourne & Newbold (1997) modified Diebold–Mariano and paired Wilcoxon signed-rank $p$-values on out-of-fold loss differentials. - Lead-Time Horizon Decay (
compute_horizon_decay) — accuracy and empirical interval coverage at each forecast step $h = 1 \dots H$, plus the crossover horizon step $h^*$ where errors exceed the baseline. - Directional Bias & Tracking Signal Audit (
compute_bias_report) — per-(ts_id, model_type)audit of signed percentage bias, Trigg / APICS Tracking Signal ($\text{TS} = \sum e_t / \text{MAD}$), and consecutive over/under-forecasting fold streaks. - Autocorrelation-Aware Temporal & Lead-Time Rollup (
rollup_time) — aggregates point forecasts and actuals to Weekly ("W"), Monthly ("MS"/"M"), Quarterly ("QS"/"Q"), or fixed $L$-step supplier lead-time windows (freq="7D"orwindow=7) while computing period-level prediction intervals from empirical multi-step error covariance rather than naively summing daily interval bounds. - Operational Inventory & Replenishment Policy Translators (
compute_inventory_policy,plot_inventory_policy) — translates calibrated multi-step forecasts, AR(1) error covariance, and supplier lead-time variability ($\mu_L, \sigma_L$) into continuous-review $(s, S)$ and periodic-review $(R, S)$ Reorder Points (ROP), Safety Stock (SS), Order-Up-To Levels ($S$), Recommended Order Quantities ($Q$), and Newsvendor Expected Holding vs. Stockout Cost tables under Cycle Service Level ($\alpha$), Unit Fill Rate ($\beta$), or critical-fractile cost optimization.
compute_anomalies
¶
compute_anomalies(
oof_df=None,
*,
series_df=None,
predictions_df=None,
actuals_df=None,
model_name=None,
anomaly_prob_threshold=0.95,
confidence_level=0.8,
anomalies_only=False,
freq="D",
)
Universal time-series anomaly detection across any of the 37 models, 9 ensembles, or STL.
Computes the exact two-sided Gaussian / conformal tail probability used by BigQuery ML
(ML.DETECT_ANOMALIES):
$$p_{\text{anom}}(t) = 2\,\Phi\left(|y_t - \hat{y}_t| / \hat{\sigma}_t\right) - 1$$
where $\hat{\sigma}t = (U_t - L_t) / (2\,z)$ is recovered from each model's
prediction interval $[L_t, U_t]$ at confidence_level (falling back to the per-series
robust MAD scale $1.4826 \cdot \text{median}(|y - \hat{y}|)$ when intervals are absent or
degenerate), and rescales lower_bound and upper_bound to
anomaly_prob_threshold ($\tau \in (0, 1)$).
Supports three input modes:
1. Out-of-fold backtest predictions (oof_df): Evaluates any Python, AutoML, BigQuery, or
Ensemble model (or model_name="all") over its out-of-fold predictions (backtest_oof).
2. Published forecasts + new actuals (predictions_df + actuals_df): Flags live
anomalies as new actuals arrive against published forecast_predictions.
3. Model-free historical series (series_df or model_name="stl"): Decomposes raw
historical series via seasonal-trend baseline + robust MAD residuals when no model OOF frame
is provided.
Returns a tidy DataFrame with columns ["ts_id", "model_type", "ds", "y", "yhat",
"is_anomaly", "anomaly_probability", "lower_bound", "upper_bound"].
Source code in src/scale_forecasting/diagnostics.py
1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 | |
compute_bias_report
¶
compute_bias_report(
oof_df,
*,
threshold_pct=0.05,
tracking_signal_limit=4.0,
min_consecutive_folds=2,
)
Audit every (ts_id, model_type) cell for chronic over- or under-forecasting bias.
Computes signed error $\hat{y}_t - y_t$, percentage bias $\sum(\hat{y}_t - y_t)/\sum|y_t|$, the Trigg / APICS Tracking Signal $\text{TS} = \sum(\hat{y}_t - y_t) / \text{MAD}$, and the longest streak of consecutive folds with positive (over-forecasting) or negative (under-forecasting) fold-mean error.
Source code in src/scale_forecasting/diagnostics.py
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 | |
compute_change_points
¶
compute_change_points(
series_df=None,
*,
oof_df=None,
model_name=None,
exclude_gap_edges=True,
min_gap_steps=1,
freq="D",
sigma_threshold=3.0,
min_segment=7,
)
Detect sustained structural change-point windows on raw series or model residuals (pure).
- When
model_nameisNone(or"raw") andseries_dfis provided, segments the raw target series $y_t$ to identify sustained level shifts (model_type = "raw"). - When
oof_dfis provided withmodel_name(e.g."xgboost","tide","arima_plus@xreg","ensemble_champion", or"all"), segments each model's residuals ($e_t = y_t - \hat{y}_t$) to identify structural regime shifts that the model failed to absorb (window_avg,window_min,window_max, andwindow_stddevare reported in the series' original $y_t$ units over the detected break window so they remain directly comparable to BigQueryML.DETECT_CHANGE_POINTS).
Annotates each detected window with gap_edge_missing_steps and is_gap_edge (filtering
out ingestion outage boundaries when exclude_gap_edges=True).
Returns a tidy DataFrame with columns ["ts_id", "model_type", "begin_date", "end_date",
"window_count", "window_avg", "window_min", "window_max", "window_stddev",
"gap_edge_missing_steps", "is_gap_edge", "status"].
Source code in src/scale_forecasting/diagnostics.py
1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 | |
compute_exceptions_queue
¶
compute_exceptions_queue(
*,
oof_df=None,
predictions_df=None,
realized_df=None,
stability_df=None,
floor=None,
cap=None,
metric="wape",
baseline=None,
top_n=50,
)
Build the Top-$N$ Exception Triage Queue across 5 operational forecasting rules.
Evaluates each (ts_id, model_type) cell and assigns a composite severity_score and
plain-English exception_reasons across:
worse_than_naive: Model's held-out backtest loss exceeds the naive benchmark on this series.chronic_bias: Trigg Tracking Signal $|\text{TS}| \ge 4.0$ or $\ge 2$ consecutive biased folds (chronic over- or under-forecasting).realized_drift: Realized live error exceeds $1.5\times$ expected backtest error.whipsaw_jump: Cross-run forecast revision jump $\ge 30\%$ on an overlapping date.capacity_ceiling_hit: Forward forecast hits or clips against physicalfloor/capbounds on $\ge 20\%$ of horizon steps.
Source code in src/scale_forecasting/diagnostics.py
1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 | |
compute_fva
¶
Compute the Forecast Value Added (FVA) Waterfall & Statistical Significance table.
Evaluates each model_type in oof_df against baseline (defaulting to
"naive_seasonal" or the simplest benchmark present) and reports:
fva_stage_rank/fva_stage: position on the FVA ladder (Baseline,Statistical,Machine Learning,Deep Learning,AutoML,Ensemble).metric: the decision metric name (e.g."wape").mean_metric/pooled_wape/mean_mae: accuracy across all scored series.baseline_model/baseline_metric: benchmark reference loss.fva_abs($L_{\text{baseline}} - L_{\text{model}}$) andfva_pct($(L_{\text{baseline}} - L_{\text{model}}) / L_{\text{baseline}}$): positive values mean the model added value over the baseline; negative values mean it destroyed value.win_rate_vs_baseline: fraction of series wheremodel_typeachieved strictly lower loss thanbaseline.dm_stat,p_value_dm,p_value_wilcoxon,significant_at_05: Harvey–Leybourne– Newbold (1997) modified Diebold–Mariano statistic/p-value and paired Wilcoxon signed-rank p-value testing whether the accuracy gain overbaselineis statistically significant.
Source code in src/scale_forecasting/diagnostics.py
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 | |
compute_horizon_decay
¶
Evaluate forecast accuracy, bias, and interval coverage by lead-time step ($h = 1 \dots H$).
Also identifies crossover_step ($h^*$) for each model: the first horizon step where the
model's loss exceeds the baseline model's loss (or None if it beats the baseline across
the entire horizon).
Source code in src/scale_forecasting/diagnostics.py
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 | |
compute_inventory_policy
¶
compute_inventory_policy(
predictions_df,
*,
oof_df=None,
inventory_df=None,
model_name=None,
model_type=None,
lead_time=7.0,
lead_time_std=0.0,
review_period=0.0,
service_level=0.95,
fill_rate=None,
on_hand=0.0,
on_order=0.0,
backorders=0.0,
holding_cost=1.0,
stockout_cost=9.0,
min_order_qty=0.0,
order_multiple=0.0,
confidence_level=0.8,
)
Translate calibrated multi-step forecasts into inventory & replenishment policies.
Combines non-stationary forward forecasts ($\hat{y}_1 \dots \hat{y}_H$), empirical AR(1)
multi-step forecast error covariance ($\hat{\rho}_1$ estimated from oof_df), and supplier
lead-time variability ($\mu_L, \sigma_L$) to compute continuous-review $(s, S)$ and
periodic-review $(R, S)$ policies:
$$\sigma_P = \sqrt{\sum_{i=1}^{\lceil P \rceil} \sigma_i^2 + 2\sum_{1 \le i < j \le \lceil P \rceil} \hat{\rho}1^{j-i}\,\sigma_i\,\sigma_j + \sigma_L^2\,\bar{\mu}$$}}^2
Supports three service-level targeting modes (globally or per-SKU via inventory_df):
1. Cycle Service Level (service_level, Type-1 CSL $\alpha \in (0, 1)$):
Sets $z = \Phi^{-1}(\alpha)$ so the probability of zero stockout during the protection
window $P = \mu_L + R$ equals $\alpha$.
2. Unit Fill Rate (fill_rate, Type-2 Fill Rate $\beta \in (0, 1)$):
Solves the standard normal loss equation $G(z) = (1 - \beta)\,\mu_P / \sigma_P$ for $z$
so the expected fraction of demand satisfied immediately from stock equals $\beta$.
3. Newsvendor Critical Fractile (service_level=None and fill_rate=None):
Computes $\alpha^* = c_u / (c_u + c_o)$ from stockout_cost ($c_u$) and holding_cost
($c_o$) to minimize total expected holding + stockout cost per cycle.
Also reports naive_iid_safety_stock ($\rho = 0$) and naive_linear_safety_stock
($\rho = 1$) alongside safety_stock so supply-chain planners can audit the buffer impact
of autocorrelation-aware interval aggregation.
Source code in src/scale_forecasting/diagnostics.py
2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 | |
compute_realized_accuracy
¶
compute_realized_accuracy(
predictions_df,
actuals_df,
*,
oof_df=None,
metric="wape",
drift_threshold=1.5,
)
Join published forward forecasts with newly arrived actuals on (ts_id, date) and score
realized accuracy, interval coverage, and accuracy drift relative to backtest expectations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predictions_df
|
DataFrame
|
Forward predictions frame containing |
required |
actuals_df
|
DataFrame
|
Newly arrived actuals containing |
required |
oof_df
|
DataFrame | None
|
Optional backtest OOF frame from the same run to compare realized error against
expected backtest error ( |
None
|
metric
|
str
|
Primary accuracy metric (default |
'wape'
|
drift_threshold
|
float
|
Ratio threshold above which |
1.5
|
Source code in src/scale_forecasting/diagnostics.py
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 | |
compute_revision_stability
¶
Measure cross-run forecast revision stability ("whipsaw") across overlapping target dates.
Accepts either a mapping {run_id: predictions_df} (ordered oldest-to-newest) or a single
concatenated DataFrame containing a run_id column. Computes per (ts_id, model_type):
n_runs/n_overlapping_datesmean_forecast: average forecast magnitude across overlapping datesmar: Mean Absolute Revision $|\hat{y}^{(r)}_t - \hat{y}^{(r-1)}_t|$revision_cov: Coefficient of Variation of revisions ($\text{std}(\Delta \hat{y}) / |\overline{\hat{y}}|$)max_single_step_jump_pct: largest single-date percentage jump between consecutive runswhipsaw_flagged: True whenmax_single_step_jump_pct >= jump_threshold_pct
Source code in src/scale_forecasting/diagnostics.py
1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 | |
diebold_mariano_test
¶
Harvey, Leybourne & Newbold (1997) modified Diebold–Mariano test on paired losses.
Tests $H_0: \mathbb{E}[L(\text{model}) - L(\text{baseline})] = 0$ against the two-sided alternative using Newey–West / Bartlett-style autocovariance up to lag $h - 1$ and the HLN small-sample Student-$t_{n-1}$ adjustment:
$$\kappa_{\text{HLN}} = \sqrt{\frac{n + 1 - 2h + h(h - 1)/n}{n}}$$
Returns (dm_stat, p_value). Negative dm_stat means loss_model < loss_baseline
(the candidate model improved on the baseline).
Source code in src/scale_forecasting/diagnostics.py
plot_fva
¶
Plot the Forecast Value Added (FVA) accuracy waterfall by model and modeling tier.
Source code in src/scale_forecasting/diagnostics.py
plot_horizon_decay
¶
Plot forecast error decay across lead-time steps $h = 1 \dots H$ per model.
Source code in src/scale_forecasting/diagnostics.py
plot_inventory_policy
¶
Plot Reorder Points (ROP = Lead-Time Demand + Safety Stock), Order-Up-To Levels ($S$),
and Current Inventory Position per (ts_id, model_type).
Source code in src/scale_forecasting/diagnostics.py
2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 | |
rollup_time
¶
Aggregate forecasts (and optional actuals) to coarser calendar buckets or lead-time windows with autocorrelation-aware prediction intervals.
Why this exists: daily point forecasts (yhat) sum linearly into Weekly ("W") or Monthly
("MS" / "M") totals, but daily yhat_lower and yhat_upper cannot be summed linearly
(summing daily bounds assumes perfect $\rho = 1.0$ error correlation across days, inflating a
7-day interval by up to $2.6\times$). rollup_time estimates the lag-1 residual
autocorrelation $\hat{\rho}1$ from oof_df (defaulting to a conservative $\rho = 0.35$
when oof_df is omitted) and computes period-level uncertainty via the AR(1) sum variance
$\text{Var}(\sum
\sigma_i\sigma_j$, reporting both the calibrated bounds (}^k e_i) = \sum_i \sigma_i^2 + 2\sum_{i<j} \hat{\rho}_1^{j-iyhat_lower, yhat_upper) and
the naive linear sum (naive_sum_lower, naive_sum_upper) for side-by-side comparison.
Source code in src/scale_forecasting/diagnostics.py
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 | |
wilcoxon_pvalue
¶
Two-sided paired Wilcoxon signed-rank $p$-value on finite non-tied loss differentials.
Source code in src/scale_forecasting/diagnostics.py
scenarios
¶
Counterfactual "What-If" scenario & price-elasticity simulator — pure, zero GCP imports.
Allows planners and AI agents to perturb future exogenous covariates (promotions, pricing, weather,
macro indices, capacity caps) across the entire forecast horizon or within specific date windows
(e.g., a 2-week promotion followed by post-promo dip) and compare baseline vs. counterfactual unit
volume, prediction intervals, and dollar revenue (price * yhat).
apply_scenario_shocks
¶
Apply counterfactual covariate shocks to a copy of df (pure).
Supports full-horizon or date-windowed perturbations:
- Multiplicative:
{"price_index": "*0.85"},{"price_index": "-15%"}, or{"price_index": {"op": "mul", "value": 0.85, "start_date": "2026-03-01", "end_date": "2026-03-07"}} - Additive:
{"temperature": "+5.0"}or{"temperature": {"op": "add", "value": 5.0}} - Override:
{"promo": "=1"},{"promo": 1}, or{"promo": {"op": "set", "value": 1.0}}
Source code in src/scale_forecasting/scenarios.py
plot_scenarios
¶
Plot baseline vs. counterfactual scenario forecast trajectories (or revenue).
Source code in src/scale_forecasting/scenarios.py
simulate_scenarios
¶
simulate_scenarios(
data,
cfg,
scenarios,
*,
models=None,
future_covariates_df=None,
price_col=None,
)
Run counterfactual covariate scenarios across data and models (pure, offline).
Fits each candidate model once on each series' historical trajectory, then evaluates both the
unshocked "baseline" future covariate trajectory and each named scenario in scenarios.
When price_col is provided (or automatically detected from "price" / "price_index"),
computes baseline_revenue, scenario_revenue, revenue_delta, revenue_delta_pct,
and implied_elasticity ($\frac{\%\Delta \hat{y}}{\%\Delta p}$).
Source code in src/scale_forecasting/scenarios.py
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 | |
summarize_scenarios
¶
Roll up step-level simulate_scenarios output into horizon totals per
(scenario, ts_id, model_type).
Source code in src/scale_forecasting/scenarios.py
report
¶
Self-contained interactive HTML & JSON executive forecast report — pure, zero GCP imports.
Generates a single-file, zero-server, offline-capable .html report (plus optional .json
summary payload) combining:
- Executive Summary & KPI Cards — run identity, series count, model count, winning model, headline Forecast Value Added (FVA %), and active exception count.
- Model Leaderboard & FVA Waterfall — full metric table alongside an inline SVG Forecast Value
Added bar chart with Harvey–Leybourne–Newbold (1997) modified Diebold–Mariano significance
indicators (
p < 0.05). - Top-N Exception Triage Queue — interactive filterable table of flagged
(ts_id, model_type)cells acrossworse_than_naive,chronic_bias,realized_drift,whipsaw_jump, andcapacity_ceiling_hitwith plain-English explanations. - Lead-Time Horizon Decay & Bias Audit — inline SVG step-by-step error curve ($h = 1 \dots H$) and Trigg Tracking Signal table.
- Interactive Series & Multi-Quantile Fan Explorer — client-side
ts_idandmodel_typeselector rendering an interactive SVG chart of historical actuals, point forecast, $80\%$ calibrated interval, and multi-percentile fan (p10–p95). - Inventory & Replenishment Policy (ROP / Safety Stock) — dynamic safety stock, reorder point ($s$), order-up-to target ($S$), and recommended order quantity combining forward forecasts, AR(1) multi-step error covariance, and supplier lead-time variability.
build_report_payload
¶
build_report_payload(
*,
title="Scale-Forecasting Executive Workbench Report",
run_id="local-workbench",
leaderboard_df=None,
oof_df=None,
predictions_df=None,
history_df=None,
fva_df=None,
horizon_decay_df=None,
bias_df=None,
exceptions_df=None,
realized_df=None,
stability_df=None,
inventory_policy_df=None,
metric="wape",
)
Assemble the structured dictionary backing both HTML and JSON report exports.
Source code in src/scale_forecasting/report.py
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | |
export_report
¶
Export a self-contained .html report (and optional .json sidecar) from a BakeOff,
PlaygroundRun, Forecaster, or dictionary of DataFrames.
Source code in src/scale_forecasting/report.py
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 | |
main
¶
CLI entry point: python -m scale_forecasting.report --out report.html.
Source code in src/scale_forecasting/report.py
render_html_report
¶
render_html_report(
*,
title="Scale-Forecasting Executive Workbench Report",
run_id="local-workbench",
leaderboard_df=None,
oof_df=None,
predictions_df=None,
history_df=None,
fva_df=None,
horizon_decay_df=None,
bias_df=None,
exceptions_df=None,
realized_df=None,
stability_df=None,
inventory_policy_df=None,
metric="wape",
)
Render a complete, self-contained single-file interactive HTML report.
Source code in src/scale_forecasting/report.py
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 | |