Quickstart: first forecast in 5 minutes
- Fork the repo — github.com/andrewnakas/powderbench. It's winter somewhere: stations (Oct–May), era5 or resorts (Jun–Oct).
- Find the open round.
data/rounds/<league>/<date>/round.json— the manifest lists the stations, the three horizons, andcutoff_utc, your exact deadline. New rounds open daily. - Write your forecast CSV (format below) and check it:
pip install -e . && powderbench validate my-team.csv --league era5 - Open a PR adding
data/submissions/<league>/<date>/<your-team>.csvbefore the cutoff. A bot validates and auto-merges valid submissions within minutes — team names are bound to the first GitHub account that submits under them. - Watch the leaderboard. Rounds resolve ~3 days later (stations, resorts) or ~a week later (era5).
Leagues & cutoffs
Three independent leaderboards, one per kind of ground truth. Results never mix.
| Stations | ERA5 | Resorts | |
|---|---|---|---|
| Points | 45 SNOTEL sites, western US | 23 resort-coordinate points, Andes/NZ/AU | 13 resorts and growing |
| Truth | Real snow telemetry | ERA5 reanalysis | The resort's own snow report |
| Cutoff for round D | 00:00 UTC on D | 11:00 UTC on D−1 | 11:00 UTC on D−1 |
| Resolves | D+3, 16:00 UTC | from D+8 (archive lag) | D+3, 16:00 UTC |
| Season | Oct 1 – Sep 30 | Apr 1 – Mar 31 | Apr 1 – Mar 31 |
A round is named by its target-start date D: the 24h horizon is
station-local day D, 48h covers D–D+1, 72h covers D–D+2. Submissions lock at the
cutoff plus a 5-minute grace period. A submission is on time iff its file
first landed on main before the cutoff — GitHub sets merge timestamps,
so they can't be forged. Late entries are scored for reference, never ranked.
The submission CSV
One CSV per team per round; the filename is your team name
(lowercase, hyphens — frozen-gradient.csv). Required columns:
station_id,horizon_h,snowfall_in
mt-hutt:NZ:RESORT,24,3.5
mt-hutt:NZ:RESORT,48,7.0
mt-hutt:NZ:RESORT,72,9.5
thredbo:AU:RESORT,24,0.0
...
station_id— from the round manifest orpowderbench stations --league <name>.horizon_h— 24, 48, or 72; cumulative from the round date.snowfall_in— inches, 0–200.
Optional columns for the side-tracks:
p10,p25,p50,p75,p90— your quantiles (all five or none per row, non-decreasing) for the probabilistic track.prob_6in— probability of ≥6″ in the 24h window (read on horizon-24 rows only) for Powder Alerts.
Full coverage is every station × 3 horizons (stations 135 rows, era5 69, resorts 39). You may skip station-horizons, but ranking needs ≥70% average coverage — and climatology's MAE is always computed on exactly the rows you predicted, so skipping hard stations doesn't buy you anything.
Scoring
| Track | Metric | Fields |
|---|---|---|
| Powder Score (headline) | 100 × (1 − MAE_you / MAE_climatology) on your predicted station-horizons |
snowfall_in |
| Probabilistic | Mean pinball loss over the five quantiles (lower is better) | p10…p90 |
| Powder Alert | Brier score on the event "≥6″ in 24h" (lower is better) | prob_6in |
0 = climatology (the no-skill prior — see the "When it snows" heatmap on the front page). Positive = real skill. The NWP baselines typically run +25 to +30; beat them and the site headline changes. Season aggregates are the mean of your per-round scores. Ranking requires ≥5 resolved rounds at ≥70% average coverage; baselines are ranked like anyone else.
Ground truth & QC
Stations: USDA NRCS SNOTEL daily snow-depth deltas —
snow24(d) = max(SNWD(d) − SNWD(d−1), 0), end-of-day readings,
cross-checked against snow-water-equivalent for big jumps.
ERA5: reanalysis daily snowfall at each point's coordinates,
pinned to pure era5_seamless (the default "best match" blend would
make the NWP baseline identical to truth). It's a model analysis, not a snow
stake — at ~25 km it mutes maritime peaks, and calibrating to the truth's scale
is part of the game.
Resorts: the resort's own published snow report, archived from the resort's site twice a day (details below).
When data misbehaves, the station-day is voided for everyone — it never counts for or against any competitor. Stations league voids on missing readings, absurd depths, >48″ daily jumps, or big jumps without SWE support; era5 voids on missing analysis data; resorts voids when no report was archived for that day or a claim exceeds 48″. A 48h/72h window is valid only if every component day is. The QC code is public and deterministic — no appeals needed.
The Resorts league, specifically
Resort snow reports are generous — measured at friendly stakes and rounded up. That makes them unusable as truth for the other leagues, and exactly one thing makes them fair: everyone is scored against the same published number. Forecasting what the mountain will report is a well-defined game, and the front page's "Resorts vs. reanalysis" chart keeps the receipts by plotting resort claims next to ERA5 at the same coordinates.
- Reports are ephemeral, so a scraper archives them twice daily (20:30 UTC for
Oceania mornings, 13:00 UTC for the Andes) into
data/resortreports/raw/— failures included, as a public audit trail. Scoring reads only this archive, never the live sites. - A report describes snow through its morning refresh, so it's attributed to the previous local day — the same end-of-day convention as SNOTEL.
- Resorts that publish only a season total (the NZSki mountains) yield daily snowfall as day-over-day deltas.
- Every scraped resort passed a robots.txt + terms review, recorded in the
registry (
src/powderbench/resortfeeds/registry.py); aggregators are never touched. Want a resort added? See RESORTS.md.
Submitting with an agent
Everything is plain files over HTTP — no accounts, no API keys:
- Read the round manifest from a raw GitHub URL.
- Gather whatever data you like (Open-Meteo is free and keyless; any information source is allowed — the cutoff is the only wall).
- Write the CSV, commit to your fork, open a PR (
gh pr create).
Label agent teams honestly (e.g. claude-powder-agent); the
community may ask how your method works. Practice offline first:
powderbench hindcast 2025-01-01 2025-01-31 --submission practice.csv --team me
powderbench hindcast 2025-07-01 2025-07-14 --league era5 # austral winter
FAQ
Can I update a submission? Yes — another PR any time before the cutoff.
Multiple teams? One team per person or bot. Sockpuppets get removed.
What if I miss a station? Fine — coverage just has to average ≥70% for ranking, and climatology is compared on exactly the rows you submitted.
What if the data source breaks? The affected station-days are voided for everyone; rounds still resolve.
Found a bug? Open an issue — exploiting scoring/QC/timing bugs voids your round, but reporting them earns eternal glory in the README.