import type { Appointment as FhirAppointment } from "fhir/r4"; import { holdDelete, holdWrite } from "../../../utils/writeThroughHold"; // Realtime appointment-broadcast helpers for the receptionist overview ( / AHR-225). // // The appointment.{created,updated,deleted} broadcast bundle already carries the full Appointment resource, so // the receptionist list can be updated straight from the payload - immediately consistent. The previous handler // instead re-fetched via a HealthLake search, which is eventually consistent: for ~1-2s after the POST the new // Appointment isn't searchable, so a patient self-booking appeared only after a manual refresh. /** * Pull the Appointment resource out of a broadcast CloudEvent's bundle (entries can also contain Slots). * Returns undefined if the payload is absent/malformed (caller falls back to a refetch). */ export function extractAppointmentFromBroadcast(cloudEvent: unknown): FhirAppointment | undefined { const data = (cloudEvent as { data?: { entry?: Array<{ resource?: { resourceType?: string } }> } })?.data; const match = (data?.entry ?? []).find((entry) => entry.resource?.resourceType === "Appointment"); return match?.resource as FhirAppointment | undefined; } /** * Merge a broadcast Appointment into the receptionist's appointment list, de-duped by id: remove on a delete * event, otherwise insert-or-replace at the front. Order is irrelevant - every consumer memo re-sorts by start * time - so a plain front insert keeps the merge simple and idempotent. */ export function mergeBroadcastAppointment( current: FhirAppointment[], appointment: FhirAppointment, isDelete: boolean, ): FhirAppointment[] { const withoutThis = current.filter((existing) => existing.id !== appointment.id); return isDelete ? withoutThis : [appointment, ...withoutThis]; } /** * Hold a broadcast appointment in the shared hold until the search can see it, as a booking made on this screen * is held. A patient's online booking arrives only as a broadcast, and the search that follows it lags by minutes. * A delete is held as one, so the lagging search cannot show it again. Returns false when the payload carries no * appointment. */ export function holdBroadcastAppointment(cloudEvent: unknown, now = Date.now()): boolean { const appointment = extractAppointmentFromBroadcast(cloudEvent); if (!appointment?.id) return false; if ((cloudEvent as { type?: string })?.type === "com.akashichealthrecords.fhir.appointment.deleted") { holdDelete({ resourceType: "Appointment", id: appointment.id }, now); } else { holdWrite(appointment, now); } return true; }