fix(analytics): resting-pulse pill drops the diagnosis-shaped verdict

classifyPulse() labelled resting pulse < 60 "Bradycardia" and > 120
"Tachycardia", the latter painted destructive-red. The interpretation layer
already frames the same signal calmly for the same range ("below the
standard resting range (common in trained hearts)", neutral valence) — a
trained user at 55 bpm resting read a diagnosis-shaped verdict on one
surface and a calm contextual band on the other.

Rename the categories to "Below typical range" / "Above typical range"
(matching the interpretation layer's own wording) and step the high band
down from destructive/danger to warning/amber — still flagged, never a
verdict noun. Neither category paints the pill red anymore.
This commit is contained in:
Marc-André Bombeck committed 2026-07-17 23:12:12 +02:00
1 parent cffebd78db
commit 30a0528ad0
9 files changed
+49 -21

No files matched your search

+2 -2
View File
@@ -6649,9 +6649,9 @@
"hypertensionGrade1": "Hypertonie Grad 1",
"hypertensionGrade2": "Hypertonie Grad 2",
"hypertensionGrade3": "Hypertonie Grad 3",
"bradycardia": "Bradykardie",
"belowTypicalRange": "Unter dem typischen Bereich",
"elevated": "Erhöht",
"tachycardia": "Tachykardie",
"aboveTypicalRange": "Über dem typischen Bereich",
"significantlyLow": "Deutlich niedrig",
"slightlyLow": "Leicht niedrig",
"onTarget": "Im Zielbereich",
+2 -2
View File
@@ -6649,9 +6649,9 @@
"hypertensionGrade1": "Hypertension Grade 1",
"hypertensionGrade2": "Hypertension Grade 2",
"hypertensionGrade3": "Hypertension Grade 3",
"bradycardia": "Bradycardia",
"belowTypicalRange": "Below typical range",
"elevated": "Elevated",
"tachycardia": "Tachycardia",
"aboveTypicalRange": "Above typical range",
"significantlyLow": "Significantly low",
"slightlyLow": "Slightly low",
"onTarget": "On target",
+2 -2
View File
@@ -6649,9 +6649,9 @@
"hypertensionGrade1": "Hipertensión grado 1",
"hypertensionGrade2": "Hipertensión grado 2",
"hypertensionGrade3": "Hipertensión grado 3",
"bradycardia": "Bradicardia",
"belowTypicalRange": "Por debajo del rango típico",
"elevated": "Elevado",
"tachycardia": "Taquicardia",
"aboveTypicalRange": "Por encima del rango típico",
"significantlyLow": "Significativamente bajo",
"slightlyLow": "Ligeramente bajo",
"onTarget": "En rango objetivo",
+2 -2
View File
@@ -6649,9 +6649,9 @@
"hypertensionGrade1": "Hypertension grade 1",
"hypertensionGrade2": "Hypertension grade 2",
"hypertensionGrade3": "Hypertension grade 3",
"bradycardia": "Bradycardie",
"belowTypicalRange": "En dessous de la plage habituelle",
"elevated": "Élevé",
"tachycardia": "Tachycardie",
"aboveTypicalRange": "Au-dessus de la plage habituelle",
"significantlyLow": "Nettement bas",
"slightlyLow": "Légèrement bas",
"onTarget": "Dans la plage cible",
+2 -2
View File
@@ -6649,9 +6649,9 @@
"hypertensionGrade1": "Ipertensione grado 1",
"hypertensionGrade2": "Ipertensione grado 2",
"hypertensionGrade3": "Ipertensione grado 3",
"bradycardia": "Bradicardia",
"belowTypicalRange": "Sotto il range tipico",
"elevated": "Elevato",
"tachycardia": "Tachicardia",
"aboveTypicalRange": "Sopra il range tipico",
"significantlyLow": "Significativamente basso",
"slightlyLow": "Lievemente basso",
"onTarget": "Nel range obiettivo",
+2 -2
View File
@@ -6649,9 +6649,9 @@
"hypertensionGrade1": "Nadciśnienie 1 stopnia",
"hypertensionGrade2": "Nadciśnienie 2 stopnia",
"hypertensionGrade3": "Nadciśnienie 3 stopnia",
"bradycardia": "Bradykardia",
"belowTypicalRange": "Poniżej typowego zakresu",
"elevated": "Podwyższone",
"tachycardia": "Tachykardia",
"aboveTypicalRange": "Powyżej typowego zakresu",
"significantlyLow": "Znacząco niskie",
"slightlyLow": "Lekko niskie",
"onTarget": "W zakresie docelowym",
@@ -33,7 +33,14 @@ describe("statusGroupForCategory", () => {
it("paints far-out categories red", () => {
expect(statusGroupForCategory("Hypertension Grade 3")).toBe("out");
expect(statusGroupForCategory("Significantly elevated")).toBe("out");
expect(statusGroupForCategory("Tachycardia")).toBe("out");
});
it("falls back to amber for the resting-pulse bands (no clinical-verdict red)", () => {
// v1.29.6 — "Below typical range" / "Above typical range" replaced the
// diagnosis-shaped "Bradycardia" / "Tachycardia" categories and no
// longer paint the pill destructive-red.
expect(statusGroupForCategory("Below typical range")).toBe("near");
expect(statusGroupForCategory("Above typical range")).toBe("near");
});
it("falls back to amber for caution + unmapped categories", () => {
@@ -51,9 +51,9 @@ const STATUS_CATEGORY_KEY: Record<string, string> = {
"Hypertension Grade 1": "hypertensionGrade1",
"Hypertension Grade 2": "hypertensionGrade2",
"Hypertension Grade 3": "hypertensionGrade3",
Bradycardia: "bradycardia",
"Below typical range": "belowTypicalRange",
Elevated: "elevated",
Tachycardia: "tachycardia",
"Above typical range": "aboveTypicalRange",
"Significantly low": "significantlyLow",
"Slightly low": "slightlyLow",
"On target": "onTarget",
@@ -128,6 +128,13 @@ export function statusGroupForCategory(
"Moderately active",
"Essential",
]);
// Note: "Below typical range" / "Above typical range" (resting pulse,
// v1.29.6) are deliberately absent from both sets — they fall through to
// the default "near" (amber) group below, same as "Elevated" already
// does. The prior "Bradycardia" / "Tachycardia" categories painted this
// pill destructive-red, a diagnosis-shaped verdict that contradicted the
// calmer, neutral framing the interpretation layer uses for the identical
// signal (see `insight-interpretation.ts` → `RESTING_HEART_RATE`).
const redCategories = new Set([
"Obesity Grade III",
"Hypertension Grade 3",
@@ -135,8 +142,6 @@ export function statusGroupForCategory(
"Significantly low",
"Far too short",
"Far too long",
"Tachycardia",
"Bradycardia",
"Low",
"High",
"Fluctuating",
+20 -4
View File
@@ -101,9 +101,25 @@ export interface PulseClassification {
severity: "info" | "normal" | "warning" | "danger";
}
/**
* v1.29.6 — dropped the bare clinical-diagnosis nouns ("Bradycardia" /
* "Tachycardia"). This is the same resting-pulse signal the interpretation
* layer (`insight-interpretation.ts` → `RESTING_HEART_RATE`) already frames
* neutrally ("below the standard resting range (common in trained hearts)",
* valence *neutral*) — a trained user at 55 bpm resting must not read a
* diagnosis-shaped verdict on one surface and a calm contextual band on the
* other. Category wording now mirrors that framing (and the "typical range"
* phrasing this codebase already uses in `pulse-targets.ts`); the < 60 band
* keeps its existing non-danger "info" tone, and the > 120 band steps down
* from destructive/"danger" to warning/amber — still flagged, never a verdict.
*/
export function classifyPulse(bpm: number): PulseClassification {
if (bpm < 60) {
return { category: "Bradycardia", color: "var(--info)", severity: "info" };
return {
category: "Below typical range",
color: "var(--info)",
severity: "info",
};
}
if (bpm <= 100) {
return { category: "Normal", color: "var(--success)", severity: "normal" };
@@ -116,9 +132,9 @@ export function classifyPulse(bpm: number): PulseClassification {
};
}
return {
category: "Tachycardia",
color: "var(--destructive)",
severity: "danger",
category: "Above typical range",
color: "var(--warning)",
severity: "warning",
};
}