From aa281d66492fad3c7de3c55820d2b92f6c060808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 20:01:48 +0200 Subject: [PATCH] feat(insights): sharpen the metric-status normalRange with age/sex norms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thread the reference-range enabler (derived/norms.ts) into the existing generic assessment path. When lookupNormalRange resolves an age/sex adjusted band for the metric + profile, the snapshot uses it in place of the coarse flat registry anchor and tags it normalRangeSource: age-sex-adjusted; otherwise the flat anchor is kept unchanged. Strictly additive — a metric with no sharper norm, or a profile missing demographics, behaves exactly as before. --- src/lib/insights/metric-status.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/insights/metric-status.ts b/src/lib/insights/metric-status.ts index 1eeb8d71d..94ca0ca76 100644 --- a/src/lib/insights/metric-status.ts +++ b/src/lib/insights/metric-status.ts @@ -37,6 +37,7 @@ import { getPreviousInsightContext, } from "@/lib/insights/memory"; import { getAgeFromDateOfBirth } from "@/lib/analytics/pulse-targets"; +import { lookupNormalRange } from "@/lib/insights/derived/norms"; import { getNoKeyGeneralStatusText } from "@/lib/insights/no-key-fallbacks"; import { applyPayloadBudget } from "@/lib/insights/bucket-series"; import { @@ -239,6 +240,19 @@ export async function generateMetricStatus(args: { ? Math.round((Date.now() - newest.getTime()) / (24 * 60 * 60 * 1000)) : null; + const ageYears = getAgeFromDateOfBirth(user?.dateOfBirth ?? null); + const sex = + user?.gender === "MALE" || user?.gender === "FEMALE" + ? user.gender + : null; + + // v1.10.0 F1 — sharpen the coarse registry `normalRange` with the + // age/sex reference-range enabler when one is available for this + // metric + profile; otherwise keep the existing flat anchor (strictly + // additive, never a regression). Age + sex only — no ancestry/region. + const sharpenedRange = lookupNormalRange(meta.id, ageYears, sex); + const normalRange = sharpenedRange ?? meta.normalRange; + const snapshot = { locale, generatedForDay: todayKey, @@ -248,14 +262,15 @@ export async function generateMetricStatus(args: { displayName: meta.displayName, unit: meta.unit, direction: meta.direction, - ...(meta.normalRange ? { normalRange: meta.normalRange } : {}), + ...(normalRange ? { normalRange } : {}), + ...(sharpenedRange ? { normalRangeSource: "age-sex-adjusted" } : {}), }, // Profile context for placement only when stored — the medical note // limits population ranges to age + sex (NO ancestry/region). Omitted // when the profile leaves them blank, so the model leans on baseline. profile: { - ageYears: getAgeFromDateOfBirth(user?.dateOfBirth ?? null), - sex: user?.gender ?? null, + ageYears, + sex, }, dataCoverage: { totalMeasurements: measurements.length,