fix(sleep): reconstruct an ordered WHOOP hypnogram timeline

WHOOP's v2 API returns only per-stage sleep duration totals — no onset
timestamps, no hypnogram endpoint. The mapper stamped all five stage
totals on the one sleep-END instant, so the hypnogram reconstructed
every stage as a span touching the night's right edge: bars stacked
right-aligned with no clock times whenever WHOOP was the sole source.

Lay the asleep stages back-to-back from sleep onset in a fixed
physiological order (AWAKE lead, then CORE → DEEP → REM), emitting one
timed row per segment with measuredAt at that segment's end and a
distinct indexed externalId. IN_BED stays a single envelope row over the
whole window. The existing reader and renderer are untouched — they now
see distinct per-segment instants and place each block at its own time.

The order is synthesised, not measured, so every reconstructed row is
flagged and the night DTO carries a per-session `reconstructed` boolean
(GET /api/sleep/night, regenerated OpenAPI). The client renders the
hypnogram but labels it an approximate layout and never recomputes;
real-series sources (Apple Health, Withings, Fitbit) stay false.
This commit is contained in:
Marc-André Bombeck committed 2026-06-14 23:16:09 +02:00
1 parent 94fd837d48
commit bc12633569
8 files changed
+246 -30

No files matched your search

+12
View File
@@ -8982,6 +8982,11 @@ components:
type: integer
minimum: 0
maximum: 9007199254740991
reconstructed:
type: boolean
description: True when the source has no per-stage onset timestamps and the server synthesised a contiguous timeline in
a fixed physiological order (WHOOP). The client renders the hypnogram but labels it an approximate
layout and never recomputes. False for real-series sources (Apple Health / Withings / Fitbit).
stages:
type: object
propertyNames:
@@ -9047,6 +9052,7 @@ components:
- inBedMinutes
- awakeMinutes
- awakenings
- reconstructed
- stages
- segments
additionalProperties: false
@@ -9102,6 +9108,11 @@ components:
type: integer
minimum: 0
maximum: 9007199254740991
reconstructed:
type: boolean
description: True when the source has no per-stage onset timestamps and the server synthesised a contiguous timeline in
a fixed physiological order (WHOOP). The client renders the hypnogram but labels it an approximate
layout and never recomputes. False for real-series sources (Apple Health / Withings / Fitbit).
stages:
type: object
propertyNames:
@@ -9167,6 +9178,7 @@ components:
- inBedMinutes
- awakeMinutes
- awakenings
- reconstructed
- stages
- segments
additionalProperties: false