mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(insights): unify sleep reconstruction and make WHOOP recovery canonical
Route the derived Sleep Score through the canonical night reconstruction instead of its own UTC-day grouping, so a night logged by more than one source is deduplicated to a single writer rather than summed. The score and the readiness that folds it now read true per-night totals; multi-source nights drop to their real figure. Emitted minutes and the night stage map round to integers. Resolve recovery to one canonical series — WHOOP-native when present, the computed proxy as fallback — in the wellness reader, the doctor report, and the coach snapshot, and stop presenting the proxy and the native score as two numbers. A WHOOP-connected account's recovery now reads the native value on the tile, the chart, and the printed report alike.
This commit is contained in:
commit
e27bd8b9bf
16 files changed
+529
-106
No files matched your search
@@ -64,7 +64,14 @@ function serializeSession(s: SleepSession) {
|
||||
inBedMinutes: s.inBedMinutes === null ? null : Math.round(s.inBedMinutes),
|
||||
awakeMinutes: s.awakeMinutes === null ? null : Math.round(s.awakeMinutes),
|
||||
awakenings: s.awakenings,
|
||||
stages: s.stages,
|
||||
// iOS #18 — round the per-stage map too; the underlying totals sum
|
||||
// second-precision segments and otherwise serialise as e.g. 88.4999.
|
||||
stages: Object.fromEntries(
|
||||
Object.entries(s.stages).map(([stage, minutes]) => [
|
||||
stage,
|
||||
Math.round(minutes),
|
||||
]),
|
||||
),
|
||||
segments: s.segments.map((seg) => ({
|
||||
stage: seg.stage,
|
||||
start: seg.start.toISOString(),
|
||||
|
||||
@@ -319,6 +319,15 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
});
|
||||
} else {
|
||||
// Every row carries its `source`. RECOVERY_SCORE is written by BOTH the
|
||||
// WHOOP-native sync (`source = WHOOP`) and the COMPUTED proxy job
|
||||
// (`source = COMPUTED`) for the same day; the client must resolve to ONE
|
||||
// canonical recovery series — WHOOP wins when present, COMPUTED is the
|
||||
// fallback — and never chart the proxy and the native value as two
|
||||
// competing series. The feed faithfully mirrors both rows (a delta-sync
|
||||
// mirror cannot drop a row mid-keyset without a tombstone); the
|
||||
// canonical pick is the same WHOOP-over-COMPUTED rule the server's
|
||||
// wellness tile + doctor PDF apply (`resolveCanonicalRecovery`).
|
||||
measurementUpserts.push({
|
||||
id: row.id,
|
||||
externalId: row.externalId,
|
||||
|
||||
Reference in new issue
Block a user