FAQ and troubleshooting¶
Common questions, every error message you are likely to hit, and what to do about each.
If a term here is unfamiliar, the Glossary has it.
Getting started¶
How long before this is actually useful?¶
That depends entirely on archive age, and it is the binding constraint on everything.
| Milestone | Requires |
|---|---|
qc and build-dataset work |
a station DB and any forecast archive |
predict works |
one provider forecast within the last 12 hours |
backtest produces folds |
issue span greater than initial_train_days — more than 90 days by default, plus usable data |
| Promotions start clearing the gate | more, and the exact amount depends on your slice sizes |
Until then predict will run and emit a degraded forecast — an ungrounded
equal-weight blend. That is a real, usable forecast; it is just not the
evidence-backed one.
The archive is the part you cannot rush. You cannot ask a provider what it predicted last Tuesday. Start the polling cron today, even if you plan to do nothing else for three months. See Scheduling.
Can I run this without waiting three months?¶
Partly. backfill fetches archived forecasts from open-data sources to build a
synthetic archive you can backtest against immediately:
grounded-weather-forecast backfill --provider open_meteo --start 2024-01-01
grounded-weather-forecast backtest --source synthetic
Two honest caveats. Open-Meteo's Previous Runs API gives instantaneous leads at exact 24-hour multiples; rain interval starts have leads of 23, 47, ... hours. The 0–6 h range where anchoring earns its keep stays entirely unevaluated. And it covers three open NWP models, so the resulting leaderboard says nothing about the commercial providers you will actually serve from.
Synthetic and live results are never pooled. Read Limitations §3 before drawing conclusions.
Using --provider dynamical gets you native sub-24h leads, at the cost of an
optional dependency (uv sync --extra backfill).
Can I use a different station, or no station at all?¶
A different station: yes. Map your columns and units in
[station.columns] and [station.units] — see the
configuration reference.
The pipeline needs a monotone event rain counter for precipitation; a field
already reporting hourly accumulation is a different quantity and will need code
changes.
No station: no. The station is not an optional extra — it is the input no provider has, and grounding and anchoring both depend on it. Without truth there is also nothing to score against, so the leaderboard has nothing to arbitrate.
Do I need API keys?¶
Not for the core system. The upstream collector needs whatever keys its providers require, but this project reads SQLite files.
Optional: truth_qc uses NWS METAR neighbours with no key, and can additionally
use Synoptic Data with a free-tier token supplied as "$SYNOPTIC_TOKEN".
Error messages¶
cannot predict: no provider forecast within 12.0h of ...¶
Your archive's most recent forecast is older than max_forecast_age_hours, so
the system refuses to serve a stale forecast rather than pretending it is current.
Either the polling cron is not running, or you are testing against an old archive. To reproduce a document as of a past instant:
grounded-weather-forecast predict --now 2026-03-22T17:00:00
no rolling-origin folds. The archive spans 0.0 days ...¶
backtest found nothing to test. Not a failure — the system being honest.
Backtesting means "train on the past, test on the future, repeatedly", which
needs history. With a more-than-90-day first-origin requirement and a one-day-old archive there is
nothing to do.
Start the cron, and optionally backfill so you can measure something today.
MixedProvenanceError¶
Something tried to combine live and synthetic rows. This is a guard, not a bug —
see Methods: verification §7.
Pass a single --source.
ContractViolationError: ... starts with 't__'¶
A truth column reached a feature matrix. This is leakage defence #4 firing. If you are adding a blender or a feature, the column naming rules are in Methods: notation §3.
A ConfigError naming a key¶
Configuration is validated at load with explicit messages. The named key has the wrong type, is out of range, or is not one of the allowed choices — check it against the configuration reference.
unknown method 'x'; available: ...¶
A --methods value or a [predict.methods] pin names an unregistered method.
The error lists every valid id.
A channel is 100% null after qc¶
Your [station.columns] mapping is wrong — the named DB column does not exist or
holds something else. This is exactly what qc is for; fix it before running
anything else, because every later command will fail more confusingly.
Interpreting output¶
Why does the truth-QC panel warn after a successful report?¶
A report refreshes the dashboard, not the retained QC verdict. The panel and central alert list warn after a failed QC refresh, when the verdict is over 48 hours old, or when its freshness is unknown. Individual chips keep their own conditions: a quiet verdict stays green, an unknown refresh time is amber, and a failed attempt does not change a fresh age’s color. Failed attempts display the bounded message saved in the run ledger. Latched drift remains red, and these operational warnings do not change fitting quarantine.
Why does my forecast say degraded?¶
With automatic selection, status: "degraded" means no finite hourly or daily
value retains a compatible promoted release. Matching dataset, config, and code
fingerprints is necessary, but the method must also produce a finite attributed
value. Zero is finite; null, NaN, and infinite outputs cannot justify readiness.
Serving may use equal_weight or native rainfall containment instead.
Explicit --method runs keep their existing ready status without requiring
release attribution. status_reason explains automatic degradation. Common ones:
| Reason | Fix |
|---|---|
| no backtest evidence for this slice | not enough archive yet — wait, or backfill |
| implementation changed since the last backtest | run recover, or re-run backtest --source live then report |
| no compatible approval after a dataset layout change | run recover, which rebuilds before backtesting; data-only fingerprint rotation preserves compatible approvals |
| configuration changed | run recover, or re-run the live backtest and report |
| some compatible promoted selections could not be fitted at issuance | inspect the named product/variable and cause: poll as live issuance advances for pending truth; later-issued rows remain unavailable at fixed historical --now; build missing matrices or incorporate valid truth as advised |
| live evidence exists but no promoted release produced a finite hourly or daily forecast | inspect per-field reasons and provider availability; this does not mean a release was never promoted |
| rainfall safeguard removed attribution | inspect wet-day counts in the affected fields; automatic rainfall needs five distinct wet training days per bucket |
| rainfall reconciliation removed attribution | inspect native minute, hourly, and observed rainfall; the adjusted value cannot inherit the model's release or uncertainty |
The pattern: anything that changes the system's identity invalidates the
evidence that justified a promotion. Re-running backtest then report
re-earns it. See
ADR 0005.
Actual wet-day guard loss takes precedence over actual reconciliation loss. Otherwise archive absence and identity changes keep their existing diagnoses; fitting failures are named only when evidence identities match. The message lists only unavailable promoted fits in stable product/variable/semantics order; it does not claim every fit failed. Existing finite truth excluded by the issue-time cutoff can become eligible on a later prediction without any dataset rebuild. Cold-start blends and native floors filling unattributed nulls record no rainfall attribution loss. Detailed field reasons retain wet-day selection history even when the document names unavailable fitting. Null hourly/daily points omit quantiles and their source; a quantile source always accompanies usable emitted quantiles.
Scheduled publish attempts that recovery automatically, at most once per
unchanged degradation signature every six hours. While it runs, an existing
ready published document is held byte-for-byte.
Degraded status prints to stderr, so --out - still gives clean JSON on stdout.
Why is n so small on my leaderboard?¶
n is the number of scored cases in that slice, and it is printed precisely so a
thin slice announces its own lack of power rather than hiding behind a p-value.
Small n usually means: a short archive, a long lead bucket (a 240h+ bucket
needs 10 days of archive per case), a variable few providers publish, or a method
that abstained on most rows because it could not fit. The last is by design —
see Methods: notation §6.
Eligibility requires n ≥ 8 and n_valid_times ≥ 8 before any promotion gate
even runs.
Why did my served method change overnight?¶
Promotions are re-decided every report. Some churn is legitimate — new evidence
arrived — and some is noise. The system tracks this: reports/selection_churn.md
reports how often the served method changes, and a high churn rate is itself a
signal that slices are too thin to decide.
The promotion gate exists to suppress exactly this. If churn is high, consider
whether [promotion] rule should be the stricter seq_mcs.
Why is a method missing from a slice entirely?¶
Either it is scoped out (precipitation heads are not offered temperature), or it
abstained — below its _MIN_FIT_ROWS, or lacking wet cases, or without a
short-lead anchor row. Abstention is a first-class outcome here: a method that
cannot fit says so rather than fitting badly on twelve rows.
The leaderboard says equal weight is winning. Is something broken?¶
Probably not. A plain average of grounded sources being within a hair of the best method is the forecast combination puzzle, a well-documented empirical result: estimating weights adds variance faster than it removes bias. On this data raw equal weight wins the 96–168 h temperature slice outright.
See Concepts §3.
anchored_* methods have identical scores to their base¶
Expected on synthetic data. Anchoring needs a forecast row with lead under 6 hours to compute its residual, and the Previous Runs backfill starts at 24 h. With no anchor row the τ search selects "no anchoring" and the wrapper degrades exactly to its base. The mechanism works; that data cannot exercise it.
Operations¶
Where does each command write?¶
Summarized in Outputs; per-command in the CLI reference.
What are the exit codes?¶
| Code | Meaning |
|---|---|
0 |
success |
1 |
command-level failure — missing inputs, no scores, a backfill error |
2 |
configuration error, or unknown command |
truth-qc deliberately returns 0 when it finds no evaluable checks: a cold
start is not a fault and should not page anyone.
What is safe to delete?¶
reports/ entirely — regenerated by report, alignment, and truth-qc.
data/scores/ via prune-scores (use --dry-run first).
Not safe: data/predict_history.parquet (the record of what you served),
artifacts/history/ (append-only ledgers), and artifacts/eprocess/ (sequential
accumulated evidence — deleting it restarts every gate from zero wealth). And
above all, the two upstream SQLite files, which cannot be recreated.
See Outputs.
report is slow / disk is filling up¶
report iterates every scores file, and expanding-window backtests accumulate
them. Run prune-scores; retention keeps the newest per group, everything the
active release depends on regardless of age, and anything a release from the
last 7 days depends on.
How do I reproduce a forecast the system served last week?¶
grounded-weather-forecast predict --now 2026-03-22T17:00:00 --no-history
--now reissues from an archived snapshot using only data available by that
instant. Add --no-history so the experiment does not pollute the
self-verification record.
How do I force one method for testing?¶
grounded-weather-forecast predict --method gbm --no-history
Or pin per slice in [predict.methods]. Both bypass the promotion gate — a
deliberate override of the system's own evidence, not a tuning knob. Always pair
ad-hoc runs with --no-history.
Extending it¶
How do I add my own blending method?¶
Implement the Blender protocol and register a factory (never an instance —
the engine builds a fresh blender per fold as a leakage defence). Worked example
in Advanced usage; the protocol is
in Methods: notation and the
API reference.
Then let the leaderboard decide. Nothing ships because it sounds good.
Can I use this as a library?¶
Yes — see Advanced usage and the
API reference. contracts, config,
blenders.protocol, blenders.registry, metrics, leads, and serve.schema
are the intended surface.