mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
Apple Health (and some WHOOP shapes) write BOTH an unspecified ASLEEP aggregate row AND the granular CORE/DEEP/REM breakdown for the same sleep period. The per-night reconstruction summed both, so a real ~8 h night surfaced as ~16 h across every sleep surface — the chart showed ~16-18 h points and the target-range 30-day average read ~16.4 h/day. Prefer the granular partition: when any CORE/DEEP/REM stage is present for a night/source, count only those; fall back to the bare ASLEEP (or a stage-less legacy/manual) row only when no granular stage exists. IN_BED and AWAKE stay excluded; dual-source nights still collapse to one canonical source via the user's sleep priority ladder. The same rule applies inside the per-source asleep tiebreak so a source writing both shapes is not over-weighted. Align the surfaces that bypassed the shared helper: - insights/targets: SLEEP_DURATION was averaged as raw per-stage rows (minutes) and fed into a target band expressed in hours, producing the bogus 16.4 h/day. Reconstruct per-night time-asleep, convert to hours, and classify + roll up consistency on the nightly hour totals. - coach snapshot: route the sleep block through reconstructSleepNights so the prompt narrates the same nightly numbers (was summing every stage row, including IN_BED + AWAKE). - sleep card chart: read /api/measurements/series?kind=sleep (night reconstruction, hours) instead of the generic measurement daily-average path, and label the axis in hours instead of minutes. Add regression fixtures: a night with both a bare ASLEEP row and the granular breakdown asserts the granular total (no double-count); a dual-source night with both shapes per source; and a multi-night 30-day average that lands at ~8 h, not ~16 h.