import { medicationCoverage, unresolvedCoverage, validCoverageResolution, COVERAGE_VERSION, type CoverageReview } from "../src/coverage"; import { createHash } from "node:crypto"; import { runCheck, type CheckInput, type CheckMedicine, } from "../../../src/domain/checks/engine"; import { withoutGuessedPronouns } from "../../../src/clinician/context/neutral-voice"; import type { Assessment, Draft, Fact, Review, Source } from "../src/types"; import { conflicts } from "../src/review"; import { policyLines, INSTRUCTION_REASON } from "./source-policy"; import { validateClarifications } from "./clarifications"; export { conflicts } from "../src/review"; export const DEMO_DATE = "2026-10-23"; export const fingerprint = (sources: Source[]) => createHash("sha256").update(JSON.stringify(sources)).digest("hex"); const unknown = /^(unknown|not known|not sure|not recorded)$/i; const none = /^(none|no known (drug )?allergies|nkda)$/i; const fields = (line: string) => Object.fromEntries( line.split(";").map((part) => { const at = part.indexOf(":"); return [ part.slice(0, at).trim().toLowerCase(), part.slice(at + 1).trim(), ]; }), ); const dateValid = (date: string) => /^\d{4}-\d{2}-\d{2}$/.test(date) && !Number.isNaN(Date.parse(date)) && new Date(date).toISOString().slice(0, 10) === date && date <= DEMO_DATE; export function parseLine( quote: string, ): Omit | null { const colon = quote.indexOf(":"); if (colon < 0) return null; const type = quote.slice(0, colon).toLowerCase().trim(); const value = quote.slice(colon + 1).trim(); if (!value) return null; if (type === "patient") return { kind: "patient", label: value, detail: "Identity supplied by this synthetic record", key: "patient", problem: null, }; if (type === "age") return { kind: "age", label: `${value} years`, detail: "Recorded age", key: "age", problem: /^\d{1,3}$/.test(value) && +value <= 120 ? null : "Age is not a supported whole number.", }; if (type === "allergies" || type === "allergy") return { kind: "allergy", label: value, detail: unknown.test(value) ? "Unknown, not an absence of allergies" : none.test(value) ? "Explicitly recorded as none known" : "Reported allergy; reaction severity is not available", key: "allergy", problem: !none.test(value) && /\b(no|not|denies|possible|suspected|maybe|mother|father|sister|brother|daughter|son|wife|husband)\b|family history/i.test( value, ) && !unknown.test(value) ? "Qualified allergy text needs clarification. Use a confirmed positive entry, Unknown, or No known allergies." : null, }; if (type === "condition") return { kind: "condition", label: value, detail: "Retained for review; condition-specific assessment is not supported", key: `condition:${value.toLowerCase()}`, problem: /\b(no|not|denies|possible|suspected|maybe|mother|father|sister|brother|daughter|son|wife|husband)\b|family history/i.test( value, ) ? "Qualified or family condition history needs clarification before inclusion." : null, }; if (type === "weight") { const match = /^(\d{1,3}(?:\.\d)?)\s*kg$/i.exec(value); return { kind: "weight", label: match ? `${+match[1]} kg` : value, detail: "Recorded body weight", key: "weight", problem: match && +match[1] >= 2 && +match[1] <= 400 ? null : "Weight must be a number in kg. No conversion is assumed.", }; } if (type === "sex") return { kind: "sex", label: value.toLowerCase(), detail: "Recorded sex", key: "sex", problem: /^(female|male)$/i.test(value) ? null : "Use female or male as recorded. Other values are not mapped.", }; if (type === "pregnancy") { const state = value.toLowerCase(); return { kind: "pregnancy", label: state, detail: "Pregnancy and breastfeeding status", key: "pregnancy", problem: [ "pregnant", "breastfeeding", "planning pregnancy", "not pregnant", ].includes(state) ? null : "Use pregnant, breastfeeding, planning pregnancy or not pregnant.", }; } const f = fields(quote); const fieldNames = quote .split(";") .map((part) => part.slice(0, part.indexOf(":")).trim().toLowerCase()); const duplicateFields = new Set(fieldNames).size !== fieldNames.length; if (type === "medication") { const dose = /^([0-9]+(?:\.[0-9]+)?) (tablet|capsule)s?$/i.exec( f.dose ?? "", ); const freq = /^([0-9]+(?:\.[0-9]+)?)\/day$/.exec(f.frequency ?? ""); const extra = Object.keys(f).filter( (key) => ![ "medication", "strength", "form", "dose", "frequency", "origin", ].includes(key), ); const problem = duplicateFields ? "Repeated fields need resolution before this medication can be included." : !f.medication ? "Medication name is missing." : extra.length ? `Unsupported medication fields: ${extra.join(", ")}. Edit the source or exclude this row.` : f.dose && (!dose || +dose[1] <= 0 || +dose[1] > 20) ? "Dose cannot be converted reliably. Edit the source or exclude this row." : f.frequency && (!freq || +freq[1] <= 0 || +freq[1] > 24) ? "Frequency is unsupported. Use an explicit number/day." : f.origin && ![ "prescribed", "over_the_counter", "hospital", "unknown", ].includes(f.origin) ? "Medicine origin is unsupported." : f.form && !["tablet", "capsule"].includes(f.form) ? "This demo supports tablets and capsules only." : null; return { kind: "medicine", label: f.medication, detail: [ f.strength || "Strength unknown", f.dose || "Dose unknown", f.frequency || "Frequency unknown", ].join(" · "), key: `medicine:${f.medication.toLowerCase()}`, problem, }; } if (type === "lab") { const code = f.lab.toLowerCase(); const numeric = /^(<=|>=|<|>)?\s*(\d+(?:\.\d+)?)$/.test(f.value ?? ""); const unitOK = code === "egfr" ? /^ml\/min\/1\.73m(?:2|²)$/i.test(f.unit ?? "") : code === "inr" ? /^(ratio|unitless)$/i.test(f.unit ?? "") : code === "potassium" ? /^mmol\/l$/i.test(f.unit ?? "") : false; const problem = duplicateFields ? "Repeated fields need resolution before this result can be included." : !["egfr", "inr", "potassium"].includes(code) ? "This demo only maps eGFR, INR and potassium into the existing engine." : !numeric ? "The result is not a supported number or numeric bound." : !unitOK ? "The result unit is missing or unsupported. No conversion is assumed." : !dateValid(f.date ?? "") ? "A valid, non-future observation date is required." : Object.keys(f).some( (key) => !["lab", "value", "unit", "date"].includes(key), ) ? "This lab row has unsupported fields." : null; return { kind: "lab", label: `${f.lab} ${f.value ?? "?"}`, detail: `${f.unit ?? "Unit unknown"} · ${f.date ?? "Date unknown"}`, key: `lab:${code}`, problem, }; } return null; } export function extractLocal(sources: Source[]): Draft { validateClarifications(sources); const start = performance.now(); const facts: Fact[] = []; const unparsed: Draft["unparsed"] = []; const warnings: NonNullable = []; for (const source of sources) { policyLines(source.text).forEach(({ text: quote, line, blocked }) => { const index = line - 1; if (!quote) return; if (blocked) { warnings.push({ sourceId: source.id, line, reason: INSTRUCTION_REASON, blocked: true, }); unparsed.push({ sourceId: source.id, line, text: quote }); return; } const parsed = parseLine(quote); if (parsed) facts.push({ ...parsed, id: `${source.id}:${index}`, sourceId: source.id, quote, line: index + 1, }); else unparsed.push({ sourceId: source.id, line: index + 1, text: quote }); }); } return { facts, coverage: medicationCoverage(sources, facts, fingerprint(sources)), coverageVersion: COVERAGE_VERSION, unparsed, warnings, sourceReviewRequired: warnings.length > 0, mode: "local", model: null, durationMs: Math.round(performance.now() - start), fingerprint: fingerprint(sources), }; } const reproductive: Record = { pregnant: "pregnant", breastfeeding: "breastfeeding", "planning pregnancy": "planning_pregnancy", }; export function toInput(facts: Fact[]): CheckInput { const unique = facts.filter( (f, index) => facts.findIndex( (other) => other.key === f.key && other.label === f.label && other.detail === f.detail, ) === index, ); const medicines: CheckMedicine[] = unique .filter((f) => f.kind === "medicine") .map((fact) => { const f = fields(fact.normalizedLine ?? fact.quote); const dose = /^([0-9]+(?:\.[0-9]+)?) (tablet|capsule)s?$/i.exec( f.dose ?? "", ); const frequency = /^([0-9]+(?:\.[0-9]+)?)\/day$/.exec(f.frequency ?? ""); return { id: fact.id, name: f.medication, strength: f.strength || null, form: f.form || null, unitsPerDose: dose ? +dose[1] : 1, doseUnit: dose ? "unit" : "unknown", timesPerDay: dose && frequency ? +frequency[1] : null, prnMaxPerDay: null, origin: (f.origin || "unknown") as CheckMedicine["origin"], contentsKnown: true, }; }); const allergyFacts = unique.filter((f) => f.kind === "allergy"); const conditionFacts = unique.filter((f) => f.kind === "condition"); const allergies = allergyFacts .map((f) => f.label) .filter((v) => !unknown.test(v) && !none.test(v)); const conditions = conditionFacts .map((f) => f.label) .filter((v) => !unknown.test(v) && !none.test(v)); return { subject: { kind: "person", name: unique.find((f) => f.kind === "patient")?.label ?? null, sex: unique.find((f) => f.kind === "sex")?.label ?? null, age: unique.some((f) => f.kind === "age") ? parseInt(unique.find((f) => f.kind === "age")!.label, 10) : null, weightKg: unique.some((f) => f.kind === "weight") ? parseFloat(unique.find((f) => f.kind === "weight")!.label) : null, }, reproductive: reproductive[ unique.find((f) => f.kind === "pregnancy")?.label ?? "" ] ?? null, medicines, allergies, conditions, allergiesStatus: allergies.length ? "listed" : allergyFacts.some((f) => none.test(f.label)) ? "none" : "not_sure", conditionsStatus: conditions.length ? "listed" : conditionFacts.some((f) => none.test(f.label)) ? "none" : "not_sure", labs: unique .filter((f) => f.kind === "lab") .map((fact) => { const f = fields(fact.normalizedLine ?? fact.quote); const match = /^(<=|>=|<|>)?\s*(\d+(?:\.\d+)?)$/.exec(f.value)!; return { code: f.lab.toLowerCase(), value: +match[2], comparator: (match[1] || null) as "<" | "<=" | ">" | ">=" | null, observedAt: f.date, evidence: { result: { id: fact.id, code: f.lab.toLowerCase(), name: f.lab, value: +match[2], valueText: f.value, comparator: (match[1] || null) as "<" | "<=" | ">" | ">=" | null, unit: f.unit, observedAt: f.date, referenceRange: { low: null, high: null, text: null }, labFlag: null, }, target: null, }, }; }), labTargets: {}, symptoms: null, emergency: { number: null, extra: null }, language: "en", today: DEMO_DATE, }; } export function assess( sources: Source[], review: Review, expectedFingerprint: string, acknowledgeUnparsed: boolean, verifiedDraft?: Draft, coverageReview: CoverageReview = {}, ): Assessment { const start = performance.now(); const draft = verifiedDraft ?? extractLocal(sources); if ( draft.fingerprint !== expectedFingerprint || fingerprint(sources) !== expectedFingerprint ) throw new Error("Sources changed. Extract and review this version again."); if (Object.keys(review).some((id) => !draft.facts.some((f) => f.id === id))) throw new Error("Review contains a fact outside this source version."); if (draft.facts.some((f) => !["include", "exclude"].includes(review[f.id]))) throw new Error("Review every extracted fact before checking."); if ( (draft.unparsed.length || draft.sourceReviewRequired) && !acknowledgeUnparsed ) throw new Error( "Review the original notes for omissions and acknowledge unread lines before checking.", ); const coverage = medicationCoverage(sources, draft.facts, draft.fingerprint); for (const [id, decision] of Object.entries(coverageReview)) { const candidate = coverage.find((item) => item.id === id); if (!candidate || candidate.matchedFactIds.length || candidate.contextExclusion || !validCoverageResolution(decision, draft.fingerprint)) throw new Error("Invalid or stale medication coverage resolution. Review the current source version."); } const unresolved = unresolvedCoverage(coverage, coverageReview, draft.fingerprint); if (unresolved.length) throw new Error(`Unresolved medication coverage: ${unresolved.map((c) => `${c.text} (line ${c.line})`).join(", ")}. Add a confirmed medication clarification or record a specific exclusion reason.`); const includedFacts = draft.facts.filter((f) => review[f.id] === "include"); const excludedFacts = draft.facts.filter((f) => review[f.id] === "exclude"); if (includedFacts.some((f) => f.problem)) throw new Error("Correct or exclude unsupported facts before checking."); if (conflicts(includedFacts).length) throw new Error( "Conflicting facts remain. Include only the record you have chosen to use.", ); const chosenPatients = new Set( includedFacts .filter((f) => f.kind === "patient") .map((f) => f.label.toLowerCase()), ); const mismatchedSources = new Set( excludedFacts .filter( (f) => f.kind === "patient" && chosenPatients.size > 0 && !chosenPatients.has(f.label.toLowerCase()), ) .map((f) => f.sourceId), ); if ( includedFacts.some( (f) => f.kind !== "patient" && mismatchedSources.has(f.sourceId), ) ) throw new Error( "A record identifies a different patient. Exclude all clinical facts from that record; removing only the identity does not reconcile it.", ); if (!includedFacts.some((f) => f.kind === "medicine")) throw new Error("Include at least one medication to run the check."); const input = toInput(includedFacts); // Worded without a guessed he or she (the backend's neutral-voice.ts). const contextual = withoutGuessedPronouns(input, runCheck(input), runCheck); // A controlled comparison: the same medication list with nothing known about the person // (no age, weight, sex, pregnancy status, labs, allergies or conditions). const baseline = runCheck({ ...input, subject: { ...input.subject, age: null, weightKg: null, sex: null }, reproductive: null, allergies: [], allergiesStatus: "not_sure", conditions: [], conditionsStatus: "not_sure", labs: [], }); const baselineRules = new Set(baseline.findings.map((f) => f.ruleId)); return { coverage, coverageReview, coverageVersion: COVERAGE_VERSION, baseline, contextual, addedRuleIds: contextual.findings .filter((f) => !baselineRules.has(f.ruleId)) .map((f) => f.ruleId), includedFacts, excludedFacts, fingerprint: draft.fingerprint, checkedAt: new Date().toISOString(), durationMs: Math.round(performance.now() - start), sourceCount: sources.length, caveats: [ "Medication coverage uses bounded English patterns, not an exhaustive drug vocabulary or proof of completeness. Reviewer exclusions are self-reported.", "Synthetic demonstration, not a clinical decision or a verified EHR connection.", `Fixed scenario date: ${DEMO_DATE}. Real-time deployment has not been assessed.`, "Clinical review of later engine changes is pending. Software tests do not establish clinical accuracy.", "Condition history is carried forward but is not assessed by the existing rules. Coverage is incomplete.", ...(excludedFacts.length ? [ `${excludedFacts.length} extracted facts were excluded by the reviewer.`, ] : []), ...(draft.unparsed.length ? [ `${draft.unparsed.length} source lines could not be mapped and were not clinically assessed.`, ] : []), ], }; }