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>
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.
|