mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Surface the population reference bands behind the metrics a Coach turn carries as a compact, citation-aware grounding block appended after the SNAPSHOT. For each present metric the block states the headline band, its source (blood pressure on the ESH 2023 line, US/ESC framings as context only), and where the user's value sits via the shared four-state placement contract. Framed strictly as general guidance: the block opens and closes with a not-a-diagnosis caveat, never overrides the existing refusal/dose/drug- level safety discipline, and carries no commercial brand name. Glucose respects the diabetes opt-in — the tighter ADA management goal only when the user has declared it, otherwise the general non-diabetic band. The block is deterministic and inspectable: built purely from the reference-range backbone over the same scalars the snapshot already surfaces, so it can never cite a value or band the snapshot does not carry.
203 lines
7.2 KiB
TypeScript
203 lines
7.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
|
||
import { buildReferenceGroundingBlock } from "../reference-grounding";
|
||
import { CITATIONS } from "@/lib/medical-citations";
|
||
|
||
/**
|
||
* W7 — the Coach reference-grounding block. These tests pin the
|
||
* deterministic shape the hallucination-QA pass audits: the right band per
|
||
* metric, the right four-state placement, the general-guidance caveat, the
|
||
* ESH-2023 blood-pressure label, the diabetes opt-in gating, and the hard
|
||
* no-commercial-brand rule.
|
||
*/
|
||
|
||
/**
|
||
* Every commercial brand the Coach prompt already names elsewhere (the
|
||
* GLP-1 block / dose-deferral rule). The grounding block must contain NONE
|
||
* of them — it is brand-free by construction, and this list is the guard.
|
||
*/
|
||
const BRAND_TOKENS = [
|
||
"Mounjaro",
|
||
"Ozempic",
|
||
"Wegovy",
|
||
"Zepbound",
|
||
"Trulicity",
|
||
"Saxenda",
|
||
"Rybelsus",
|
||
"Eli Lilly",
|
||
"Novo Nordisk",
|
||
"Withings",
|
||
"WHOOP",
|
||
"Oura",
|
||
"Fitbit",
|
||
"Garmin",
|
||
"Apple",
|
||
"Polar",
|
||
"Nightscout",
|
||
"Dexcom",
|
||
"Libre",
|
||
];
|
||
|
||
describe("buildReferenceGroundingBlock", () => {
|
||
it("returns null when no metric is covered by the backbone", () => {
|
||
expect(buildReferenceGroundingBlock({ metrics: [], hasDiabetes: false })).toBeNull();
|
||
});
|
||
|
||
it("cites the ESH 2023 source for blood pressure and never the US/ESC line", () => {
|
||
const block = buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "BLOOD_PRESSURE", value: 118 }],
|
||
hasDiabetes: false,
|
||
});
|
||
expect(block).not.toBeNull();
|
||
expect(block).toContain("ESH 2023");
|
||
// The headline normal band edge (≤120 mmHg) is surfaced.
|
||
expect(block).toContain("120 mmHg");
|
||
// The conflicting US/ESC framings stay out of the per-line copy; only
|
||
// the preamble's neutral "(ACC/AHA)" context mentions them.
|
||
expect(block).not.toContain("Stage 1");
|
||
// 118 sits inside the optimal band → "within" placement phrasing.
|
||
expect(block).toContain("sits inside the general reference band");
|
||
});
|
||
|
||
it("places an out-of-band value as outside", () => {
|
||
const block = buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "BLOOD_PRESSURE", value: 165 }],
|
||
hasDiabetes: false,
|
||
});
|
||
expect(block).toContain("sits outside the general reference band");
|
||
});
|
||
|
||
it("opens and closes with a general-guidance, not-a-diagnosis caveat", () => {
|
||
const block = buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "BMI", value: 22 }],
|
||
hasDiabetes: false,
|
||
})!;
|
||
expect(block).toContain("general guidance");
|
||
expect(block).toContain("not a diagnosis");
|
||
expect(block.toLowerCase()).toContain(
|
||
"not personal medical advice",
|
||
);
|
||
// The closing reminder line is present.
|
||
expect(block).toMatch(/general guidance, not personal medical advice\.$/);
|
||
});
|
||
|
||
it("grounds glucose against the general non-diabetic band when the opt-in is off", () => {
|
||
const block = buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "BLOOD_GLUCOSE", value: 92 }],
|
||
hasDiabetes: false,
|
||
})!;
|
||
expect(block).toContain("general non-diabetic normal");
|
||
// The non-diabetic fasting normal ceiling is 100 mg/dL.
|
||
expect(block).toContain("100 mg/dL");
|
||
expect(block).not.toContain("80–130");
|
||
expect(block).toContain("sits inside the general reference band");
|
||
});
|
||
|
||
it("grounds glucose against the tighter ADA goal band when diabetes is opted in", () => {
|
||
const inGoal = buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "BLOOD_GLUCOSE", value: 110 }],
|
||
hasDiabetes: true,
|
||
})!;
|
||
// The ADA diabetic fasting goal band, framed as a clinician-set goal.
|
||
expect(inGoal).toContain("80–130 mg/dL");
|
||
expect(inGoal).toContain("management goal");
|
||
expect(inGoal).toContain("clinician-set goal");
|
||
// The line explicitly frames the band as a goal, NOT a screening line.
|
||
expect(inGoal).toContain("not a screening line");
|
||
expect(inGoal).toContain("inside the typical diabetes management goal");
|
||
|
||
const outOfGoal = buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "BLOOD_GLUCOSE", value: 200 }],
|
||
hasDiabetes: true,
|
||
})!;
|
||
expect(outOfGoal).toContain("outside the typical diabetes management goal");
|
||
});
|
||
|
||
it("treats a band-less metric (HRV) as baseline-led, never out-of-band", () => {
|
||
const block = buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "HEART_RATE_VARIABILITY", value: 45 }],
|
||
hasDiabetes: false,
|
||
})!;
|
||
expect(block).toContain("no fixed population band");
|
||
expect(block).not.toContain("sits outside");
|
||
});
|
||
|
||
it("is deterministic — identical inputs produce byte-identical output", () => {
|
||
const input = {
|
||
metrics: [
|
||
{ metric: "BLOOD_PRESSURE" as const, value: 134 },
|
||
{ metric: "BLOOD_GLUCOSE" as const, value: 105 },
|
||
{ metric: "SLEEP_DURATION" as const, value: 7.5 },
|
||
],
|
||
hasDiabetes: false,
|
||
};
|
||
expect(buildReferenceGroundingBlock(input)).toEqual(
|
||
buildReferenceGroundingBlock(input),
|
||
);
|
||
});
|
||
|
||
it("caps the number of grounded lines and de-dupes metrics", () => {
|
||
const block = buildReferenceGroundingBlock({
|
||
metrics: [
|
||
{ metric: "BMI", value: 22 },
|
||
{ metric: "BMI", value: 23 },
|
||
],
|
||
hasDiabetes: false,
|
||
})!;
|
||
// De-duped: only one BMI line.
|
||
const bmiLines = block.split("\n").filter((l) => l.startsWith("- BMI:"));
|
||
expect(bmiLines).toHaveLength(1);
|
||
});
|
||
|
||
it("contains NO commercial brand token in any branch", () => {
|
||
// Exercise both the non-diabetic and diabetic glucose branches plus a
|
||
// broad metric spread so every code path's text is scanned.
|
||
const blocks = [
|
||
buildReferenceGroundingBlock({
|
||
metrics: [
|
||
{ metric: "BLOOD_PRESSURE", value: 130 },
|
||
{ metric: "BLOOD_GLUCOSE", value: 95 },
|
||
{ metric: "RESTING_HEART_RATE", value: 58 },
|
||
{ metric: "OXYGEN_SATURATION", value: 97 },
|
||
{ metric: "RESPIRATORY_RATE", value: 16 },
|
||
{ metric: "BODY_TEMPERATURE", value: 36.8 },
|
||
{ metric: "BMI", value: 27 },
|
||
{ metric: "STEPS", value: 9000 },
|
||
{ metric: "VISCERAL_FAT", value: 8 },
|
||
{ metric: "SLEEP_DURATION", value: 8 },
|
||
{ metric: "HEART_RATE_VARIABILITY", value: 40 },
|
||
],
|
||
hasDiabetes: false,
|
||
}),
|
||
buildReferenceGroundingBlock({
|
||
metrics: [{ metric: "BLOOD_GLUCOSE", value: 140 }],
|
||
hasDiabetes: true,
|
||
}),
|
||
];
|
||
for (const block of blocks) {
|
||
expect(block).not.toBeNull();
|
||
for (const brand of BRAND_TOKENS) {
|
||
expect(block!.toLowerCase()).not.toContain(brand.toLowerCase());
|
||
}
|
||
}
|
||
});
|
||
|
||
it("only cites real entries from the medical-citations registry", () => {
|
||
const block = buildReferenceGroundingBlock({
|
||
metrics: [
|
||
{ metric: "BLOOD_PRESSURE", value: 120 },
|
||
{ metric: "STEPS", value: 9000 },
|
||
{ metric: "SLEEP_DURATION", value: 8 },
|
||
],
|
||
hasDiabetes: false,
|
||
})!;
|
||
// Each cited source label ("NAME (YEAR)") corresponds to a real
|
||
// registry entry — no fabricated guideline names.
|
||
const citationNames = Object.values(CITATIONS).map((c) => c.name);
|
||
// The BP / steps / sleep headline sources must all appear by name.
|
||
expect(citationNames.some((n) => block.includes(n))).toBe(true);
|
||
expect(block).toContain("ESH 2023");
|
||
expect(block).toContain("AASM");
|
||
});
|
||
});
|