fix(health-score): grade the blood-pressure pillar by clinical proximity

The BP pillar value equalled the binary all-time in-target rate: the
share of paired readings where both systolic and diastolic sat at or
below the age-band ceiling. A borderline stage-1 profile (diastolic a
few mmHg over the ceiling on most readings) collapsed the pillar to the
teens even though the reading is "a bit high", not catastrophic — and
improvement never surfaced because the rate is all-time and binary.

Replace the binary rate AS THE SCORE with a smooth clinical-proximity
grade. A recency-weighted representative reading (45-day half-life) is
mapped per axis onto a 0-100 curve anchored on the same age-band ceiling
the rest of the app uses — optimal ≈ 100, at the ceiling ≈ 85, then
piecewise-linear down through the over-target bands; hypotension is
penalised symmetrically below the clinical floors. The pillar takes the
worse of the two axes, so an elevated diastolic counts without
binary-zeroing the result. A 134/87 profile against a 129/79 ceiling now
reads in the mid-50s.

The in-target rate stays available as a secondary "X% of readings in
range" stat. The healthScore.components.bp shape is unchanged — only the
value moves.
This commit is contained in:
Marc-André Bombeck committed 2026-06-07 17:36:13 +02:00
1 parent 2851776af6
commit 60359dbcb7
8 files changed
+502 -5

No files matched your search

+13
View File
@@ -258,6 +258,11 @@ async function buildAnalyticsResponse(user: AuthedUser) {
// pillar compares the same window on both ends (the tile headline keeps
// its 30-day scope; the score reads all-time per its documented contract).
let windowsPriorWeekAllTime: number | null = null;
// v1.15.12 A1 — graded clinical-proximity BP score (recency-weighted
// representative reading) for the Health-Score BP pillar value, plus
// the prior-week run for the week-over-week delta.
let bpGradedScore: number | null = null;
let bpGradedScorePriorWeek: number | null = null;
const bpTargets = getBpTargets(user.dateOfBirth);
if (bpTargets) {
const now = new Date();
@@ -309,6 +314,8 @@ async function buildAnalyticsResponse(user: AuthedUser) {
bpInTargetPctPriorMonth = windows.priorMonth?.pct ?? null;
bpInTargetPctPriorYear = windows.priorYear?.pct ?? null;
bpInTargetPctPriorWeek = windowsPriorWeek.last30Days?.pct ?? null;
bpGradedScore = windows.gradedScore;
bpGradedScorePriorWeek = windowsPriorWeek.gradedScore;
// The Health-Score BP pillar reads the all-time window (see the
// `bpInTargetPctForScore` derivation below). Capture the prior-week
// run's all-time pct too so the week-over-week delta compares the
@@ -395,6 +402,12 @@ async function buildAnalyticsResponse(user: AuthedUser) {
// same all-time window as the current snapshot so the delta is
// apples-to-apples.
bpInTargetPctPriorWeek: bpInTargetPctPriorWeekForScore,
// v1.15.12 A1 — graded clinical-proximity BP score drives the pillar
// VALUE; the binary in-target rate above only gates presence and is
// surfaced as a secondary stat. Closes the "borderline-stage-1 reads
// 16/100" trust bug.
bpGradedScore,
bpGradedScorePriorWeek,
heightCm: user.heightCm ?? null,
now: new Date(),
coverage,