Skip to content

Guides

Weather observations

weather.observations() returns historical reports. weather.live.latest() and weather.live.stream() return live reports with the same measurement fields. Every row states its source.

from mostlyright import weather
reports = weather.observations(
"KNYC",
"2025-01-06",
"2025-01-08",
)
print(reports[["event_time_utc", "temp_f", "source", "local_standard_date"]].head())

observations() is eager and date-bound. Both inclusive dates are required, and the result is always per-report METAR/SPECI data. The 1.x granularity= switch is gone; use weather.training_table() when you want one row per local-standard day.

Each row identifies the report with:

  • station: canonical ICAO/ASOS station id
  • event_time_utc: UTC observation time
  • observation_type: METAR or SPECI
  • source: awc, iem, or ghcnh
  • local_standard_date: the station’s local-standard-time day

Measurements include Celsius and Fahrenheit temperature/dewpoint, wind, pressure, visibility, precipitation, four sky layers, weather codes, peak wind, snow depth, upstream QC text, and raw_metar. See the observation schema for the complete contract.

Without source=, the SDK queries the sources that can cover the requested window and merges overlapping reports using the fixed priority AWC > IEM > GHCNh.

iem = weather.observations(
"KNYC",
"2025-01-06",
"2025-01-12",
source="iem",
)

Pinning source= changes row composition: it requests the full single-source coverage rather than the merged survivors. Treat a change between fused and pinned data as a model retraining event.

The valid source pins are "awc", "iem", and "ghcnh". Unknown values fail before network I/O.

Set quality_control=True to append the SDK’s obs_qc_status bitfield. Rows are annotated, never dropped:

checked = weather.observations(
"KNYC",
"2025-01-06",
"2025-01-08",
quality_control=True,
)
clean = checked[checked["obs_qc_status"] == 0]
flagged = checked[checked["obs_qc_status"] != 0]

The separate qc_field column preserves the upstream archive’s marker. obs_qc_status is the SDK’s local physics-check result.

The live methods are asynchronous and use one provider per call:

import asyncio
from mostlyright import weather
async def watch():
latest = await weather.live.latest("KNYC")
print(latest["event_time_utc"], latest["temp_f"], latest["source"])
async for report in weather.live.stream("KNYC", source="awc"):
print(report["event_time_utc"], report["temp_f"])
asyncio.run(watch())

Live calls do not perform a multi-source merge. source=None uses the method’s documented default provider. A live row’s source tag identifies the provider and delivery method, for example awc.live.

When no fresh report exists, latest() raises NoLiveDataError rather than returning a stale observation.

import asyncio
from mostlyright import weather
STRIKE_F = 79
async def monitor(station: str):
async for report in weather.live.stream(station, source="awc"):
temp = report.get("temp_f")
if temp is not None and temp >= STRIKE_F:
await page_the_desk(station, report) # your webhook
asyncio.run(monitor("KNYC"))

Use live reports for decisions, but replay settlement research from archived reports. Providers can correct reports later, so cached historical rows are the reproducible input.

Closed observation months are cached as parquet under ~/.mostlyright/cache/v1/observations/. The current local-standard-time month stays volatile and is fetched fresh. Move the root with MOSTLYRIGHT_CACHE_DIR.

TypeScript uses the same data model but returns { rows, provenance }:

import { weather } from "mostlyright";
const { rows } = await weather.observations(
"KNYC",
"2025-01-06",
"2025-01-08",
);

Browser requests remain subject to upstream CORS. See Browser integration before moving an archive fetch into a page or worker.