mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
* fix(medical): protect against silent DBP hypotension and align BP citations Three correctness issues that the v1.3.3 ecosystem audit surfaced and that we deferred until the v1.4 cycle: 1. **Diastolic blood-pressure orange band reached 60 mmHg.** With the default age-based targets (DBP 70-79), the lower orange wing was computed as `diaLow - 10 = 60`, putting a sustained reading of 60 mmHg into "mildly low" territory — visually a yellow tile, not a red one. 60 mmHg is widely treated as a hypotension / perfusion-risk threshold (general-adult <90/60 from AHA/NIH; J-curve evidence in ONTARGET / TRANSCEND for treated hypertensives; ESH 2023's explicit caution against treating below 70 in the elderly). The orange floor is now clamped at 65 mmHg, so a reading of 60 lands in red. The user-override path stays untouched and is already audit-logged. 2. **"ESC/ESH 2023" doesn't exist as a joint guideline.** ESC withdrew from the 2023 authoring; the 2023 hypertension document is ESH-only. The codebase had a mix of "ESC/ESH 2018" (analytics) and "ESC/ESH 2023" (AI prompts). Both are wrong in different directions. Every site (`bp-targets.ts`, `targets/route.ts`, `ai/prompts/blood-pressure.ts`, `ai/prompts/base-system.ts`) now cites "ESH 2023" with the published source URL. Target bands are unchanged 2018 → 2023 — only the citation moves. 3. **"WHO ≥ 8 000 steps/day" was a recurring hallucination.** WHO's 2020 Physical Activity guidelines publish minutes per week, not a step quota. The cohort study HealthLog actually relies on is Saint-Maurice et al., JAMA 2020 (mortality plateau 8 000–12 000 steps). The v1.3.3 fix landed in `effective-range.ts` but four AI prompt strings (`base-system.ts` DE+EN, `general-status.ts` DE+EN) carried the old wording forward. Same fix shape: cite Saint-Maurice for the step number and add an explicit "WHO does not publish a step quota" note so the model doesn't regress. Sleep target moves from "ESC" to AASM 2015 in the same surfaces — ESC publishes no adult sleep duration guideline. Adds `src/lib/medical-citations.ts` as the new single source of truth for cited guidelines (id, name, year, url, caveat). v1.4 surfaces will import these constants instead of duplicating strings, and the new `medical-citations.test.ts` drift-test asserts that every entry has a non-empty url + caveat, that ids are unique, and that the WHO-as-step- source hallucination cannot reappear as a constant. Tests: 358 → 363 (5 new for the master citations list). Typecheck and existing analytics tests pass. No clinical numbers changed beyond the DBP orange-floor clamp. Co-Authored-By: Marc-André Bombeck <noreply@anthropic.com> * fix(medical): consolidate body-fat ACE bands and document bedtime-glucose source Two related medical-truthfulness issues from the v1.3.3 ecosystem audit. **Body-fat classification: ACE bands corrected.** v1.3.3's classifier used `essential = 6 (M) / 14 (F)` as the *floor* of the "Essential" category, which is actually ACE's *Athletes* lower bound. Anything below was lumped under "Essential" — including readings (e.g. 1% male) that are clinically concerning, not just "athletically extreme". The classifier now mirrors the ACE table precisely: - Below essential (M <2 / F <10): danger. - Essential (M 2–5 / F 10–13): warning. - Athletic (M 6–13 / F 14–20): normal. - Fitness (M 14–17 / F 21–24): normal. - Acceptable (M 18–24 / F 25–31): warning. - Obese (M ≥25 / F ≥32): danger. **Three-way drift in body-fat target ranges consolidated.** v1.3.3 had three sites with three different green-band numbers: - `value-bands.ts:118-120` returned M{10,20} / F{18,28}. - `app/api/insights/targets/route.ts:336-338` hardcoded M{14,24} / F{21,31} with `source: "ACE"`. - `classifications.ts:155-205` used a third overlapping set of thresholds for the band labels. ACE fitness + acceptable (M 14–24 / F 21–31) is the realistic healthy band for a non-athlete adult and is now the single source in `getBodyFatTargetRange`. Targets route and chart value-bands import the helper instead of duplicating the numbers. **Bedtime glucose citation softened.** ADA Standards of Care 2024 §6 publishes pre-prandial 80–130 and post-prandial <180; the document does NOT publish an adult bedtime target. The 90–150 mg/dL band HealthLog ships is reasonable advice (within the daytime pre-prandial range, slightly raised floor to avoid nocturnal hypoglycaemia) but it's not ADA-published. The inline comment in `effective-range.ts` now states the absence of an adult guideline explicitly and cites ISPAD 2022 (pediatric T1D, 80–140) as the closest comparator. Numbers unchanged. Tests: 363 → 369 (6 new for the ACE bands). Existing assertions unchanged. Co-Authored-By: Marc-André Bombeck <noreply@anthropic.com> * fix(medical): align step-range target with the effective-range source `getStepsRange()` in classifications.ts returned `{7 000, 10 000}` while `effective-range.ts` (the canonical resolver behind `getEffectiveRange`) returned `{8 000, 15 000}`. The two surfaces showed different "green" bands to the same user — `/api/insights/ targets` used the lower pair, the dashboard chart-bands used the upper pair. Aligns both on the upper pair, anchored on Saint-Maurice et al. JAMA 2020 (mortality plateau 8 000–12 000 steps/day). Adds the citation header to the steps section so the next audit pass can verify in place. WHO is explicitly called out as NOT a source for a step quota (only an activity-time guideline). The classifier function `classifySteps` is unchanged — its 3 / 5 / 7.5 / 10 k bands match Tudor-Locke 2004 pedometer indices and are kept distinct from the two-number "target range" helper. Tests stay at 369/369. Co-Authored-By: Marc-André Bombeck <noreply@anthropic.com> --------- Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
/**
|
||
* Automatic BP target calculation based on ESH 2023 guidelines (the
|
||
* last *joint* ESC/ESH document was 2018; ESC withdrew from the 2023
|
||
* authoring, so "ESC/ESH 2023" does not exist as a reference). Target
|
||
* bands are unchanged 2018 → 2023 — only the citation moves.
|
||
*
|
||
* Mancia G et al. "2023 ESH Guidelines for the management of
|
||
* arterial hypertension." J Hypertens. 2023.
|
||
* https://journals.lww.com/jhypertension/fulltext/2023/12000/2023_esh_guidelines_for_the_management_of_arterial.2.aspx
|
||
*
|
||
* Age-based systolic/diastolic target ranges:
|
||
* - <65: Sys 120–129, Dia 70–79
|
||
* - 65–79: Sys 130–139, Dia 70–79
|
||
* - ≥80: Sys 130–139, Dia 70–79
|
||
*
|
||
* Cross-checked against:
|
||
* - ACC/AHA 2017 hypertension guideline (≥130/80 stage 1) — HealthLog
|
||
* intentionally uses the European bands; ACC/AHA's lower threshold
|
||
* is referenced only as a comparison anchor in the AI prompt.
|
||
* - DEGAM S3 / Hausärzteverband (2024 update): aligns with ESH
|
||
* targets for primary care.
|
||
*/
|
||
|
||
export interface BpTargets {
|
||
sysLow: number;
|
||
sysHigh: number;
|
||
diaLow: number;
|
||
diaHigh: number;
|
||
}
|
||
|
||
function getAge(dateOfBirth: Date): number {
|
||
const today = new Date();
|
||
let age = today.getFullYear() - dateOfBirth.getFullYear();
|
||
const monthDiff = today.getMonth() - dateOfBirth.getMonth();
|
||
if (
|
||
monthDiff < 0 ||
|
||
(monthDiff === 0 && today.getDate() < dateOfBirth.getDate())
|
||
) {
|
||
age--;
|
||
}
|
||
return age;
|
||
}
|
||
|
||
export function getBpTargets(dateOfBirth: Date | null): BpTargets | null {
|
||
if (!dateOfBirth) return null;
|
||
|
||
const age = getAge(dateOfBirth);
|
||
|
||
if (age < 65) {
|
||
return { sysLow: 120, sysHigh: 129, diaLow: 70, diaHigh: 79 };
|
||
}
|
||
// 65+ (both 65–79 and ≥80 have the same targets per ESH 2023)
|
||
return { sysLow: 130, sysHigh: 139, diaLow: 70, diaHigh: 79 };
|
||
}
|