From b1a3d59918610037641333a5eb7c3a852a5f10de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 14 May 2026 13:24:25 +0200 Subject: [PATCH] feat(withings): ingest meastype 123 (VO2 max) into VO2_MAX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ScanWatch family reports VO2 max under meastype 123 in mL/(kg·min), which matches HealthLog's canonical DB unit verbatim (no conversion needed beyond the standard value × 10^unit exponent decode). Targets the VO2_MAX enum slot added in v1.4.23 for the Apple Health passthrough. --- src/lib/withings/__tests__/client.test.ts | 14 ++++++++++++++ src/lib/withings/client.ts | 3 +++ 2 files changed, 17 insertions(+) diff --git a/src/lib/withings/__tests__/client.test.ts b/src/lib/withings/__tests__/client.test.ts index e61b8ca49..4335b9e9a 100644 --- a/src/lib/withings/__tests__/client.test.ts +++ b/src/lib/withings/__tests__/client.test.ts @@ -64,6 +64,20 @@ describe("MEASURE_TYPE_MAP", () => { it("maps Withings meastype 35 (legacy SpO2) → OXYGEN_SATURATION", () => { expect(MEASURE_TYPE_MAP[35]).toEqual({ type: "OXYGEN_SATURATION" }); }); + + it("maps Withings meastype 123 (VO2 max) → VO2_MAX", () => { + expect(MEASURE_TYPE_MAP[123]).toEqual({ type: "VO2_MAX" }); + }); +}); + +describe("fetchMeasurements — VO2 max (meastype 123)", () => { + it("decodes a ScanWatch VO2 max reading into VO2_MAX mL/(kg·min)", async () => { + // 42.5 mL/(kg·min) as Withings exponent encoding: value=425, unit=-1. + installFetchMock(fakeGetmeasPayload([{ type: 123, value: 425, unit: -1 }])); + const out = await fetchMeasurements("token"); + expect(out).toHaveLength(1); + expect(out[0]).toMatchObject({ type: "VO2_MAX", value: 42.5 }); + }); }); describe("fetchMeasurements — SpO2 alt code (meastype 35)", () => { diff --git a/src/lib/withings/client.ts b/src/lib/withings/client.ts index 7c1b67ce6..dc9ab3baf 100644 --- a/src/lib/withings/client.ts +++ b/src/lib/withings/client.ts @@ -142,6 +142,9 @@ export const MEASURE_TYPE_MAP: Record // SpO2 under meastype 35 instead of 54. Both target OXYGEN_SATURATION // in percent; the exponent decode handles 0.97 vs 97 transparently. 35: { type: "OXYGEN_SATURATION" }, + // v1.4.25 — VO2 max from the ScanWatch family. Withings reports + // mL/(kg·min) directly, matching the canonical DB unit. + 123: { type: "VO2_MAX" }, }; export interface WithingsMeasure {