import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { Button } from "../../../components/ui/button.tsx"; import { CheckboxControl } from "../../../components/ui/checkbox-control.tsx"; import { Input } from "../../../components/ui/input.tsx"; import { Label } from "../../../components/ui/label.tsx"; import { SelectControl } from "../../../components/ui/select-control.tsx"; import { HttpError } from "../../../services/HttpService.tsx"; import { newOperationId, type RecordsBasis, type RecordsRequest, type RecordsRequestState } from "../../../services/RecordsService.tsx"; import { useRecordsReleaseService, type AthenaRecipientTuple, type AthenaRegistration, type ReleaseCallOwner, } from "../../../services/RecordsReleaseService.tsx"; import { heldConfirmation, holdConfirmation, releaseConfirmation, type HeldConfirmation } from "./athenaConfirmationHold.ts"; import { relayServerFailure } from "../../../utils/fetchFailureMessage"; interface Props { request: RecordsRequest; canStep: boolean; onChanged: (request: RecordsRequest) => void; } type Match = | { kind: "unique"; patientId: number; score: number; returned: number } | { kind: "ambiguous" | "none" | "malformed"; returned?: number }; type Frozen = HeldConfirmation; const SUBCLASSES = [ { value: "CLINICALDOCUMENT", label: "Clinical document" }, { value: "CONSULTNOTE", label: "Consult note" }, { value: "ADMISSIONDISCHARGE", label: "Admission or discharge" }, { value: "OPERATIVENOTE", label: "Operative note" }, { value: "URGENTCARE", label: "Urgent care" }, { value: "MENTALHEALTH", label: "Mental health" }, ]; const REOPENED = "A confirmation sent earlier has no known outcome yet. Check it before confirming another; it is applied at most once."; const FENCED = "The earlier confirmation's condition is no longer current; whether it was applied is not known here. Choose the recipient again to confirm it as a new change."; const matchWords = (match: Match): string => { if (match.kind === "unique") return `athena names one patient: athena patient ${match.patientId} (score ${match.score}).`; if (match.kind === "ambiguous") return "athena finds more than one plausible patient, so it cannot be sent there."; if (match.kind === "none") return "athena finds no patient with these details."; return "athena's answer about the patient could not be read."; }; const STATES = new Set(Object.keys({ received: true, "in-progress": true, "awaiting-approval": true, approved: true, delivering: true, "delivery-unconfirmed": true, delivered: true, draft: true, "awaiting-records": true, "records-received": true, filed: true, cancelled: true, } satisfies Record)); const BASES = new Set(Object.keys({ treatment: true, "patient-directed": true, authorization: true, other: true } satisfies Record)); const RECIPIENT_METHODS = new Set(["fax", "download", "print", "athena-document", "epic-hl7-document"]); const CHANNELS = new Set(["athena-document", "epic-hl7-document"]); const OWED_STEPS = new Set(["audit", "mirror", "provenance"]); const isObject = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); const isText = (value: unknown): value is string => typeof value === "string"; const absentOr = (value: unknown, ok: (value: unknown) => boolean) => value === undefined || ok(value); const textFields = (value: unknown, keys: string[]): value is Record => isObject(value) && keys.every((key) => isText(value[key])); const isParty = (value: unknown): value is Record => textFields(value, ["name"]) && ["organization", "fax", "email", "address"].every((key) => absentOr(value[key], isText)); /** * A current request row of exactly the held request at its practice: a whole object with a safe whole edition from 1, * every required field in its wire type, and each optional field the request's own view reads typed where present. * Nothing is stripped or filled in; a row that is not whole is not adopted and decides nothing. */ const isCurrentRequest = (value: unknown, of: { organizationId: string; requestId: string }): value is RecordsRequest => { if (!isObject(value)) return false; const row = value; return isText(row.requestId) && row.requestId !== "" && row.requestId === of.requestId && isText(row.organizationId) && row.organizationId !== "" && row.organizationId === of.organizationId && typeof row.version === "number" && Number.isSafeInteger(row.version) && row.version >= 1 && isText(row.lastOperationId) && row.lastOperationId !== "" && (row.direction === "incoming" || row.direction === "outgoing") && STATES.has(row.state as string) && (row.scope === "practice" || row.scope === "all") && ["createdAt", "createdBy", "updatedAt", "updatedBy"].every((key) => isText(row[key])) && isParty(row.counterpart) && absentOr(row.patient, (patient) => textFields(patient, ["id"])) && absentOr(row.recipient, (recipient) => isParty(recipient) && RECIPIENT_METHODS.has(recipient.method as string)) && absentOr(row.basis, (basis) => isObject(basis) && BASES.has(basis.kind as string) && absentOr(basis.note, isText)) && absentOr(row.dueAt, isText) && absentOr(row.claim, (claim) => textFields(claim, ["by", "until"])) && absentOr(row.manifest, (manifest) => textFields(manifest, ["manifestId", "digest"])) && absentOr(row.dispatch, (dispatch) => textFields(dispatch, ["attemptId"]) && CHANNELS.has(dispatch.channel as string)) && absentOr(row.evidenceOwed, (owed) => Array.isArray(owed) && owed.every((entry) => textFields(entry, ["operationId", "since"]) && OWED_STEPS.has(entry.step as string))) && absentOr(row.approval, (approval) => isObject(approval) && absentOr(approval.deliverables, (deliverables) => isObject(deliverables) && Object.values(deliverables).every(isText))); }; const where = (tuple: AthenaRecipientTuple) => `${tuple.name}, athena practice ${tuple.practiceId}, department ${tuple.departmentId}`; /** * Chooses an athena recipient from what the server lists, then confirms it with athena's match. Each lookup answers * only the opening and selection it was asked for. A confirmation whose answer is not known stays frozen as sent until * its own operation, or a later version, settles it; no other choice is confirmed meanwhile. */ const AthenaRecipientChooser = ({ request, canStep, onChanged }: Props) => { const { athenaRegistrations, athenaPreflight, athenaConfirmRecipient, readRequest } = useRecordsReleaseService(); const restored = () => { const held = heldConfirmation(request.organizationId, request.requestId); return held && { ...held, said: held.said ?? REOPENED }; }; const frozen = useRef(); const [frozenShown, setFrozenShown] = useState(() => (frozen.current = restored())); // The request and practice this opening holds for, fixed with the opening: a Check is judged against them only. const heldFor = useRef({ organizationId: request.organizationId, requestId: request.requestId }); const opening = useRef(null); const openingKey = request.organizationId + "/" + request.requestId; useLayoutEffect(() => { const controller = new AbortController(); opening.current = controller; heldFor.current = { organizationId: request.organizationId, requestId: request.requestId }; // A confirmation left unanswered by an earlier opening is still unanswered: it is restored, never presumed failed. frozen.current = restored(); setFrozenShown(frozen.current); return () => { controller.abort(); if (opening.current === controller) opening.current = null; }; }, [openingKey]); const generation = useRef(0); const [registrations, setRegistrations] = useState(); const [practice, setPractice] = useState(""); const [departments, setDepartments] = useState>(); const [departmentId, setDepartmentId] = useState(""); const [lookup, setLookup] = useState<{ match: Match; queryDigest: string }>(); const [matchConfirmed, setMatchConfirmed] = useState(false); const [name, setName] = useState(""); const [subclass, setSubclass] = useState("CLINICALDOCUMENT"); const [busy, setBusy] = useState(); const [said, setSaid] = useState(); const freeze = (next: Frozen | undefined) => { const was = frozen.current; frozen.current = next; setFrozenShown(next); if (next) holdConfirmation(request.organizationId, request.requestId, next); else if (was) releaseConfirmation(request.organizationId, request.requestId, was.write.operationId); }; /** A call for this opening; fenced also by the selection when it is a lookup. */ const capture = (fenced: boolean) => { const mine = opening.current; if (!mine) return undefined; const asked = generation.current; const alive = () => opening.current === mine && !mine.signal.aborted && (!fenced || generation.current === asked); const call: ReleaseCallOwner = { organizationId: request.organizationId, signal: mine.signal, admit: () => { if (!alive()) throw new DOMException("This choice was replaced.", "AbortError"); }, }; return { call, alive }; }; useEffect(() => { const run = capture(false); if (!run) return; athenaRegistrations(request.requestId, run.call) .then((listed) => { if (run.alive()) setRegistrations(listed); }) .catch(() => { if (run.alive()) setRegistrations("unreadable"); }); }, [openingKey]); // eslint-disable-line react-hooks/exhaustive-deps const [registrationId, practiceText] = practice.split("|"); const practiceId = Number(practiceText); /** Any change to what a lookup asks about retires every lookup still out. */ const reselect = () => { generation.current += 1; setLookup(undefined); setMatchConfirmed(false); setSaid(undefined); }; const choosePractice = (value: string) => { reselect(); setPractice(value); setDepartments(undefined); setDepartmentId(""); const [id, at] = value.split("|"); const run = capture(true); if (!run || !id || !Number(at)) return; athenaPreflight(request.requestId, { registrationId: id, practiceId: Number(at) }, run.call) .then((answer) => { if (run.alive()) setDepartments(answer.departments); }) .catch((failure) => { if (run.alive()) setSaid(relayServerFailure(failure, "athena's departments could not be read.")); }); }; const chooseDepartment = (value: string) => { reselect(); setDepartmentId(value); const run = capture(true); if (!run || !value) return; athenaPreflight(request.requestId, { registrationId, practiceId, departmentId: Number(value) }, run.call) .then((answer) => { if (run.alive() && answer.match && typeof answer.queryDigest === "string") setLookup({ match: answer.match as Match, queryDigest: answer.queryDigest }); }) .catch((failure) => { if (run.alive()) setSaid(relayServerFailure(failure, "athena's match could not be read.")); }); }; const confirmed = (tuple: AthenaRecipientTuple, current: RecordsRequest) => { freeze(undefined); reselect(); setPractice(""); setDepartments(undefined); setDepartmentId(""); setName(""); setSaid(`Confirmed: ${where(tuple)}.`); onChanged(current); }; /** Reads the request again: only this confirmation's own operation, or a later version, settles it. */ const check = async () => { const sent = frozen.current; const of = heldFor.current; const run = capture(false); if (!sent || !run) return; setBusy("check"); try { const read: unknown = await readRequest(of.requestId, run.call); if (!run.alive()) return; // Only a well-formed current row of the held request decides anything; anything else leaves it held as it was. if (!isCurrentRequest(read, of)) { freeze({ ...sent, said: "The request read back could not be used, so nothing is decided about the earlier confirmation. Check again." }); return; } const later = read.version > sent.write.expectedVersion; // Its own operation on an edition after the one it was conditioned on: the conditional write applied. if (later && read.lastOperationId === sent.write.operationId) confirmed(sent.tuple, read); else if (later) { freeze(undefined); setSaid(FENCED); onChanged(read); } else freeze({ ...sent, said: "The request is unchanged, so the earlier confirmation may still land. Check again, or retry the same." }); } catch { if (run.alive()) freeze({ ...sent, said: "The request could not be read. Nothing is decided about the earlier confirmation." }); } finally { if (run.alive()) setBusy(undefined); } }; /** Sends the frozen confirmation exactly as it was first sent. */ const apply = async () => { const sent = frozen.current; const run = capture(false); if (!sent || !run) return; setBusy("confirm"); setSaid(undefined); let recheck = false; try { const answer = await athenaConfirmRecipient(request.requestId, sent.write, sent.tuple, run.call); if (run.alive()) confirmed(sent.tuple, answer.request); } catch (failure) { if (!run.alive()) return; const body = failure instanceof HttpError ? (failure.body as { code?: unknown; message?: unknown } | undefined) : undefined; if (body?.code === "version-conflict") recheck = true; else if (body?.code === "operation-conflict") freeze({ ...sent, held: true, said: "This confirmation's id is tied to another change. Reload the request; nothing was adopted." }); // Refused before its write, or not answered: nothing is known about whether the first one applied. else freeze({ ...sent, said: failure instanceof HttpError ? relayServerFailure(failure, "athena's confirmation was refused.") : undefined }); } finally { if (run.alive()) setBusy(undefined); } if (recheck) await check(); }; const confirm = async () => { if (frozen.current || lookup?.match.kind !== "unique" || !matchConfirmed || !name.trim() || !registrationId) return; const tuple: AthenaRecipientTuple = { name: name.trim(), registrationId, practiceId, departmentId: Number(departmentId), documentSubclass: subclass, vendorPatientId: lookup.match.patientId, queryDigest: lookup.queryDigest, }; freeze({ tuple, write: { operationId: newOperationId(), expectedVersion: request.version } }); await apply(); }; // A held confirmation is shown whatever the listing says: no listing can answer it. const heldShown = frozenShown && ( <>

Confirming {where(frozenShown.tuple)}: not known yet. It is applied at most once.

{frozenShown.said &&

{frozenShown.said}

}
{!frozenShown.held && ( )}
); const listing = registrations === undefined ? "Reading which athena practices can receive records." : registrations === "unreadable" ? "Which athena practices can receive records could not be read. Nothing was sent." : registrations.length === 0 ? "Sending through athena is not available here. Nothing was sent." : undefined; if (listing !== undefined || typeof registrations !== "object") { return
{heldShown}

{listing}

; } const unique = lookup?.match.kind === "unique"; return (
{heldShown} {said &&

{said}

} registration.practices.map((entry) => ({ value: `${registration.id}|${entry.practiceId}`, label: `Practice ${entry.practiceId} (${registration.environment})`, })))} onChange={choosePractice} /> {departments && ( ({ value: String(department.departmentId), label: department.name }))} onChange={chooseDepartment} /> )} {lookup &&

{matchWords(lookup.match)}

} {unique && ( <> setMatchConfirmed(on)} />
setName(event.target.value)} />
{!frozenShown && (
)} )}
); }; export default AthenaRecipientChooser;