From 841593a5b2f297dac31a3b7fec0b3873a05a430c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 3 Sep 2026 05:47:01 +0200 Subject: [PATCH] fix: converge the two factors a glucose reading was converted by A value imported in mmol/L was multiplied by 18.016; one displayed in mmol/L was divided by 18.0182. The two sat in different files, both correct-looking, and a reading that went in as 5.3 did not come back out as 5.3. Latent while nobody could select mmol/L, live the moment they can. One exported constant now, on the guideline figure the display path already used, and the glucose insight page takes it from there rather than keeping a third copy. The test names the constant instead of repeating the digits, which is how the two came apart in the first place, and it asserts the round trip a reader on mmol/L actually experiences. --- src/app/insights/blood-glucose/page.tsx | 4 +--- src/lib/glucose.ts | 10 +++++++++- src/lib/measurements/__tests__/unit-aliases.test.ts | 12 ++++++++++-- src/lib/measurements/unit-aliases.ts | 6 ++---- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/app/insights/blood-glucose/page.tsx b/src/app/insights/blood-glucose/page.tsx index 9f8cb64ff..d8dcdddb2 100644 --- a/src/app/insights/blood-glucose/page.tsx +++ b/src/app/insights/blood-glucose/page.tsx @@ -7,7 +7,7 @@ import { GlucoseClinicalPanel } from "@/components/insights/glucose/glucose-clin import { useAuth } from "@/hooks/use-auth"; import { useModulePageGuard } from "@/hooks/use-module-page-guard"; import { useTranslations } from "@/lib/i18n/context"; -import { resolveGlucoseUnit } from "@/lib/glucose"; +import { MGDL_PER_MMOL, resolveGlucoseUnit } from "@/lib/glucose"; /** * v1.7.0 — `/insights/blood-glucose`. @@ -25,8 +25,6 @@ import { resolveGlucoseUnit } from "@/lib/glucose"; * exports. mg/dL-preference users are unaffected (scale = 1, integer * precision). */ -const MGDL_PER_MMOL = 18.0182; - export default function InsightsBlutzuckerPage() { const { user } = useAuth(); const { t } = useTranslations(); diff --git a/src/lib/glucose.ts b/src/lib/glucose.ts index 96cb33ab7..14d2dee5b 100644 --- a/src/lib/glucose.ts +++ b/src/lib/glucose.ts @@ -9,7 +9,15 @@ export type GlucoseUnit = "mg/dL" | "mmol/L"; -const MGDL_PER_MMOL = 18.0182; +/** + * mg/dL per mmol/L, per the DGIM / DDG S3 guideline. Exported because it is + * the one factor for this conversion in the tree: the inbound alias resolver + * normalises a mmol/L cell with it, the glucose insight page scales its chart + * by its reciprocal, and the helpers below round with it. Two nearly-equal + * copies of a health conversion is how the same reading comes out as two + * different numbers on two screens. + */ +export const MGDL_PER_MMOL = 18.0182; export function mgdlToMmol(mgdl: number): number { return Math.round((mgdl / MGDL_PER_MMOL) * 10) / 10; diff --git a/src/lib/measurements/__tests__/unit-aliases.test.ts b/src/lib/measurements/__tests__/unit-aliases.test.ts index a12f4d860..dcf49f9ac 100644 --- a/src/lib/measurements/__tests__/unit-aliases.test.ts +++ b/src/lib/measurements/__tests__/unit-aliases.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect } from "vitest"; import { resolveToCanonicalUnit } from "../unit-aliases"; +import { MGDL_PER_MMOL, mgdlToMmol } from "@/lib/glucose"; describe("resolveToCanonicalUnit", () => { it("passes the canonical unit through case-insensitively", () => { @@ -53,10 +54,17 @@ describe("resolveToCanonicalUnit", () => { }); }); - it("converts glucose mmol/L to canonical mg/dL", () => { + it("converts glucose mmol/L to canonical mg/dL on the app's one factor", () => { + // This used to assert 18.016 while every display path divided by + // 18.0182, so a reading imported as mmol/L and read back as mmol/L did + // not return the number it went in as. Both ends take the same factor + // now, and the assertion names it rather than repeating a literal — + // pinning the constant twice is how the two drifted apart. const out = resolveToCanonicalUnit("BLOOD_GLUCOSE", 5.3, "mmol/L"); expect(out?.unit).toBe("mg/dL"); - expect(out?.value).toBeCloseTo(5.3 * 18.016, 3); + expect(out?.value).toBeCloseTo(5.3 * MGDL_PER_MMOL, 3); + // The round trip a mmol/L reader actually experiences. + expect(mgdlToMmol(out?.value ?? 0)).toBe(5.3); }); it("refuses an unrecognised or type-mismatched unit (never mis-stores)", () => { diff --git a/src/lib/measurements/unit-aliases.ts b/src/lib/measurements/unit-aliases.ts index bb43693ed..e068187d2 100644 --- a/src/lib/measurements/unit-aliases.ts +++ b/src/lib/measurements/unit-aliases.ts @@ -17,9 +17,7 @@ * Pure + side-effect-free; safe to import from an API route or a script. */ import { getUnitForType } from "@/lib/validations/measurement"; - -/** Glucose mmol/L → mg/dL. */ -const GLUCOSE_MMOL_TO_MGDL = 18.016; +import { MGDL_PER_MMOL } from "@/lib/glucose"; /** Exact pound → kilogram (1 lb = 0.45359237 kg). */ const LB_TO_KG = 0.45359237; /** Exact inch → centimetre (1 in = 2.54 cm). */ @@ -87,7 +85,7 @@ export function resolveToCanonicalUnit( // Glucose: mmol/L → mg/dL. if (type === "BLOOD_GLUCOSE" && MMOL_ALIASES.has(lower)) { - return { value: value * GLUCOSE_MMOL_TO_MGDL, unit: canonical }; + return { value: value * MGDL_PER_MMOL, unit: canonical }; } // Body mass: lb → kg.