// The fixture's reading and check: the preview's real local reader and the real engine,
// wrapped in the /api/clinician shapes. Clinician wording for the film's two rules is the PRD's.
import type * as T from "../../src/api/types";
import type { Source } from "../../src/types";
import { assess } from "../../server/context";
import { extract, modelReady } from "../../server/model";
import { findingEvidence, patientProfile } from "../../src/flow";
import { notCheckedLines } from "../../src/logic/checks";
import { withEngineOptions } from "./engine-options";
import type { StoredRecord } from "./data";
import { nextId } from "./data";
export type StoredExtraction = T.Extraction & { sources: Source[] };
export const toSources = (records: StoredRecord[]): Source[] =>
records
.filter((r) => r.reading === "read" && r.text)
.map((r) => ({ id: r.id, title: r.title, kind: r.type, date: r.recordDate ?? "", text: r.text!, ...(r.clarifies ? { clarification: r.clarifies } : {}) }));
/** Reads with the configured model (Meta Muse Spark) when there is one, else the local structured reader. */
export async function readRecords(patientId: string, records: StoredRecord[]): Promise<StoredExtraction> {
const sources = toSources(records);
const draft = await extract(sources, modelReady());
return {
...draft,
id: nextId("ext"),
patientId,
revision: 0,
createdAt: new Date().toISOString(),
records: records.filter((r) => !r.clarifies).map((r) => ({
recordId: r.id,
title: r.title,
status: r.heldBack ? "held_back" : r.reading === "read" ? "read" : r.reading === "failed" ? "failed" : "queued",
error: r.reading === "failed" ? r.readingErrorCode ?? "READ_FAILED" : null,
})),
titles: { [`profile-${patientId}`]: "Patient profile", ...Object.fromEntries(records.map((r) => [r.id, r.title])) },
recordsNotReady: records.filter((r) => r.heldBack || r.reading !== "read").map((r) => ({ recordId: r.id, title: r.title, status: r.heldBack ? "held_back" : r.reading })),
decisions: {},
coverageReview: {},
acknowledgedOriginals: false,
confirmedHardToRead: [],
stale: false,
sources,
};
}
const day = (iso: string) => new Date(`${iso}T12:00:00Z`).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric", timeZone: "UTC" });
const CLINICIAN_WORDING: Record<string, (facts: T.FactRef[], placeOf: (recordId: string) => string) => { title: string; explanation: string }> = {
"allergy-penicillin": (facts, placeOf) => {
const medicine = facts.find((f) => f.factId && /amoxicillin|ampicillin|augmentin|ampiclox/i.test(f.label));
const allergy = facts.find((f) => /penicillin/i.test(f.label) && !/amoxicillin/i.test(f.label));
return {
title: `${medicine?.label ?? "Amoxicillin"} and a recorded Penicillin allergy`,
explanation: `${medicine?.label ?? "Amoxicillin"} is in the penicillin class. A Penicillin allergy is recorded${allergy ? ` (${placeOf(allergy.recordId)})` : ""}. Its reaction and severity are not in a form the rules read.`,
};
},
"lab-metformin-egfr-45": (facts, placeOf) => {
const egfr = facts.find((f) => /egfr/i.test(f.label));
const date = egfr?.detail.split(" · ")[1];
return {
title: "Metformin and the latest eGFR result",
explanation: `${egfr ? `${egfr.label} (${placeOf(egfr.recordId)}, ${date ? day(date) : "date not recorded"})` : "The latest eGFR"} is in the range where the Metformin label limits the dose to 1,000 mg a day.`,
};
},
};
const plainTitle = (title: string) => title.replace(/^(Call today|Get help now|Ask this week|Good to know):\s*/i, "").replace(/\btheir\b/g, "a recorded");
export function runCheckRun(
ex: StoredExtraction,
records: StoredRecord[],
ranBy: string,
withheldSome: boolean,
newMedicineNames: string[],
reactions: Record<string, { reaction: string | null; onset: string | null; year: number | null }> = {},
): T.CheckRun {
const assessment = assess(ex.sources, ex.decisions, ex.fingerprint, ex.acknowledgedOriginals, ex, ex.coverageReview);
const title = (id: string) => records.find((r) => r.id === id)?.title ?? "Record";
const refs = assessment.contextual.what_we_checked.references;
const details: Record<string, T.FindingDetail> = {};
for (const finding of assessment.contextual.findings) {
const facts: T.FactRef[] = findingEvidence(assessment, finding).map((f) => ({
factId: f.id,
label: f.label,
detail: f.detail,
recordId: f.sourceId,
recordTitle: title(f.sourceId),
line: f.line,
quote: f.quote,
}));
const placeOf = (id: string) => records.find((r) => r.id === id)?.place ?? title(id);
const wording = CLINICIAN_WORDING[finding.ruleId]?.(facts, placeOf) ?? { title: plainTitle(finding.title), explanation: finding.clinical || finding.why };
details[finding.id] = {
findingId: finding.id,
ruleId: finding.ruleId,
ruleReviewed: assessment.contextual.rulesReviewed,
// As the backend: the engine's clinician option wrote the film's two, the rest come from the rule's note.
wording: CLINICIAN_WORDING[finding.ruleId] ? "engine" : "derived",
clinicianTitle: wording.title,
clinicianExplanation: wording.explanation,
references: finding.source_ids.map((id) => {
const ref = refs.find((r) => r.id === id);
return { name: ref?.name ?? id, version: null, section: null, date: null, url: ref?.url ?? null };
}),
factsUsed: facts,
questions: [],
};
}
const profile = patientProfile(assessment);
const missingFactors: T.MissingFactor[] = profile
.filter((p) => p.status === "missing")
.map((p) => ({ id: p.id, label: p.label, reason: p.note, questions: [] }));
const meds = assessment.includedFacts.filter((f) => f.kind === "medicine");
const monitoring: T.CheckRun["monitoring"] = assessment.contextual.tests_to_ask_about.map((t) => ({
test: t.test,
medicines: [...new Set(meds.filter((m) => t.why.toLowerCase().includes(m.label.toLowerCase())).map((m) => m.label))],
onFile: t.last_result_at,
status: t.status,
}));
if (missingFactors.some((m) => m.id === "weight"))
monitoring.push({ test: "Weight", medicines: [], onFile: null, status: "no_result" });
const products = Object.values(assessment.coverageReview ?? {}).length
? (assessment.coverage ?? []).filter((c) => assessment.coverageReview?.[c.id]).map((c) => c.text)
: [];
const notChecked: T.NotChecked = {
productsWithoutRules: [...new Set(products)],
unconfirmedFacts: assessment.excludedFacts.length + ex.unparsed.length,
rulesAwaitingReview: assessment.contextual.rulesReviewed ? 0 : 8,
checkInAnswers: true,
other: assessment.contextual.what_we_couldnt_check.some((c) => c.reason === "condition")
? [`Conditions are recorded but no rule reads them: ${assessment.contextual.what_we_couldnt_check.filter((c) => c.reason === "condition").map((c) => c.item).join(", ")}.`]
: [],
};
const egfrFact = assessment.includedFacts.find((f) => f.kind === "lab" && /^egfr /i.test(f.label));
const egfr = egfrFact ? { value: Number(egfrFact.label.split(" ")[1]), date: egfrFact.detail.split(" · ")[1] ?? "" } : null;
assessment.contextual = withEngineOptions(
assessment.contextual,
Object.fromEntries(Object.values(details).map((d) => [d.findingId, { title: d.clinicianTitle, explanation: d.clinicianExplanation }])),
reactions,
meds.map((m) => ({ id: m.id, label: m.label, detail: m.detail })),
notCheckedLines(notChecked, assessment.contextual.rulesReviewed),
egfr && Number.isFinite(egfr.value) ? egfr : null,
);
const before = assessment.baseline.findings.length;
const after = assessment.contextual.findings.length;
const added = assessment.addedRuleIds.length;
const urgentIds = assessment.contextual.findings.filter((f) => f.tier === "get_help_now").map((f) => f.id);
return {
id: nextId("chk"),
patientId: ex.patientId,
extractionId: ex.id,
revision: ex.revision,
ranAt: new Date().toISOString(),
ranBy,
checkedOn: new Date(Date.now() + 3_600_000).toISOString().slice(0, 10),
summary: {
before,
after,
added,
headline: added ? `Personalized: ${added} ${added === 1 ? "finding" : "findings"} the medication list alone did not show.` : after ? `${after} findings for clinician review.` : "No findings. This does not mean the prescription is safe.",
subline: "Each finding links back to the record lines it came from. The clinician makes every decision.",
},
urgentReview: { show: urgentIds.length > 0, findingIds: urgentIds, line: urgentIds.length ? "Urgent review: phone the patient before the next dose." : null },
latest: true,
newInformation: null,
informationTool: "MARMAR Context shows information and where it came from. The clinician makes every decision.",
assessment,
recordTitles: Object.fromEntries(records.map((r) => [r.id, r.title])),
details,
marks: [],
missingFactors,
newMedicineIds: meds.filter((m) => newMedicineNames.includes(m.label.toLowerCase())).map((m) => m.id),
monitoring,
notChecked,
withheldSome,
versions: {
engine: assessment.contextual.engineVersion,
rules: assessment.contextual.rulesVersion,
rulesReviewed: assessment.contextual.rulesReviewed,
signedOffOnly: false,
optionsVersion: null,
},
};
}
/** The printable page the backend answers for format=pdf (it has no PDF library): escaped text, no scripts. */
export function printableHtml(title: string, lines: string[]): string {
const esc = (s: string) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${esc(title)}</title></head><body><h1>${esc(title)}</h1>${lines
.map((l) => (l ? `<p>${esc(l)}</p>` : ""))
.join("")}</body></html>`;
}
/** The readable export: patient, sources, decisions, both results, findings, marks, versions. */
export function exportLines(check: T.CheckRun, patientName: string, clinicianName: string, records: StoredRecord[]): string[] {
const a = check.assessment;
const lines = [
`Patient: ${patientName}`,
`Checked: ${check.ranAt} by ${clinicianName}`,
`Engine ${check.versions.engine}, rules ${check.versions.rules}${check.versions.rulesReviewed ? "" : " (rules not yet reviewed)"}`,
"",
`The medication list alone: ${a.baseline.findings.length} findings. Personalized: ${a.contextual.findings.length} findings.`,
a.contextual.findings.length ? "" : "No findings. This does not mean the prescription is safe.",
"Findings, for clinician review:",
...a.contextual.findings.flatMap((f) => {
const d = check.details[f.id];
const mark = check.marks.filter((m) => m.findingId === f.id).at(-1);
return [
`- ${d?.clinicianTitle ?? f.title} (${f.tier})`,
` ${d?.clinicianExplanation ?? f.clinical}`,
...(d?.factsUsed ?? []).map((x) => ` Based on: ${x.label}, ${x.recordTitle} line ${x.line}: "${x.quote}"`),
...(d?.references ?? []).map((r) => ` Reference: ${r.name}${r.version ? `, ${r.version}` : ", version not recorded"}`),
mark ? ` Marked ${mark.kind.replace("_", " ")} by ${mark.by}${mark.reason ? `: ${mark.reason}` : ""}` : " Not marked",
];
}),
"",
"Records and review decisions:",
...records.map((r) => `- ${r.title}${r.place ? `, ${r.place}` : ""}${r.recordDate ? `, ${r.recordDate}` : ""}`),
...a.includedFacts.map((f) => ` Included: ${f.label} (${f.detail})`),
...a.excludedFacts.map((f) => ` Excluded: ${f.label} (${f.detail})`),
"",
"Not checked:",
...check.notChecked.productsWithoutRules.map((p) => `- ${p}: recorded, no rules for this product yet`),
`- ${check.notChecked.unconfirmedFacts} facts or lines not confirmed for the check`,
`- ${check.notChecked.rulesAwaitingReview} rule changes waiting for clinical review`,
"- Answers from check-ins: no rule reads them",
...check.notChecked.other.map((o) => `- ${o}`),
"",
"MARMAR Context shows information and where it came from. The clinician makes every decision.",
];
return lines;
}