From 03d4496d3a17bafcf3bf45d59201c7ccfffe656f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 5 Jul 2026 20:33:44 +0200 Subject: [PATCH] feat(measurements): surface the WHO5_SCORE and SCI_SCORE trends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the two new screening totals through the read surfaces on the PHQ-9 / GAD-7 precedent: signal-registry entries (higher-better direction, off the Coach snapshot and MCP by construction, no FHIR coding attached — neither total has a verified LOINC, and the export skips uncoded signals rather than shipping an invented code), the scores category cluster, list labels/icons/colours, the generic insight-chart tokens, a null PR direction (screeners are never gamified), and the module-gated mental-health section of the doctor report. The vitals-table exclusion rationale is documented alongside the existing screener entries. --- .../measurements/measurement-list-meta.ts | 9 ++++ src/lib/doctor-report-data.ts | 3 ++ src/lib/doctor-report-pdf-core.ts | 5 +++ src/lib/insights/chart-tokens.ts | 4 ++ src/lib/measurements/categories.ts | 3 ++ src/lib/personal-records/pr-direction.ts | 4 ++ src/lib/signals/registry.ts | 41 +++++++++++++++++++ 7 files changed, 69 insertions(+) diff --git a/src/components/measurements/measurement-list-meta.ts b/src/components/measurements/measurement-list-meta.ts index aabef8a6d..799c02bd5 100644 --- a/src/components/measurements/measurement-list-meta.ts +++ b/src/components/measurements/measurement-list-meta.ts @@ -125,6 +125,9 @@ export const MEASUREMENT_TYPE_LABEL_KEYS: Record = { // ── v1.25 — clinical-signals wave ── PHQ9_SCORE: "measurements.typePhq9Score", GAD7_SCORE: "measurements.typeGad7Score", + // ── v1.27.9 — screening scores ── + WHO5_SCORE: "measurements.typeWho5Score", + SCI_SCORE: "measurements.typeSciScore", GRIP_STRENGTH: "measurements.typeGripStrength", PAIN_NRS: "measurements.typePainNrs", WAIST_CIRCUMFERENCE: "measurements.typeWaistCircumference", @@ -257,6 +260,9 @@ export const MEASUREMENT_TYPE_ICONS: Record = { // Activity the pain trace; Scale the anthropometric waist pair. PHQ9_SCORE: Gauge, GAD7_SCORE: Gauge, + // ── v1.27.9 — screening scores ride the same composite-dial icon ── + WHO5_SCORE: Gauge, + SCI_SCORE: Gauge, GRIP_STRENGTH: Dumbbell, PAIN_NRS: Activity, WAIST_CIRCUMFERENCE: Scale, @@ -380,6 +386,9 @@ export const MEASUREMENT_TYPE_COLORS: Record = { // chart-3 (cardio/alert family) for pain; chart-4 (body family) for waist. PHQ9_SCORE: "bg-chart-1/20 text-chart-1", GAD7_SCORE: "bg-chart-1/20 text-chart-1", + // ── v1.27.9 — screening scores join the composite/score colour family ── + WHO5_SCORE: "bg-chart-1/20 text-chart-1", + SCI_SCORE: "bg-chart-1/20 text-chart-1", GRIP_STRENGTH: "bg-chart-1/20 text-chart-1", PAIN_NRS: "bg-chart-3/20 text-chart-3", WAIST_CIRCUMFERENCE: "bg-chart-4/20 text-chart-4", diff --git a/src/lib/doctor-report-data.ts b/src/lib/doctor-report-data.ts index b129d7ac7..b4aa23537 100644 --- a/src/lib/doctor-report-data.ts +++ b/src/lib/doctor-report-data.ts @@ -1620,6 +1620,9 @@ const MEASUREMENT_TYPE_MODULE: Record = { // opted into. PHQ9_SCORE: "mentalHealth", GAD7_SCORE: "mentalHealth", + // v1.27.9 — the WHO-5 / SCI totals ride the same module gate. + WHO5_SCORE: "mentalHealth", + SCI_SCORE: "mentalHealth", }; function filterMeasurementKeys( diff --git a/src/lib/doctor-report-pdf-core.ts b/src/lib/doctor-report-pdf-core.ts index 40a06a450..40784dd8d 100644 --- a/src/lib/doctor-report-pdf-core.ts +++ b/src/lib/doctor-report-pdf-core.ts @@ -201,6 +201,11 @@ export const DOCTOR_REPORT_TYPE_UNIT_KEYS: Record = { * v1.19.0 — Oura resilience. `RESILIENCE` is an ordinal-encoded categorical * band (limited=1 … exceptional=5), a derived recovery composite — descriptive, * not a measured clinical vital — so it is excluded too. + * + * v1.27.9 — WHO-5 / SCI screening totals. `WHO5_SCORE` + `SCI_SCORE` follow + * the PHQ-9 / GAD-7 precedent: opt-in, derived from an in-app questionnaire, + * excluded from the vitals table (they ride the module-gated mental-health + * export section instead, see `doctor-report-data.ts`). */ export const DOCTOR_REPORT_VITAL_TYPES = [ "WEIGHT", diff --git a/src/lib/insights/chart-tokens.ts b/src/lib/insights/chart-tokens.ts index df71e2e86..b73fc1539 100644 --- a/src/lib/insights/chart-tokens.ts +++ b/src/lib/insights/chart-tokens.ts @@ -133,6 +133,10 @@ export const ALLOWED_CHART_TOKENS = [ // generic chart. "metric:PHQ9_SCORE", "metric:GAD7_SCORE", + // v1.27.9 — WHO-5 (0–100 percentage) + SCI (0–32) screening totals; same + // continuous-series contract, higher = better. + "metric:WHO5_SCORE", + "metric:SCI_SCORE", "metric:GRIP_STRENGTH", "metric:PAIN_NRS", "metric:WAIST_CIRCUMFERENCE", diff --git a/src/lib/measurements/categories.ts b/src/lib/measurements/categories.ts index b9065d0a2..8e758ad0c 100644 --- a/src/lib/measurements/categories.ts +++ b/src/lib/measurements/categories.ts @@ -209,6 +209,9 @@ export const MEASUREMENT_CATEGORIES: ReadonlyMap< // PHQ-9 / GAD-7 totals are composite screener indices → scores cluster. ["PHQ9_SCORE", "scores"], ["GAD7_SCORE", "scores"], + // ── v1.27.9 — WHO-5 / SCI totals join the same screener-index cluster. + ["WHO5_SCORE", "scores"], + ["SCI_SCORE", "scores"], // Grip strength is a strength/fitness reading → activity cluster. ["GRIP_STRENGTH", "activity"], // Pain NRS is a patient-reported clinical point-in-time signal → vitals. diff --git a/src/lib/personal-records/pr-direction.ts b/src/lib/personal-records/pr-direction.ts index 097905945..ddf3b86ae 100644 --- a/src/lib/personal-records/pr-direction.ts +++ b/src/lib/personal-records/pr-direction.ts @@ -205,6 +205,10 @@ export function getPRDirection( // all). All defer to a null PR direction. case "PHQ9_SCORE": case "GAD7_SCORE": + // v1.27.9 — WHO-5 / SCI screening totals follow the same no-gamification + // safety rule (no streaks / "best score" framing around a screener). + case "WHO5_SCORE": + case "SCI_SCORE": case "PAIN_NRS": case "WAIST_CIRCUMFERENCE": case "WAIST_TO_HEIGHT": diff --git a/src/lib/signals/registry.ts b/src/lib/signals/registry.ts index 788b7e7c3..84dfd2670 100644 --- a/src/lib/signals/registry.ts +++ b/src/lib/signals/registry.ts @@ -1272,6 +1272,47 @@ export const SIGNALS: Record = { category: "survey", }, }, + // ── v1.27.9 — WHO-5 well-being + Sleep Condition Indicator totals ─────── + // Same privacy posture as PHQ-9 / GAD-7 (off Coach snapshot + MCP; item + // content never rides a Measurement row). Direction inverts: HIGHER is + // better for both. No verified total-score LOINC exists for either, so no + // FHIR coding is attached (the export skips uncoded signals rather than + // shipping an invented code). + WHO5_SCORE: { + key: "WHO5_SCORE", + kind: "score", + source: { measurementType: "WHO5_SCORE" }, + displayName: "WHO-5 well-being index (0–100)", + unit: "score", + direction: "higher-better", + archetype: "physiological-vital", + // "Below 50" is the WHO-suggested cut-off; achievable totals are + // multiples of 4, so ≥ 52 is the good side. + normalRange: { low: 52, high: 100 }, + surfaces: { + detailPage: false, + correlationEligible: false, + coachSnapshot: false, + mcp: false, + }, + }, + SCI_SCORE: { + key: "SCI_SCORE", + kind: "score", + source: { measurementType: "SCI_SCORE" }, + displayName: "Sleep Condition Indicator (total)", + unit: "score", + direction: "higher-better", + archetype: "physiological-vital", + // > 16 identified "no insomnia disorder" in Espie et al. 2014. + normalRange: { low: 17, high: 32 }, + surfaces: { + detailPage: false, + correlationEligible: false, + coachSnapshot: false, + mcp: false, + }, + }, // ── v1.25 clinical-signals wave — physical / clinical measurements ────── GRIP_STRENGTH: { key: "GRIP_STRENGTH",