feat(measurements): add WHOOP source, native score types, and source-priority ladders

Add the WHOOP integration data layer (migration 0111): seven new
MeasurementType values (HRV_RMSSD, DAY_STRAIN, WORKOUT_STRAIN,
SLEEP_PERFORMANCE, SLEEP_EFFICIENCY, SLEEP_CONSISTENCY, SLEEP_NEED,
ENERGY_EXPENDITURE_KJ), a WHOOP MeasurementSource, the WhoopConnection
and WhoopOAuthState models, and the per-user encrypted BYO-key columns
on User. Day-strain takes DAY_STRAIN (not the COMPUTED STRAIN_SCORE) and
RMSSD takes HRV_RMSSD (not the SDNN HEART_RATE_VARIABILITY) so a native
value never shares a bucket with a derived proxy.

Carry the new types through the completeness walls (units, value ranges,
chart tokens, PR direction, categories, list-meta colours/icons/labels,
HealthKit-mapping exclusions) and feed the cross-source picker: recovery
ladder (WHOOP native above the COMPUTED proxy), WHOOP inserted into the
overlapping vital ladders and the workout ladder, plus skinTemperature
and respiratoryRate metric keys.
This commit is contained in:
Marc-André Bombeck committed 2026-06-04 00:49:31 +02:00
1 parent 747a62516a
commit 6db3bef413
21 files changed
+649 -31

No files matched your search

+29 -5
View File
@@ -122,6 +122,17 @@ export const SOURCE_PRIORITY_METRIC_KEYS = [
"hrv",
"restingHeartRate",
"vo2Max",
// v1.11.0 — WHOOP-overlapping metric classes that had no key before. The
// E-slice cross-source picker resolves WHOOP vs Apple vs Withings for
// these the same way it does the existing keys. (`sleep`, `hrv`, `spo2`,
// `restingHeartRate`, `weight` already exist.)
"skinTemperature",
"respiratoryRate",
// v1.11.0 — native-vs-derived recovery. WHOOP ships a device-native
// Recovery; HealthLog computes its own COMPUTED proxy. Both persist as
// `RECOVERY_SCORE` rows distinguished by source — this ladder lets the
// same picker resolve native-above-proxy without a second engine.
"recovery",
] as const;
export type SourcePriorityMetricKey =
@@ -207,16 +218,29 @@ export const DEFAULT_SOURCE_PRIORITY: Required<MetricPriority> = {
activeEnergy: ["APPLE_HEALTH", "WITHINGS", "MANUAL"],
walkingRunningDistance: ["APPLE_HEALTH", "WITHINGS", "MANUAL"],
flightsClimbed: ["APPLE_HEALTH", "WITHINGS", "MANUAL"],
sleep: ["APPLE_HEALTH", "WITHINGS"],
hrv: ["APPLE_HEALTH", "WITHINGS"],
restingHeartRate: ["APPLE_HEALTH", "WITHINGS"],
weight: ["WITHINGS", "APPLE_HEALTH", "MANUAL"],
// v1.11.0 — WHOOP leads the recovery-input ladders (sleep / HRV / RHR):
// a worn-all-night strap has higher-resolution overnight sampling than
// the iPhone-relayed HealthKit summary or the Withings nightly summary.
sleep: ["WHOOP", "APPLE_HEALTH", "WITHINGS"],
hrv: ["WHOOP", "APPLE_HEALTH", "WITHINGS"],
restingHeartRate: ["WHOOP", "APPLE_HEALTH", "WITHINGS"],
// A real scale beats a strap's body-measurement estimate for weight.
weight: ["WITHINGS", "APPLE_HEALTH", "MANUAL", "WHOOP"],
bloodPressure: ["WITHINGS", "APPLE_HEALTH", "MANUAL"],
pulse: ["WITHINGS", "APPLE_HEALTH", "MANUAL"],
bodyFat: ["WITHINGS", "APPLE_HEALTH", "MANUAL"],
bodyTemperature: ["WITHINGS", "APPLE_HEALTH", "MANUAL"],
spo2: ["WITHINGS", "APPLE_HEALTH", "MANUAL"],
// Withings ScanWatch pulse-ox is the primary SpO2 sensor; WHOOP second.
spo2: ["WITHINGS", "WHOOP", "APPLE_HEALTH", "MANUAL"],
vo2Max: ["WITHINGS", "APPLE_HEALTH", "MANUAL"],
// v1.11.0 — new WHOOP-overlapping keys. ScanWatch dermal reading is the
// primary skin-temperature sensor; WHOOP's strap is second.
skinTemperature: ["WITHINGS", "WHOOP", "APPLE_HEALTH"],
respiratoryRate: ["WHOOP", "APPLE_HEALTH", "WITHINGS"],
// v1.11.0 — native-vs-derived recovery. WHOOP's device-native Recovery
// outranks HealthLog's COMPUTED proxy when both exist; the proxy is the
// fallback for users without a strap.
recovery: ["WHOOP", "COMPUTED"],
};
/**