import type { Attachment, Bundle, DiagnosticReport } from "fhir/r4"; /** * A CORRECTED LAB REPORT, as its patient reads it. The server records every correction on the report * and decides which earlier versions reach the patient at all; a version that held somebody else's * results never arrives here, so nothing below filters for it. */ export const REPORT_CORRECTION_EXTENSION_URL = "https://akashichealthrecords.com/fhir/StructureDefinition/report-correction"; export interface CorrectionMarker { on?: string; reason?: string; } export interface EarlierVersion { versionId: string; when?: string; status: string; conclusion?: string; reports: Attachment[]; } const part = (entry: { extension?: { url?: string; valueString?: string; valueDateTime?: string }[] }, name: string) => entry.extension?.find((sub) => sub.url === name); // The newest recorded correction, or for a report corrected before corrections were recorded, its // status and the date it was last written, with no reason to give. export const correctionMarker = (report?: DiagnosticReport): CorrectionMarker | undefined => { if (!report) return undefined; const recorded = (report.extension ?? []) .filter((entry) => entry.url === REPORT_CORRECTION_EXTENSION_URL) .map((entry) => ({ on: part(entry, "at")?.valueDateTime, reason: part(entry, "reason")?.valueString?.trim() || undefined })) .sort((a, b) => (b.on ?? "").localeCompare(a.on ?? "")); if (recorded.length > 0) return recorded[0]; if (report.status === "corrected" || report.status === "amended") return { on: report.meta?.lastUpdated }; return undefined; }; const STATUS_WORDS: Record = { final: "Final", corrected: "Corrected", amended: "Amended", appended: "Added to", }; export const versionStatus = (status: string): string => STATUS_WORDS[status] ?? status; // Every version older than the one on screen, newest first. export const earlierVersions = (history: Bundle | undefined, current?: DiagnosticReport): EarlierVersion[] => { const currentVersion = Number(current?.meta?.versionId); return (history?.entry ?? []) .map((entry) => entry.resource) .filter((version): version is DiagnosticReport => version?.resourceType === "DiagnosticReport") .filter((version) => { const number = Number(version.meta?.versionId); return Number.isFinite(number) && (!Number.isFinite(currentVersion) || number < currentVersion); }) .sort((a, b) => Number(b.meta?.versionId) - Number(a.meta?.versionId)) .map((version) => ({ versionId: String(version.meta?.versionId), when: version.meta?.lastUpdated ?? version.issued, status: version.status, conclusion: version.conclusion, reports: (version.presentedForm ?? []).filter((form) => Boolean(form.url)), })); }; // What staff record when they correct a report: the kind and the reason. The server stamps when, by // whom and which version it replaces, so none of that is sent. export const correctionEntry = (kind: "correction" | "misattribution", reason: string) => ({ url: REPORT_CORRECTION_EXTENSION_URL, extension: [ { url: "kind", valueCode: kind }, { url: "reason", valueString: reason.trim() }, ], }); /** A misattribution withdraws the misfiled version's files: they are somebody else's, and the server refuses to keep them. */ export const withoutMisfiledFiles = (report: T, misattribution: boolean): T => misattribution ? (({ presentedForm: _withdrawn, ...rest }) => rest as T)(report) : report;