feat(train): reminders to move and workouts suggested from the steps (ADR 0037)

Settings' Moving more, on Android: a reminder to get up after an idle
time, and a suggested walk or run, each off until turned on (then every
30 minutes, weekdays 9:00 to 18:00, not 12:00 to 13:00, each value the
person's), turning on asking for the activity and notification
permissions.

A WorkManager job every 15 minutes reads the phone's step counter and
keeps two hours of readings. It reminds where fewer than 50 steps came
over the idle time, all of it within the hours and outside the break,
on the person's days, never while Train records or the phone is in a
car (Android's car mode, or a car's Bluetooth audio or hands-free). It
suggests a walk or a run where the last span averaged a walking or
running cadence, once an hour; tapped, the Record sheet for it opens.
The decisions are pure Kotlin (Movement.kt), tested on the JVM; the
notifications' words are the app's, written into the job's
configuration as the person's app opens.

Polar's SDK sets neither the band's inactivity alert nor tells of one
without an all-day connection, hence the phone's steps (ADR 0037).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-02 14:32:02 +01:00
commit e4585b478d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,75 @@
# ADR 0037: Train's reminders to move, workouts suggested, and the Loop's own detection
- **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: D19 no background location, D21 the recorder, D37 to
D39 the Loop's days and the sessions it detects).
## Context
The developer asked for two things around the Polar Loop: to be reminded to get up when
idle for some 30 minutes (not while driving, not at lunch), and for Train to start or at
least suggest a workout from movement, such as a walk when one is on a stroll.
Polar's SDK (8.3.0) was read for both. The band's own inactivity alert is an on/off flag
in its general settings, which the SDK's public API doesn't set (`PolarUserDeviceSettings`
lacks it; only a low-level write of the settings file would); the band tells a phone of
an alert as a device-to-host notification (`INACTIVITY_ALERT`), over a live connection
alone, which would mean a foreground service holding the band all day. The SDK does set
the band's automatic training detection (`setAutomaticTrainingDetectionSettings`: on or
off, a sensitivity, a minimum duration in seconds) and reads it back
(`getUserDeviceSettings`); the sensitivity's range is documented nowhere (Polar Flow
offers low, medium and high).
D19 rules out background location, and Train uses no Play services, so Android's
activity recognition (a Play service) is out too.
## Decision
1. **The reminder counts the phone's steps** (D1, the recommendation taken). A
WorkManager job every 15 minutes (its shortest period; `MovementWorker.kt`), while
reminders or suggestions are on, reads Android's step counter (`TYPE_STEP_COUNTER`,
kept by the phone's own low-power hardware, under the activity permission the
recorder asks for already) and keeps the last two hours of readings in
`movement_state.json` (the app's files, never synced, never backed up, D53). It
reminds where fewer than 50 steps were counted over the idle time the person chose
(30 minutes by default), all of it within their hours and outside their break, on
their days, at most once each idle time, never while Train records, and never while
the phone is in a car: Android's car mode (Android Auto) or a car's Bluetooth audio or
hands-free connected (`CarCheck`; nothing else tells a car without location). A
reminder can come up to 15 minutes late. *Alternatives:* the band's own alert (a
foreground service connected all day, its setting written below the SDK's API, no car
detection), or both.
2. **A workout is suggested from the steps, and the band detects its own** (D2, the
recommendation taken). The same job: where the last reading's span (10 to 25
minutes) averaged a walking cadence (80 steps a minute or more) or a running one (140
or more) while nothing records, a notification asks "Walking?" or "Running?", at most
once an hour; tapped, it opens the Record sheet for that activity (the minutes before
aren't recorded: the band's detection catches those). The Loop's own automatic
training detection is switched and tuned on the band's screen (on or off, and the
shortest session it keeps: 10, 15, 20 or 30 minutes), so a session nobody recorded
still comes in afterwards as D39's. Its sensitivity stays as the band has it (agent's
choice): the SDK documents no range, and a guessed value could set the band wrong.
*Alternatives:* the phone's suggestion alone, or the band's detection alone.
3. **Off until turned on; then every 30 minutes, weekdays 9:00 to 18:00, not 12:00 to
13:00** (D3, the recommendation taken). Settings' *Moving more* screen holds both
switches (the suggestion its own, also off) and every value: the idle time (15 to 120
minutes), the hours, the break, the days. Turning either on asks for the activity
and notification permissions; refused, it stays off. The settings are the person's,
on this device alone (`movement.json` in their folder, never synced), and hold for
the person whose app is open: the job follows whoever was last open.
4. **The job's words come from the app** (agent's choice): the app writes the
notifications' titles and texts in the person's language into the job's
configuration whenever it configures it, since the job runs while the app is closed
and Android's resources hold none of Train's texts.
## Consequences
- No server change and no migration.
- A phone without a step counter (rare; most since Android 4.4) offers neither: the
screen says so.
- The logic deciding a reminder or a suggestion is pure Kotlin (`Movement.kt`), tested
on the JVM as the recorder's.