// What the engine adds with its clinician options on (feat/engine-clinician-context,
// docs/engine-clinician-options.md), for the fixture's film patients. Fictional use of draft,
// unreviewed engine output; the specific references are the ones that branch records.
import type * as T from "../../src/api/types";
export const SPECIFIC_REFERENCES: Record<string, T.EngineSpecificReference[]> = {
"lab-metformin-egfr-45": [
{
sourceId: "smpc-metformin",
document: "Glucophage 500 mg film-coated tablets: UK SmPC (Merck)",
section: "4.2 Posology and method of administration: renal impairment table",
version: "Date of revision of the text: November 2025 (emc, updated 22 December 2025)",
url: "https://www.medicines.org.uk/emc/product/987/smpc",
checkedOn: "2026-10-30",
says: "GFR 30-44 mL/min: total maximum daily dose 1000 mg, divided into 2-3 doses. GFR 45-59 mL/min: 2000 mg.",
},
],
"allergy-penicillin": [
{
sourceId: "smpc-amoxicillin",
document: "Amoxicillin 500mg Capsules: UK SmPC (Aurobindo Pharma - Milpharm Ltd)",
section: "4.3 Contraindications",
version: "emc, updated 1 April 2025",
url: "https://www.medicines.org.uk/emc/product/526/smpc",
checkedOn: "2026-10-30",
says: "Contraindicated with hypersensitivity to the active substance or to any of the penicillins, or a history of a severe immediate hypersensitivity reaction (for example anaphylaxis) to another beta-lactam.",
appliesTo: ["amoxicillin"],
},
{
sourceId: "nice-cg183",
document: "NICE clinical guideline CG183: Drug allergy: diagnosis and management",
section: "Recommendations 1.2.2 and 1.2.6",
version: "Published 3 September 2014",
url: "https://www.nice.org.uk/guidance/cg183/chapter/Recommendations",
checkedOn: "2026-10-30",
says: "Record the drug, the signs, symptoms and severity of the reaction and its date; check and confirm drug allergy status before prescribing.",
},
],
};
type Result = T.CheckRun["assessment"]["contextual"];
const bp = /amlodipine|lisinopril|losartan|nifedipine/i;
/**
* Decorate a plain engine result as the options would: clinician wording marked draft, rule review,
* specific references, the allergy reaction, monitoring medicines, the metformin kidney limit and
* coverage lines. `reactions` maps an allergy to its shared detail (from MARMAR Health).
*/
export function withEngineOptions(
result: Result,
wording: Record<string, { title: string; explanation: string }>,
reactions: Record<string, { reaction: string | null; onset: string | null; year: number | null }>,
medicines: { id: string; label: string; detail: string }[],
coverageLines: string[],
egfr: { value: number; date: string } | null,
): Result & T.EngineResultExtras {
const findings = result.findings.map((f): typeof f & T.EngineFindingExtras => {
const extra: T.EngineFindingExtras = {
clinician_title: wording[f.id]?.title,
clinician_why: wording[f.id]?.explanation,
clinician_wording_review: "draft",
rule_review: "not_in_accepted_sign_off",
specific_references: SPECIFIC_REFERENCES[f.ruleId] ?? [],
};
if (f.kind === "allergy") {
const allergy = result.what_we_checked.allergies[0] ?? "Penicillin";
const detail = reactions[allergy.toLowerCase()] ?? null;
const mild = !!detail?.reaction && /^(hives|rash|itch|itching|itchy rash)$/i.test(detail.reaction);
extra.allergy_reaction = {
allergy,
reaction: detail?.reaction ?? null,
reaction_kind: null,
onset: detail?.onset === "immediate" || detail?.onset === "delayed" || detail?.onset === "unknown" ? detail.onset : null,
severity: null,
year: detail?.year ?? null,
reaction_class: mild ? "not_severe" : "unknown",
};
extra.tier_without_reaction = f.tier;
if (!mild) return { ...f, ...extra, tier: "get_help_now" };
}
return { ...f, ...extra };
});
const tests = result.tests_to_ask_about.map((t) => ({
...t,
medicines: medicines
.filter((m) => t.why.toLowerCase().includes(m.label.toLowerCase()) || (/blood pressure/i.test(t.test) && bp.test(m.label)))
.map((m) => ({ id: m.id, name: m.label })),
rule_ids: [t.source_id],
}));
// CHK-11: the Glucophage SmPC bands; at 60 and over the usual maximum applies, as before.
const band = !egfr ? null : egfr.value < 30 ? { band: "under 30", max: null } : egfr.value < 45 ? { band: "30-44", max: 1000 } : egfr.value < 60 ? { band: "45-59", max: 2000 } : null;
const dose = result.dose_check.map((d) => {
if (!/metformin/i.test(d.medicine) || !band || !egfr) return d;
const med = medicines.find((m) => m.label.toLowerCase() === d.medicine.toLowerCase());
const strength = Number(/(\d+)\s*mg/i.exec(med?.detail ?? "")?.[1] ?? 0);
const units = Number(/(\d+(?:\.\d+)?)\s*(tablet|capsule)/i.exec(med?.detail ?? "")?.[1] ?? 0);
const perDay = Number(/(\d+(?:\.\d+)?)\/day/.exec(med?.detail ?? "")?.[1] ?? 0);
const daily = strength && units && perDay ? strength * units * perDay : null;
const over = band.max === null || (daily !== null && daily > band.max);
return {
...d,
status: over ? ("above_limit" as const) : ("within_usual_range" as const),
plain: band.max === null
? `The label lists metformin as not for use at eGFR under 30 mL/min.`
: `${over ? "Above" : "Within"} the label's limit for eGFR ${band.band} mL/min (${band.max.toLocaleString("en")} mg a day). This does not confirm the right dose to take.`,
kidney_limit: { ingredient: "metformin", egfr: egfr.value, egfr_comparator: null, egfr_observed_at: egfr.date, egfr_calculated: false, band: band.band, max_daily_mg: band.max, daily_mg: daily, source_id: "smpc-metformin", section: "4.2" },
};
});
return {
...result,
findings,
tests_to_ask_about: tests,
dose_check: dose,
clinician: { options_version: "0.1.0-draft", options: { clinicianWording: true, allergyTiers: true, kidneyDoseLimits: true, monitoringMedicines: true, coverage: true, specificReferences: true }, wording_review: "draft", plain: "Clinician wording and options are drafts until the clinical safety lead signs them off." },
coverage: {
products_not_checked: [],
facts_not_read: [],
facts_missing: [],
rules_waiting_for_review: findings.map((f) => ({ rule_id: f.ruleId, kind: "table" as const, review: "not_in_accepted_sign_off" as const, medicines: f.medicines, ran: true })),
check_ins: { read: false, plain: "Answers from check-ins are recorded as notes; no rule reads them." },
lines: coverageLines,
},
};
}