Data
Everything published here is recomputable from the archived station values, the truth table and this repository at a given commit. Nothing is hand-edited. Errors are stored in °C and displayed in °F on the site; the downloads below are in °C.
Downloads
| File | Contents | Rows | Size |
|---|---|---|---|
scores_latest.csv | every published aggregate: station × model × init × lead × variable × method × window, with n, MAE, bias, RMSE, hit rates, skill and bootstrap intervals (values in °C). | 47552 | 10.6 MB |
pairwise_latest.csv.gz | paired model-vs-model MAE differences on common days, with the bootstrap interval and the significance flag (°C). | 160384 | 2.0 MB |
daily_errors.csv.gz | the complete per-day record behind every score: one row per station, model, initialization, lead day, method, variable and climatological day, with the forecast, the observation and the signed error (°C). This is the file to download if you want to recompute anything. | 177180 | 2.0 MB |
stations.csv | station metadata: identifier, name, truth product, fixed standard UTC offset, coordinates and elevation. | 23 | 1.8 kB |
models.csv | model registry, including the persistence baseline. | 12 | 792 B |
The parquet shards behind these CSVs are mirrored as a Hugging Face dataset: https://huggingface.co/datasets/castcheck/temperature-verification. The code that produces them is at https://github.com/zifanzhang/castcheck (MIT).
JSON API (v1)
Static files, CORS-open, cached one hour. See the API page for
worked curl and Python examples and openapi.json.
| Endpoint | Contents | Rows/files |
|---|---|---|
scores/latest.json | every published score (all stations, models, leads, windows) | 47552 |
scores/leaderboard.json | the station_id=ALL slice used by the front page | — |
leaderboard/{window}-{init}z-{method}-{variable}.json | one pre-built file per leaderboard view (32 of them) | — |
scores/{station}/{model}/{lead}.json | one permanent-link card plus the last 90 days of daily errors | — |
pairwise/latest.json | model-vs-model paired bootstrap (station_id=ALL) | 160384 |
stations.json | station metadata | — |
models.json | model metadata | — |
status.json | pipeline completeness report | — |
openapi.json | OpenAPI 3.1 description of the endpoints above | — |
Table schemas
forecast_values
one extracted station value per model run, valid time, variable and interpolation method (DESIGN §3.1).
| Column | Type | Unit | Meaning |
|---|---|---|---|
model_id | string | — | stable model identifier from config/models.yaml |
model_version | string | — | upstream cycle/version string as advertised by the producer |
init_time | timestamp | UTC | model initialization time |
valid_time | timestamp | UTC | forecast valid time |
lead_h | int16 | hours | valid_time − init_time |
station_id | string | — | ICAO identifier, or ALL for the cross-station aggregate |
variable | string | — | tmax or tmin (daily extreme of the four common samples) |
bucket_h | int8 | hours | accumulation window of a native extreme field, 0 if instantaneous |
method | string | — | grid-to-station interpolation: bilinear (headline) or nearest |
value_c | float32 | °C | extracted station value, NaN when missing |
missing_reason | string | — | why a value is absent; empty when present |
source_url | string | — | exact object or URL the value was read from |
fetched_at | timestamp | UTC | when the value was fetched |
truth_daily
one row per station-day-source with the first-final policy and QC flags (DESIGN §3.2).
| Column | Type | Unit | Meaning |
|---|---|---|---|
station_id | string | — | ICAO identifier, or ALL for the cross-station aggregate |
climo_date | date | LST day | climatological day, midnight to midnight local standard time |
source | string | — | truth product: CLI, CF6 or OBS |
tmax_f | int16 | °F | daily maximum as reported by the NWS (whole degrees) |
tmin_f | int16 | °F | daily minimum as reported by the NWS (whole degrees) |
tmax_c | float32 | °C | daily maximum converted to °C |
tmin_c | float32 | °C | daily minimum converted to °C |
issuance_time | timestamp | UTC | issuance time of the truth product |
is_final | bool | — | first CLI issued after local midnight (the first-final policy) |
revised | bool | — | a later corrected report exists (never used in scores) |
revised_tmax_f | int16 | °F | latest corrected maximum, published but not scored |
revised_tmin_f | int16 | °F | latest corrected minimum, published but not scored |
qc_flag | string | — | quality note, empty when clean |
product_id | string | — | api.weather.gov product id or IEM archive key |
schema_version | string | — | data-model version (DESIGN §3) |
methodology_version | string | — | METHODOLOGY version that produced the numbers |
daily_forecasts
sampled and native daily extremes per model run, station and climatological day (DESIGN §3.3).
| Column | Type | Unit | Meaning |
|---|---|---|---|
model_id | string | — | stable model identifier from config/models.yaml |
model_version | string | — | upstream cycle/version string as advertised by the producer |
init_time | timestamp | UTC | model initialization time |
station_id | string | — | ICAO identifier, or ALL for the cross-station aggregate |
climo_date | date | LST day | climatological day, midnight to midnight local standard time |
lead_day | int8 | days | target climatological date − UTC date of the initialization |
method | string | — | grid-to-station interpolation: bilinear (headline) or nearest |
tmax_sampled_c | float32 | °C | max of the four common samples in the climatological day |
tmin_sampled_c | float32 | °C | min of the four common samples in the climatological day |
n_samples | int8 | count | how many of the four common samples were present (0–4) |
tmax_native_c | float32 | °C | daily max from the model's native extreme field (diagnostic) |
tmin_native_c | float32 | °C | daily min from the model's native extreme field (diagnostic) |
missing_reason | string | — | why a value is absent; empty when present |
schema_version | string | — | data-model version (DESIGN §3) |
methodology_version | string | — | METHODOLOGY version that produced the numbers |
scores
published aggregates with bootstrap intervals (DESIGN §3.4).
| Column | Type | Unit | Meaning |
|---|---|---|---|
station_id | string | — | ICAO identifier, or ALL for the cross-station aggregate |
model_id | string | — | stable model identifier from config/models.yaml |
init_hour | int8 | UTC hour | model initialization hour, 0 or 12 |
lead_day | int8 | days | target climatological date − UTC date of the initialization |
variable | string | — | tmax or tmin (daily extreme of the four common samples) |
method | string | — | grid-to-station interpolation: bilinear (headline) or nearest |
window | string | — | scoring window: 30d, 90d, 365d or all |
n | int32 | days | number of scored climatological days in the window |
n_stations | float32 | stations | mean number of stations behind each day of an ALL row (1 for a single-station row) |
n_flagged | int32 | days | how many of those days carry a QC flag on the observation |
mae | float32 | °C | mean absolute error, forecast − observed |
bias | float32 | °C | mean signed error; positive = model too warm |
rmse | float32 | °C | root mean squared error |
hit1f | float32 | fraction | share of days with |error| ≤ 1 °F |
hit2f | float32 | fraction | share of days with |error| ≤ 2 °F |
hit3f | float32 | fraction | share of days with |error| ≤ 3 °F |
mae_debiased | float32 | °C | MAE after removing the per-station constant bias of the window; the part of the error that is not a fixed offset |
skill_persistence | float32 | fraction | 1 − MAE/MAE(persistence); positive is better |
skill_persistence_debiased | float32 | fraction | the same skill score computed on the debiased errors, so a station with a large constant offset is not scored as skill-less |
mae_ci_low | float32 | °C | 2.5th percentile, moving-block bootstrap |
mae_ci_high | float32 | °C | 97.5th percentile, moving-block bootstrap |
bias_ci_low | float32 | °C | 2.5th percentile of the bias bootstrap |
bias_ci_high | float32 | °C | 97.5th percentile of the bias bootstrap |
rmse_ci_low | float32 | °C | 2.5th percentile of the RMSE bootstrap |
rmse_ci_high | float32 | °C | 97.5th percentile of the RMSE bootstrap |
hit1f_ci_low | float32 | fraction | 2.5th percentile of the ±1 °F hit-rate bootstrap |
hit1f_ci_high | float32 | fraction | 97.5th percentile of the ±1 °F hit-rate bootstrap |
model_version | string | — | upstream cycle/version string as advertised by the producer |
segment_start | date | LST day | first day of the current model-version segment; scores cover only this segment |
period_start | date | LST day | first climatological day contributing to the window |
period_end | date | LST day | last climatological day contributing to the window |
computed_at | timestamp | UTC | when this table was computed |
methodology_version | string | — | METHODOLOGY version that produced the numbers |
schema_version | string | — | data-model version (DESIGN §3) |
pairwise
paired model-vs-model MAE differences (DESIGN §3.5).
| Column | Type | Unit | Meaning |
|---|---|---|---|
station_id | string | — | ICAO identifier, or ALL for the cross-station aggregate |
init_hour | int8 | UTC hour | model initialization hour, 0 or 12 |
lead_day | int8 | days | target climatological date − UTC date of the initialization |
variable | string | — | tmax or tmin (daily extreme of the four common samples) |
window | string | — | scoring window: 30d, 90d, 365d or all |
model_a | string | — | first model of the pair |
model_b | string | — | second model of the pair |
n_common | int32 | days | days on which both models of the pair have a forecast |
mae_diff | float32 | °C | MAE(model_a) − MAE(model_b) on their common days |
ci_low | float32 | °C | 2.5th percentile of the paired bootstrap difference |
ci_high | float32 | °C | 97.5th percentile of the paired bootstrap difference |
significant | bool | — | true when the 95 % interval of the difference excludes zero |
method | string | — | grid-to-station interpolation: bilinear (headline) or nearest |
computed_at | timestamp | UTC | when this table was computed |
methodology_version | string | — | METHODOLOGY version that produced the numbers |
schema_version | string | — | data-model version (DESIGN §3) |
Licences and attribution
- CastCheck derived tables and this site — CC BY 4.0. Please cite the permanent link of the page you used.
- CastCheck code — MIT, https://github.com/zifanzhang/castcheck.
- ECMWF IFS HRES and AIFS Single open data — CC BY 4.0, © ECMWF. Source:
data.ecmwf.intand the AWS Open Data mirror. - NCEP GFS (AWS
noaa-gfs-bdp-pds) — U.S. Government work, public domain. - NOAA/CIRA AIWP model output (AWS
noaa-oar-mlwp-data) — U.S. Government work, public domain. Cite: Radford, J. T., et al. (2025). Domain-agnostic evaluation of AI weather prediction models. Bulletin of the American Meteorological Society. doi:10.1175/BAMS-D-24-0057.1. - NWS Daily Climate Report (CLI), CF6 and station observations — U.S. Government work, public domain, via
api.weather.gov. - Historical NWS text products — Iowa Environmental Mesonet (IEM) AFOS archive, Iowa State University.
Raw model output, no post-processing. These are values interpolated from each model's native grid, without MOS, bias correction, downscaling or any other post-processing, and they are not the products that ECMWF, NOAA, the NWS or any app vendor issue to end users.
Cite the dataset
CastCheck (2026). Station-level verification of raw weather-model 2 m temperature forecasts [data set]. Methodology version 0.2, schema version 0.1, data through 2026-08-30. https://castcheck.zifanzhang.com
For a specific number, cite its permanent link instead — every score card carries a short and a long citation with the access date.
Changelog
| Methodology | Date | Change |
|---|---|---|
| v0.1 | 2026-08-30 | First public build: 2 m temperature, 23 stations, ECMWF IFS HRES, NCEP GFS and the NOAA/CIRA AIWP models, persistence baseline, moving-block bootstrap intervals. |
Data events (a source outage, a corrected observation, a model version change) are
recorded in status and in the gaps list of
status.json.
Known limitations
- Daily extremes are the max/min of four 6-hourly samples, so they under-state the true afternoon peak and over-state the pre-dawn trough. The bias is identical for every model and is therefore fair for comparison, but the absolute errors are not what a user of a post-processed forecast experiences (METHODOLOGY §2.3).
- No elevation or lapse-rate correction is applied; stations whose elevation differs sharply from the 0.25° grid cell carry a representativeness error that is charged to the model.
- Truth is the first final NWS CLI report. Later corrections are stored but never change a published score, so a corrected observation leaves a permanent, documented discrepancy.
- Models enter the record on different dates, so their windows are not identical. Pairwise comparisons are computed on common days only; the leaderboard columns are not.
- Groups with fewer than 30 scored days are published but greyed out and excluded from every ranking; early in a model's record most windows are in that state.
- The 0.25° AIWP archive is a research product; outages there appear here as gaps, not as bad forecasts (see /status/).