From 270979949fbdbf5ea90a9e8396870c51ec830779 Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Mon, 27 Jul 2026 16:50:40 +0000 Subject: [PATCH] Add Environment Canada as a third weather provider app/weather/ec.py -- api.weather.gc.ca's MSC GeoMet OGC API (citypageweather-realtime collection), the modern replacement for the old dd.weatheroffice.gc.ca XML feed (that host no longer resolves). Unlike Open-Meteo/NWS's simple lat/lon REST, this collection is only queryable by bounding box, so _nearest_site widens the box progressively and picks the closest of the ~844 sites by straight-line distance -- capped at 300km, calibrated against a real bug caught in development where an unconditional "nearest site, however far" matched a Miami, FL query to a site in Ontario 1824km away once the box widened to cover the whole country. EC's own numeric icon codes get a small confirmed-against-live-data mapping table plus the same keyword-on-condition-text fallback NWS already uses for anything unmapped. Daily periods are named ("Today"/ "Tonight"/"Tuesday"/...) rather than dated, so dates are inferred by walking them in issued order. Verified end-to-end against the real live API (Toronto, rural Saskatchewan, a US border city, and a rejected far-away match) and through the browser (daily mode, composited panel preview). Test fixtures mirror the actual response shapes captured live. docs/ widgets.md and CLAUDE.md's TODO updated -- EC is no longer a documented gap. --- CLAUDE.md | 1 - docs/widgets.md | 30 ++- .../app/templates/_widget_dialog_weather.html | 2 +- server/app/weather/__init__.py | 21 +- server/app/weather/ec.py | 227 ++++++++++++++++++ server/tests/test_weather_providers.py | 172 ++++++++++++- 6 files changed, 425 insertions(+), 28 deletions(-) create mode 100644 server/app/weather/ec.py diff --git a/CLAUDE.md b/CLAUDE.md index 722e994..23f3239 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,6 @@ photos/calendar/whiteboard/weather widget system to the device. CURRENT TODO -add more actions for buttons (i.e. change widget/layout) -Fix spurious button assignment stuff (probably but buttons on widget config with sane defaults) --add Environment Canada as a weather widget provider (app/weather/ -- station/grid-lookup API, more involved than Open-Meteo/NWS) -widget border option -battery life widget -sharing layouts with linked users diff --git a/docs/widgets.md b/docs/widgets.md index 35d8af6..d7222f8 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -227,21 +227,25 @@ which share the same `{widget_id}`-parameterized path shape). **Providers** (`app/weather/`, a dispatch registry over pluggable implementations mirroring `app/widgets/` itself): `WeatherWidgetConfig. provider` selects which of `app/weather.PROVIDERS` actually fetches -- -`"open_meteo"` (worldwide, no API key) or `"nws"` (api.weather.gov, US +`"open_meteo"` (worldwide, no API key), `"nws"` (api.weather.gov, US only, no API key, approximates "current" with the first hourly forecast -period rather than a real station observation). Every provider function -returns already-normalized `{"category": ...}` entries (one of `clear`/ -`partly_cloudy`/`cloudy`/`fog`/`rain`/`snow`/`thunderstorm`) so -`app/weather_render.py`'s drawing code never needs to know which -provider supplied an entry. `geocode_city` (name -> lat/lon) always goes -through Open-Meteo's free geocoder regardless of which provider is -chosen to fetch with the result. +period rather than a real station observation), or `"ec"` (Environment +Canada, api.weather.gc.ca's MSC GeoMet OGC API, Canada only, no API key). +Every provider function returns already-normalized `{"category": ...}` +entries (one of `clear`/`partly_cloudy`/`cloudy`/`fog`/`rain`/`snow`/ +`thunderstorm`) so `app/weather_render.py`'s drawing code never needs to +know which provider supplied an entry. `geocode_city` (name -> lat/lon) +always goes through Open-Meteo's free geocoder regardless of which +provider is chosen to fetch with the result. -**Environment Canada is a deliberate gap, not an oversight** -- its free -API (the MSC GeoMet OGC service) is built around station/grid lookups, -not simple lat/lon REST like the two providers above, and would have -meaningfully expanded the initial pass. Next provider to add if this -gets revisited. +EC's `citypageweather-realtime` collection is only queryable by bounding +box (OGC API - Features), not a direct by-coordinate endpoint -- unlike +Open-Meteo/NWS's simple lat/lon REST, `app/weather/ec.py`'s +`_nearest_site` widens the box progressively and picks the closest site +by straight-line distance, rejecting anything beyond 300 km (calibrated +against a real bug caught in development: an unconditional "nearest +site, however far" matched a Miami, FL query to a site in Ontario, +1824 km away, once the box widened enough to cover the whole country). `app/weather_render.py` holds every weather-related drawing primitive: `draw_cloud`/`draw_weather_icon`/`draw_weather_row` (extracted out of diff --git a/server/app/templates/_widget_dialog_weather.html b/server/app/templates/_widget_dialog_weather.html index 75f7364..3efe73d 100644 --- a/server/app/templates/_widget_dialog_weather.html +++ b/server/app/templates/_widget_dialog_weather.html @@ -18,7 +18,7 @@ {% endfor %} -

National Weather Service only covers US locations.

+

National Weather Service only covers US locations; Environment Canada only covers Canadian locations.