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 |
initial_train_days + step_days — 97 days by default |
| 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 leads at exact 24-hour multiples, so the 0–24 h range — where the product actually lives and 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 97-day 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 my forecast say degraded?¶
status: "degraded" means no promoted release matched the current dataset,
config, and code fingerprints, so serving fell back to equal_weight rather than
using evidence it cannot vouch for. status_reason says which. Common ones:
| Reason | Fix |
|---|---|
| no backtest evidence for this slice | not enough archive yet — wait, or backfill |
| implementation changed since the last backtest | re-run backtest --source live then report |
| dataset fingerprint changed | re-run backtest then report after build-dataset |
| configuration changed | as above |
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.
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. 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 plus 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.