fix(workouts): read a day with activity data and no workout as rest

The strain run writes no impulse row on a day without a scorable workout
or active energy, so a rest day for someone without daily active energy
was never rated: fitness and fatigue held for months instead of
decaying, and the figures named the last stored day while the coverage
count ended there too.

A day without a stored impulse is now classified from the underlying
data: a workout on the day leaves it unrated, steps, heart rate or
active energy without a workout make it rest, and no data at all leaves
it unrated. The series runs to the day the strain run scores, so a
stretch without data after the last rated day shows in the coverage
count, and the card names the last rated day when it lies further back
than the day before.
This commit is contained in:
Marc-André Bombeck committed 2026-10-22 00:25:29 +02:00
1 parent 0918451e02
commit 29d6029999
15 files changed
+286 -66

No files matched your search

+11 -8
View File
@@ -49472,8 +49472,8 @@ components:
const: ok
asOf:
type: string
description: "The last UTC day with a stored impulse, at most yesterday: a nightly run that has not happened yet never
reads as a rest day."
description: The last rated UTC day, at most yesterday; the figures are those of this day. The series runs on to
yesterday, so a later stretch without data shows as unrated days.
ctl:
type: number
description: "Fitness: 42-day exponentially weighted TRIMP."
@@ -49495,7 +49495,8 @@ components:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
description: Days of the trailing `recentDays` (42, or fewer when covered less) with a stored impulse.
description: "Rated days of the trailing `recentDays` (42 up to yesterday, or fewer when covered less): a stored
impulse, or activity data without a workout."
recentDays:
type: integer
minimum: -9007199254740991
@@ -49518,8 +49519,9 @@ components:
type: number
rated:
type: boolean
description: "False for a day without a stored impulse (never scored): the averages held through it rather than counting
it as rest."
description: "False for a day that could not be rated: no stored impulse and no activity data (steps, heart rate, active
energy), or a workout that was never scored. The averages held through it rather than counting it
as rest. A day with activity data and no workout is rest (impulse 0)."
required:
- day
- ctl
@@ -49565,9 +49567,10 @@ components:
- daysCovered
- trainingDays
additionalProperties: false
description: "v1.44 — the fitness-fatigue model over the stored daily heart-rate training impulse (Banister TRIMP; a
scored day without training is 0, a day without a stored impulse is not rated and the averages hold through it).
Needs 28 days and three training days. TRIMP units: comparable with the person's own past only."
description: "v1.44 — the fitness-fatigue model over the stored daily heart-rate training impulse (Banister TRIMP; a day
with activity data but no training is 0, a day with no data or an unscored workout is not rated and the averages
hold through it). Needs 28 days and three training days. TRIMP units: comparable with the person's own past
only."
GetWorkoutResponse:
type: object
properties: