Guides
Weather observations
Three observation sources feed the same canonical schema. The SDK fetches in parallel, tags every row with its source, and writes a parquet/JSON cache; on overlap it applies a deterministic client-side dedup tiebreak. Every row carries its source identity — you can branch on it.
The three sources
Section titled “The three sources”| Tiebreak | Source | Endpoint | Window | Notes |
|---|---|---|---|---|
| 3 | AWC | aviationweather.gov/api/data/metar | Last 168h | Live, lowest-latency. CORS-allowed |
| 2 | IEM | mesonet.agron.iastate.edu/cgi-bin/request/asos.py | Full history | Near-real-time mirror, multi-year archive |
| 1 | GHCNh | ncei.noaa.gov/oa/global-historical-climatology-network/... | Full history | Quality-flagged archive |
When fetched feeds overlap on a (station, observed_at) tuple, a client-side dedup tiebreak in your process keeps one row: AWC > IEM > GHCNh.
See Data sources for the rationale (it’s about raw-as-reported, not absolute correctness).
Fetch one window
Section titled “Fetch one window”The high-level call is obs():
from mostlyright.weather import obs
rows = obs("KNYC", "2025-01-06", "2025-01-12")print(rows[0])# {# "station": "KNYC",# "observed_at": "2025-01-06T00:51:00Z",# "source": "awc.live",# "temp_c": 0.5,# "temp_f": 33,# "raw_metar": "KNYC 060051Z 35008KT 10SM CLR M01/M05 A3020",# ...# }import { obs } from "@mostlyrightmd/weather";
const rows = await obs("KNYC", "2025-01-06", "2025-01-12");console.log(rows[0]);Default routing — auto-router picks exact_window for ≤7-day windows, warm_cache for larger. See Ingest strategies for the routing logic.
Single-source query
Section titled “Single-source query”const rows = await obs("KNYC", "2025-01-06", "2025-01-12", { source: "iem" });// Every row.source === "iem.archive"Pinning to one source forces exact_window (single-source can’t honor the multi-source merge that warm_cache enables).
Observation schema
Section titled “Observation schema”The canonical observation row (schema.observation.v1):
{ "station": "KNYC", "observed_at": "2025-01-06T00:51:00Z", "source": "awc.live", "observation_type": "METAR", "raw_metar": "KNYC 060051Z 35008KT 10SM CLR M01/M05 A3020",
"temp_c": 0.5, "temp_f": 33, "dewpoint_c": -2.0, "dewpoint_f": 28, "wind_speed_kt": 8, "wind_direction_deg": 350, "wind_gust_kt": null, "pressure_inhg": 30.20, "precip_mm_1h": 0,
"sky_cover_1": "CLR", "sky_base_1_m": null}See Observation schema for the full field list, units, and dtype conventions.
Source identity
Section titled “Source identity”Every row carries its source on the source field:
- AWC live rows:
"awc.live" - IEM archive rows:
"iem.archive" - GHCNh rows:
"ghcnh" - Plus
.livevariants onlive.stream()(see Live streaming)
You can branch on this without parsing strings:
for row in rows: if row["source"].endswith(".live"): # Live feed; might still be revised ... else: # Archive; final ...Raw METAR
Section titled “Raw METAR”raw_metar is the exact text the station broadcast (for AWC + IEM). GHCNh rows have raw_metar: null (the archive carries decoded values only). MetPy can re-parse the raw METAR if you need fields the SDK doesn’t surface:
from metpy.io import parse_metar_to_dataframeimport pandas as pd
raw_rows = obs("KNYC", "2025-01-06", "2025-01-12")metars = [r["raw_metar"] for r in raw_rows if r["raw_metar"]]metpy_df = pd.concat([parse_metar_to_dataframe(m) for m in metars])Daily extremes
Section titled “Daily extremes”For per-day max/min temperature rollups (which is what Kalshi NHIGH/NLOW settles on):
from mostlyright.international import daily_extremes
days = daily_extremes("KNYC", "2025-01-06", "2025-01-12")print(days[0])# {# "station": "KNYC",# "date": "2025-01-06",# "high_f": 37,# "low_f": 23,# "high_at": "2025-01-06T17:51:00Z",# "low_at": "2025-01-06T06:51:00Z",# }import { dailyExtremes } from "@mostlyrightmd/weather";
const days = await dailyExtremes("KNYC", "2025-01-06", "2025-01-12");console.log(days[0]);The TS port uses the station’s IANA tz from the registry to bucket observations by station-local date. Integer-°F precision matches Kalshi’s settlement rounding for US stations.
Observations cache as per-month parquet (Python) or JSON (TypeScript) under ~/.mostlyright/cache/observations/{station}/{year}/{month}.{parquet|json}. The current LST month is skipped (incomplete data is never cached).
See the Cache guide for the full layout, skip rules, and schema-version invalidation.
Per-source adapters
Section titled “Per-source adapters”If you need to call the source-specific fetcher directly:
from mostlyright.weather.catalog import get_adapter
awc = get_adapter("awc.live") # AwcAdapteriem = get_adapter("iem.archive") # IEMAdapterghcnh = get_adapter("ghcnh.archive") # GhcnhAdaptercli = get_adapter("cli.archive") # CliAdapter (climate, not observations)
rows = awc.fetch_observations("KNYC", "2025-01-06", "2025-01-12")Unknown sources raise DataAvailabilityError(reason="model_unavailable").
See also
Section titled “See also”- Data sources — why the dedup tiebreak order matters
- Raw as reported — the first-observation-wins rule
- Observation schema — full field reference
- Live streaming — async generator on top of
obs() - Research API — daily-bucketed settlement output that uses these rows
- API reference:
mostlyright.weather·obs