mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
feat(schema): MeasurementType enum additions for Withings full coverage
Seven new enum values for v1.4.25 W5d. Source: Withings developer
guide — Data API → All Available Health Data — cross-referenced
against `.planning/research/withings-api-coverage.md` §3.
- FAT_FREE_MASS (meastype 5) — Body+, Body Cardio, Body Comp, Body Scan
- FAT_MASS (meastype 8) — same devices; mass form of BODY_FAT
- MUSCLE_MASS (meastype 76) — Body+ family
- SKIN_TEMPERATURE (meastype 73) — ScanWatch dermal reading. DISTINCT
from BODY_TEMPERATURE: surface temps run ~32 °C, core ~37 °C, so
they cannot share a bucket without corrupting analytics.
- PULSE_WAVE_VELOCITY (meastype 91) — Body Cardio / Body Scan;
indicator of arterial stiffness
- VASCULAR_AGE (meastype 155) — Body Scan; composite of PWV + age
- VISCERAL_FAT (meastype 170) — Body Comp / Body Scan; Withings'
1–12 rating scale (not a percent)
Migration 0049 is additive only — every value is unconditional, no
backfill needed. The Withings client picks these values up once the
meastype → enum mapping lands (separate commit in this phase).
Unit map + plausibility ranges updated in `src/lib/validations/
measurement.ts` so the create-measurement validator accepts the new
types end-to-end.
This commit is contained in:
1 parent
03f58c75b8
commit
c50931b4e7
3 files changed
+74
No files matched your search
@@ -0,0 +1,25 @@
|
||||
-- v1.4.25 W5d — Withings full coverage. Adds seven new
|
||||
-- MeasurementType enum values so the Withings ingest path can land
|
||||
-- body composition (Body+, Body Cardio, Body Comp, Body Scan),
|
||||
-- skin temperature (ScanWatch dermal reading — DISTINCT from core
|
||||
-- body temperature; sharing the bucket would corrupt analytics
|
||||
-- because surface temps run ~32 °C and core ~37 °C), pulse-wave
|
||||
-- velocity + vascular age (Body Cardio / Body Scan exclusives), and
|
||||
-- visceral fat (Body Comp / Body Scan rating).
|
||||
--
|
||||
-- See `.planning/research/withings-api-coverage.md` §3 for the gap
|
||||
-- scoring + the rationale of which meastypes ship in v1.4.25 vs
|
||||
-- defer to v1.5.
|
||||
--
|
||||
-- Additive only, forward-only — every new enum value is unconditional
|
||||
-- and no existing row needs to be backfilled. The Withings client
|
||||
-- starts emitting these values once `src/lib/withings/client.ts`
|
||||
-- gains the meastype → enum mapping (separate commit).
|
||||
|
||||
ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'FAT_FREE_MASS';
|
||||
ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'FAT_MASS';
|
||||
ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'MUSCLE_MASS';
|
||||
ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'SKIN_TEMPERATURE';
|
||||
ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'PULSE_WAVE_VELOCITY';
|
||||
ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'VASCULAR_AGE';
|
||||
ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'VISCERAL_FAT';
|
||||
@@ -255,6 +255,14 @@ enum MeasurementType {
|
||||
WALKING_RUNNING_DISTANCE // metres — HealthKit `distanceWalkingRunning`; SI metres in DB, UI converts per locale.
|
||||
VO2_MAX // mL/(kg·min) — HealthKit `vo2Max`; one sample every few days at most.
|
||||
BODY_TEMPERATURE // celsius — HealthKit `bodyTemperature`; iOS DTO already encodes this enum value.
|
||||
// ── v1.4.25 W5d Withings full coverage ──
|
||||
FAT_FREE_MASS // kg — Withings meastype 5 (Body+, Body Cardio, Body Comp, Body Scan). Algorithmically = weight − fat mass.
|
||||
FAT_MASS // kg — Withings meastype 8. Mass form of BODY_FAT (kg, not %).
|
||||
MUSCLE_MASS // kg — Withings meastype 76. Body+ family.
|
||||
SKIN_TEMPERATURE // celsius — Withings meastype 73 (ScanWatch dermal reading). DISTINCT from BODY_TEMPERATURE — surface temps run ~32 °C, core ~37 °C; sharing a bucket would corrupt analytics.
|
||||
PULSE_WAVE_VELOCITY // m/s — Withings meastype 91 (Body Cardio / Body Scan). Indicator of arterial stiffness.
|
||||
VASCULAR_AGE // years — Withings meastype 155 (Body Scan). Composite of PWV + age.
|
||||
VISCERAL_FAT // rating — Withings meastype 170 (Body Comp / Body Scan). Withings' rating scale (1–12); not directly comparable to a percent.
|
||||
|
||||
@@map("measurement_type")
|
||||
}
|
||||
|
||||
@@ -20,6 +20,14 @@ export const measurementTypeEnum = z.enum([
|
||||
"WALKING_RUNNING_DISTANCE",
|
||||
"VO2_MAX",
|
||||
"BODY_TEMPERATURE",
|
||||
// ── v1.4.25 W5d Withings full coverage ──
|
||||
"FAT_FREE_MASS",
|
||||
"FAT_MASS",
|
||||
"MUSCLE_MASS",
|
||||
"SKIN_TEMPERATURE",
|
||||
"PULSE_WAVE_VELOCITY",
|
||||
"VASCULAR_AGE",
|
||||
"VISCERAL_FAT",
|
||||
]);
|
||||
|
||||
export const glucoseContextEnum = z.enum([
|
||||
@@ -68,6 +76,18 @@ const unitMap: Record<string, string> = {
|
||||
WALKING_RUNNING_DISTANCE: "m",
|
||||
VO2_MAX: "mL/(kg·min)",
|
||||
BODY_TEMPERATURE: "celsius",
|
||||
// ── v1.4.25 W5d Withings full coverage ──
|
||||
FAT_FREE_MASS: "kg",
|
||||
FAT_MASS: "kg",
|
||||
MUSCLE_MASS: "kg",
|
||||
// Distinct from BODY_TEMPERATURE — surface temps run ~32 °C; sharing
|
||||
// the bucket would corrupt analytics. Same canonical unit (°C).
|
||||
SKIN_TEMPERATURE: "celsius",
|
||||
PULSE_WAVE_VELOCITY: "m/s",
|
||||
VASCULAR_AGE: "years",
|
||||
// Withings reports visceral fat as a 1–12 rating, not a percent. The
|
||||
// string mirrors what Withings prints in Health Mate.
|
||||
VISCERAL_FAT: "rating",
|
||||
};
|
||||
|
||||
export function getUnitForType(type: string): string {
|
||||
@@ -114,6 +134,27 @@ const VALUE_RANGES: Record<string, { min: number; max: number }> = {
|
||||
VO2_MAX: { min: 5, max: 100 },
|
||||
// Body temperature °C — survivable lows ~28; severe hyperthermia ~45.
|
||||
BODY_TEMPERATURE: { min: 28, max: 45 },
|
||||
// ── v1.4.25 W5d Withings full coverage ──
|
||||
// Fat-free mass kg — adult plausibility, ~30 kg (small adult) to 120 kg
|
||||
// (large lean athlete). Same bounds as weight minus a fat-mass floor.
|
||||
FAT_FREE_MASS: { min: 10, max: 250 },
|
||||
// Fat mass kg — pairs with FAT_FREE_MASS so totals reconcile to weight.
|
||||
FAT_MASS: { min: 0, max: 250 },
|
||||
// Muscle mass kg — sub-component of fat-free mass; widest sensible bound.
|
||||
MUSCLE_MASS: { min: 5, max: 200 },
|
||||
// Skin temperature °C — surface temps run cooler than core. ScanWatch's
|
||||
// dermal sensor reports a relative offset that lands in the 25–40 °C
|
||||
// band; treat anything outside as a sensor glitch.
|
||||
SKIN_TEMPERATURE: { min: 20, max: 45 },
|
||||
// Pulse-wave velocity m/s — clinical ranges sit ~4–15 m/s. Higher
|
||||
// numbers indicate stiffer arteries (cardiovascular risk).
|
||||
PULSE_WAVE_VELOCITY: { min: 1, max: 30 },
|
||||
// Vascular age in years — Withings derives this from PWV + chronological
|
||||
// age; the value is a "biological age" not a chronological one. Hard cap
|
||||
// at 130 because that's the human longevity record.
|
||||
VASCULAR_AGE: { min: 10, max: 130 },
|
||||
// Visceral fat rating 1–12 (Withings' own scale; not a percent).
|
||||
VISCERAL_FAT: { min: 0, max: 30 },
|
||||
};
|
||||
|
||||
export function validateMeasurementRange(
|
||||
|
||||
Reference in new issue
Block a user