import type { DiagnosticOrderQueueRow } from "../hooks/useDiagnosticOrderQueue"; import { progressionFor } from "./diagnosticOrderQueue"; /** * How far one order has got, as a queue row says it: its stage, whether its result is released or it was sent out, * the latest instant it records, and the return it carries. The queue is a search that lags its writes, so a later * read can carry an older copy of an order; comparing progress is how such a copy is told apart from a real change. */ export interface OrderProgress { rank: number; released: boolean; sent: boolean; latest: number; returnVersion: number; returnOutcome?: string; /** When that return was last attempted, where the evidence says. */ returnAt: number; } const instant = (value?: string): number => { const at = Date.parse(value ?? ""); return Number.isFinite(at) ? at : 0; }; export const progressOf = (row: DiagnosticOrderQueueRow): OrderProgress => ({ rank: row.state ? progressionFor(row.kind ?? "laboratory").indexOf(row.state) : -1, released: Boolean(row.report?.releasedAt) || (Boolean(row.resultOnFile) && row.awaitingRelease === false), sent: Boolean(row.sentOut) || Boolean(row.akashicRoute?.deliveredAt), latest: Math.max( instant(row.report?.writtenAt), instant(row.report?.releasedAt), instant(row.report?.addendedAt), instant(row.sentOut?.at), instant(row.performed?.at), instant(row.transmission?.at), instant(row.akashicRoute?.deliveredAt), instant(row.akashicRoute?.acceptedAt), instant(row.akashicRoute?.refusedAt), instant(row.akashicRoute?.returnedAt), instant(row.refusal?.recorded), instant(row.pastRefusal?.resolved), instant(row.rejection?.recorded), ), returnVersion: row.returned?.version ?? -1, returnOutcome: row.returned?.outcome, returnAt: row.returned?.at ? instant(row.returned.at) : row.returned?.after ? instant(row.returned.after) + 1 : 0, }); /** * Whether copy is older than held. Anything recorded later is never older; otherwise a copy earlier in the progression, * unreleased, unsent, or of an earlier return version is older. At one version a timed earlier attempt is older; untimed, * a fall from delivered to no evidence is. */ export const isOlderCopy = (copy: OrderProgress, held: OrderProgress): boolean => { if (copy.latest > held.latest) return false; if (copy.rank < held.rank) return true; if (held.released && !copy.released) return true; if (held.sent && !copy.sent) return true; if (copy.returnVersion < held.returnVersion) return true; if (copy.returnVersion !== held.returnVersion) return false; if (copy.returnAt && held.returnAt) return copy.returnAt < held.returnAt; return held.returnOutcome === "delivered" && copy.returnOutcome === "none"; }; /** Whether copy carries an earlier return than held: an earlier version, or an earlier recorded attempt at that version. */ export const returnIsOlder = (copy: OrderProgress, held: OrderProgress): boolean => copy.returnVersion < held.returnVersion || (copy.returnVersion === held.returnVersion && copy.returnAt > 0 && held.returnAt > 0 && copy.returnAt < held.returnAt); /** Whether `now` shows anything `before` did not: the order moved on, or its return changed. */ export const movedPast = (now: OrderProgress, before: OrderProgress): boolean => now.latest > before.latest || now.rank > before.rank || (now.released && !before.released) || (now.sent && !before.sent) || now.returnVersion > before.returnVersion || (now.returnVersion === before.returnVersion && now.returnOutcome !== before.returnOutcome); /** * The rows a read answered, keeping each held row the read carries only an older copy of. A held order the read left out * stays where keepMissing says its absence is not yet settled, since a search that lags can omit it for a while. */ export const mergeQueueRead = ( held: DiagnosticOrderQueueRow[], read: DiagnosticOrderQueueRow[], keepMissing: (row: DiagnosticOrderQueueRow) => boolean = () => false, ): DiagnosticOrderQueueRow[] => { const byOrder = new Map(held.filter((row) => row.orderId).map((row) => [row.orderId as string, row])); const answered = new Set(read.map((row) => row.orderId).filter(Boolean)); const merged = read.map((row) => { const kept = row.orderId ? byOrder.get(row.orderId) : undefined; if (!kept) return row; const copy = progressOf(row); const held = progressOf(kept); if (isOlderCopy(copy, held)) return kept; // Return evidence is read on its own: a newer refusal or entry in the row does not vouch for an older return. return returnIsOlder(copy, held) ? { ...row, returned: kept.returned } : row; }); return [...merged, ...held.filter((row) => row.orderId && !answered.has(row.orderId) && keepMissing(row))]; };