73 lines
4.6 KiB
Markdown
73 lines
4.6 KiB
Markdown
|
|
# 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.
|