marmar-context / web / context / test / fixture / engine-options.ts
engine-options.ts
Raw
// 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,
    },
  };
}