import { findLabTest, findLabUnit } from '../labs/catalog.js'; import type { CheckInput, CheckLab } from './engine.js'; /** * CHK-10 (egfrFromCreatinine): race-free CKD-EPI 2021 (NIDDK, National Kidney Foundation), adults only: * 142 x min(Scr/k, 1)^a x max(Scr/k, 1)^-1.200 x 0.9938^age x 1.012 [female]; Scr mg/dL, k 0.7 F / 0.9 M, a -0.241 F / -0.302 M. */ export const CKD_EPI_2021 = 'CKD-EPI 2021 (race-free)'; /** µmol/L per mg/dL, as the CKD-EPI publications use. */ export const CREATININE_UMOL_PER_MG_DL = 88.4; export type Sex = 'female' | 'male'; /** eGFR in mL/min/1.73m², unrounded. */ export function ckdEpi2021(creatinineMgDl: number, age: number, sex: Sex): number { const k = sex === 'female' ? 0.7 : 0.9; const a = sex === 'female' ? -0.241 : -0.302; const ratio = creatinineMgDl / k; return 142 * Math.min(ratio, 1) ** a * Math.max(ratio, 1) ** -1.2 * 0.9938 ** age * (sex === 'female' ? 1.012 : 1); } /** A sex the equation can use, or null. Anything else is not guessed. */ export function sexForEquation(sex: string | null | undefined): Sex | null { const said = (sex ?? '').trim().toLowerCase(); if (['female', 'f', 'woman', 'girl'].includes(said)) return 'female'; if (['male', 'm', 'man', 'boy'].includes(said)) return 'male'; return null; } /** Creatinine in mg/dL from a value and its printed unit; null when the unit is missing or not one we know. */ export function creatinineMgDl(value: number, unit: string | null | undefined): number | null { const test = findLabTest('creatinine'); const found = test && unit ? findLabUnit(test, unit) : null; if (!found || !Number.isFinite(value) || value <= 0) return null; if (found.unit === 'mg/dL') return value; if (found.unit === test!.unit) return value / CREATININE_UMOL_PER_MG_DL; if (found.unit === 'mmol/L') return (value * 1000) / CREATININE_UMOL_PER_MG_DL; return null; } export type EgfrNotCalculated = 'age_not_recorded' | 'age_under_18' | 'sex_not_recorded' | 'creatinine_not_exact' | 'creatinine_unit_unknown'; const NOT_CALCULATED_PLAIN: Record = { age_not_recorded: 'Age: needed to calculate eGFR from creatinine (CKD-EPI 2021).', age_under_18: 'eGFR was not calculated from creatinine: CKD-EPI 2021 is for adults.', sex_not_recorded: 'Sex: needed to calculate eGFR from creatinine (CKD-EPI 2021).', creatinine_not_exact: 'eGFR was not calculated: the creatinine result is not an exact number.', creatinine_unit_unknown: 'eGFR was not calculated: the creatinine unit is not recorded or not recognised.', }; export type CalculatedEgfr = | { status: 'calculated'; value: number; value_unrounded: number; unit: string; label: 'calculated'; formula: string; observed_at: string; inputs: { creatinine: { value: number; unit: string; value_mg_dl: number; observed_at: string }; age: number; sex: Sex } } | { status: 'not_calculated'; reason: EgfrNotCalculated; plain: string; formula: string }; /** * With a creatinine result and no eGFR at all, the eGFR to use and how it was worked out. The latest creatinine is * used, dated like the creatinine. When only the birth year is known, the older age is used (the lower eGFR). * Null when there is no creatinine, or there is an eGFR. */ export function calculateEgfr(input: CheckInput): { lab: CheckLab | null; output: CalculatedEgfr } | null { if (input.labs.some((lab) => lab.code === 'egfr')) return null; const creatinine = [...input.labs].filter((lab) => lab.code === 'creatinine').sort((x, y) => x.observedAt.localeCompare(y.observedAt)).pop(); if (!creatinine) return null; const notCalculated = (reason: EgfrNotCalculated) => ({ lab: null, output: { status: 'not_calculated' as const, reason, plain: NOT_CALCULATED_PLAIN[reason], formula: CKD_EPI_2021 } }); const age = input.subject.ageRange?.max ?? input.subject.age; if (age === null || age === undefined) return notCalculated('age_not_recorded'); if ((input.subject.ageRange?.min ?? age) < 18) return notCalculated('age_under_18'); const sex = sexForEquation(input.subject.sex); if (!sex) return notCalculated('sex_not_recorded'); if (creatinine.comparator) return notCalculated('creatinine_not_exact'); const unit = creatinine.unit ?? creatinine.evidence?.result.unit ?? null; const mgDl = creatinineMgDl(creatinine.value, unit); if (mgDl === null) return notCalculated('creatinine_unit_unknown'); const exact = ckdEpi2021(mgDl, age, sex); // Rounded to a whole number, as laboratories report eGFR. const value = Math.round(exact); return { lab: { code: 'egfr', value, observedAt: creatinine.observedAt, comparator: null }, output: { status: 'calculated', value, value_unrounded: Math.round(exact * 10) / 10, unit: 'mL/min/1.73m²', label: 'calculated', formula: CKD_EPI_2021, observed_at: creatinine.observedAt, inputs: { creatinine: { value: creatinine.value, unit: unit!, value_mg_dl: Math.round(mgDl * 100) / 100, observed_at: creatinine.observedAt }, age, sex } }, }; }