import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; import { useHttpService } from "../services/HttpService.tsx"; import { environment } from "../environments/environment.ts"; import { relayServerFailure } from "../utils/fetchFailureMessage"; import type { QualityControlPosition } from "../utils/qualityControlNotice"; import type { DiagnosticPublicationStatus, DiagnosticReviewBasis } from "../utils/diagnosticPublicationOperation"; import { useSaveScope, type SaveOperation } from "../utils/saveOperation"; import { onViewerChange, viewerEpoch } from "../utils/writeThroughHold"; export interface EnteredResult { orderId?: string; patientId?: string; publicationOperation?: { kind: 'release' | 'addendum'; operationId: string }; publicationUnavailable?: boolean; publishedOperation?: { kind: 'release' | 'addendum'; operationId: string }; reviewBasis?: DiagnosticReviewBasis; reviewUnavailable?: boolean; operations?: { state: "available" | "unavailable"; items: DiagnosticPublicationStatus[]; complete: boolean; cursor?: string }; status?: "preliminary" | "final"; measurements: { name?: string; value?: string; unit?: string; flag?: string }[]; findings?: string; /** The line a reader acts on; absent on a report written before findings and impression were split. */ impression?: string; addenda?: { text?: string; at?: string; by?: string }[]; /** Who released (signed) the report and when, as the report records it; present only once it was released. */ released?: { by?: string; at?: string }; /** A report filed from a laboratory: which one, their report and version, and the laboratory's own status and time. */ fromLaboratory?: { laboratory?: string; theirReport: string; version: string; laboratoryStatus?: string; issued?: string }; /** Present only when somebody was rung about a critical finding at release. */ criticalFinding?: { told?: string; how?: string; at?: string }; // One entry per report version; absent with deliveriesUnavailable when the evidence could not be read. deliveries?: VersionDelivery[]; deliveriesUnavailable?: boolean; // This test's control position; unknown means the record could not be read, never that it passed. /** "outside" is a test another laboratory performed; this bench’s controls say nothing about it. */ qualityControl?: QualityControlPosition; /** On a released result, the position stamped when it went out; null on one released before positions were stamped. */ qualityControlAtRelease?: QualityControlPosition | null; /** The stored report edition a correction is entered on; absent when it could not be named. */ basis?: { reportId: string; versionId: string }; /** An entry of this result still being saved, by its operation only. */ entryOperation?: { state: "in-progress"; operationId: string }; entryRecovery?: import('../utils/resultEntryOperation').EntryRecovery; entrySettlements?: { operationId: string; outcome: 'completed' | 'superseded' | 'refused' }[]; } export interface ResultDelivery { outcome: "delivered" | "failed" | "unconfirmed"; recipient?: string; method?: string; reference?: string; at?: string; /** Why an attempt was not filed, as the server wrote it for the laboratory. */ reason?: { code: string; text: string }; } export interface VersionDelivery { version: number; delivery?: ResultDelivery; } export const RESULT_UNREADABLE = "The entered result could not be read. Nothing can be released until it is."; const pendingForOrder = (value: unknown, orderId: string): NonNullable | undefined => { if (!value || typeof value !== 'object') return undefined; const body = value as { orderId?: unknown; entryOperation?: { state?: unknown; operationId?: unknown } }; const pending = body.entryOperation; if (body.orderId === orderId && pending?.state === 'in-progress' && typeof pending.operationId === 'string' && (pending.operationId === 'legacy' || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(pending.operationId))) { return { state: 'in-progress', operationId: pending.operationId }; } return undefined; }; /** What was entered on one order, read for review before release. */ export const useDiagnosticResult = (orderId?: string) => { const { get } = useHttpService(); const epoch = useSyncExternalStore(onViewerChange, viewerEpoch); const opening = useRef({ orderId, epoch }); if (opening.current.orderId !== orderId || opening.current.epoch !== epoch) opening.current = { orderId, epoch }; const owner = opening.current; const identity = JSON.stringify([orderId, epoch]); const saves = useSaveScope(identity); const active = useRef(); const [readState, setReadState] = useState<{ owner: typeof owner; result?: EnteredResult; error?: string; entryPending?: NonNullable; loading: boolean; }>({ owner, loading: Boolean(orderId) }); const current = orderId && readState.owner === owner && viewerEpoch() === epoch ? readState : undefined; const result = current?.result; const read = useCallback((cursor?: string) => { if (opening.current !== owner || viewerEpoch() !== epoch) return; active.current?.retire(); if (!orderId) { setReadState({ owner, loading: false }); return; } const operation = saves.start(); active.current = operation; const belongs = () => operation.belongs() && active.current === operation && opening.current === owner && viewerEpoch() === epoch; setReadState(previous => ({ owner, loading: true, ...(cursor && previous.owner === owner ? { result: previous.result } : {}) })); const url = `${environment.fhirBaseApi}/diagnostic-order/${orderId}/result${cursor ? `?operationCursor=${encodeURIComponent(cursor)}` : ''}`; void operation.step(() => get(url, operation.transport)) .then((answer) => { if (!belongs()) return; if (answer?.entryOperation !== undefined) { setReadState({ owner, entryPending: pendingForOrder(answer, orderId), error: RESULT_UNREADABLE, loading: false }); return; } if (!answer || !['preliminary', 'final'].includes(answer.status ?? '') || !Array.isArray(answer.measurements) || answer.entryRecovery !== undefined || !answer.measurements.every(item => item && typeof item === 'object' && [item.name, item.value, item.unit].every(value => value === undefined || typeof value === 'string'))) { throw new Error(RESULT_UNREADABLE); } setReadState(previous => { if (cursor && (previous.owner !== owner || previous.result?.operations?.cursor !== cursor)) return previous; const operations = cursor && answer.operations ? { ...answer.operations, items: [...(previous.result?.operations?.items ?? []), ...answer.operations.items].filter((status, index, all) => all.findIndex(other => other.operationId === status.operationId && other.kind === status.kind) === index), } : answer.operations; return { owner, result: { ...answer, operations }, loading: false }; }); }) .catch((failure: unknown) => { if (!belongs()) return; const failed = failure as { status?: unknown; body?: unknown }; const entryPending = failed?.status === 409 ? pendingForOrder(failed.body, orderId) : undefined; setReadState({ owner, entryPending, error: relayServerFailure(failure, RESULT_UNREADABLE), loading: false }); }) .finally(() => { if (belongs()) setReadState(previous => previous.owner === owner ? { ...previous, loading: false } : previous); operation.retire(); }); }, [get, orderId, epoch, owner, saves]); useEffect(() => { read(); return () => active.current?.retire(); }, [read]); return { result, error: current?.error, loading: current?.loading ?? Boolean(orderId), entryPending: current?.entryPending, reload: () => read(), loadMore: () => read(result?.operations?.cursor) }; };