fix(insights): wire grip / pain / waist / waist-to-height across web and MCP

Grip strength, pain (0–10 NRS), waist circumference, and waist-to-height
existed in the measurement enum, signal registry, and FHIR export but had
no web entry, no detail page, single non-sex-aware bands, and a split-brain
MCP exposure. This closes those gaps.

Web capture: the four signals join the manual measurement form, so a
web-only self-hoster can log them (the iOS client and the measurements API
already could).

Detail pages: flip detailPage on for the four signals and mount each on the
shared HealthKit metric template (chart, stat strip, Coach-read strip,
assessment card) via routed sub-pages and tab-strip pills, the same detail
spine every other metric carries.

Sex-aware bands: grip strength (EWGSOP2 men 27 / women 16 kg) and waist
(WHO men 94 / women 80 cm) now classify by the profile sex through the
age/sex norm resolver the assessment already threads; the resolver also
returns a sex-specific, age-independent band when no date of birth is set.
Fix the waist-to-height boundary so the NICE >= 0.5 rule is correct (the
last in-range value is 0.49).

MCP single source of truth: decouple the registry mcp facet from the Coach
snapshot — a signal can be MCP-readable without being on the Coach surface.
Mark the four physical signals mcp:true, derive the MCP rich-reads clinical
set from the registry flag instead of a duplicate allowlist, and relax the
invariant to coachSnapshot implies mcp (not the reverse). PHQ-9 / GAD-7 and
the environmental signals stay mcp:false and unreachable. compare_metric and
detect_changepoints now advertise clinical-signal support to match
get_metric_baseline.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 18:33:53 +02:00
1 parent 4f39058629
commit a69697b50f
25 files changed
+726 -62

No files matched your search

@@ -185,6 +185,8 @@ export const SUB_PAGE_TABS: Record<
labelKey: "insights.navRespiratoryRate",
metric: "RESPIRATORY_RATE",
},
// v1.25 — pain (0–10 NRS).
pain: { labelKey: "insights.navPainNrs", metric: "PAIN_NRS" },
// ── body composition ──
weight: { labelKey: "insights.navWeight", metric: "WEIGHT" },
bmi: { labelKey: "insights.navBmi", metric: "BMI" },
@@ -207,6 +209,15 @@ export const SUB_PAGE_TABS: Record<
labelKey: "insights.navLeanBodyMass",
metric: "LEAN_BODY_MASS",
},
// v1.25 — waist circumference + waist-to-height ratio.
waist: {
labelKey: "insights.navWaistCircumference",
metric: "WAIST_CIRCUMFERENCE",
},
"waist-to-height": {
labelKey: "insights.navWaistToHeight",
metric: "WAIST_TO_HEIGHT",
},
// ── activity ──
steps: {
labelKey: "insights.navSteps",
@@ -270,6 +281,11 @@ export const SUB_PAGE_TABS: Record<
labelKey: "insights.navStairDescentSpeed",
metric: "STAIR_DESCENT_SPEED",
},
// v1.25 — grip strength.
"grip-strength": {
labelKey: "insights.navGripStrength",
metric: "GRIP_STRENGTH",
},
// ── sleep ──
sleep: { labelKey: "insights.navSleep", metric: "SLEEP_DURATION" },
// v1.10.0 — sleep breathing-disturbance index.
@@ -95,6 +95,32 @@ const MEASUREMENT_TYPES = [
unit: "°C",
placeholder: "36.6",
},
// v1.25 — physical / clinical signals. Manual web capture so a web-only
// self-hoster can log them (the iOS client + measurements API already do).
{
value: "WAIST_CIRCUMFERENCE",
labelKey: "measurements.typeWaistCircumference",
unit: "cm",
placeholder: "84",
},
{
value: "WAIST_TO_HEIGHT",
labelKey: "measurements.typeWaistToHeight",
unitKey: "measurements.unitRatio",
placeholder: "0.48",
},
{
value: "GRIP_STRENGTH",
labelKey: "measurements.typeGripStrength",
unit: "kg",
placeholder: "38",
},
{
value: "PAIN_NRS",
labelKey: "measurements.typePainNrs",
unitKey: "measurements.unitScore",
placeholder: "0",
},
] as const;
// v1.4.34 IW-G — single source of truth for the `/measurements?add=<TYPE>`