halkyon/docs/adr/0036-train-workout-weather.md
Tizian 「ティツィアーン」 3f7b409e80
feat(train): workouts' weather from Open-Meteo, through the instance (ADR 0036)
A workout with a track gets the weather over it as its page opens, once
while it has none: the hours from the one it started in to the one it
ended in, kept in its sealed payload (temperature, feels-like, humidity,
precipitation, the WMO code, the wind, its gusts and direction) and shown
under its totals with the link to Open-Meteo its licence asks for.

Train's server asks Open-Meteo in the person's place (the `weather`
endpoint), so Open-Meteo never sees their address: the place rounded to
two decimals (also on the phone), a span of at most 48 hours, the
forecast API for recent spans and the archive for older ones, for people
signed in, at most 30 requests a person a minute, nothing kept or
logged. On by default; `WEATHER=off` switches it off, and
`OPEN_METEO_API_KEY` moves it to the paid hosts for an instance used
commercially.

Health Connect's fingerprint leaves the weather out, so keeping it
writes nothing again. No table and no migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 14:01:34 +01:00

4.6 KiB
Raw Blame History

ADR 0036: Train's workouts' weather

  • Status: decided 2 Nov 2026, by the developer (D1–D3, each the agent's recommendation), the rest proposed by the agent and flagged where it chose.
  • Builds on: ADR 0030 (Train: D7 sealed rows, D11 and D44 the instance planning routes, D50 the instance searching for places, D24 a workout's page).

Context

A run's or a ride's figures read differently in 30 °C or against a strong wind, and Train keeps no weather. Open-Meteo answers the weather of any hour at any place, past (its archive, ERA5 from 1940, five days behind) and recent (its forecast API, three months back), without an account or a key, as JSON over HTTPS. Its free API is for non-commercial use alone (no ads, no subscriptions: Halkyon's instance qualifies), 600 calls a minute, 10,000 a day from one address, its data under CC BY 4.0 with a link "Weather data by Open-Meteo.com" next to it. Its paid plans answer the same requests at customer-api.open-meteo.com with an apikey.

Asking for a workout's weather tells Open-Meteo where and when the person trained.

Decision

  1. The instance asks, not the phone (D1, the recommendation taken). Train's server gets a weather endpoint, as routing and search (lib/src/weather/): for people signed in alone, it asks Open-Meteo for the hours of a place and a span, so Open-Meteo sees the server's address and never the person's; nothing is kept or logged; at most 30 requests a person a minute (a page opened asks once). Refusals are WeatherRefused (off, invalid, tooOften, unreachable), as SearchRefused. On by default; the operator switches it off with WEATHER=off (offered false, every request refused), and an instance used commercially sets OPEN_METEO_API_KEY, which moves the requests to the paid hosts with the key. Alternatives: the phone asking Open-Meteo directly (simpler, but Open-Meteo would see each person's address beside their place and time), or both with the person choosing, as the Loop's firmware (more to build for a choice few would make).

  2. The place is rounded before it leaves the phone (agent's choice): to two decimals, about a kilometre, which Open-Meteo's grids (1 to 11 km, ERA5's 25 km) don't resolve anyway; the server rounds again, so a client sending more is no help. The span is the workout's start and end, at most 48 hours, from 1940 to a day ahead. The server asks the archive where the span ended more than seven days ago, else the forecast API with the dates (it reaches three months back), and answers the hours from the one before the start to the one after the end.

  3. Outdoor workouts' weather, kept with them (D2, the recommendation taken): a workout with a track (anything recorded outdoors, a session brought in with its track) keeps the hours over it (WorkoutWeather in its sealed payload: time, temperature, feels-like, humidity, precipitation, the WMO weather code, the wind's speed, direction and gusts; the payload's schema stays 1, as older apps leave out what they don't know). Its page shows what it was under the totals: the conditions' icon and words, the temperature (the range where it changed by a degree or more) and how it felt, the wind with where it came from and its gusts, the rain or snow that fell, and the link to Open-Meteo the licence asks for. No forecast on Home or on the Record sheet: that would ask for the phone's place as it opens, not once a workout. Indoor workouts, strength sessions and sessions without a track get none.

  4. A workout's weather is asked for as its page opens (D3, the recommendation taken): once each time a page of a workout with a track and no weather opens, so a workout just recorded gets it as its page opens after saving, and older ones as they are looked at; never in bulk. A refusal or a failure leaves it without, asked again the next time the page opens. One recorded within the last hour waits on nothing: the forecast API holds the current hour. The weather doesn't change what goes to Health Connect, FIT, TCX or GPX (their formats hold none Train could fill).

  5. Units (agent's choice): kept in °C, mm, m/s and degrees (where the wind comes from), shown in °C, mm and km/h, the direction as one of eight (N, NE, …).

Consequences

  • No table and no migration: the weather lives in the workout's sealed payload.
  • The operator's README names WEATHER and OPEN_METEO_API_KEY; production's compose file passes them through, empty by default.
  • Workouts brought in from Health Connect without a track, and the Loop's, get no weather: they say nothing of where they were.