Skip to content

Normalized Schema

All provider responses are normalized into a common set of Pydantic models. Units are standardized: temperatures in °C, wind speeds in m/s, pressure in hPa, precipitation in mm, visibility in km.

A few fields carry provider-specific semantics that survive normalization — notably the split between liquid-equivalent and depth snow fields, and the probability scales. See Unit and semantics notes before comparing values across providers.

A note on pressure data. Pressure is the least reliable field providers report — implausible sea-level values have been observed in the wild (Stormglass emitting 885 hPa, Weatherbit 1074 hPa). And if you compare pressure_sea against a personal weather station, calibrate the station first: consumer stations report an absolute (station-level) pressure and a relative (sea-level) pressure, and the relative reading requires an elevation offset to be configured — an uncalibrated station at altitude can read more than 150 hPa below the true sea-level value while its absolute sensor is perfectly healthy. Pressure plausibility checks are planned; the author will be working on this soon.

WeatherDataPoint (hourly)

Field Type Unit
timestamp, timestamp_unix datetime (UTC), int ISO/Unix seconds
temperature, apparent_temperature, dew_point float | None °C
humidity float | None % (0-100)
wind_speed, wind_gust float | None m/s
wind_direction float | None degrees
pressure_sea, pressure_surface float | None hPa
precipitation, rain, snow (liquid equivalent), snow_depth float | None mm
snowfall_depth (new snow depth; may coexist with liquid-equivalent snow when a provider reports both) float | None mm
precipitation_probability float | None 0-1
cloud_cover, cloud_cover_low, cloud_cover_mid, cloud_cover_high float | None %
visibility float | None km
uv_index float | None 0-11+
solar_radiation_ghi, solar_radiation_dni, solar_radiation_dhi float | None W/m²
condition WeatherCondition | None enum
condition_original, condition_code_original str/int | None provider-native
is_day bool | None

DailyDataPoint

Field Type Unit
date date
temperature_max, temperature_min float | None °C
apparent_temperature_max, apparent_temperature_min float | None °C
wind_speed_max, wind_gust_max float | None m/s
wind_direction_dominant float | None degrees
precipitation_sum, rain_sum, snowfall_sum (liquid equivalent), snowfall_depth_sum (depth) float | None mm
precipitation_probability_max float | None 0-1
cloud_cover_mean float | None %
humidity_mean float | None %
visibility_min float | None km
pressure_sea_mean float | None hPa
uv_index_max float | None 0-11+
sunrise, sunset, moonrise, moonset datetime | None UTC
moon_phase float | None 0-1
daylight_duration float | None seconds
solar_radiation_sum float | None MJ/m²
condition WeatherCondition | None enum
summary str | None

Daily dates are the provider's local calendar dates, not UTC days. Absolute timestamps and astronomical instants remain UTC.

Location timezones

ForecastRequest.timezone accepts an optional loadable IANA timezone name such as America/Los_Angeles. Fixed numeric offsets are rejected because they cannot apply daylight-saving rules across a forecast horizon. Normalization chooses a valid provider-supplied IANA name first, then the request value, then an Open-Meteo coordinate lookup.

SourceForecast.timezone records the IANA name actually used for that source. A timezone lookup or ambiguous/nonexistent provider wall time becomes a provider-level PARSE/network error; it does not throw from the aggregate request or suppress successful results from other providers.

MinutelyDataPoint

Field Type Unit
timestamp, timestamp_unix datetime (UTC), int ISO/Unix seconds
precipitation_intensity float | None mm/h
precipitation_probability float | None 0-1

WeatherAlert

Field Type
sender_name str
event str
start, end datetime (UTC)
description str
severity EXTREME | SEVERE | MODERATE | MINOR | UNKNOWN
url str | None

WeatherCondition enum

CLEAR, MOSTLY_CLEAR, PARTLY_CLOUDY, MOSTLY_CLOUDY, OVERCAST, FOG, DRIZZLE, LIGHT_RAIN, RAIN, HEAVY_RAIN, FREEZING_RAIN, LIGHT_SNOW, SNOW, HEAVY_SNOW, SLEET, HAIL, THUNDERSTORM, THUNDERSTORM_RAIN, THUNDERSTORM_HEAVY, DUST, SAND, SMOKE, HAZE, TORNADO, HURRICANE, UNKNOWN

Each plugin maps its native condition codes onto this enum. When persisting to SQLite the provider's original values are retained alongside the normalized one in condition_original and condition_code_original (see Database Design).

Responses and errors

The library is designed for partial-failure tolerance. When some providers fail — network errors, rate limits, auth issues — the response still completes with results from the providers that succeeded.

Each entry in response.results is either a ProviderSuccess or a ProviderError, discriminated by the status field. response.summary gives counts at a glance:

response.summary
# ForecastResponseSummary(total=3, succeeded=2, failed=1)

ProviderSuccess carries forecasts, a list of SourceForecast — one per model or upstream source the provider returned (multi-model providers such as Open-Meteo and Stormglass return several). Each SourceForecast holds the minutely, hourly, daily, and alerts collections described above, plus the optional IANA timezone used for civil-time normalization.

ProviderError carries a typed error.code, a human-readable error.message, the error.http_status when one is available, and error.latency_ms for how long the request ran before failing.

ErrorCode Raised when Retried
AUTH_FAILED Credentials were rejected (HTTP 401 or 403)
RATE_LIMITED The provider returned HTTP 429
QUOTA_EXCEEDED The registration's max_requests_per_day budget for the UTC day is spent, so no request is made
TIMEOUT The request exceeded the effective per-provider timeout
NETWORK Connection failure, or the provider returned a 5xx status
PARSE The response arrived but could not be normalized
NOT_AVAILABLE The provider is unconfigured, disabled, failed to initialize, supports none of the requested granularities, or returned HTTP 404
NO_DATA The response arrived successfully (HTTP 200) but carried no usable forecast content — every source had empty hourly, daily, minutely, and alert collections
UNKNOWN Anything else, including unmapped HTTP statuses

Only NETWORK, TIMEOUT, and RATE_LIMITED are treated as transient; see Retry policy.

Matching on the result type keeps both branches typed:

from omni_weather_forecast_apis import ProviderError, ProviderSuccess

for result in response.results:
    match result:
        case ProviderSuccess(provider=pid, forecasts=forecasts):
            for fc in forecasts:
                for pt in fc.hourly:
                    print(f"{pid} {pt.timestamp}: {pt.temperature}°C, {pt.condition}")
        case ProviderError(provider=pid, error=err):
            print(f"{pid} failed: {err.code}{err.message}")

The CLI mirrors this in its exit codes: partial provider failures exit 1 while still writing the successful results to SQLite.