scale-forecasting 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scale_forecasting/__init__.py +275 -0
- scale_forecasting/_entry.py +110 -0
- scale_forecasting/_infra_args.py +91 -0
- scale_forecasting/agent_surfaces.py +1571 -0
- scale_forecasting/airflow_emit.py +325 -0
- scale_forecasting/airflow_tasks.py +414 -0
- scale_forecasting/automl_submit.py +383 -0
- scale_forecasting/backtest.py +1153 -0
- scale_forecasting/batch_infra.py +322 -0
- scale_forecasting/batch_telemetry.py +241 -0
- scale_forecasting/calibration.py +900 -0
- scale_forecasting/capacity.py +659 -0
- scale_forecasting/cluster_deps.py +113 -0
- scale_forecasting/cluster_submit.py +383 -0
- scale_forecasting/cluster_telemetry.py +108 -0
- scale_forecasting/code_delivery.py +196 -0
- scale_forecasting/commands.py +459 -0
- scale_forecasting/compute_fallback.py +190 -0
- scale_forecasting/config.py +2530 -0
- scale_forecasting/dag.py +551 -0
- scale_forecasting/data_gen/__init__.py +3 -0
- scale_forecasting/data_gen/generator.py +415 -0
- scale_forecasting/data_gen/seed_spark.py +505 -0
- scale_forecasting/dataproc_cluster.py +869 -0
- scale_forecasting/device_audit.py +219 -0
- scale_forecasting/diagnostics.py +1510 -0
- scale_forecasting/engines/__init__.py +6 -0
- scale_forecasting/engines/automl_engine.py +2162 -0
- scale_forecasting/engines/bigquery_engine.py +623 -0
- scale_forecasting/engines/bigquery_names.py +101 -0
- scale_forecasting/engines/bigquery_sql.py +783 -0
- scale_forecasting/engines/ray_engine.py +809 -0
- scale_forecasting/engines/ray_io.py +1007 -0
- scale_forecasting/engines/spark_explode.py +358 -0
- scale_forecasting/engines/spark_io.py +763 -0
- scale_forecasting/engines/vertex_engine.py +1171 -0
- scale_forecasting/ensemble_run.py +1102 -0
- scale_forecasting/ensembler.py +1279 -0
- scale_forecasting/errors.py +188 -0
- scale_forecasting/features.py +1352 -0
- scale_forecasting/gce_submit.py +1028 -0
- scale_forecasting/gke_submit.py +1587 -0
- scale_forecasting/hardware.py +265 -0
- scale_forecasting/hpo.py +424 -0
- scale_forecasting/identity.py +94 -0
- scale_forecasting/job_launch.py +768 -0
- scale_forecasting/job_outcome.py +241 -0
- scale_forecasting/job_wait.py +111 -0
- scale_forecasting/launch_plan.py +1107 -0
- scale_forecasting/main.py +799 -0
- scale_forecasting/mcp.py +1385 -0
- scale_forecasting/metrics/__init__.py +253 -0
- scale_forecasting/metrics/_intervals.py +19 -0
- scale_forecasting/metrics/base_metric.py +217 -0
- scale_forecasting/metrics/bias.py +34 -0
- scale_forecasting/metrics/coverage.py +40 -0
- scale_forecasting/metrics/cv.py +24 -0
- scale_forecasting/metrics/interval_score.py +42 -0
- scale_forecasting/metrics/interval_width.py +38 -0
- scale_forecasting/metrics/maape.py +38 -0
- scale_forecasting/metrics/mae.py +30 -0
- scale_forecasting/metrics/mape.py +33 -0
- scale_forecasting/metrics/mase.py +45 -0
- scale_forecasting/metrics/mase_seasonal.py +49 -0
- scale_forecasting/metrics/mse.py +32 -0
- scale_forecasting/metrics/msis.py +30 -0
- scale_forecasting/metrics/msse.py +28 -0
- scale_forecasting/metrics/ope.py +24 -0
- scale_forecasting/metrics/pinball.py +49 -0
- scale_forecasting/metrics/r2.py +27 -0
- scale_forecasting/metrics/rmse.py +30 -0
- scale_forecasting/metrics/rmsle.py +25 -0
- scale_forecasting/metrics/rmsse.py +41 -0
- scale_forecasting/metrics/smape.py +33 -0
- scale_forecasting/metrics/wape.py +34 -0
- scale_forecasting/models/__init__.py +112 -0
- scale_forecasting/models/_lag_forecaster.py +407 -0
- scale_forecasting/models/_neuralforecast_base.py +605 -0
- scale_forecasting/models/_vertex_automl_base.py +268 -0
- scale_forecasting/models/auto_arima.py +142 -0
- scale_forecasting/models/auto_ces.py +103 -0
- scale_forecasting/models/auto_theta.py +113 -0
- scale_forecasting/models/autoets.py +106 -0
- scale_forecasting/models/base_model.py +600 -0
- scale_forecasting/models/bigquery_native.py +177 -0
- scale_forecasting/models/catboost_model.py +159 -0
- scale_forecasting/models/croston.py +137 -0
- scale_forecasting/models/fft.py +108 -0
- scale_forecasting/models/holtwinters.py +85 -0
- scale_forecasting/models/kalman.py +95 -0
- scale_forecasting/models/lightgbm_model.py +160 -0
- scale_forecasting/models/naive_drift.py +69 -0
- scale_forecasting/models/naive_mean.py +60 -0
- scale_forecasting/models/naive_moving_average.py +93 -0
- scale_forecasting/models/naive_seasonal.py +72 -0
- scale_forecasting/models/neuralprophet_model.py +421 -0
- scale_forecasting/models/patchtst.py +51 -0
- scale_forecasting/models/prophet_model.py +121 -0
- scale_forecasting/models/random_forest.py +153 -0
- scale_forecasting/models/regression_lags.py +171 -0
- scale_forecasting/models/sarimax.py +92 -0
- scale_forecasting/models/stl_bagging.py +109 -0
- scale_forecasting/models/tbats_model.py +102 -0
- scale_forecasting/models/tft.py +46 -0
- scale_forecasting/models/theta.py +89 -0
- scale_forecasting/models/tide.py +47 -0
- scale_forecasting/models/tsmixer.py +48 -0
- scale_forecasting/models/ucm.py +91 -0
- scale_forecasting/models/vertex_l2l.py +31 -0
- scale_forecasting/models/vertex_seq2seq.py +30 -0
- scale_forecasting/models/vertex_tft.py +32 -0
- scale_forecasting/models/vertex_tide.py +32 -0
- scale_forecasting/models/xgboost_model.py +164 -0
- scale_forecasting/notebook_acceptance.py +755 -0
- scale_forecasting/playground.py +793 -0
- scale_forecasting/probes/__init__.py +3 -0
- scale_forecasting/probes/cancel.py +408 -0
- scale_forecasting/probes/reconcile.py +577 -0
- scale_forecasting/probes/runtimes.py +941 -0
- scale_forecasting/probes/settle.py +466 -0
- scale_forecasting/probes/vocabulary.py +245 -0
- scale_forecasting/profiling/__init__.py +3 -0
- scale_forecasting/profiling/baseline.py +240 -0
- scale_forecasting/profiling/cost.py +705 -0
- scale_forecasting/profiling/measure.py +440 -0
- scale_forecasting/profiling/numeric.py +73 -0
- scale_forecasting/profiling/sampling.py +290 -0
- scale_forecasting/profiling/signature.py +136 -0
- scale_forecasting/profiling/source.py +473 -0
- scale_forecasting/profiling/stats.py +221 -0
- scale_forecasting/py.typed +0 -0
- scale_forecasting/quota.py +1767 -0
- scale_forecasting/ray_cluster.py +767 -0
- scale_forecasting/ray_entry.py +46 -0
- scale_forecasting/ray_infra.py +121 -0
- scale_forecasting/ray_jobs.py +462 -0
- scale_forecasting/ray_reaper.py +392 -0
- scale_forecasting/ray_submit.py +323 -0
- scale_forecasting/ray_telemetry.py +106 -0
- scale_forecasting/reconciliation.py +929 -0
- scale_forecasting/registry/__init__.py +3 -0
- scale_forecasting/registry/artifacts.py +223 -0
- scale_forecasting/registry/cells.py +115 -0
- scale_forecasting/registry/ddl.py +408 -0
- scale_forecasting/registry/harvest.py +252 -0
- scale_forecasting/registry/header.py +368 -0
- scale_forecasting/registry/ids.py +556 -0
- scale_forecasting/registry/jobs.py +191 -0
- scale_forecasting/registry/lifecycle.py +284 -0
- scale_forecasting/registry/ops.py +1666 -0
- scale_forecasting/registry/params.py +150 -0
- scale_forecasting/registry/reads.py +971 -0
- scale_forecasting/registry/rows.py +556 -0
- scale_forecasting/registry/tables.py +150 -0
- scale_forecasting/registry/views.py +399 -0
- scale_forecasting/registry/write_api.py +386 -0
- scale_forecasting/report.py +932 -0
- scale_forecasting/resources/__init__.py +3 -0
- scale_forecasting/resources/audit.py +64 -0
- scale_forecasting/resources/catalog.py +263 -0
- scale_forecasting/resources/cluster.py +352 -0
- scale_forecasting/resources/fleet.py +613 -0
- scale_forecasting/resources/serverless.py +428 -0
- scale_forecasting/resources/slot.py +295 -0
- scale_forecasting/retry_policy.py +378 -0
- scale_forecasting/retry_run.py +535 -0
- scale_forecasting/review.py +2689 -0
- scale_forecasting/router.py +41 -0
- scale_forecasting/scenarios.py +564 -0
- scale_forecasting/sdk.py +2073 -0
- scale_forecasting/seasonality.py +60 -0
- scale_forecasting/settings.py +171 -0
- scale_forecasting/shared_clusters.py +348 -0
- scale_forecasting/smoke_run.py +83 -0
- scale_forecasting/spark_entry.py +47 -0
- scale_forecasting/staging.py +121 -0
- scale_forecasting/submit.py +667 -0
- scale_forecasting/submitters.py +515 -0
- scale_forecasting/validation.py +201 -0
- scale_forecasting/vertex_entry.py +81 -0
- scale_forecasting/vertex_submit.py +680 -0
- scale_forecasting/worker.py +1432 -0
- scale_forecasting-1.0.0.dist-info/METADATA +361 -0
- scale_forecasting-1.0.0.dist-info/RECORD +186 -0
- scale_forecasting-1.0.0.dist-info/WHEEL +4 -0
- scale_forecasting-1.0.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,1571 @@
|
|
|
1
|
+
"""Single-source generator and environment probe for AI agent, skill, schema, and LLM surfaces.
|
|
2
|
+
|
|
3
|
+
Generates and verifies the repository's machine-readable and agent-facing artifacts directly from
|
|
4
|
+
the canonical Python declarations (`RunConfig`, `list_models`, `METRIC_NAMES`, `VIEW_NAMES`,
|
|
5
|
+
`REGISTRY_TABLE_NAMES`, `SOURCE_TABLE_NAMES`, `EXTRA_MODULES`, and `configs/`):
|
|
6
|
+
|
|
7
|
+
1. ``docs/schemas/run_config.schema.json`` — JSON Schema (Draft 2020-12) for `RunConfig`.
|
|
8
|
+
2. ``skills/scale-forecasting/references/config_reference.md`` — complete field-by-field reference
|
|
9
|
+
for `RunConfig` and every nested Pydantic model, cross-field rules, and ``run_id`` exclusions.
|
|
10
|
+
3. ``skills/scale-forecasting/references/catalog_reference.md`` — all 34 models, 21 metrics,
|
|
11
|
+
7 compute runtimes, 4 GPU types, 12 dependency extras, BigQuery tables/views, and 62 configs.
|
|
12
|
+
4. ``skills/scale-forecasting/references/execution_paths_and_ops.md`` — all 5 execution paths,
|
|
13
|
+
environment variables, monitoring/review/calibration, surgical repair, and registry operations.
|
|
14
|
+
5. ``docs/llms.txt`` — concise LLM index following the ``llms.txt`` convention.
|
|
15
|
+
6. ``docs/llms-full.txt`` — single-file comprehensive context bundle for LLM and RAG ingestion.
|
|
16
|
+
|
|
17
|
+
Also provides `probe_environment`, which inspects the active Python environment (installed extras,
|
|
18
|
+
locally available vs. missing-extra models, ``SF_*`` environment variables, `.env.infra`, and ADC
|
|
19
|
+
credentials) without making network calls or importing cloud SDKs.
|
|
20
|
+
|
|
21
|
+
CLI usage::
|
|
22
|
+
|
|
23
|
+
python -m scale_forecasting.agent_surfaces --write
|
|
24
|
+
python -m scale_forecasting.agent_surfaces --check
|
|
25
|
+
python -m scale_forecasting.agent_surfaces --probe-env
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import sys
|
|
34
|
+
from importlib.metadata import version as _pkg_version
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Any, get_args, get_origin
|
|
37
|
+
|
|
38
|
+
from pydantic import BaseModel
|
|
39
|
+
from pydantic.fields import FieldInfo
|
|
40
|
+
from pydantic_core import PydanticUndefined
|
|
41
|
+
|
|
42
|
+
from .config import (
|
|
43
|
+
COMPUTE_FAMILIES,
|
|
44
|
+
LEARNED_STRATEGIES,
|
|
45
|
+
PROFILE_SOURCE_KEYWORDS,
|
|
46
|
+
RECONCILIATION_METHODS,
|
|
47
|
+
RUN_CONFIG_SCHEMA_URI,
|
|
48
|
+
BacktestConfig,
|
|
49
|
+
CapacityConfig,
|
|
50
|
+
CapacityServicePolicy,
|
|
51
|
+
ComputeConfig,
|
|
52
|
+
DataConfig,
|
|
53
|
+
EnsembleCompute,
|
|
54
|
+
EnsembleConfig,
|
|
55
|
+
FamilyCompute,
|
|
56
|
+
FeaturesConfig,
|
|
57
|
+
HierarchyConfig,
|
|
58
|
+
HpoConfig,
|
|
59
|
+
OutputConfig,
|
|
60
|
+
ProfileConfig,
|
|
61
|
+
RetryResources,
|
|
62
|
+
RunConfig,
|
|
63
|
+
)
|
|
64
|
+
from .errors import EXTRA_MODULES, configure_cli_logging, is_importable
|
|
65
|
+
from .metrics import METRIC_NAMES, get_metric
|
|
66
|
+
from .models import get_model, list_models
|
|
67
|
+
from .registry.ddl import REGISTRY_TABLE_NAMES, SOURCE_TABLE_NAMES
|
|
68
|
+
from .registry.views import VIEW_NAMES
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"ALL_EXTRA_MODULES",
|
|
72
|
+
"EXTRA_DESCRIPTIONS",
|
|
73
|
+
"GPU_CATALOG",
|
|
74
|
+
"RUNTIME_CATALOG",
|
|
75
|
+
"build_run_config_json_schema",
|
|
76
|
+
"check_agent_surfaces",
|
|
77
|
+
"expected_agent_surface_files",
|
|
78
|
+
"main",
|
|
79
|
+
"probe_environment",
|
|
80
|
+
"render_llms_full_txt",
|
|
81
|
+
"render_llms_txt",
|
|
82
|
+
"render_run_config_json_schema",
|
|
83
|
+
"render_skill_catalog_reference",
|
|
84
|
+
"render_skill_config_reference",
|
|
85
|
+
"render_skill_execution_paths_reference",
|
|
86
|
+
"write_agent_surfaces",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
_SITE_BASE = "https://statmike.github.io/scale-forecasting"
|
|
90
|
+
_REPO_URL = "https://github.com/statmike/scale-forecasting"
|
|
91
|
+
|
|
92
|
+
_MODELS_STATS_MODULES: tuple[str, ...] = ("statsforecast",)
|
|
93
|
+
_MODELS_TREES_MODULES: tuple[str, ...] = ("xgboost", "lightgbm", "catboost")
|
|
94
|
+
_MODELS_PROPHET_MODULES: tuple[str, ...] = ("prophet",)
|
|
95
|
+
_MODELS_DL_MODULES: tuple[str, ...] = ("torch", "neuralprophet", "neuralforecast")
|
|
96
|
+
_MODELS_AUTOML_MODULES: tuple[str, ...] = EXTRA_MODULES["models-automl"]
|
|
97
|
+
|
|
98
|
+
ALL_EXTRA_MODULES: dict[str, tuple[str, ...]] = {
|
|
99
|
+
"gcp": EXTRA_MODULES["gcp"],
|
|
100
|
+
"notebook": EXTRA_MODULES["notebook"],
|
|
101
|
+
"spark": EXTRA_MODULES["spark"],
|
|
102
|
+
"ray": EXTRA_MODULES["ray"],
|
|
103
|
+
"submit": EXTRA_MODULES["submit"],
|
|
104
|
+
"models-stats": _MODELS_STATS_MODULES,
|
|
105
|
+
"models-trees": _MODELS_TREES_MODULES,
|
|
106
|
+
"models-prophet": _MODELS_PROPHET_MODULES,
|
|
107
|
+
"models-dl": _MODELS_DL_MODULES,
|
|
108
|
+
"models-automl": _MODELS_AUTOML_MODULES,
|
|
109
|
+
"models": (
|
|
110
|
+
*_MODELS_STATS_MODULES,
|
|
111
|
+
*_MODELS_TREES_MODULES,
|
|
112
|
+
*_MODELS_PROPHET_MODULES,
|
|
113
|
+
*_MODELS_DL_MODULES,
|
|
114
|
+
*_MODELS_AUTOML_MODULES,
|
|
115
|
+
),
|
|
116
|
+
"all": tuple(
|
|
117
|
+
dict.fromkeys(
|
|
118
|
+
[
|
|
119
|
+
*EXTRA_MODULES["gcp"],
|
|
120
|
+
*EXTRA_MODULES["notebook"],
|
|
121
|
+
*EXTRA_MODULES["spark"],
|
|
122
|
+
*EXTRA_MODULES["ray"],
|
|
123
|
+
*_MODELS_STATS_MODULES,
|
|
124
|
+
*_MODELS_TREES_MODULES,
|
|
125
|
+
*_MODELS_PROPHET_MODULES,
|
|
126
|
+
*_MODELS_DL_MODULES,
|
|
127
|
+
*_MODELS_AUTOML_MODULES,
|
|
128
|
+
]
|
|
129
|
+
)
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
EXTRA_DESCRIPTIONS: dict[str, str] = {
|
|
134
|
+
"gcp": "Google Cloud clients (BigQuery, Storage Read/Write APIs, GCS, Dataproc, Vertex AI)",
|
|
135
|
+
"notebook": "Interactive notebook kernel and matplotlib plotting helpers",
|
|
136
|
+
"spark": "PySpark runtime client plus [gcp]",
|
|
137
|
+
"ray": "Ray cluster/job submission client plus [gcp]",
|
|
138
|
+
"submit": "Alias for [ray] (thin launch-host client for all cloud runtimes)",
|
|
139
|
+
"models-stats": "Nixtla statsforecast and pmdarima statistical models (5 models)",
|
|
140
|
+
"models-trees": "Gradient-boosted tree models: XGBoost, LightGBM, CatBoost (3 models)",
|
|
141
|
+
"models-prophet": "Prophet additive/multiplicative decomposable model (1 model)",
|
|
142
|
+
"models-dl": "PyTorch, NeuralProphet, and NeuralForecast deep-learning models (5 models)",
|
|
143
|
+
"models-automl": "Vertex AI Pipelines / KFP components for Tabular Workflows (4 models)",
|
|
144
|
+
"models": "All 5 model family extras combined",
|
|
145
|
+
"all": "Complete installation (all cloud clients, runtimes, notebooks, and model families)",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
RUNTIME_CATALOG: tuple[dict[str, Any], ...] = (
|
|
149
|
+
{
|
|
150
|
+
"runtime": "spark",
|
|
151
|
+
"modes": "spark_mode: 'serverless' (default) | 'cluster' (plus interactive Spark Connect)",
|
|
152
|
+
"families": ("statistical", "ml", "deep_learning"),
|
|
153
|
+
"gpu_types": ("L4 (serverless/cluster)", "T4, A100, A100_80GB (cluster only)"),
|
|
154
|
+
"scaling": "Dynamic executor allocation (max_executors / min_workers / max_workers)",
|
|
155
|
+
"submitter": "scale_forecasting.submit",
|
|
156
|
+
"summary": "Dataproc Serverless batches or ephemeral/existing Dataproc GCE clusters.",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"runtime": "ray",
|
|
160
|
+
"modes": "ray_mode: 'vertex' (default) | 'gke'",
|
|
161
|
+
"families": ("statistical", "ml", "deep_learning"),
|
|
162
|
+
"gpu_types": ("T4", "L4", "A100", "A100_80GB"),
|
|
163
|
+
"scaling": "Autoscaling CPU + GPU worker pools (ray_autoscale, min_workers, max_workers)",
|
|
164
|
+
"submitter": "scale_forecasting.ray_submit",
|
|
165
|
+
"summary": "Vertex AI Managed Ray or KubeRay on GKE with fractional GPU task packing.",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"runtime": "vertex",
|
|
169
|
+
"modes": "Managed Vertex AI CustomJob worker pool",
|
|
170
|
+
"families": ("statistical", "ml", "deep_learning"),
|
|
171
|
+
"gpu_types": ("T4", "L4", "A100", "A100_80GB"),
|
|
172
|
+
"scaling": "Fixed multi-VM worker pool (workers >= 1, auto-expands for multi-model DL)",
|
|
173
|
+
"submitter": "scale_forecasting.vertex_submit",
|
|
174
|
+
"summary": "Serverless multi-worker Vertex AI CustomJob with BQ Storage Read sharding.",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"runtime": "gce",
|
|
178
|
+
"modes": "Single-VM Container-Optimized OS instance",
|
|
179
|
+
"families": ("statistical", "ml", "deep_learning"),
|
|
180
|
+
"gpu_types": ("T4", "L4", "A100", "A100_80GB"),
|
|
181
|
+
"scaling": "Strictly single-VM (workers = 1) with triple-redundant self-deletion",
|
|
182
|
+
"submitter": "scale_forecasting.gce_submit",
|
|
183
|
+
"summary": "Lowest-overhead single-VM execution with automatic guest + host teardown.",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"runtime": "gke",
|
|
187
|
+
"modes": "gke_mode: 'job' (default, K8s Indexed Job) | 'ray' (KubeRay)",
|
|
188
|
+
"families": ("statistical", "ml", "deep_learning"),
|
|
189
|
+
"gpu_types": ("T4", "L4", "A100", "A100_80GB"),
|
|
190
|
+
"scaling": "Multi-pod Indexed Job (workers >= 1) or autoscaling Ray pods on GKE",
|
|
191
|
+
"submitter": "scale_forecasting.gke_submit",
|
|
192
|
+
"summary": "Runs on an existing or ephemeral Google Kubernetes Engine Standard cluster.",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"runtime": "vertex_automl",
|
|
196
|
+
"modes": "automl_mode: 'tabular_workflow' (default) | 'training_job'",
|
|
197
|
+
"families": ("automl",),
|
|
198
|
+
"gpu_types": ("T4", "L4", "A100", "A100_80GB"),
|
|
199
|
+
"scaling": "Managed Vertex AI Pipelines / AutoML worker pools (min_workers, max_workers)",
|
|
200
|
+
"submitter": "scale_forecasting.automl_submit",
|
|
201
|
+
"summary": "Managed global cross-series AutoML (TiDE, TFT, Seq2Seq+, L2L) in Vertex AI.",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"runtime": "bigquery",
|
|
205
|
+
"modes": "In-warehouse BigQuery ML SQL",
|
|
206
|
+
"families": ("native",),
|
|
207
|
+
"gpu_types": (),
|
|
208
|
+
"scaling": "Managed BigQuery slots (no VM provisioning)",
|
|
209
|
+
"submitter": "scale_forecasting.engines.bigquery_engine",
|
|
210
|
+
"summary": "Zero-copy in-warehouse SQL execution for arima_plus and timesfm.",
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
GPU_CATALOG: tuple[dict[str, Any], ...] = (
|
|
215
|
+
{
|
|
216
|
+
"gpu_type": "T4",
|
|
217
|
+
"vertex_enum": "NVIDIA_TESLA_T4",
|
|
218
|
+
"vram_gib": 16,
|
|
219
|
+
"allowed_counts": (1, 2, 4),
|
|
220
|
+
"auto_machine_type": "n1-standard-8 (1-2 GPUs) | n1-standard-16 (4 GPUs)",
|
|
221
|
+
"compatible_machines": "Any n1-* shape (1-2 GPUs max 48 vCPUs; 4 GPUs up to 96 vCPUs)",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"gpu_type": "L4",
|
|
225
|
+
"vertex_enum": "NVIDIA_L4",
|
|
226
|
+
"vram_gib": 24,
|
|
227
|
+
"allowed_counts": (1, 2, 4, 8),
|
|
228
|
+
"auto_machine_type": "g2-standard-8 (1 GPU) | g2-standard-{24,48,96} (2/4/8 GPUs)",
|
|
229
|
+
"compatible_machines": (
|
|
230
|
+
"1 GPU: g2-standard-{4,8,12,16,32}; 2 GPUs: g2-standard-24; "
|
|
231
|
+
"4 GPUs: g2-standard-48; 8 GPUs: g2-standard-96"
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"gpu_type": "A100",
|
|
236
|
+
"vertex_enum": "NVIDIA_TESLA_A100",
|
|
237
|
+
"vram_gib": 40,
|
|
238
|
+
"allowed_counts": (1, 2, 4, 8, 16),
|
|
239
|
+
"auto_machine_type": "a2-highgpu-{1,2,4,8}g | a2-megagpu-16g",
|
|
240
|
+
"compatible_machines": "Strict 1:1 mapping with accelerator_count",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"gpu_type": "A100_80GB",
|
|
244
|
+
"vertex_enum": "NVIDIA_A100_80GB",
|
|
245
|
+
"vram_gib": 80,
|
|
246
|
+
"allowed_counts": (1, 2, 4, 8),
|
|
247
|
+
"auto_machine_type": "a2-ultragpu-{1,2,4,8}g",
|
|
248
|
+
"compatible_machines": "Strict 1:1 mapping with accelerator_count",
|
|
249
|
+
},
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
_ENV_VARS_DOC: tuple[tuple[str, bool, str, str], ...] = (
|
|
253
|
+
(
|
|
254
|
+
"SF_PROJECT_ID",
|
|
255
|
+
True,
|
|
256
|
+
"—",
|
|
257
|
+
"Google Cloud project ID hosting BigQuery and compute runtimes.",
|
|
258
|
+
),
|
|
259
|
+
(
|
|
260
|
+
"SF_CONNECTION",
|
|
261
|
+
True,
|
|
262
|
+
"—",
|
|
263
|
+
"BigLake connection reference (`project.region.connection_name`).",
|
|
264
|
+
),
|
|
265
|
+
(
|
|
266
|
+
"SF_WAREHOUSE_URI",
|
|
267
|
+
True,
|
|
268
|
+
"—",
|
|
269
|
+
"Cloud Storage warehouse root (`gs://<bucket>/warehouse`).",
|
|
270
|
+
),
|
|
271
|
+
(
|
|
272
|
+
"SF_DATASET_ID",
|
|
273
|
+
False,
|
|
274
|
+
"scale_forecasting",
|
|
275
|
+
"BigQuery dataset containing source series tables (and registry unless overridden).",
|
|
276
|
+
),
|
|
277
|
+
(
|
|
278
|
+
"SF_REGISTRY_DATASET_ID",
|
|
279
|
+
False,
|
|
280
|
+
"<SF_DATASET_ID>",
|
|
281
|
+
"Optional separate BigQuery dataset for the 5 registry tables and 5 views.",
|
|
282
|
+
),
|
|
283
|
+
(
|
|
284
|
+
"SF_REGION",
|
|
285
|
+
False,
|
|
286
|
+
"us-central1",
|
|
287
|
+
"Default Google Cloud region for BigQuery, Dataproc, Vertex AI, and GKE.",
|
|
288
|
+
),
|
|
289
|
+
(
|
|
290
|
+
"SF_SERVICE_ACCOUNT",
|
|
291
|
+
False,
|
|
292
|
+
"ADC / default SA",
|
|
293
|
+
"Least-privilege runner service account email attached to cloud worker jobs.",
|
|
294
|
+
),
|
|
295
|
+
(
|
|
296
|
+
"SF_CONTAINER_IMAGE",
|
|
297
|
+
False,
|
|
298
|
+
"Derived from project/region",
|
|
299
|
+
"Artifact Registry container URI for Ray, Vertex CustomJob, GCE, and GKE.",
|
|
300
|
+
),
|
|
301
|
+
(
|
|
302
|
+
"SF_GKE_CLUSTER",
|
|
303
|
+
False,
|
|
304
|
+
"Ephemeral per-run",
|
|
305
|
+
"Existing GKE cluster name to reuse when `runtime='gke'` or `ray_mode='gke'`.",
|
|
306
|
+
),
|
|
307
|
+
(
|
|
308
|
+
"SF_SUBNET",
|
|
309
|
+
False,
|
|
310
|
+
"default",
|
|
311
|
+
"VPC subnet name or self-link for private-IP Dataproc, Ray, Vertex, and GCE jobs.",
|
|
312
|
+
),
|
|
313
|
+
(
|
|
314
|
+
"SF_LOG_LEVEL",
|
|
315
|
+
False,
|
|
316
|
+
"INFO",
|
|
317
|
+
"Root log level for CLI entry points (`DEBUG`, `INFO`, `WARNING`, `ERROR`).",
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
_CONFIG_MODELS: tuple[tuple[str, str, type[BaseModel]], ...] = (
|
|
322
|
+
("RunConfig (top-level)", "Root run configuration object.", RunConfig),
|
|
323
|
+
(
|
|
324
|
+
"data (DataConfig)",
|
|
325
|
+
"Source BigQuery table, column names, frequency, and horizon.",
|
|
326
|
+
DataConfig,
|
|
327
|
+
),
|
|
328
|
+
(
|
|
329
|
+
"features (FeaturesConfig)",
|
|
330
|
+
(
|
|
331
|
+
"Target transforms, country holidays, covariates (future/past/static), lags, "
|
|
332
|
+
"and Fourier terms."
|
|
333
|
+
),
|
|
334
|
+
FeaturesConfig,
|
|
335
|
+
),
|
|
336
|
+
(
|
|
337
|
+
"backtest (BacktestConfig)",
|
|
338
|
+
"Expanding, sliding, frozen, or stale cross-validation geometry and decision metric.",
|
|
339
|
+
BacktestConfig,
|
|
340
|
+
),
|
|
341
|
+
(
|
|
342
|
+
"output (OutputConfig)",
|
|
343
|
+
"Point-forecast arm selection (`raw`, `median`, `mean`, or per-cell `auto`).",
|
|
344
|
+
OutputConfig,
|
|
345
|
+
),
|
|
346
|
+
(
|
|
347
|
+
"hpo (HpoConfig)",
|
|
348
|
+
"Optuna hyperparameter optimization (`fleetwide` or `per_series`).",
|
|
349
|
+
HpoConfig,
|
|
350
|
+
),
|
|
351
|
+
(
|
|
352
|
+
"ensemble (EnsembleConfig)",
|
|
353
|
+
(
|
|
354
|
+
"Calculated (`mean`, `median`, `inverse_error`) and learned (`nnls`, `ridge`, `xgb`) "
|
|
355
|
+
"stackers."
|
|
356
|
+
),
|
|
357
|
+
EnsembleConfig,
|
|
358
|
+
),
|
|
359
|
+
(
|
|
360
|
+
"hierarchy (HierarchyConfig)",
|
|
361
|
+
(
|
|
362
|
+
"Cross-sectional aggregation levels and FPP3 reconciliation (`bottom_up`, "
|
|
363
|
+
"`mint_shrink`, ...)."
|
|
364
|
+
),
|
|
365
|
+
HierarchyConfig,
|
|
366
|
+
),
|
|
367
|
+
(
|
|
368
|
+
"compute (ComputeConfig)",
|
|
369
|
+
(
|
|
370
|
+
"Default runtime, GPU/VM shape, autoscaling bounds, profiling, capacity, and family "
|
|
371
|
+
"overrides."
|
|
372
|
+
),
|
|
373
|
+
ComputeConfig,
|
|
374
|
+
),
|
|
375
|
+
(
|
|
376
|
+
"compute.families.<family> (FamilyCompute)",
|
|
377
|
+
(
|
|
378
|
+
"Per-family runtime and hardware override (`statistical`, `ml`, `deep_learning`, "
|
|
379
|
+
"`automl`)."
|
|
380
|
+
),
|
|
381
|
+
FamilyCompute,
|
|
382
|
+
),
|
|
383
|
+
(
|
|
384
|
+
"compute.ensemble (EnsembleCompute)",
|
|
385
|
+
"When the ensemble DAG node executes (`barrier` vs. `microbatch`).",
|
|
386
|
+
EnsembleCompute,
|
|
387
|
+
),
|
|
388
|
+
(
|
|
389
|
+
"compute.profile (ProfileConfig)",
|
|
390
|
+
"Empirical compute profiling (`mode`, `measure`, `source`, safety margins).",
|
|
391
|
+
ProfileConfig,
|
|
392
|
+
),
|
|
393
|
+
(
|
|
394
|
+
"compute.capacity (CapacityConfig)",
|
|
395
|
+
(
|
|
396
|
+
"Quota preflight and per-service regional capacity retry policies (excluded from "
|
|
397
|
+
"`run_id`)."
|
|
398
|
+
),
|
|
399
|
+
CapacityConfig,
|
|
400
|
+
),
|
|
401
|
+
(
|
|
402
|
+
"compute.capacity.<service> (CapacityServicePolicy)",
|
|
403
|
+
"Per-service attempt, wall-clock, pass, and exponential backoff bounds.",
|
|
404
|
+
CapacityServicePolicy,
|
|
405
|
+
),
|
|
406
|
+
(
|
|
407
|
+
"compute.capacity.retry (RetryResources)",
|
|
408
|
+
"Resource ceiling (`max_executors`) for surgical `--retry` repairs.",
|
|
409
|
+
RetryResources,
|
|
410
|
+
),
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _default_repo_root() -> Path:
|
|
415
|
+
return Path(__file__).resolve().parents[2]
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _adc_present() -> bool:
|
|
419
|
+
"""Check whether Application Default Credentials appear configured without network I/O."""
|
|
420
|
+
env_creds = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", "").strip()
|
|
421
|
+
if env_creds and Path(env_creds).is_file():
|
|
422
|
+
return True
|
|
423
|
+
gcloud_adc = Path.home() / ".config" / "gcloud" / "application_default_credentials.json"
|
|
424
|
+
if gcloud_adc.is_file():
|
|
425
|
+
return True
|
|
426
|
+
return bool(
|
|
427
|
+
os.environ.get("GCE_METADATA_HOST")
|
|
428
|
+
or os.environ.get("K_SERVICE")
|
|
429
|
+
or os.environ.get("CLOUD_RUN_JOB")
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def probe_environment(repo_root: Path | None = None) -> dict[str, Any]:
|
|
434
|
+
"""Inspect the active Python environment, installed extras, models, and GCP readiness.
|
|
435
|
+
|
|
436
|
+
Pure local inspection — performs zero network calls and imports no cloud libraries, so it is
|
|
437
|
+
safe to call in a bare ``pip install scale-forecasting`` environment.
|
|
438
|
+
"""
|
|
439
|
+
root = repo_root or _default_repo_root()
|
|
440
|
+
extras_status: dict[str, dict[str, Any]] = {}
|
|
441
|
+
for extra_name, modules in ALL_EXTRA_MODULES.items():
|
|
442
|
+
missing_mods = [m for m in modules if not is_importable(m)]
|
|
443
|
+
extras_status[extra_name] = {
|
|
444
|
+
"installed": len(missing_mods) == 0,
|
|
445
|
+
"missing_modules": missing_mods,
|
|
446
|
+
"install_command": f'pip install "scale-forecasting[{extra_name}]"',
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
available_models_list: list[str] = []
|
|
450
|
+
missing_models_list: list[dict[str, str]] = []
|
|
451
|
+
for name in list_models():
|
|
452
|
+
cls = get_model(name)
|
|
453
|
+
extra_needed = cls.optional_extra or ("gcp" if cls.runtime == "bigquery" else "core")
|
|
454
|
+
if cls.is_available():
|
|
455
|
+
available_models_list.append(name)
|
|
456
|
+
else:
|
|
457
|
+
missing_models_list.append(
|
|
458
|
+
{
|
|
459
|
+
"model": name,
|
|
460
|
+
"family": cls.family,
|
|
461
|
+
"runtime": cls.runtime,
|
|
462
|
+
"package": cls.package,
|
|
463
|
+
"extra": extra_needed,
|
|
464
|
+
"install_command": f'pip install "scale-forecasting[{extra_needed}]"',
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
env_vars: dict[str, dict[str, Any]] = {}
|
|
469
|
+
for var_name, required, default_val, _desc in _ENV_VARS_DOC:
|
|
470
|
+
raw = os.environ.get(var_name, "").strip()
|
|
471
|
+
env_vars[var_name] = {
|
|
472
|
+
"set": bool(raw),
|
|
473
|
+
"required_for_cloud": required,
|
|
474
|
+
"default": default_val,
|
|
475
|
+
"value": raw if raw else None,
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
required_cloud_vars = ("SF_PROJECT_ID", "SF_CONNECTION", "SF_WAREHOUSE_URI")
|
|
479
|
+
missing_cloud_vars = [k for k in required_cloud_vars if not env_vars[k]["set"]]
|
|
480
|
+
settings_configured = len(missing_cloud_vars) == 0
|
|
481
|
+
dotenv_present = (Path.cwd() / ".env.infra").is_file() or (root / ".env.infra").is_file()
|
|
482
|
+
adc_ok = _adc_present()
|
|
483
|
+
gcp_installed = bool(extras_status["gcp"]["installed"])
|
|
484
|
+
|
|
485
|
+
cloud_ready = gcp_installed and settings_configured
|
|
486
|
+
recommendations: list[str] = []
|
|
487
|
+
if not gcp_installed:
|
|
488
|
+
recommendations.append(
|
|
489
|
+
"Install Google Cloud clients for BigQuery/Vertex/Dataproc: "
|
|
490
|
+
'pip install "scale-forecasting[gcp]"'
|
|
491
|
+
)
|
|
492
|
+
if missing_cloud_vars:
|
|
493
|
+
if dotenv_present:
|
|
494
|
+
recommendations.append(
|
|
495
|
+
f"Load infrastructure environment variables "
|
|
496
|
+
f"({', '.join(missing_cloud_vars)} missing): "
|
|
497
|
+
"set -a && source .env.infra && set +a"
|
|
498
|
+
)
|
|
499
|
+
else:
|
|
500
|
+
recommendations.append(
|
|
501
|
+
f"Export required cloud environment variables: {', '.join(missing_cloud_vars)}"
|
|
502
|
+
)
|
|
503
|
+
if missing_models_list:
|
|
504
|
+
recommendations.append(
|
|
505
|
+
f"{len(missing_models_list)} optional model(s) not installed locally; install all "
|
|
506
|
+
'with: pip install "scale-forecasting[models]" (or pass --ignore-unavailable-models)'
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
"python_version": sys.version.split()[0],
|
|
511
|
+
"package_version": _pkg_version("scale-forecasting"),
|
|
512
|
+
"extras": extras_status,
|
|
513
|
+
"models": {
|
|
514
|
+
"total": len(list_models()),
|
|
515
|
+
"available_count": len(available_models_list),
|
|
516
|
+
"available": available_models_list,
|
|
517
|
+
"missing": missing_models_list,
|
|
518
|
+
},
|
|
519
|
+
"settings_env": {
|
|
520
|
+
"configured": settings_configured,
|
|
521
|
+
"missing_required": missing_cloud_vars,
|
|
522
|
+
"dotenv_infra_present": dotenv_present,
|
|
523
|
+
"variables": env_vars,
|
|
524
|
+
},
|
|
525
|
+
"adc_configured": adc_ok,
|
|
526
|
+
"execution_paths_ready": {
|
|
527
|
+
"path_1_offline_playground_and_dry_run": True,
|
|
528
|
+
"path_2_cloud_plan_feasibility_and_stage": cloud_ready,
|
|
529
|
+
"path_3_cloud_multi_family_dag_run": cloud_ready,
|
|
530
|
+
"path_4_direct_family_submitters": cloud_ready,
|
|
531
|
+
"path_5_airflow_dag_emission": True,
|
|
532
|
+
},
|
|
533
|
+
"recommended_actions": recommendations,
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def build_run_config_json_schema() -> dict[str, Any]:
|
|
538
|
+
"""Generate the canonical Draft 2020-12 JSON Schema for `RunConfig`."""
|
|
539
|
+
raw = RunConfig.model_json_schema()
|
|
540
|
+
props = dict(raw.get("properties", {}))
|
|
541
|
+
props["$schema"] = {
|
|
542
|
+
"description": "Optional JSON Schema URI for editor autocomplete and static validation.",
|
|
543
|
+
"title": "$Schema",
|
|
544
|
+
"type": "string",
|
|
545
|
+
}
|
|
546
|
+
schema: dict[str, Any] = {
|
|
547
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
548
|
+
"$id": RUN_CONFIG_SCHEMA_URI,
|
|
549
|
+
**raw,
|
|
550
|
+
"properties": props,
|
|
551
|
+
}
|
|
552
|
+
return schema
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def render_run_config_json_schema() -> str:
|
|
556
|
+
"""Serialize `build_run_config_json_schema` deterministically as formatted JSON."""
|
|
557
|
+
return json.dumps(build_run_config_json_schema(), indent=2, sort_keys=True) + "\n"
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _format_type_annotation(annotation: Any) -> str:
|
|
561
|
+
"""Render a Python/Pydantic field annotation as a concise, table-safe string."""
|
|
562
|
+
if annotation is None or annotation is type(None):
|
|
563
|
+
return "None"
|
|
564
|
+
origin = get_origin(annotation)
|
|
565
|
+
args = get_args(annotation)
|
|
566
|
+
if origin is not None:
|
|
567
|
+
origin_name = getattr(origin, "__name__", str(origin))
|
|
568
|
+
if origin_name == "Literal":
|
|
569
|
+
return " \\| ".join(repr(a) for a in args)
|
|
570
|
+
if origin_name in ("Union", "UnionType") or "Union" in str(origin):
|
|
571
|
+
return " \\| ".join(_format_type_annotation(a) for a in args)
|
|
572
|
+
if origin in (list, tuple, set, frozenset):
|
|
573
|
+
inner = ", ".join(_format_type_annotation(a) for a in args) if args else "Any"
|
|
574
|
+
return f"{origin_name}[{inner}]"
|
|
575
|
+
if origin is dict:
|
|
576
|
+
if len(args) == 2:
|
|
577
|
+
return (
|
|
578
|
+
f"dict[{_format_type_annotation(args[0])}, {_format_type_annotation(args[1])}]"
|
|
579
|
+
)
|
|
580
|
+
return "dict"
|
|
581
|
+
if args:
|
|
582
|
+
return _format_type_annotation(args[0])
|
|
583
|
+
if isinstance(annotation, type):
|
|
584
|
+
return annotation.__name__
|
|
585
|
+
return str(annotation).replace("typing.", "").replace("|", "\\|")
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _format_default(field: FieldInfo) -> str:
|
|
589
|
+
"""Render a Pydantic `FieldInfo` default value for a Markdown table cell."""
|
|
590
|
+
if field.is_required():
|
|
591
|
+
return "**required**"
|
|
592
|
+
if field.default_factory is not None:
|
|
593
|
+
factory_name = getattr(field.default_factory, "__name__", "")
|
|
594
|
+
if factory_name in ("list", "dict"):
|
|
595
|
+
return "`[]`" if factory_name == "list" else "`{}`"
|
|
596
|
+
return f"`{factory_name}()`"
|
|
597
|
+
if field.default is PydanticUndefined:
|
|
598
|
+
return "**required**"
|
|
599
|
+
return f"`{json.dumps(field.default)}`"
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _format_constraints(field: FieldInfo) -> str:
|
|
603
|
+
"""Extract numeric or length constraints from Pydantic field metadata."""
|
|
604
|
+
parts: list[str] = []
|
|
605
|
+
for meta in field.metadata:
|
|
606
|
+
for attr, symbol in (
|
|
607
|
+
("gt", ">"),
|
|
608
|
+
("ge", ">="),
|
|
609
|
+
("lt", "<"),
|
|
610
|
+
("le", "<="),
|
|
611
|
+
("min_length", "min_len="),
|
|
612
|
+
):
|
|
613
|
+
val = getattr(meta, attr, None)
|
|
614
|
+
if val is not None:
|
|
615
|
+
parts.append(f"`{symbol} {val}`")
|
|
616
|
+
return ", ".join(parts) if parts else "—"
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def render_skill_config_reference() -> str:
|
|
620
|
+
"""Render ``skills/scale-forecasting/references/config_reference.md`` from `RunConfig`."""
|
|
621
|
+
lines: list[str] = [
|
|
622
|
+
"# `RunConfig` Schema & Validation Reference",
|
|
623
|
+
"",
|
|
624
|
+
(
|
|
625
|
+
"> **Auto-generated from [`src/scale_forecasting/config.py`]"
|
|
626
|
+
"(../../../src/scale_forecasting/config.py) by "
|
|
627
|
+
"`python -m scale_forecasting.agent_surfaces --write`.** "
|
|
628
|
+
"Do not edit by hand; pre-commit (`test_agent_surfaces.py`) enforces zero drift."
|
|
629
|
+
),
|
|
630
|
+
"",
|
|
631
|
+
f"- **JSON Schema URI:** `{RUN_CONFIG_SCHEMA_URI}`",
|
|
632
|
+
(
|
|
633
|
+
"- **Local Schema Path:** [`docs/schemas/run_config.schema.json`]"
|
|
634
|
+
"(../../../docs/schemas/run_config.schema.json)"
|
|
635
|
+
),
|
|
636
|
+
(
|
|
637
|
+
"- **Strictness:** Every config block sets `ConfigDict(frozen=True, extra='forbid')`. "
|
|
638
|
+
"Unknown keys fail immediately at load time (`ConfigError`)."
|
|
639
|
+
),
|
|
640
|
+
(
|
|
641
|
+
'- **Optional `$schema` Key:** Any config JSON file may include `"$schema": "'
|
|
642
|
+
+ RUN_CONFIG_SCHEMA_URI
|
|
643
|
+
+ '"` at the top level. `RunConfig` validates and strips `$schema` before model '
|
|
644
|
+
"construction so `model_dump()` and `run_id` hashes are completely unaffected."
|
|
645
|
+
),
|
|
646
|
+
"",
|
|
647
|
+
"---",
|
|
648
|
+
"",
|
|
649
|
+
"## 1. All Config Blocks & Fields",
|
|
650
|
+
"",
|
|
651
|
+
]
|
|
652
|
+
|
|
653
|
+
for section_title, summary, model_cls in _CONFIG_MODELS:
|
|
654
|
+
lines.append(f"### `{section_title}`")
|
|
655
|
+
lines.append("")
|
|
656
|
+
lines.append(summary)
|
|
657
|
+
lines.append("")
|
|
658
|
+
lines.append("| Field | Type / Allowed Values | Default | Constraints |")
|
|
659
|
+
lines.append("| :--- | :--- | :--- | :--- |")
|
|
660
|
+
for fname, finfo in model_cls.model_fields.items():
|
|
661
|
+
type_str = _format_type_annotation(finfo.annotation)
|
|
662
|
+
default_str = _format_default(finfo)
|
|
663
|
+
constraint_str = _format_constraints(finfo)
|
|
664
|
+
lines.append(f"| `{fname}` | `{type_str}` | {default_str} | {constraint_str} |")
|
|
665
|
+
lines.append("")
|
|
666
|
+
|
|
667
|
+
metrics_inline = ", ".join(f"`{m}`" for m in METRIC_NAMES)
|
|
668
|
+
learned_inline = ", ".join(f"`{s}`" for s in sorted(LEARNED_STRATEGIES))
|
|
669
|
+
reconcil_inline = ", ".join(f"`{m}`" for m in sorted(RECONCILIATION_METHODS))
|
|
670
|
+
families_inline = ", ".join(f"`{f}`" for f in COMPUTE_FAMILIES)
|
|
671
|
+
profile_inline = ", ".join(f"`{k}`" for k in PROFILE_SOURCE_KEYWORDS)
|
|
672
|
+
|
|
673
|
+
lines.extend(
|
|
674
|
+
[
|
|
675
|
+
"---",
|
|
676
|
+
"",
|
|
677
|
+
"## 2. Dynamic Vocabularies & Special Keys",
|
|
678
|
+
"",
|
|
679
|
+
"| Field | Allowed Values / Rule |",
|
|
680
|
+
"| :--- | :--- |",
|
|
681
|
+
(
|
|
682
|
+
f"| `models` | Non-empty list of registered model names (`{len(list_models())}` "
|
|
683
|
+
"available; see [`catalog_reference.md`](./catalog_reference.md)). "
|
|
684
|
+
"No duplicates allowed. |"
|
|
685
|
+
),
|
|
686
|
+
(
|
|
687
|
+
f"| `backtest.decision_metric` | Any of the `{len(METRIC_NAMES)}` registered "
|
|
688
|
+
f"metrics: {metrics_inline}. |"
|
|
689
|
+
),
|
|
690
|
+
(
|
|
691
|
+
"| `ensemble.strategies` | Calculated: `mean`, `median`, `inverse_error` (work "
|
|
692
|
+
f"with or without backtest). Learned stackers: {learned_inline} (require "
|
|
693
|
+
"`backtest.enabled=true`). Singular shorthand `strategy` is also accepted. |"
|
|
694
|
+
),
|
|
695
|
+
f"| `hierarchy.reconciliation_methods` | Any subset of {reconcil_inline}. |",
|
|
696
|
+
(
|
|
697
|
+
f"| `compute.families` | Keys must be in {families_inline}. `native` models "
|
|
698
|
+
"always run in BigQuery and never take a `compute.families` entry. |"
|
|
699
|
+
),
|
|
700
|
+
(
|
|
701
|
+
f"| `compute.profile.source` | One of {profile_inline} or an existing "
|
|
702
|
+
"`<slug>-<12hex>` `run_id`. |"
|
|
703
|
+
),
|
|
704
|
+
(
|
|
705
|
+
"| `model_params` | Dict mapping `<model_name>` to `<param_dict>` of JSON-safe "
|
|
706
|
+
"scalars or flat lists (`bool`, `int`, `float`, `str`, `None`). Non-finite floats "
|
|
707
|
+
"(`NaN`, `Infinity`) are rejected. |"
|
|
708
|
+
),
|
|
709
|
+
"",
|
|
710
|
+
"---",
|
|
711
|
+
"",
|
|
712
|
+
"## 3. Cross-Field Validation Rules (`RunConfig._normalize`)",
|
|
713
|
+
"",
|
|
714
|
+
(
|
|
715
|
+
"1. **HPO requires Backtesting:** `hpo.enabled = true` requires "
|
|
716
|
+
"`backtest.enabled = true` (raises `ConfigError` otherwise)."
|
|
717
|
+
),
|
|
718
|
+
(
|
|
719
|
+
"2. **Learned Ensembles require Backtesting:** If `ensemble.enabled = true` and "
|
|
720
|
+
"`backtest.enabled = false`, learned strategies (`nnls`, `ridge`, `xgb`) are "
|
|
721
|
+
"automatically dropped with a warning; always set `backtest.enabled = true` when "
|
|
722
|
+
"using learned stackers."
|
|
723
|
+
),
|
|
724
|
+
"3. **Point Forecast Arm (`output.point_forecast`):**",
|
|
725
|
+
(
|
|
726
|
+
' - Defaults to `"auto"` when `backtest.enabled = true` and `"median"` when '
|
|
727
|
+
"`backtest.enabled = false`."
|
|
728
|
+
),
|
|
729
|
+
(
|
|
730
|
+
' - Setting `"mean"` or `"auto"` when `backtest.enabled = false` raises '
|
|
731
|
+
"`ConfigError`."
|
|
732
|
+
),
|
|
733
|
+
"4. **Short-Series Backtest Policies (`backtest.short_series`):**",
|
|
734
|
+
" - `min_folds` cannot exceed `n_folds`.",
|
|
735
|
+
(
|
|
736
|
+
' - `short_series = "shrink_train"` requires `min_train_floor` to be set (and '
|
|
737
|
+
"`min_train_floor` is forbidden on other `short_series` policies)."
|
|
738
|
+
),
|
|
739
|
+
' - `control_arm = true` is forbidden when `scheme = "expanding_stale"`.',
|
|
740
|
+
"5. **Covariate Hygiene (`features`):**",
|
|
741
|
+
" - `future_covariates` and `past_covariates` must be disjoint.",
|
|
742
|
+
(
|
|
743
|
+
" - `static_covariates` must be disjoint from all dynamic covariates (`exog`, "
|
|
744
|
+
"`future_covariates`, `past_covariates`)."
|
|
745
|
+
),
|
|
746
|
+
(
|
|
747
|
+
" - Every key in `exog_lags` must name a declared dynamic covariate, and lags "
|
|
748
|
+
"must be positive integers."
|
|
749
|
+
),
|
|
750
|
+
"6. **Hierarchy Hygiene (`hierarchy`):**",
|
|
751
|
+
(
|
|
752
|
+
" - When `hierarchy.enabled = true`, both `levels` and "
|
|
753
|
+
"`reconciliation_methods` must be non-empty, and `middle_level` (if set) must "
|
|
754
|
+
"match one of the entries in `levels`."
|
|
755
|
+
),
|
|
756
|
+
"7. **Per-Family Runtime & Hardware Constraints (`compute.families`):**",
|
|
757
|
+
(
|
|
758
|
+
" - Only `deep_learning` and `automl` families may request "
|
|
759
|
+
'`hardware = "gpu"` or `gpu_type`.'
|
|
760
|
+
),
|
|
761
|
+
(
|
|
762
|
+
' - Family `automl` must use `runtime = "vertex_automl"`, and no other family '
|
|
763
|
+
"may use `vertex_automl` or `automl_mode`."
|
|
764
|
+
),
|
|
765
|
+
(
|
|
766
|
+
' - `spark_mode` / `spark_cluster_name` are only valid when `runtime = "spark"`; '
|
|
767
|
+
'Dataproc Serverless (`spark_mode = "serverless"`) supports `L4` GPUs only and '
|
|
768
|
+
"forbids `machine_type`."
|
|
769
|
+
),
|
|
770
|
+
(
|
|
771
|
+
' - `gke_mode` is only valid when `runtime = "gke"`; `ray_mode` is only valid '
|
|
772
|
+
'when `runtime = "ray"`.'
|
|
773
|
+
),
|
|
774
|
+
' - `runtime = "gce"` is strictly single-VM (`workers = 1`).',
|
|
775
|
+
(
|
|
776
|
+
" - `min_workers` / `max_workers` are only valid on autoscaling runtimes "
|
|
777
|
+
'(`ray`, `spark`, `vertex_automl`, or `gke` with `gke_mode = "ray"`).'
|
|
778
|
+
),
|
|
779
|
+
"",
|
|
780
|
+
"---",
|
|
781
|
+
"",
|
|
782
|
+
"## 4. GPU-to-VM Machine Type Compatibility (`resolve_vm_machine_type`)",
|
|
783
|
+
"",
|
|
784
|
+
(
|
|
785
|
+
"| `gpu_type` | Vertex Enum | VRAM | Allowed `accelerator_count` | "
|
|
786
|
+
"Auto-Resolved `machine_type` | Valid Explicit `machine_type` Shapes |"
|
|
787
|
+
),
|
|
788
|
+
"| :--- | :--- | :--- | :--- | :--- | :--- |",
|
|
789
|
+
]
|
|
790
|
+
)
|
|
791
|
+
for g in GPU_CATALOG:
|
|
792
|
+
counts = ", ".join(f"`{c}`" for c in g["allowed_counts"])
|
|
793
|
+
lines.append(
|
|
794
|
+
f"| `{g['gpu_type']}` | `{g['vertex_enum']}` | {g['vram_gib']} GiB | {counts} | "
|
|
795
|
+
f"`{g['auto_machine_type']}` | {g['compatible_machines']} |"
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
lines.extend(
|
|
799
|
+
[
|
|
800
|
+
"",
|
|
801
|
+
"---",
|
|
802
|
+
"",
|
|
803
|
+
"## 5. Content-Addressed `run_id` Digest Rules (`registry/ids.py`)",
|
|
804
|
+
"",
|
|
805
|
+
"- `run_id` is `<slug>-<12hex>` computed from the normalized `RunConfig` JSON dump.",
|
|
806
|
+
(
|
|
807
|
+
"- **Operational fields excluded from `run_id` "
|
|
808
|
+
"(changing these keeps the same `run_id`):**"
|
|
809
|
+
),
|
|
810
|
+
(
|
|
811
|
+
" - Infrastructure environment (`SF_PROJECT_ID`, `SF_DATASET_ID`, "
|
|
812
|
+
"`SF_REGISTRY_DATASET_ID`, `SF_REGION`, `SF_WAREHOUSE_URI`, `SF_CONNECTION`)."
|
|
813
|
+
),
|
|
814
|
+
(
|
|
815
|
+
" - `compute.capacity` (all regional retry policies and "
|
|
816
|
+
"`compute.capacity.retry.max_executors`)."
|
|
817
|
+
),
|
|
818
|
+
" - `compute.profile.source` (resolved profile pointer).",
|
|
819
|
+
(
|
|
820
|
+
' - Default/unset `machine_type` (`"auto"`) and default `workers` (`1`) when '
|
|
821
|
+
"left at their baseline defaults."
|
|
822
|
+
),
|
|
823
|
+
' - Top-level `"$schema"` URI key.',
|
|
824
|
+
]
|
|
825
|
+
)
|
|
826
|
+
return "\n".join(lines) + "\n"
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def render_skill_catalog_reference(repo_root: Path | None = None) -> str:
|
|
830
|
+
"""Render ``skills/scale-forecasting/references/catalog_reference.md`` from registries."""
|
|
831
|
+
root = repo_root or _default_repo_root()
|
|
832
|
+
lines: list[str] = [
|
|
833
|
+
"# Canonical Platform Catalogs (Models, Metrics, Runtimes, Extras, Views & Configs)",
|
|
834
|
+
"",
|
|
835
|
+
(
|
|
836
|
+
"> **Auto-generated from code registries by "
|
|
837
|
+
"`python -m scale_forecasting.agent_surfaces --write`.** "
|
|
838
|
+
"Do not edit by hand; pre-commit (`test_agent_surfaces.py`) enforces zero drift."
|
|
839
|
+
),
|
|
840
|
+
"",
|
|
841
|
+
"---",
|
|
842
|
+
"",
|
|
843
|
+
f"## 1. Forecasting Models ({len(list_models())} Models Across 5 Families)",
|
|
844
|
+
"",
|
|
845
|
+
(
|
|
846
|
+
"| Model (`models` key) | Family | Runtime | Upstream Package | Install Extra | "
|
|
847
|
+
"Future Cov. | Past Cov. | Static Cov. | Explainability | Global / Hybrid | "
|
|
848
|
+
"GPU Capable |"
|
|
849
|
+
),
|
|
850
|
+
"| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |",
|
|
851
|
+
]
|
|
852
|
+
|
|
853
|
+
model_names = sorted(
|
|
854
|
+
list_models(),
|
|
855
|
+
key=lambda n: (get_model(n).family, get_model(n).runtime, n),
|
|
856
|
+
)
|
|
857
|
+
for name in model_names:
|
|
858
|
+
cls = get_model(name)
|
|
859
|
+
extra = cls.optional_extra or ("gcp" if cls.runtime == "bigquery" else "core")
|
|
860
|
+
modes = ["local"]
|
|
861
|
+
if cls.supports_global:
|
|
862
|
+
modes.append("global")
|
|
863
|
+
if cls.supports_hybrid:
|
|
864
|
+
modes.append("hybrid")
|
|
865
|
+
lines.append(
|
|
866
|
+
f"| `{name}` | `{cls.family}` | `{cls.runtime}` | `{cls.package}` | `{extra}` | "
|
|
867
|
+
f"{'Yes' if cls.supports_future_covariates else 'No'} | "
|
|
868
|
+
f"{'Yes' if cls.supports_past_covariates else 'No'} | "
|
|
869
|
+
f"{'Yes' if cls.supports_static_covariates else 'No'} | "
|
|
870
|
+
f"{'Yes' if cls.supports_explainability else 'No'} | "
|
|
871
|
+
f"`{'/'.join(modes)}` | "
|
|
872
|
+
f"{'Yes' if cls.gpu_capable else 'No'} |"
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
lines.extend(
|
|
876
|
+
[
|
|
877
|
+
"",
|
|
878
|
+
"---",
|
|
879
|
+
"",
|
|
880
|
+
f"## 2. Evaluation Metrics ({len(METRIC_NAMES)} Metrics: 16 Point + 5 Interval)",
|
|
881
|
+
"",
|
|
882
|
+
(
|
|
883
|
+
"| Metric (`decision_metric`) | Kind | Direction | Needs Intervals | "
|
|
884
|
+
"Needs Train History | Needs Seasonal Period | Optimal Point Arm |"
|
|
885
|
+
),
|
|
886
|
+
"| :--- | :--- | :--- | :--- | :--- | :--- | :--- |",
|
|
887
|
+
]
|
|
888
|
+
)
|
|
889
|
+
for mname in METRIC_NAMES:
|
|
890
|
+
mcls = get_metric(mname)
|
|
891
|
+
kind = "interval" if mcls.needs_intervals else "point"
|
|
892
|
+
arm = "mean" if mcls.mean_optimal else "median"
|
|
893
|
+
lines.append(
|
|
894
|
+
f"| `{mname}` | `{kind}` | `{mcls.direction}` | "
|
|
895
|
+
f"{'Yes' if mcls.needs_intervals else 'No'} | "
|
|
896
|
+
f"{'Yes' if mcls.needs_train_history else 'No'} | "
|
|
897
|
+
f"{'Yes' if mcls.needs_seasonal_period else 'No'} | "
|
|
898
|
+
f"`{arm}` |"
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
lines.extend(
|
|
902
|
+
[
|
|
903
|
+
"",
|
|
904
|
+
"---",
|
|
905
|
+
"",
|
|
906
|
+
f"## 3. Compute Runtimes ({len(RUNTIME_CATALOG)} Runtimes)",
|
|
907
|
+
"",
|
|
908
|
+
(
|
|
909
|
+
"| Runtime | Sub-Modes | Supported Families | GPU Support | Scaling Model | "
|
|
910
|
+
"Submitter Module |"
|
|
911
|
+
),
|
|
912
|
+
"| :--- | :--- | :--- | :--- | :--- | :--- |",
|
|
913
|
+
]
|
|
914
|
+
)
|
|
915
|
+
for r in RUNTIME_CATALOG:
|
|
916
|
+
fams = ", ".join(f"`{f}`" for f in r["families"])
|
|
917
|
+
gpus = ", ".join(f"`{g}`" for g in r["gpu_types"]) if r["gpu_types"] else "CPU / SQL only"
|
|
918
|
+
modes_escaped = str(r["modes"]).replace("|", "\\|")
|
|
919
|
+
lines.append(
|
|
920
|
+
f"| `{r['runtime']}` | {modes_escaped} | {fams} | {gpus} | {r['scaling']} | "
|
|
921
|
+
f"`{r['submitter']}` |"
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
lines.extend(
|
|
925
|
+
[
|
|
926
|
+
"",
|
|
927
|
+
"---",
|
|
928
|
+
"",
|
|
929
|
+
f"## 4. Dependency Extras ({len(ALL_EXTRA_MODULES)} Extras in `pyproject.toml`)",
|
|
930
|
+
"",
|
|
931
|
+
"| Extra | Install Command | Key Probe Modules | Purpose |",
|
|
932
|
+
"| :--- | :--- | :--- | :--- |",
|
|
933
|
+
(
|
|
934
|
+
"| `core` (base) | `pip install scale-forecasting` | `pydantic`, `pandas`, "
|
|
935
|
+
"`statsmodels`, `sklearn`, `optuna` | Pure offline layer: 14 core models, "
|
|
936
|
+
"21 metrics, backtesting, ensembling, reconciliation, playground, dry-run, "
|
|
937
|
+
"MCP server |"
|
|
938
|
+
),
|
|
939
|
+
]
|
|
940
|
+
)
|
|
941
|
+
for extra_name, mods in ALL_EXTRA_MODULES.items():
|
|
942
|
+
mod_list = ", ".join(f"`{m}`" for m in mods[:4])
|
|
943
|
+
if len(mods) > 4:
|
|
944
|
+
mod_list += f" (+{len(mods) - 4} more)"
|
|
945
|
+
lines.append(
|
|
946
|
+
f'| `[{extra_name}]` | `pip install "scale-forecasting[{extra_name}]"` | '
|
|
947
|
+
f"{mod_list} | {EXTRA_DESCRIPTIONS[extra_name]} |"
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
src_tables_inline = ", ".join(f"`{t}`" for t in SOURCE_TABLE_NAMES)
|
|
951
|
+
reg_tables_inline = ", ".join(f"`{t}`" for t in REGISTRY_TABLE_NAMES)
|
|
952
|
+
views_inline = ", ".join(f"`{v}`" for v in VIEW_NAMES)
|
|
953
|
+
|
|
954
|
+
lines.extend(
|
|
955
|
+
[
|
|
956
|
+
"",
|
|
957
|
+
"---",
|
|
958
|
+
"",
|
|
959
|
+
"## 5. BigQuery Source Tables, Registry Tables & Analytical SQL Views",
|
|
960
|
+
"",
|
|
961
|
+
"| Surface | Canonical Names | Description |",
|
|
962
|
+
"| :--- | :--- | :--- |",
|
|
963
|
+
(
|
|
964
|
+
f"| **Source Tables (4)** | {src_tables_inline}, `source_series_wide_native`, "
|
|
965
|
+
"`source_series_hierarchical_native` | Input time-series panels (Apache Iceberg "
|
|
966
|
+
"on GCS via BigLake + native BigQuery tables). |"
|
|
967
|
+
),
|
|
968
|
+
(
|
|
969
|
+
f"| **Registry Tables ({len(REGISTRY_TABLE_NAMES)})** | {reg_tables_inline} | "
|
|
970
|
+
"Append-only experiment header, per-family job ledger, cell metadata/metrics, "
|
|
971
|
+
"forward predictions, and out-of-fold backtests. |"
|
|
972
|
+
),
|
|
973
|
+
(
|
|
974
|
+
f"| **Analytical SQL Views ({len(VIEW_NAMES)})** | {views_inline} | "
|
|
975
|
+
"Deduplicated leaderboards, comparable cohort rankings, backtest fold coverage, "
|
|
976
|
+
"run time ledger (`overhead_seconds >= 0`), and current job state. |"
|
|
977
|
+
),
|
|
978
|
+
"",
|
|
979
|
+
"---",
|
|
980
|
+
"",
|
|
981
|
+
"## 6. Shipped Configuration Catalog (`configs/` & `configs/smokes/`)",
|
|
982
|
+
"",
|
|
983
|
+
"### Root Demonstration & Scale Configs (`configs/*.json`)",
|
|
984
|
+
"",
|
|
985
|
+
"| Config File | `run_name` | Default Runtime | Models | Backtest | Ensemble |",
|
|
986
|
+
"| :--- | :--- | :--- | :--- | :--- | :--- |",
|
|
987
|
+
]
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
demo_dir = root / "configs"
|
|
991
|
+
if demo_dir.is_dir():
|
|
992
|
+
for p in sorted(demo_dir.glob("*.json")):
|
|
993
|
+
if p.name == "compute_fallback.json":
|
|
994
|
+
continue
|
|
995
|
+
cfg = RunConfig.model_validate_json(p.read_text(encoding="utf-8"))
|
|
996
|
+
models_preview = ", ".join(f"`{m}`" for m in cfg.models[:4])
|
|
997
|
+
if len(cfg.models) > 4:
|
|
998
|
+
models_preview += f" (+{len(cfg.models) - 4})"
|
|
999
|
+
ens = (
|
|
1000
|
+
", ".join(f"`{s}`" for s in cfg.ensemble.strategies)
|
|
1001
|
+
if cfg.ensemble.enabled
|
|
1002
|
+
else "off"
|
|
1003
|
+
)
|
|
1004
|
+
bt = (
|
|
1005
|
+
f"`{cfg.backtest.scheme}` ({cfg.backtest.n_folds}f)"
|
|
1006
|
+
if cfg.backtest.enabled
|
|
1007
|
+
else "off"
|
|
1008
|
+
)
|
|
1009
|
+
lines.append(
|
|
1010
|
+
f"| `configs/{p.name}` | `{cfg.run_name}` | `{cfg.python_runtime}` | "
|
|
1011
|
+
f"{models_preview} | {bt} | {ens} |"
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
lines.extend(
|
|
1015
|
+
[
|
|
1016
|
+
"",
|
|
1017
|
+
"### Live Smoke Test Configs (`configs/smokes/*.json`)",
|
|
1018
|
+
"",
|
|
1019
|
+
"| Smoke Config | `run_name` | Default Runtime | Family Overrides | Models |",
|
|
1020
|
+
"| :--- | :--- | :--- | :--- | :--- |",
|
|
1021
|
+
]
|
|
1022
|
+
)
|
|
1023
|
+
smoke_dir = root / "configs" / "smokes"
|
|
1024
|
+
if smoke_dir.is_dir():
|
|
1025
|
+
for p in sorted(smoke_dir.glob("*.json")):
|
|
1026
|
+
cfg = RunConfig.model_validate_json(p.read_text(encoding="utf-8"))
|
|
1027
|
+
fam_ov = (
|
|
1028
|
+
", ".join(
|
|
1029
|
+
f"`{f}:{fc.runtime or cfg.python_runtime}`"
|
|
1030
|
+
for f, fc in sorted(cfg.compute.families.items())
|
|
1031
|
+
)
|
|
1032
|
+
if cfg.compute.families
|
|
1033
|
+
else "—"
|
|
1034
|
+
)
|
|
1035
|
+
models_preview = ", ".join(f"`{m}`" for m in cfg.models[:4])
|
|
1036
|
+
if len(cfg.models) > 4:
|
|
1037
|
+
models_preview += f" (+{len(cfg.models) - 4})"
|
|
1038
|
+
lines.append(
|
|
1039
|
+
f"| `configs/smokes/{p.name}` | `{cfg.run_name}` | `{cfg.python_runtime}` | "
|
|
1040
|
+
f"{fam_ov} | {models_preview} |"
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
return "\n".join(lines) + "\n"
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
def render_skill_execution_paths_reference() -> str:
|
|
1047
|
+
"""Render ``skills/scale-forecasting/references/execution_paths_and_ops.md``."""
|
|
1048
|
+
lines: list[str] = [
|
|
1049
|
+
"# Execution Paths, Environment Variables & Operations Reference",
|
|
1050
|
+
"",
|
|
1051
|
+
(
|
|
1052
|
+
"> **Auto-generated by `python -m scale_forecasting.agent_surfaces --write`.** "
|
|
1053
|
+
"Do not edit by hand; pre-commit (`test_agent_surfaces.py`) enforces zero drift."
|
|
1054
|
+
),
|
|
1055
|
+
"",
|
|
1056
|
+
"---",
|
|
1057
|
+
"",
|
|
1058
|
+
"## 1. Environment Variables (`Settings.resolve`)",
|
|
1059
|
+
"",
|
|
1060
|
+
(
|
|
1061
|
+
"All cloud execution paths resolve their Google Cloud infrastructure target from "
|
|
1062
|
+
"`SF_*` environment variables (typically exported by `terraform/main` into "
|
|
1063
|
+
"`.env.infra`). `RunConfig` never embeds project IDs or bucket names so the same "
|
|
1064
|
+
"config is portable across environments."
|
|
1065
|
+
),
|
|
1066
|
+
"",
|
|
1067
|
+
"| Variable | Required for Cloud? | Default | Purpose |",
|
|
1068
|
+
"| :--- | :--- | :--- | :--- |",
|
|
1069
|
+
]
|
|
1070
|
+
for var_name, req, default_val, desc in _ENV_VARS_DOC:
|
|
1071
|
+
lines.append(f"| `{var_name}` | {'**Yes**' if req else 'No'} | `{default_val}` | {desc} |")
|
|
1072
|
+
|
|
1073
|
+
lines.extend(
|
|
1074
|
+
[
|
|
1075
|
+
"",
|
|
1076
|
+
"Load `.env.infra` in any shell before running cloud commands:",
|
|
1077
|
+
"",
|
|
1078
|
+
"```bash",
|
|
1079
|
+
"set -a && source .env.infra && set +a",
|
|
1080
|
+
"```",
|
|
1081
|
+
"",
|
|
1082
|
+
"---",
|
|
1083
|
+
"",
|
|
1084
|
+
"## 2. The 5 Execution Paths",
|
|
1085
|
+
"",
|
|
1086
|
+
"### Path 1: Offline Playground & Dry-Run (Zero GCP Required)",
|
|
1087
|
+
"",
|
|
1088
|
+
(
|
|
1089
|
+
"Works on a bare `pip install scale-forecasting` core install with no Google "
|
|
1090
|
+
"Cloud credentials."
|
|
1091
|
+
),
|
|
1092
|
+
"",
|
|
1093
|
+
"```bash",
|
|
1094
|
+
"# Probe installed extras, available models, and environment readiness",
|
|
1095
|
+
"python -m scale_forecasting.agent_surfaces --probe-env",
|
|
1096
|
+
"",
|
|
1097
|
+
"# List all 34 models and 21 metrics with availability and capabilities",
|
|
1098
|
+
"python -m scale_forecasting.playground --list",
|
|
1099
|
+
"python -m scale_forecasting.playground --catalog",
|
|
1100
|
+
"",
|
|
1101
|
+
"# Run any installed Python model on synthetic panel data with 3-fold backtesting",
|
|
1102
|
+
"python -m scale_forecasting.playground --model holtwinters --horizon 14 --backtest",
|
|
1103
|
+
"",
|
|
1104
|
+
"# Validate a RunConfig, compute deterministic run_id, and estimate workload offline",
|
|
1105
|
+
"python -m scale_forecasting.main --config configs/ensemble_demo.json --dry-run",
|
|
1106
|
+
"```",
|
|
1107
|
+
"",
|
|
1108
|
+
"Python SDK equivalent:",
|
|
1109
|
+
"",
|
|
1110
|
+
"```python",
|
|
1111
|
+
"from scale_forecasting import Forecaster",
|
|
1112
|
+
"",
|
|
1113
|
+
'f = Forecaster.from_file("configs/ensemble_demo.json")',
|
|
1114
|
+
"dry = f.dry_run() # DryRunResult(run_id, fanout, python_models, bq_models)",
|
|
1115
|
+
"dag_nodes = f.dag() # Planned per-family DagNode tuple",
|
|
1116
|
+
"```",
|
|
1117
|
+
"",
|
|
1118
|
+
"### Path 2: Cloud Readiness, Feasibility, Quota Preflight & Staging (`[gcp]`)",
|
|
1119
|
+
"",
|
|
1120
|
+
(
|
|
1121
|
+
"Reads BigQuery source metadata or regional quota meters and stages artifacts to "
|
|
1122
|
+
"Cloud Storage without launching compute jobs."
|
|
1123
|
+
),
|
|
1124
|
+
"",
|
|
1125
|
+
"```bash",
|
|
1126
|
+
"# Check achieved backtest fold cohorts against live BigQuery series history lengths",
|
|
1127
|
+
(
|
|
1128
|
+
"python -m scale_forecasting.main "
|
|
1129
|
+
"--config configs/ensemble_demo.json --dry-run --feasibility"
|
|
1130
|
+
),
|
|
1131
|
+
"",
|
|
1132
|
+
"# Check regional CPU/GPU quota headroom and GPU usefulness warnings",
|
|
1133
|
+
"python -m scale_forecasting.main --config configs/ray_gpu_demo.json --quota",
|
|
1134
|
+
"",
|
|
1135
|
+
"# Upload config + code archive to GCS, write STAGED manifest, and print commands",
|
|
1136
|
+
"python -m scale_forecasting.main --config configs/ensemble_demo.json --stage-only",
|
|
1137
|
+
"```",
|
|
1138
|
+
"",
|
|
1139
|
+
"### Path 3: Unified Multi-Family DAG Execution (`main.run` / `Forecaster.run`)",
|
|
1140
|
+
"",
|
|
1141
|
+
(
|
|
1142
|
+
"Dispatches 1 independent job per active model family (`statistical`, `ml`, "
|
|
1143
|
+
"`deep_learning`, `automl`, `native`) in parallel under one shared `run_id`, "
|
|
1144
|
+
"followed by the `ensemble` node."
|
|
1145
|
+
),
|
|
1146
|
+
"",
|
|
1147
|
+
"```bash",
|
|
1148
|
+
"# Run full multi-family DAG end-to-end",
|
|
1149
|
+
"python -m scale_forecasting.main --config configs/ensemble_demo.json",
|
|
1150
|
+
"",
|
|
1151
|
+
"# Force a fresh attempt of an already-completed config",
|
|
1152
|
+
("python -m scale_forecasting.main --config configs/ensemble_demo.json --force"),
|
|
1153
|
+
"",
|
|
1154
|
+
"# Filter config to models whose optional Python packages are installed locally",
|
|
1155
|
+
(
|
|
1156
|
+
"python -m scale_forecasting.main "
|
|
1157
|
+
"--config configs/all_models.json --ignore-unavailable-models --dry-run"
|
|
1158
|
+
),
|
|
1159
|
+
"```",
|
|
1160
|
+
"",
|
|
1161
|
+
"Python SDK equivalent:",
|
|
1162
|
+
"",
|
|
1163
|
+
"```python",
|
|
1164
|
+
"from scale_forecasting import Forecaster",
|
|
1165
|
+
"",
|
|
1166
|
+
'f = Forecaster.from_file("configs/ensemble_demo.json")',
|
|
1167
|
+
(
|
|
1168
|
+
"result = f.run(n_series=100) "
|
|
1169
|
+
"# RunResult(run_id, dataset_ref, views, status, runtime_seconds)"
|
|
1170
|
+
),
|
|
1171
|
+
"```",
|
|
1172
|
+
"",
|
|
1173
|
+
"### Path 4: Direct Per-Family Cloud Submitters",
|
|
1174
|
+
"",
|
|
1175
|
+
(
|
|
1176
|
+
"Submit a single family or runtime directly when driving jobs from an external "
|
|
1177
|
+
"scheduler:"
|
|
1178
|
+
),
|
|
1179
|
+
"",
|
|
1180
|
+
"```bash",
|
|
1181
|
+
(
|
|
1182
|
+
"python -m scale_forecasting.submit "
|
|
1183
|
+
"--config configs/example_config.json --family statistical"
|
|
1184
|
+
),
|
|
1185
|
+
(
|
|
1186
|
+
"python -m scale_forecasting.ray_submit "
|
|
1187
|
+
"--config configs/ray_gpu_demo.json --family deep_learning"
|
|
1188
|
+
),
|
|
1189
|
+
(
|
|
1190
|
+
"python -m scale_forecasting.vertex_submit "
|
|
1191
|
+
"--config configs/vertex_demo.json --family ml"
|
|
1192
|
+
),
|
|
1193
|
+
(
|
|
1194
|
+
"python -m scale_forecasting.gce_submit "
|
|
1195
|
+
"--config configs/gce_demo.json --family statistical"
|
|
1196
|
+
),
|
|
1197
|
+
(
|
|
1198
|
+
"python -m scale_forecasting.gke_submit "
|
|
1199
|
+
"--config configs/gke_demo.json --family statistical"
|
|
1200
|
+
),
|
|
1201
|
+
(
|
|
1202
|
+
"python -m scale_forecasting.automl_submit "
|
|
1203
|
+
"--config configs/automl_demo.json --family automl"
|
|
1204
|
+
),
|
|
1205
|
+
"python -m scale_forecasting.ensemble_run --config configs/ensemble_demo.json",
|
|
1206
|
+
"```",
|
|
1207
|
+
"",
|
|
1208
|
+
"### Path 5: Cloud Composer 3 / Apache Airflow DAG Emission",
|
|
1209
|
+
"",
|
|
1210
|
+
(
|
|
1211
|
+
"Renders a self-contained Airflow DAG file (`dag_<run_id>.py`) offline with one "
|
|
1212
|
+
"task per active model family, optional surgical repair node (`--with-retry`), "
|
|
1213
|
+
"and downstream ensemble task:"
|
|
1214
|
+
),
|
|
1215
|
+
"",
|
|
1216
|
+
"```bash",
|
|
1217
|
+
(
|
|
1218
|
+
"python -m scale_forecasting.main "
|
|
1219
|
+
"--config configs/ensemble_demo.json --emit-airflow --with-retry"
|
|
1220
|
+
),
|
|
1221
|
+
(
|
|
1222
|
+
"make composer-sync "
|
|
1223
|
+
"# syncs src/scale_forecasting to Composer plugins bucket when Composer is enabled"
|
|
1224
|
+
),
|
|
1225
|
+
"```",
|
|
1226
|
+
"",
|
|
1227
|
+
"---",
|
|
1228
|
+
"",
|
|
1229
|
+
"## 3. Monitoring, Post-Run Review, Calibration & Surgical Repair",
|
|
1230
|
+
"",
|
|
1231
|
+
"| Goal | CLI Command | Python SDK / Function |",
|
|
1232
|
+
"| :--- | :--- | :--- |",
|
|
1233
|
+
(
|
|
1234
|
+
"| **Reconcile live job status** | "
|
|
1235
|
+
"`python -m scale_forecasting.main --config <cfg> --probe [--job <family>]` | "
|
|
1236
|
+
"`Forecaster(cfg).monitor(probe=True)` / `monitor_run(run_id, probe=True)` |"
|
|
1237
|
+
),
|
|
1238
|
+
(
|
|
1239
|
+
"| **Preview stale row repair** | "
|
|
1240
|
+
"`python -m scale_forecasting.main --config <cfg> --settle` | "
|
|
1241
|
+
"`Forecaster(cfg).settle(yes=False)` |"
|
|
1242
|
+
),
|
|
1243
|
+
(
|
|
1244
|
+
"| **Apply stale row repair** | "
|
|
1245
|
+
"`python -m scale_forecasting.main --config <cfg> --settle --force "
|
|
1246
|
+
'--reason "..."` | '
|
|
1247
|
+
'`Forecaster(cfg).settle(yes=True, reason="...")` |'
|
|
1248
|
+
),
|
|
1249
|
+
(
|
|
1250
|
+
"| **Preview job cancellation** | "
|
|
1251
|
+
"`python -m scale_forecasting.main --config <cfg> --cancel` | "
|
|
1252
|
+
"`Forecaster(cfg).cancel(confirm=False)` |"
|
|
1253
|
+
),
|
|
1254
|
+
(
|
|
1255
|
+
"| **Confirm job cancellation** | "
|
|
1256
|
+
"`python -m scale_forecasting.main --config <cfg> --cancel --force "
|
|
1257
|
+
'--reason "..."` | '
|
|
1258
|
+
'`Forecaster(cfg).cancel(confirm=True, reason="...")` |'
|
|
1259
|
+
),
|
|
1260
|
+
(
|
|
1261
|
+
"| **Preview surgical cell repair** | "
|
|
1262
|
+
"`python -m scale_forecasting.main --run-id <run_id> --retry` | "
|
|
1263
|
+
"`Forecaster(cfg).retry(confirm=False)` / `retry_run(cfg, confirm=False)` |"
|
|
1264
|
+
),
|
|
1265
|
+
(
|
|
1266
|
+
"| **Submit surgical cell repair** | "
|
|
1267
|
+
"`python -m scale_forecasting.main --run-id <run_id> --retry --force "
|
|
1268
|
+
'--reason "..."` | '
|
|
1269
|
+
'`Forecaster(cfg).retry(confirm=True, reason="...")` |'
|
|
1270
|
+
),
|
|
1271
|
+
(
|
|
1272
|
+
"| **Full data-science run review** | — | "
|
|
1273
|
+
"`review_run(run_id)` / `Forecaster(cfg).review()` |"
|
|
1274
|
+
),
|
|
1275
|
+
"| **Interval & arm calibration** | — | `calibration_report(run_id)` |",
|
|
1276
|
+
(
|
|
1277
|
+
"| **Forecaster's Workbench diagnostics** | — | "
|
|
1278
|
+
"`Forecaster(cfg).fva()` / `.horizon_decay()` / `.bias_report()` / "
|
|
1279
|
+
"`.exceptions_queue()` |"
|
|
1280
|
+
),
|
|
1281
|
+
(
|
|
1282
|
+
"| **Executive HTML + JSON report** | "
|
|
1283
|
+
"`python -m scale_forecasting.report --model holtwinters --output-dir ./report` | "
|
|
1284
|
+
"`Forecaster(cfg).export_report()` / `export_report(bench, './report')` |"
|
|
1285
|
+
),
|
|
1286
|
+
"",
|
|
1287
|
+
"---",
|
|
1288
|
+
"",
|
|
1289
|
+
"## 4. Registry Lifecycle Operations (`python -m scale_forecasting.registry.ops`)",
|
|
1290
|
+
"",
|
|
1291
|
+
(
|
|
1292
|
+
"All mutating verbs default to **preview mode** and require `--yes` (CLI) or "
|
|
1293
|
+
"`yes=True` (Python) to execute:"
|
|
1294
|
+
),
|
|
1295
|
+
"",
|
|
1296
|
+
"| Verb | CLI Invocation | What It Does |",
|
|
1297
|
+
"| :--- | :--- | :--- |",
|
|
1298
|
+
(
|
|
1299
|
+
"| `init` | `python -m scale_forecasting.registry.ops init` | "
|
|
1300
|
+
"Idempotently create the 5 registry tables and 5 analytical SQL views. |"
|
|
1301
|
+
),
|
|
1302
|
+
(
|
|
1303
|
+
"| `doctor` | `python -m scale_forecasting.registry.ops doctor` | "
|
|
1304
|
+
"Read-only health report: table row counts, stuck `RUNNING` runs, orphaned "
|
|
1305
|
+
"GCS artifacts. |"
|
|
1306
|
+
),
|
|
1307
|
+
(
|
|
1308
|
+
"| `close-runs` | `python -m scale_forecasting.registry.ops close-runs [--yes]` | "
|
|
1309
|
+
"Finalize abandoned `RUNNING` headers whose `run_jobs` rows are all terminal. |"
|
|
1310
|
+
),
|
|
1311
|
+
(
|
|
1312
|
+
"| `drop-run` | "
|
|
1313
|
+
"`python -m scale_forecasting.registry.ops drop-run <run_id> [--yes]` | "
|
|
1314
|
+
"Delete named run(s) across GCS artifacts, BigQuery rows, and BQML model "
|
|
1315
|
+
"objects. |"
|
|
1316
|
+
),
|
|
1317
|
+
(
|
|
1318
|
+
"| `sweep-orphans` | "
|
|
1319
|
+
"`python -m scale_forecasting.registry.ops sweep-orphans [--yes]` | "
|
|
1320
|
+
"Delete GCS artifact prefixes under this registry with no `run_registry` "
|
|
1321
|
+
"header row. |"
|
|
1322
|
+
),
|
|
1323
|
+
(
|
|
1324
|
+
"| `reap-clusters` | "
|
|
1325
|
+
"`python -m scale_forecasting.registry.ops reap-clusters [--yes]` | "
|
|
1326
|
+
"Delete orphaned Vertex AI Ray clusters whose owning run is already terminal. |"
|
|
1327
|
+
),
|
|
1328
|
+
(
|
|
1329
|
+
"| `snapshot` | "
|
|
1330
|
+
"`python -m scale_forecasting.registry.ops snapshot --suffix <tag> [--yes]` | "
|
|
1331
|
+
"Create expirable BigQuery table snapshots of all 5 registry tables. |"
|
|
1332
|
+
),
|
|
1333
|
+
(
|
|
1334
|
+
"| `export` | "
|
|
1335
|
+
"`python -m scale_forecasting.registry.ops export "
|
|
1336
|
+
"--Dest gs://... [--format PARQUET\\|JSON] [--yes]` | "
|
|
1337
|
+
"Export registry tables to Cloud Storage in Parquet or newline-delimited JSON. |"
|
|
1338
|
+
),
|
|
1339
|
+
]
|
|
1340
|
+
)
|
|
1341
|
+
return "\n".join(lines) + "\n"
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
def render_llms_txt() -> str:
|
|
1345
|
+
"""Render ``docs/llms.txt`` following the standard ``llms.txt`` specification."""
|
|
1346
|
+
return "\n".join(
|
|
1347
|
+
[
|
|
1348
|
+
"# scale-forecasting",
|
|
1349
|
+
"",
|
|
1350
|
+
(
|
|
1351
|
+
"> Declarative, multi-runtime enterprise time-series forecasting platform for "
|
|
1352
|
+
"Google Cloud. One frozen Pydantic `RunConfig` orchestrates 34 models across 5 "
|
|
1353
|
+
"families (`statistical`, `ml`, `deep_learning`, `automl`, `native`), 21 "
|
|
1354
|
+
"evaluation metrics, 7 compute runtimes (`spark`, `ray`, `vertex`, `gce`, `gke`, "
|
|
1355
|
+
"`vertex_automl`, `bigquery`), rolling-origin backtesting, Optuna HPO, "
|
|
1356
|
+
"conformal/quantile calibration, FPP3 hierarchical reconciliation, and stacked "
|
|
1357
|
+
"ensembling into a 5-table, 5-view BigQuery registry."
|
|
1358
|
+
),
|
|
1359
|
+
"",
|
|
1360
|
+
"## Agent-First Surfaces (MCP, Skills & Schemas)",
|
|
1361
|
+
"",
|
|
1362
|
+
(
|
|
1363
|
+
f"- [AI Agents, Skills & MCP Guide]({_SITE_BASE}/agent_and_mcp_guide/): How to "
|
|
1364
|
+
"connect Google Antigravity (`agy` CLI & IDE), Claude Code, Cursor, VS Code "
|
|
1365
|
+
"Copilot, and Windsurf via the built-in MCP server (`python -m "
|
|
1366
|
+
"scale_forecasting.mcp`), `SKILL.md`, and JSON Schema."
|
|
1367
|
+
),
|
|
1368
|
+
(
|
|
1369
|
+
f"- [RunConfig JSON Schema (Draft 2020-12)]"
|
|
1370
|
+
f"({_SITE_BASE}/schemas/run_config.schema.json): Machine-readable schema for "
|
|
1371
|
+
f'`RunConfig` with editor autocomplete (`"$schema": "{RUN_CONFIG_SCHEMA_URI}"`).'
|
|
1372
|
+
),
|
|
1373
|
+
(
|
|
1374
|
+
f"- [Comprehensive LLM Context Bundle (`llms-full.txt`)]"
|
|
1375
|
+
f"({_SITE_BASE}/llms-full.txt): Single-file reference containing all `RunConfig` "
|
|
1376
|
+
"fields, all 34 models, 21 metrics, 7 runtimes, 12 extras, and 5 execution paths."
|
|
1377
|
+
),
|
|
1378
|
+
(
|
|
1379
|
+
f"- [Repository Portable Skill (`skills/scale-forecasting/SKILL.md`)]"
|
|
1380
|
+
f"({_REPO_URL}/blob/main/skills/scale-forecasting/SKILL.md): Portable Agent Skill "
|
|
1381
|
+
"with Progressive Disclosure reference tables."
|
|
1382
|
+
),
|
|
1383
|
+
"",
|
|
1384
|
+
"## Start Here & Positioning",
|
|
1385
|
+
"",
|
|
1386
|
+
f"- [Documentation Home]({_SITE_BASE}/): Navigation hub by role and goal.",
|
|
1387
|
+
(
|
|
1388
|
+
f"- [Platform Overview]({_SITE_BASE}/overview/): End-to-end architecture, "
|
|
1389
|
+
"capabilities summary, and registry model."
|
|
1390
|
+
),
|
|
1391
|
+
(
|
|
1392
|
+
f"- [Getting Started]({_SITE_BASE}/getting_started/): 5-minute offline sandbox "
|
|
1393
|
+
"(`pip install scale-forecasting`) to first cloud run."
|
|
1394
|
+
),
|
|
1395
|
+
(
|
|
1396
|
+
f"- [Choosing a Runtime]({_SITE_BASE}/choosing_a_runtime/): Decision flow and fit "
|
|
1397
|
+
"matrix across all 7 Google Cloud runtimes."
|
|
1398
|
+
),
|
|
1399
|
+
(
|
|
1400
|
+
f"- [Cost Estimates]({_SITE_BASE}/cost_estimates/): Service-by-service cost "
|
|
1401
|
+
"drivers, zero-idle vs. always-on resources, and run cost bands."
|
|
1402
|
+
),
|
|
1403
|
+
(
|
|
1404
|
+
f"- [Why Google Cloud]({_SITE_BASE}/why_google_cloud/): Six architectural proof "
|
|
1405
|
+
"points with code and validation citations."
|
|
1406
|
+
),
|
|
1407
|
+
(
|
|
1408
|
+
f"- [FAQ]({_SITE_BASE}/faq/): Direct answers on installation, runtimes, pricing, "
|
|
1409
|
+
"covariates, and operations."
|
|
1410
|
+
),
|
|
1411
|
+
(
|
|
1412
|
+
f"- [Glossary]({_SITE_BASE}/glossary/): Definitions of platform, forecasting, and "
|
|
1413
|
+
"BigQuery registry terms."
|
|
1414
|
+
),
|
|
1415
|
+
"",
|
|
1416
|
+
"## Reference & Operations",
|
|
1417
|
+
"",
|
|
1418
|
+
(
|
|
1419
|
+
f"- [Configuration Reference (`RunConfig`)]"
|
|
1420
|
+
f"({_SITE_BASE}/configuration_reference/): Complete JSON configuration guide."
|
|
1421
|
+
),
|
|
1422
|
+
(
|
|
1423
|
+
f"- [Forecaster's Workbench]({_SITE_BASE}/forecaster_workbench/): Physical "
|
|
1424
|
+
"bounds, monotone quantiles, Newsvendor targets, SBC demand regimes, fold-safe "
|
|
1425
|
+
"hygiene, FVA, horizon decay, Trigg bias, AR(1) time rollup, scenarios, "
|
|
1426
|
+
"lifecycle governance, and executive HTML/JSON reports."
|
|
1427
|
+
),
|
|
1428
|
+
(
|
|
1429
|
+
f"- [Models Reference (34 Models)]({_SITE_BASE}/models_reference/): All 34 "
|
|
1430
|
+
"models, covariate support, explainability, and granular extras."
|
|
1431
|
+
),
|
|
1432
|
+
(
|
|
1433
|
+
f"- [Metrics Reference (21 Metrics)]({_SITE_BASE}/metrics_reference/): All 16 "
|
|
1434
|
+
"point and 5 interval metrics."
|
|
1435
|
+
),
|
|
1436
|
+
(
|
|
1437
|
+
f"- [Runtimes Reference (7 Runtimes)]({_SITE_BASE}/runtimes_reference/): "
|
|
1438
|
+
"Dataproc, Ray, Vertex CustomJob, GCE, GKE, Vertex AutoML, and BigQuery ML."
|
|
1439
|
+
),
|
|
1440
|
+
(
|
|
1441
|
+
f"- [Using the Python SDK (`Forecaster`)]({_SITE_BASE}/using_the_sdk/): "
|
|
1442
|
+
"Programmatic execution, monitoring, review, and plotting."
|
|
1443
|
+
),
|
|
1444
|
+
(
|
|
1445
|
+
f"- [Output Schemas & Views]({_SITE_BASE}/output_schemas/): DDL and query "
|
|
1446
|
+
"recipes for the 5 registry tables and 5 analytical views."
|
|
1447
|
+
),
|
|
1448
|
+
(
|
|
1449
|
+
f"- [Operations & Surgical Repair]({_SITE_BASE}/operations/): Probing, settling, "
|
|
1450
|
+
"canceling, and repairing runs (`retry_run`, `registry.ops`)."
|
|
1451
|
+
),
|
|
1452
|
+
(
|
|
1453
|
+
f"- [Live Validation Ledger]({_SITE_BASE}/validation/): Proven `run_id` records "
|
|
1454
|
+
"across 42 smokes, 20 demo configs, and 11 notebooks."
|
|
1455
|
+
),
|
|
1456
|
+
"",
|
|
1457
|
+
]
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
|
|
1461
|
+
def render_llms_full_txt(repo_root: Path | None = None) -> str:
|
|
1462
|
+
"""Render ``docs/llms-full.txt`` combining all generated agent reference surfaces."""
|
|
1463
|
+
root = repo_root or _default_repo_root()
|
|
1464
|
+
parts = [
|
|
1465
|
+
render_llms_txt().rstrip(),
|
|
1466
|
+
"",
|
|
1467
|
+
"================================================================================",
|
|
1468
|
+
"",
|
|
1469
|
+
render_skill_config_reference().rstrip(),
|
|
1470
|
+
"",
|
|
1471
|
+
"================================================================================",
|
|
1472
|
+
"",
|
|
1473
|
+
render_skill_catalog_reference(root).rstrip(),
|
|
1474
|
+
"",
|
|
1475
|
+
"================================================================================",
|
|
1476
|
+
"",
|
|
1477
|
+
render_skill_execution_paths_reference().rstrip(),
|
|
1478
|
+
"",
|
|
1479
|
+
]
|
|
1480
|
+
return "\n".join(parts)
|
|
1481
|
+
|
|
1482
|
+
|
|
1483
|
+
def expected_agent_surface_files(repo_root: Path | None = None) -> dict[Path, str]:
|
|
1484
|
+
"""Return ``{path: expected_content}`` for all 6 code-generated agent files."""
|
|
1485
|
+
root = repo_root or _default_repo_root()
|
|
1486
|
+
refs_dir = root / "skills" / "scale-forecasting" / "references"
|
|
1487
|
+
return {
|
|
1488
|
+
root / "docs" / "schemas" / "run_config.schema.json": render_run_config_json_schema(),
|
|
1489
|
+
root / "docs" / "llms.txt": render_llms_txt(),
|
|
1490
|
+
root / "docs" / "llms-full.txt": render_llms_full_txt(root),
|
|
1491
|
+
refs_dir / "config_reference.md": render_skill_config_reference(),
|
|
1492
|
+
refs_dir / "catalog_reference.md": render_skill_catalog_reference(root),
|
|
1493
|
+
refs_dir / "execution_paths_and_ops.md": render_skill_execution_paths_reference(),
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
def write_agent_surfaces(repo_root: Path | None = None) -> list[Path]:
|
|
1498
|
+
"""Write all generated agent surface files to disk and return their paths."""
|
|
1499
|
+
expected = expected_agent_surface_files(repo_root)
|
|
1500
|
+
written: list[Path] = []
|
|
1501
|
+
for path, content in expected.items():
|
|
1502
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1503
|
+
path.write_text(content, encoding="utf-8")
|
|
1504
|
+
written.append(path)
|
|
1505
|
+
return written
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
def check_agent_surfaces(repo_root: Path | None = None) -> list[str]:
|
|
1509
|
+
"""Return a list of drift errors if any generated agent surface file is missing or stale."""
|
|
1510
|
+
root = repo_root or _default_repo_root()
|
|
1511
|
+
expected = expected_agent_surface_files(root)
|
|
1512
|
+
drifted: list[str] = []
|
|
1513
|
+
for path, content in expected.items():
|
|
1514
|
+
rel = path.relative_to(root)
|
|
1515
|
+
if not path.is_file():
|
|
1516
|
+
drifted.append(f"{rel}: missing on disk (run `make agent-surfaces`)")
|
|
1517
|
+
continue
|
|
1518
|
+
actual = path.read_text(encoding="utf-8")
|
|
1519
|
+
if actual != content:
|
|
1520
|
+
drifted.append(
|
|
1521
|
+
f"{rel}: out of sync with canonical code declarations (run `make agent-surfaces`)"
|
|
1522
|
+
)
|
|
1523
|
+
return drifted
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
def main(argv: list[str] | None = None) -> int:
|
|
1527
|
+
"""CLI entry point for generating, checking, or probing agent surfaces."""
|
|
1528
|
+
configure_cli_logging()
|
|
1529
|
+
parser = argparse.ArgumentParser(
|
|
1530
|
+
prog="scale_forecasting.agent_surfaces",
|
|
1531
|
+
description="Generate, verify, or probe scale-forecasting AI agent surfaces.",
|
|
1532
|
+
)
|
|
1533
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
1534
|
+
group.add_argument(
|
|
1535
|
+
"--write",
|
|
1536
|
+
action="store_true",
|
|
1537
|
+
help="Regenerate JSON Schema, llms.txt, llms-full.txt, and skill reference tables.",
|
|
1538
|
+
)
|
|
1539
|
+
group.add_argument(
|
|
1540
|
+
"--check",
|
|
1541
|
+
action="store_true",
|
|
1542
|
+
help="Verify all generated agent surface files match code declarations (exit 1 on drift).",
|
|
1543
|
+
)
|
|
1544
|
+
group.add_argument(
|
|
1545
|
+
"--probe-env",
|
|
1546
|
+
action="store_true",
|
|
1547
|
+
help="Print a JSON report of installed extras, available models, and SF_* cloud readiness.",
|
|
1548
|
+
)
|
|
1549
|
+
args = parser.parse_args(argv)
|
|
1550
|
+
|
|
1551
|
+
if args.probe_env:
|
|
1552
|
+
print(json.dumps(probe_environment(), indent=2, sort_keys=True))
|
|
1553
|
+
return 0
|
|
1554
|
+
|
|
1555
|
+
if args.write:
|
|
1556
|
+
written = write_agent_surfaces()
|
|
1557
|
+
for path in written:
|
|
1558
|
+
print(f"wrote {path}")
|
|
1559
|
+
return 0
|
|
1560
|
+
|
|
1561
|
+
drifted = check_agent_surfaces()
|
|
1562
|
+
if drifted:
|
|
1563
|
+
for msg in drifted:
|
|
1564
|
+
print(f"DRIFT: {msg}", file=sys.stderr)
|
|
1565
|
+
return 1
|
|
1566
|
+
print("All agent surface files are in sync.")
|
|
1567
|
+
return 0
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
if __name__ == "__main__": # pragma: no cover - CLI entrypoint
|
|
1571
|
+
raise SystemExit(main())
|