import { implausibleResult } from '../labs/targets.js';
import type { CheckLab } from './engine.js';
import type { EngineHooks, KidneyDoseLine } from './options.js';
import { SPECIFIC_REFERENCES } from './sources.js';
/**
* CHK-11 (kidneyDoseLimits): metformin's kidney-adjusted maximum from the Glucophage 500 mg UK SmPC (Merck) 4.2 and 4.3,
* read 30 October 2026 (revised November 2025). The SmPC gives GFR in mL/min; the engine compares eGFR, as its lab rules do.
*/
export const METFORMIN_KIDNEY_BANDS: ReadonlyArray<{ band: string; from: number; below: number | null; maxDailyMg: number | null }> = [
{ band: 'under 30', from: -Infinity, below: 30, maxDailyMg: null },
{ band: '30-44', from: 30, below: 45, maxDailyMg: 1000 },
{ band: '45-59', from: 45, below: 60, maxDailyMg: 2000 },
// 60 and over: the adult maximum (3000 mg) applies, which the usual dose check already uses.
];
const REFERENCE = SPECIFIC_REFERENCES['kidney-dose-metformin']![0];
/** A result older than this isn't used, as the lab rules don't use it (engine STALE_RESULT_DAYS). */
const STALE_DAYS = 90;
const dateOf = (iso: string) => new Date(`${iso.slice(0, 10)}T00:00:00Z`).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' });
/** The band a result is in; for a value printed as a bound, the band only when every value it allows is in it. */
function bandOf(lab: Pick<CheckLab, 'value' | 'comparator'>) {
const low = lab.comparator === '<' || lab.comparator === '<=' ? -Infinity : lab.value;
const high = lab.comparator === '>' || lab.comparator === '>=' ? Infinity : lab.value;
if (low >= 60) return 'none' as const;
return METFORMIN_KIDNEY_BANDS.find((band) => low >= band.from
&& (band.below === null || high < band.below || (high === band.below && lab.comparator === '<'))) ?? ('unclear' as const);
}
/** The hook the engine calls for each medicine's dose line (EngineHooks.kidneyDose). */
export function kidneyDoseHook(labs: readonly CheckLab[], today: string, calculatedLab: object | null): NonNullable<EngineHooks['kidneyDose']> {
const egfr = [...labs].filter((lab) => lab.code === 'egfr').sort((x, y) => x.observedAt.localeCompare(y.observedAt)).pop();
return ({ key, total }) => {
if (key !== 'metformin' || !egfr) return null;
const ageDays = (Date.parse(`${today}T00:00:00Z`) - Date.parse(egfr.observedAt)) / 86_400_000;
if (ageDays > STALE_DAYS || implausibleResult('egfr', egfr.value)) return null;
const band = bandOf(egfr);
if (band === 'none') return null;
const printed = `${egfr.comparator ?? ''}${egfr.value}`;
const latest = `latest eGFR ${printed}${calculatedLab === egfr ? ', calculated from creatinine' : ''}, ${dateOf(egfr.observedAt)}`;
const base = { ingredient: 'metformin', egfr: egfr.value, egfr_comparator: egfr.comparator ?? null, egfr_observed_at: egfr.observedAt,
egfr_calculated: calculatedLab === egfr, daily_mg: total, source_id: REFERENCE.sourceId, section: REFERENCE.section };
if (band === 'unclear') {
return { status: 'check_with_pharmacist', contraindicated: false, kidney_limit: { ...base, band: 'unclear', max_daily_mg: null },
plain: `The latest eGFR was printed as "${printed}", so the label's kidney-adjusted maximum for metformin couldn't be worked out.` } satisfies KidneyDoseLine;
}
const limit = { ...base, band: band.band, max_daily_mg: band.maxDailyMg };
if (band.maxDailyMg === null) {
return { status: 'above_limit', contraindicated: true, kidney_limit: limit,
plain: `The metformin label lists metformin as contraindicated below an eGFR of 30 (${latest}).` };
}
if (total === null) return null;
const max = band.maxDailyMg.toLocaleString('en-GB');
if (total > band.maxDailyMg) {
return { status: 'above_limit', contraindicated: false, kidney_limit: limit,
plain: `About ${Math.round(total)} mg of metformin a day is more than the label's ${max} mg maximum for an eGFR of ${band.band.replace('-', ' to ')} (${latest}).` };
}
return { status: 'within_usual_range', contraindicated: false, kidney_limit: limit,
plain: `The daily amount entered does not exceed the label's ${max} mg maximum for an eGFR of ${band.band.replace('-', ' to ')} (${latest}). `
+ 'This does not confirm the right dose to take.' };
};
}