feat(measurements): surface the WHO5_SCORE and SCI_SCORE trends

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.
This commit is contained in:
Marc-André Bombeck committed 2026-07-05 20:33:44 +02:00
1 parent 1262333acc
commit 03d4496d3a
7 files changed
+69

No files matched your search

@@ -125,6 +125,9 @@ export const MEASUREMENT_TYPE_LABEL_KEYS: Record<string, string> = {
// ── 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<string, LucideIcon> = {
// 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<string, string> = {
// 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",
+3
View File
@@ -1620,6 +1620,9 @@ const MEASUREMENT_TYPE_MODULE: Record<string, ModuleKey> = {
// 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<T>(
+5
View File
@@ -201,6 +201,11 @@ export const DOCTOR_REPORT_TYPE_UNIT_KEYS: Record<string, string | null> = {
* 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",
+4
View File
@@ -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",
+3
View File
@@ -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.
+4
View File
@@ -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":
+41
View File
@@ -1272,6 +1272,47 @@ export const SIGNALS: Record<string, SignalDefinition> = {
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",