Forecaster (SDK)¶
sdk
¶
The easy path: a thin, well-typed Forecaster facade over main.run.
This is the "simple SDK on top" — one class you point at a config and call. It adds no
forecasting logic; every method delegates to the same code the CLI and Composer run
(scale_forecasting.main.run, the run registry, the config layer), so a run driven from the
SDK is byte-for-byte the run driven from python -m scale_forecasting.main. Users who need
to drive Spark or Ray themselves skip this class entirely and call the direct surface
(run_group, make_group_runner,
make_chunk_runner, run_cell) — both paths reuse the identical model machinery. See
docs/using_the_sdk.md.
Import cost: this module is cheap to import. The heavy model modules load only when a method that
runs models is called (main is imported lazily inside run/dry_run), which preserves the
near-instant import scale_forecasting contract enforced by test_sdk.py.
DryRunResult
dataclass
¶
What a run would do, computed offline — no GCP calls, no compute launched.
run_id is the deterministic config hash (identical to what an actual run lands under);
workload is the estimated work (cells always; fits and fold cohorts only once a caller has
supplied per-series lengths, which this offline path has not); python_models/bq_models
are the runtime split. fanout is the same estimate narrowed to its four count fields, kept
because it is the older name and is in the public __init__.
Forecaster
¶
The easy path: point it at a config, then dry_run, run, status, wait, or results.
A run driven here is identical to the CLI/Composer run — this class only wraps
scale_forecasting.main.run. Construct from an in-memory RunConfig, or use
from_file / from_dict, or from_run_id to recover the config a run already landed under.
An optional settings injects the GCP infra identity;
None resolves it from the SF_* environment at run time (the default deployments use).
The lifecycle closes the loop from one object: dry_run (offline plan), dag (the planned
per-job DAG), run (execute), status/wait (track a submission), results (read the
per-model leaderboard), jobs (the per-job cross-system trace), and trace (the per-job +
per-cell execution timeline) — all keyed by the config's deterministic run_id, so
status/results/jobs/trace work as a reattach path even in a fresh process.
Source code in src/scale_forecasting/sdk.py
attributions_df
¶
attributions_df(
run_id=None,
*,
ts_id=None,
models=None,
model_type=None,
level="global",
limit=5000,
)
Return a tidy DataFrame of Tier 1 (level="global") or Tier 2 (level="local")
feature attributions for run_id.
level="global"readsforecast_metadata.fit_diagnostics["feature_attributions"]and returns["ts_id", "model_type", "compute_engine", "feature", "importance"].level="local"reads per-horizon-stepforecast_predictions.explanationsand returns["ts_id", "model_type", "compute_engine", "forecast_date", "yhat", "baseline_score", "feature", "attribution"].
Source code in src/scale_forecasting/sdk.py
best_params_df
¶
Return a DataFrame unpacking forecast_metadata.best_params for run_id.
Includes both Optuna HPO winning hyperparameters (for base models) and learned ensemble
stacking weights (nnls / ridge / xgb for ensemble_* rows).
Source code in src/scale_forecasting/sdk.py
bias_df
¶
Alias for :meth:bias_report_df matching :meth:BakeOff.bias_df.
Source code in src/scale_forecasting/sdk.py
bias_report_df
¶
Audit every (ts_id, model_type) cell for directional bias and Trigg Tracking Signal
on run_id.
Source code in src/scale_forecasting/sdk.py
calibration
¶
Return the review.CalibrationReport for run_id (point-forecast arm selection and
per-step prediction-interval coverage).
Source code in src/scale_forecasting/sdk.py
calibration_df
¶
Return (arms_df, coverage_df) DataFrames from calibration for run_id (or a
single DataFrame when by='horizon' or by='arms').
Source code in src/scale_forecasting/sdk.py
cancel
¶
Cancel a run (or one family) — a preview by default; stop for real only on confirm.
Delegates to probes.cancel.cancel_run for run_id (default: this config's id): it
probes the run to reconcile live state, then returns a CancelReport. Without confirm
the report is a blast-radius preview — it touches no runtime and no registry, so you see
exactly which jobs would stop and what partial data is retained. With confirm=True it
stops each non-terminal family's runtime job, finalizes its registry row to CANCELLED
(retaining — never deleting — landed partial results), records reason + the ADC actor
for audit, and rolls the run header up. job narrows to one family
(statistical/ml/deep_learning/native/ensemble); an already-terminal job
is a no-op.
Source code in src/scale_forecasting/sdk.py
cohorts_df
¶
Return a DataFrame of backtest cohort coverage, achieved fold histograms, refit
modes, staleness_gap, and holdout-fold pooled_wape for run_id.
Source code in src/scale_forecasting/sdk.py
dag
¶
The planned execution DAG for this config — pure, offline, no GCP.
Resolves the config into its nodes (dag.dag_nodes): one per model family plus the ensemble
(when enabled), each with the deterministic job_key it will run under, its resolved
runtime/hardware, and its upstream dependencies. Because job_key\ s are derived from
the config alone, this gives every job's identity before the run — the offline counterpart
to jobs, so a caller can line up planned nodes against executed rows by job_key.
Planning never refuses — this stays total, so inspecting a config is always safe — but it
does log dag.gpu_usefulness_report, because someone reading the planned hardware is
exactly the person who should be told a device will be billed and barely used.
Source code in src/scale_forecasting/sdk.py
detect_anomalies_df
¶
detect_anomalies_df(
run_id=None,
*,
model_name=None,
anomaly_prob_threshold=0.95,
cutoff_back_steps=None,
anomalies_only=False,
actuals_df=None,
engine="auto",
limit=50000,
)
Detect time-series anomalies across any of the 37 models, 9 ensembles, or past runs.
Supports four complementary execution paths controlled by model_name and engine:
- In-database BigQuery ML (
engine="auto"or"bqml"witharima_plus/timesfm): RunsML.DETECT_ANOMALIES(MODEL ...)forarima_plus/arima_plus@xregor serverlessAI.DETECT_ANOMALIES(TimesFM) directly in BigQuery. If inspecting an older pastrun_idwhose BQML model object was already dropped andengine="auto", automatically falls back to the storedbacktest_oofintervals. - Universal Model / Ensemble OOF (
model_name="<any model>","ensemble_*","all", orengine="oof"): Scores storedbacktest_oofout-of-fold predictions and calibrated prediction intervals for any statistical, ML, deep-learning, Vertex AI AutoML, BigQuery ML, or ensemble model on current or past runs via :func:diagnostics.compute_anomalies— zero re-fitting required. - Forward Forecast Realized Actuals (
actuals_df=...): Joins publishedforecast_predictionsintervals against newly arrivedactuals_dfto flag live post-cutoff anomalies. - Model-Free STL + Robust MAD (
model_name="stl"orengine="stl"): Decomposes raw historical series when no backtest OOF intervals are available.
Returns a tidy DataFrame with columns ["ts_id", "model_type", "ds", "y", "yhat",
"lower_bound", "upper_bound", "anomaly_probability", "is_anomaly"].
Source code in src/scale_forecasting/sdk.py
1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 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 | |
detect_change_points_df
¶
detect_change_points_df(
run_id=None,
*,
model_name=None,
exclude_gap_edges=True,
min_gap_steps=1,
sigma_threshold=3.0,
engine="auto",
limit=50000,
)
Detect structural regime change points across raw series or any model's residuals on current or past runs.
- Raw series mode (
model_name=Noneor"raw"): Runs BigQuery's nativeML.DETECT_CHANGE_POINTSwith gap-edge artifact filtering whenengine in ("auto", "bqml"), or pure-Python binary segmentation (:func:diagnostics.compute_change_points) whenengine="oof". - Model-residual mode (
model_name="<model>","ensemble_*", or"all"): Reads storedbacktest_ooftrajectories forrun_idand segments the model's residuals $e_t = y_t - \hat{y}_t$ via :func:diagnostics.compute_change_pointsto pinpoint structural regime shifts that the model failed to anticipate (distinguishing unmodeled structural breaks from seasonal or promotional jumps the model already explained).
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/sdk.py
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 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 | |
dry_run
¶
Validate the config and report the planned fan-out + runtime split — touches no GCP.
Delegates the run_id to main.run (dry_run=True) so it is the single source of
truth, then adds the estimated fanout and the python/BigQuery model split.
Source code in src/scale_forecasting/sdk.py
emit_airflow
¶
Render this config's Airflow DAG as a dag_<run_id>.py source string — pure, offline.
The Composer counterpart to dag: instead of the node list, it returns a standalone Airflow
DAG file (airflow_emit.emit_airflow_dag) whose tasks reproduce this exact run — one job
per family on its resolved runtime, the ensemble node, and (when the run has several
ephemeral Ray/Dataproc-cluster families) the shared-cluster bracket — all under the same
deterministic run_id. config_uri is embedded verbatim as the DAG's CONFIG_URI
(what each task loads the config from); pass the gs:// URI of a staged config for a
Composer-runnable DAG, or omit it to reference this config's own run_id file under the
convention (runs/<run_id>.json). dag_id overrides the default
scale_forecasting_<run_id>. Touches no GCP — write the returned string to the Airflow
DAGs folder (or stage it with staging.stage_dag).
with_retry adds the repair node between the family join and the ensemble — the
unattended form of retry, so a scheduled run recovers what it can before anything reads
it. Off by default, and an argument rather than a config field: config fields feed the
run_id digest, and repairing on a schedule must not be a different run from repairing by
hand. A microbatch ensemble gets no node even when asked (it has already drained by then);
airflow_emit.emit_airflow_dag logs when it declines.
Source code in src/scale_forecasting/sdk.py
ensemble_runs
¶
Blend base models across multiple completed runs into a consolidated cross-run ensemble;
return target_run_id.
Convenience forwarder to Registry.ensemble_runs (ensemble_run.ensemble_cross_runs). If
source_run_ids contains a single run id distinct from self.run_id, self.run_id
is automatically prepended so forecaster_b.ensemble_runs([run_a_id]) combines run_b
with run_a.
Source code in src/scale_forecasting/sdk.py
ensemble_weights_df
¶
Return a long-form DataFrame of learned ensemble stacking weights (nnls, ridge,
xgb) unpacked from forecast_metadata.best_params for run_id.
Source code in src/scale_forecasting/sdk.py
exceptions_df
¶
Build the Top-N operational exception triage queue for run_id.
Source code in src/scale_forecasting/sdk.py
explain
¶
Return a per-family execution plan DataFrame for this config — pure, offline.
Each row represents one node in the planned execution DAG (dag), detailing the resolved
runtime, machine_type, workers, hardware, gpu_type, selected models,
per-model training_modes (local / global / hybrid), expected cell count,
active covariate tiers, and upstream depends_on keys. validate_data is accepted for
parity with dry-run workflows (when False, stays 100% offline).
Source code in src/scale_forecasting/sdk.py
explain_forecast
¶
explain_forecast(
run_id=None,
*,
ts_id=None,
model_type=None,
seasonal_period=7,
history_tail=120,
)
Decompose a series' historical trajectory, OOF backtest, and future forecast into
structural trend, level shift, seasonal cycle, exogenous covariate attribution, and OOF
residuals / interval width for run_id.
Source code in src/scale_forecasting/sdk.py
export_report
¶
Export a self-contained .html report for run_id.
Source code in src/scale_forecasting/sdk.py
feasibility
¶
Read the source panel's series lengths and report what this run's fold geometry does.
The SDK face of --dry-run --feasibility. Reads one aggregation off the panel and returns
the report as lines; launches nothing and writes nothing. See
launch_plan.feasibility_report for why this one planning question cannot be answered
offline.
Source code in src/scale_forecasting/sdk.py
from_dict
classmethod
¶
Build from an already-parsed config dict (validates via RunConfig, raising
ConfigError on a schema violation).
Source code in src/scale_forecasting/sdk.py
from_file
classmethod
¶
Build from a JSON config file (delegates to
load_config, which raises
ConfigError on a bad file).
Source code in src/scale_forecasting/sdk.py
from_run_id
classmethod
¶
Build from a run that already happened, by reading its config back out of the registry.
The third way in, for the operator who inherits a broken run: they have its id, from a
pager or a dashboard or a ledger row, and not its config file. The run's header carries the
config it landed under, so this recovers a Forecaster identical to the one that launched
it — and every method here then works as usual, retry included.
Delegates to retry_run.config_for_run, which refuses loudly rather than returning
something plausible: no stored config, a config that no longer validates, or a config
whose own digest is not run_id each raise errors.ConfigError naming the id. That
matters most for retry, where every one of those failures has a wrong answer available
that looks like a small repair.
This is why retry still takes no run_id of its own. A repair is planned from a config;
what an id buys you is the config, not a second way to plan.
Source code in src/scale_forecasting/sdk.py
fva_df
¶
Compute Forecast Value Added (FVA) & Diebold–Mariano statistical significance for
run_id (diagnostics.compute_fva).
Source code in src/scale_forecasting/sdk.py
hierarchy_df
¶
Summarize hierarchical predictions by level (total, aggregate, bottom) and
verify additive coherence ($\max_t |\hat{y}{\text{total},t} - \sum_b \hat{y}|$)
for run_id.
Source code in src/scale_forecasting/sdk.py
horizon_decay_df
¶
Compute lead-time accuracy decay across horizon steps ($h = 1 \dots H$).
Source code in src/scale_forecasting/sdk.py
inventory_policy_df
¶
inventory_policy_df(
run_id=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,
limit=20000,
)
Translate run_id calibrated forecasts into operational inventory & replenishment
policies (diagnostics.compute_inventory_policy).
Computes 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 Expected Holding vs. Stockout Cost tables using AR(1)
multi-step forecast error covariance ($\hat{\rho}_1$ from backtest_oof) and supplier
lead-time variability ($\mu_L, \sigma_L$).
Source code in src/scale_forecasting/sdk.py
jobs
¶
The per-job cross-system trace for a run — one JobTrace per family, plus the ensemble.
Reads v_run_jobs (registry.jobs.read_run_jobs) for run_id (default: this config's
id): the authoritative map from each family's canonical job_key to the platform job that
actually ran it (its system_job_id on Spark/Ray/BigQuery) and how it fared. Returns
[] when the run has no jobs yet. This is the "where did each family run, and can I go
look at that platform's job?" read — the executed counterpart to the offline dag.
Source code in src/scale_forecasting/sdk.py
jobs_df
¶
Return a tidy DataFrame of per-family job execution traces from v_run_jobs for
run_id (default: this config's id).
Source code in src/scale_forecasting/sdk.py
leaderboard
¶
Alias for :meth:leaderboard_df matching :meth:RunResult.leaderboard.
leaderboard_df
¶
Return a ranked leaderboard DataFrame for run_id (default: this config's id).
Delegates to review_run and review.build_leaderboard_frame. Pass all_metrics=True
to include mean_<metric> and p50_<metric> for all 21 evaluation metrics.
Source code in src/scale_forecasting/sdk.py
monitor
¶
Live progress of a run: per-family job state + series done vs. expected (RunProgress).
Delegates to review.monitor_run for run_id (default: this config's id) — header
status, each family's runner and status, how many cells have landed vs. the expected total,
and how long each family has been quiet. Poll it while a run is in flight; pair with
review.plot_progress for the bar.
probe=True also escalates the non-terminal jobs to their runtime and attaches the
reconciled probes.reconcile.ProbeReport — use it when a bar has stopped moving and you
need to know whether the job is still alive. It is off by default because a poll loop must
stay registry-only; probe (the drill-down) is the deliberate version of the same read.
Source code in src/scale_forecasting/sdk.py
plot_attributions
¶
plot_attributions(
run_id=None,
*,
ts_id=None,
model_type=None,
level="global",
top_k=12,
ax=None,
title=None,
)
Plot Tier 1 global driver importance or Tier 2 per-horizon-step local feature
attributions for run_id.
Source code in src/scale_forecasting/sdk.py
plot_calibration
¶
Plot empirical interval coverage and mean interval width by horizon step ($h = 1 \dots
H$) against the nominal coverage target for run_id.
Source code in src/scale_forecasting/sdk.py
plot_ensemble_weights
¶
Plot normalized base-model weights across learned ensemble strategies (nnls, ridge,
xgb) as a horizontal stacked bar chart for run_id.
Source code in src/scale_forecasting/sdk.py
plot_forecast_explanation
¶
plot_forecast_explanation(
run_id=None,
*,
ts_id=None,
model_type=None,
seasonal_period=7,
history_tail=120,
title=None,
)
Render a 4-panel forecast explainability & decomposition chart for one series and model
on run_id.
Source code in src/scale_forecasting/sdk.py
plot_forecasts
¶
plot_forecasts(
run_id=None,
*,
ts_id=None,
ts_ids=None,
models=None,
max_series=3,
history_tail=120,
ax=None,
title=None,
)
Plot historical actuals, backtest OOF trajectories, and future forecasts with 80%
prediction intervals for up to max_series series on run_id.
Source code in src/scale_forecasting/sdk.py
plot_fva
¶
Plot the Forecast Value Added (FVA) accuracy waterfall for run_id.
Source code in src/scale_forecasting/sdk.py
plot_hierarchy
¶
Plot the top-level (__total__) forecast against the sum of bottom-level leaf
forecasts for run_id to visually verify hierarchical coherence.
Source code in src/scale_forecasting/sdk.py
plot_horizon_decay
¶
Plot lead-time accuracy decay by horizon step ($h = 1 \dots H$) for run_id.
Source code in src/scale_forecasting/sdk.py
plot_inventory_policy
¶
plot_inventory_policy(
run_id=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,
max_rows=15,
limit=20000,
ax=None,
title=None,
)
Plot Reorder Points (ROP), Safety Stock (SS), and Recommended Order Quantities ($Q$)
for run_id.
Source code in src/scale_forecasting/sdk.py
plot_scenarios
¶
Plot baseline vs. counterfactual scenario trajectories from simulate_scenarios.
Source code in src/scale_forecasting/sdk.py
plot_series
¶
Convenience wrapper around :meth:plot_forecasts for a single ts_id.
Source code in src/scale_forecasting/sdk.py
predictions_df
¶
predictions_df(
run_id=None,
*,
ts_id=None,
ts_ids=None,
models=None,
include_history=True,
include_oof=True,
history_tail=120,
series_limit=None,
limit=10000,
)
Return a tidy DataFrame of forecasts (plus optional OOF and historical actuals) for
run_id.
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/sdk.py
probe
¶
Reconcile a run against its runtime: per-family verdict + a disagreement flag.
Delegates to probes.reconcile.probe_run for run_id (default: this config's id): the
registry-first drill-down that fuses the header + landed artifacts with, only for the
incomplete or stale jobs, their live native state (Spark / Ray / BigQuery) — flagging each
row where the runtime contradicts the registry. job narrows the escalation to one family
(statistical/ml/deep_learning/native/ensemble). A run that is already
terminal short-circuits and touches no runtime; for the fleet-wide view use monitor.
Source code in src/scale_forecasting/sdk.py
reensemble
¶
Execute post-hoc re-ensembling on an already-completed run without re-fitting base
models; return the resulting ensemble_id.
Delegates to ensemble_run.run_ensembles with strategies overriding the config's
ensemble.strategies block. New strategies land under a distinct ensemble_id and
appear alongside existing ensembles on v_model_leaderboard.
Source code in src/scale_forecasting/sdk.py
registry
¶
A Registry handle for the registry this config's runs land in.
The lifecycle verbs (Registry.doctor, Registry.drop_run, …) are keyed on the registry,
not on a config, so they live on their own object — but reaching them from the Forecaster
you already have is the common case (f.registry().drop_run(f.run_id)).
Source code in src/scale_forecasting/sdk.py
results
¶
The per-model leaderboard for a run — one ModelResult each, best (lowest WAPE) first.
Reads v_model_leaderboard (registry.reads.read_leaderboard) for run_id (default:
this config's id). Returns [] when the run has produced no scored rows yet. This is the
first-class "which model won?" read that RunResult only pointed at before.
Source code in src/scale_forecasting/sdk.py
retry
¶
Repair what this run failed to produce — a preview by default.
Delegates to retry_run.retry_run. It reads the run's per-cell outcomes, derives the cells
that were never attempted at all (expected series at the run's pinned snapshot, minus what
landed), classifies every one of them with retry_policy, and returns the decision table
inside a retry_run.RetryReport. Without confirm that is all it does — no launch, no
write. With confirm=True it re-submits only the models that could still succeed as a
fresh attempt under the same run_id, and records the table it acted on under
job_telemetry.$.retry.
Two things it will refuse, both on purpose. A cell that already has predictions is never
retried — a retry appends, so it can add a second forecast beside the first but never
replace it, and quietly swapping a reviewed forecast for a fresh fit is a different run, not
a repair. And because v1 submits per model over the whole series universe, a model with
any landed predictions is refused wholesale; the report names those under blocked
rather than dropping them silently. Changing an answer that already landed means changing
the config, which changes the run_id — the honest way to say the answer changed.
There is no run_id parameter, unlike probe / cancel / settle. Those inspect a run;
this one plans one, and the plan comes from the config — the DAG to narrow, the family
each model belongs to, the source table and the series subset. A different run has a
different config by definition, so build a Forecaster on that config instead. If all you
have is the id, from_run_id reads the config back out of the run's header and gives you
exactly that — which is why the id is a constructor argument here and not a method one.
Source code in src/scale_forecasting/sdk.py
review
¶
Return a pointer to this config's results without running anything (offline).
The run_id is the deterministic config hash, so this resolves the same location a completed
run landed under. dataset_ref is None when the GCP identity can't be resolved (no
SF_* env and no injected settings) — the run_id + view names are still returned so a
caller can construct the query once infra is known.
Source code in src/scale_forecasting/sdk.py
review_run
¶
Data-science review of a finished run: bests, metric panel, ensemble lift (RunReview).
Delegates to review.review_run for run_id (default: this config's id) — best model per
family and overall, the full metric panel aggregated across series, and each ensemble's lift
over the best base model. The reading counterpart to the offline review pointer; pair with
review.plot_leaderboard / review.plot_metric_distribution.
Source code in src/scale_forecasting/sdk.py
revision_stability_df
¶
Measure cross-run forecast revision stability ("whipsaw") across run_ids.
Source code in src/scale_forecasting/sdk.py
rollup_time
¶
Aggregate run_id future predictions to Weekly ("W"), Monthly ("MS"/"M"), or
Quarterly ("QS"/"Q") buckets with autocorrelation-aware prediction intervals.
Source code in src/scale_forecasting/sdk.py
run
¶
Execute the run (Spark/Ray ∥ BigQuery under one run_id) and return where to query it.
Delegates to main.run, threading this forecaster's settings (so an injected
identity is honored). spark optionally injects a SparkSession /
DataprocSparkSession for the in-process Spark path (notebook / Connect demo).
n_series overrides data.series_limit (the scale knob — it changes the run_id, so
each scale is its own run); max_executors caps the remote Spark batch's executor
ceiling. Returns a RunResult pointing at the registry views under the resolved dataset.
Note the returned run_id reflects any n_series override, so it may differ from this
forecaster's base run_id; use it (or status/results with it) to track this run.
Source code in src/scale_forecasting/sdk.py
run_live
¶
run_live(
*,
spark=None,
n_series=None,
max_executors=None,
poll_seconds=10.0,
poll_s=None,
probe=False,
plot=True,
)
Execute run while polling monitor for live progress updates, then return RunResult.
Launches run in a background thread and polls monitor every poll_seconds (or
poll_s) seconds until the run finishes (or when poll_seconds <= 0, executes
synchronously). When plot=True and the terminal progress snapshot contains family rows,
renders review.plot_progress once at completion. Safe in both interactive notebooks and
headless automation (notebook_acceptance).
Source code in src/scale_forecasting/sdk.py
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 | |
score_realized
¶
Score published forward predictions for run_id against newly arrived actuals_df
and flag series whose live error exceeds drift_threshold times backtest expectation.
Source code in src/scale_forecasting/sdk.py
settle
¶
Repair stale job rows from the probe's own verdicts — a preview by default.
Delegates to probes.settle.settle_run for run_id (default: this config's id). probe
already decides that a family whose row still says RUNNING finished forty minutes ago and
then discards that answer; this is the verb that writes it down. Without yes it probes,
returns the SettlePlan inside a SettleReport, and changes nothing. With yes=True
it sets each settleable family's status (COMPLETED, or FAILED with a
RUNTIME_FAILED / RUNTIME_LOST / CAPACITY_ABANDONED token), records reason +
the ADC actor and the full evidence under job_telemetry.$.settle, and re-reads every row
so the outcome is the status it actually ended up with. job narrows to one family.
Only five (verdict, runtime-state) readings settle; UNKNOWN and RUNNING_CONFIRMED
are always refused, and the plan says why each untouched family was left alone. Settle
never deletes and never stamps ended_at/runtime_seconds — a row settled days later
would report days of runtime. It leaves the run header to registry.ops.close_runs, which
settling the job rows is precisely what unblocks; the report carries the hint.
yes= rather than confirm= on purpose: cancel's confirm= gates stopping running
work in the cloud, yes= gates repairing a status field in our own registry — the same
gate registry.ops.close_runs and registry.ops.sweep_orphans use.
Source code in src/scale_forecasting/sdk.py
simulate_scenarios
¶
simulate_scenarios(
scenarios,
*,
data=None,
models=None,
future_covariates_df=None,
price_col=None,
run_id=None,
)
Run counterfactual covariate shocks (scenarios.simulate_scenarios) across data
(or BigQuery source series when run_id is provided, or synthetic sample data when
data is omitted) using this config's settings.
Source code in src/scale_forecasting/sdk.py
status
¶
The current registry status of a run (RUNNING/COMPLETED/FAILED/PARTIAL).
Reads the run's header (registry.header.header_status); returns None when this config
has never run, and STAGED when it has only been staged — the artifacts and the commands
exist, but nothing has launched them, so wait on such a run polls to its timeout.
run_id defaults to this config's deterministic id, so
forecaster.status() answers "did my config's run finish?" — the reattach path for a
wait=False submit.
Source code in src/scale_forecasting/sdk.py
trace
¶
The run's execution timeline as a long-form frame — per-job spans + per-cell spans.
Reads the per-job trace (v_run_jobs via jobs' reader) and the per-cell wall-clock
brackets (forecast_metadata via registry.reads.read_cell_timing, capped at
cell_limit) for run_id (default: this config's id), then stacks them into one frame
(columns kind/lane/label/start/end/duration_s/status/
runtime/model_type/ts_id) via build_trace_frame. Feed it to plot_trace for a
Gantt/waterfall, or slice it directly (frame[frame.kind == "cell"]). Returns an empty
frame (with the columns) when the run has no timed rows — e.g. an older run written before
the trace columns existed. This is the "how did this run unfold over wall-clock time, and
which worker ran what?" read that sits under the per-job jobs summary.
Source code in src/scale_forecasting/sdk.py
variant_matrix
¶
Return the resolved model-variant taxonomy and spec-hash matrix DataFrame (offline).
One row per model entry in config.models (including auto-expanded univariate baselines
and ablation-ladder variants), detailing model_type, base_model, variant_tag,
family, scope_capability, training_mode, cov_profile, is_univariate,
covariates_used, and model_spec_hash.
Source code in src/scale_forecasting/sdk.py
wait
¶
Block until a run reaches a terminal status and return it; raise on timeout/not-found.
Polls status every poll_seconds until the status is terminal
(COMPLETED/FAILED/PARTIAL). Raises TimeoutError if timeout elapses first,
or ConfigError if the run has no header yet (never submitted). Each poll re-reads the
header through a fresh client, so a long wait re-authenticates rather than reusing a stale
token.
Source code in src/scale_forecasting/sdk.py
JobTrace
dataclass
¶
JobTrace(
family,
job_key,
system_job_id,
runtime,
status,
attempt,
hardware,
gpu_type,
spark_mode,
runtime_seconds,
)
One family's job on a run, read from v_run_jobs — the cross-system trace row.
job_key is the job's canonical id (run_jobs.job_id); system_job_id is the platform's
own id for the same job (a Dataproc batch/job id, a Ray submission_id, or a BigQuery parent
job id), so a status query can go straight to that platform. runtime says which platform to
query (spark / ray / bigquery); hardware / gpu_type / spark_mode record
the resolved placement. status is the job's registry/terminal status and runtime_seconds
its wall-clock; attempt distinguishes a forced re-run's job from the original under one
run_id.
ModelResult
dataclass
¶
ModelResult(
model_type,
ensemble_id,
compute_engine,
n_cells,
no_artifact_rate,
median_fit_seconds,
mean_wape,
mean_mae,
)
One model's outcome on a run, read from v_model_leaderboard.
ensemble_id is None for a base model and the ensemble-config digest for an ensemble
pseudo-model. mean_wape / mean_mae are the mean decision metrics where a backtest scored
them (None otherwise); no_artifact_rate near 1.0 flags a model that failed most
cells. Rows come back best-first (lowest mean_wape).
Registry
¶
The easy path for managing a registry — the operator counterpart to Forecaster.
Forecaster produces runs; this reads and prunes them. It is keyed on the registry the SF_*
environment resolves to (Settings.registry_dataset_ref), not on a config, so it needs no
config at all — which is exactly right for "what is in this registry, and clean up run X".
Like Forecaster, it adds no logic: every method delegates to registry.ops, the same code
python -m scale_forecasting.registry.ops runs, so the notebook, the SDK, and the CLI can
never diverge. The destructive verbs preview by default and execute only on yes=True.
>>> from scale_forecasting import Registry
>>> reg = Registry()
>>> print(reg.doctor()) # read-only: counts, stuck runs, orphans
>>> reg.drop_run("abc123") # preview — deletes nothing
>>> reg.drop_run("abc123", yes=True) # artifacts, then models, then rows
There is deliberately no wipe-everything method: a full teardown is bq rm -r -f <dataset>.
Source code in src/scale_forecasting/sdk.py
dataset_ref
property
¶
project.dataset for this registry, or None when SF_* can't be resolved.
close_runs
¶
Finalize abandoned RUNNING headers to what their job rows already imply.
No arguments means every stuck header in this registry. Preview unless yes. Deletes
nothing and skips any run with a job row that is not yet terminal — probe those first.
Forecaster.monitor (probe=True) is the usual way in, but it needs a config;
probes.reconcile.probe_run takes a bare run_id, which is what you have here. See
registry.ops.close_runs.
Source code in src/scale_forecasting/sdk.py
detect_anomalies_df
¶
detect_anomalies_df(
run_id,
*,
model_name=None,
anomaly_prob_threshold=0.95,
cutoff_back_steps=None,
anomalies_only=False,
actuals_df=None,
engine="auto",
limit=50000,
)
Detect time-series anomalies on any completed run_id in this registry without
needing its original config file. Delegates to :meth:Forecaster.detect_anomalies_df.
Source code in src/scale_forecasting/sdk.py
detect_change_points_df
¶
detect_change_points_df(
run_id,
*,
model_name=None,
exclude_gap_edges=True,
min_gap_steps=1,
sigma_threshold=3.0,
engine="auto",
limit=50000,
)
Detect structural regime change points on any completed run_id in this registry
(either on the run's raw source series or on a specific model_name's residuals).
Delegates to :meth:Forecaster.detect_change_points_df.
Source code in src/scale_forecasting/sdk.py
doctor
¶
A read-only registry.ops.DoctorReport — row counts, stuck runs, orphaned artifacts.
drop_run
¶
Delete named run(s) — artifacts, then BQML models, then rows. Preview unless yes.
See registry.ops.drop_run for the ordering rule and the in-flight refusal force
overrides.
Source code in src/scale_forecasting/sdk.py
ensemble_runs
¶
Blend base models across multiple completed runs into a consolidated cross-run ensemble;
return target_run_id.
Delegates to ensemble_run.ensemble_cross_runs: validates that the source runs share the
same source table, horizon, and backtest geometry, copies deduplicated base rows into
target_run_id (default: make_run_id(merged_cfg)), and runs the requested calculated
and/or learned ensemble strategies so all base models and cross-run ensembles appear
together on v_model_leaderboard and review_run.
Source code in src/scale_forecasting/sdk.py
export
¶
Export every registry table to GCS; returns {table: destination prefix}.
Source code in src/scale_forecasting/sdk.py
init
¶
Create this registry's tables + views (idempotent). See registry.ops.init.
Source code in src/scale_forecasting/sdk.py
probe
¶
Reconcile run_id against its live cloud runtime (probes.reconcile.probe_run).
Source code in src/scale_forecasting/sdk.py
reap_clusters
¶
Delete Ray clusters belonging to this registry whose run has already finished.
The compute counterpart of sweep_orphans, and the only protection against a launcher that
was killed before its teardown could run — a Vertex Ray cluster has no idle TTL to fall back
on the way a Dataproc one does. Preview unless yes. See ray_reaper.reap_clusters for
the rules that decide a cluster is abandoned, and why an ambiguous one is left running.
Source code in src/scale_forecasting/sdk.py
revision_stability_df
¶
Measure cross-run forecast revision stability ("whipsaw") across run_ids.
Source code in src/scale_forecasting/sdk.py
runs_df
¶
Return a DataFrame of the most recent runs in this registry from v_run_summary,
newest first, optionally filtered by status.
Beside the header columns comes the run's time ledger, rolled up from its job rows:
n_jobs, longest_job_seconds (the critical-path family), jobs_seconds (every
job's wall added up — more than the run's own wall when families overlapped),
jobs_span_seconds (first job start to last job end), and overhead_seconds /
overhead_fraction (the span the slowest job did not account for — ensemble, native
job, scheduler gaps; never negative). dcu_milli_seconds is the last Dataproc
Serverless batch's usage, NULL on other runtimes. See registry.views.
Source code in src/scale_forecasting/sdk.py
score_realized
¶
Join run_id predictions against actuals_df and compute realized accuracy.
Source code in src/scale_forecasting/sdk.py
snapshot
¶
Table-snapshot every registry table; returns {table: snapshot ref}.
Source code in src/scale_forecasting/sdk.py
sweep_orphans
¶
Delete artifacts under this registry with no run_registry row.
Preview unless yes. See registry.ops.sweep_orphans.
Source code in src/scale_forecasting/sdk.py
RunResult
dataclass
¶
A pointer to where a run's results live — the run_id plus how to query them.
dataset_ref is project.dataset (None when the GCP identity can't be resolved, e.g.
an offline Forecaster.review); views are the registry view names to query under it
(e.g. v_run_summary, v_model_leaderboard). Filter any of them by run_id.
status and runtime_seconds carry the terminal outcome when returned by Forecaster.run
or Forecaster.run_live.
build_best_params_frame
¶
Unpack read_best_params rows (with JSON best_params) into a tidy DataFrame
(pure, offline).
Source code in src/scale_forecasting/sdk.py
build_explain_frame
¶
Build a per-family execution plan DataFrame for cfg — pure, offline, no GCP.
One row per node in dag.plan_dag (each active model family plus the downstream ensemble
node when enabled), summarizing run_id, family, job_key, runtime,
machine_type, workers, hardware, gpu_type, models, training_modes,
n_series, n_folds, expected_cells, covariates, and depends_on.
Source code in src/scale_forecasting/sdk.py
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 | |
build_trace_frame
¶
Stack per-job and per-cell timing rows into one long-form trace frame — pure, no I/O.
job_rows are v_run_jobs rows (see registry.jobs.read_run_jobs) and cell_rows are
per-cell timing rows (see registry.reads.read_cell_timing); this is the pure assembly step
Forecaster.trace calls after reading them, split out so it unit-tests without GCP. Each input
row becomes one span record with the shared _TRACE_COLUMNS schema: a job lands on its
family lane, a cell on its worker_id lane, so a plot can show the DAG over the workers
that ran it. Rows without a start stamp are dropped (nothing to place on a timeline); a row with
a start but no end (a job that never recorded completion) keeps a zero-width span at its start.
Returns an empty frame carrying the columns when both inputs are empty.
Source code in src/scale_forecasting/sdk.py
build_variant_matrix_frame
¶
Build a per-variant taxonomy and spec-hash DataFrame for cfg — pure, offline.
Source code in src/scale_forecasting/sdk.py
plot_trace
¶
Render a build_trace_frame frame as a Gantt/waterfall and return the matplotlib Axes.
A convenience over the frame (the frame is the real deliverable): one horizontal bar per span,
lanes stacked on the y-axis (jobs and cells kept apart so a family job and a worker_id
cell never share a track), colored by kind (job vs cell). Pass an existing ax to compose
into a larger figure, or let it create one. matplotlib imports lazily here so it never touches
the near-instant import scale_forecasting path. An empty frame renders an empty titled axes
rather than raising.