import type { Appointment, Encounter } from 'fhir/r4'; import { isArchived } from './archivedResource'; export const DAY_EVENT_TYPES = [ 'com.akashichealthrecords.fhir.appointment.created', 'com.akashichealthrecords.fhir.appointment.updated', 'com.akashichealthrecords.fhir.appointment.deleted', 'com.akashichealthrecords.fhir.encounter.created', 'com.akashichealthrecords.fhir.encounter.updated', 'com.akashichealthrecords.fhir.encounter.deleted', ] as const; export type DayEvent = | { kind: 'appointment'; removed: boolean; resource: Appointment } | { kind: 'encounter'; removed: boolean; resource: Encounter }; type Resource = { resourceType?: string; id?: string }; /** The payload's resource of this type: sent bare (Encounter) or inside a Bundle with its Slot (Appointment). */ const resourceIn = (data: unknown, resourceType: string): Resource | undefined => { const payload = data as (Resource & { entry?: Array<{ resource?: Resource }> }) | undefined; if (payload?.resourceType === resourceType) return payload; if (payload?.resourceType !== 'Bundle') return undefined; return (payload.entry ?? []).find((entry) => entry.resource?.resourceType === resourceType)?.resource; }; /** Reads a broadcast CloudEvent; undefined for anything the day boards do not show or a payload without an id. */ export const readDayEvent = (cloudEvent: { type?: string; data?: unknown } | undefined): DayEvent | undefined => { const match = /^com\.akashichealthrecords\.fhir\.(appointment|encounter)\.(created|updated|deleted)$/.exec(cloudEvent?.type ?? ''); if (!match) return undefined; const resource = resourceIn(cloudEvent?.data, match[1] === 'appointment' ? 'Appointment' : 'Encounter'); if (!resource?.id) return undefined; const removed = match[2] === 'deleted' || isArchived(resource); return match[1] === 'appointment' ? { kind: 'appointment', removed, resource: resource as Appointment } : { kind: 'encounter', removed, resource: resource as Encounter }; }; /** The list with `item` replacing its id, added when `keep` and absent, or dropped when not `keep`. */ export const applyById = (list: readonly T[], item: T, keep: boolean): T[] => { const rest = list.filter((entry) => entry.id !== item.id); if (!keep) return rest.length === list.length ? (list as T[]) : rest; return list.some((entry) => entry.id === item.id) ? list.map((entry) => (entry.id === item.id ? item : entry)) : [...list, item]; }; /** Whether an encounter belongs on a board showing these appointments (or is already on it). */ export const encounterBelongsToDay = ( encounter: Encounter, appointmentIds: ReadonlySet, shown: readonly Encounter[], ): boolean => shown.some((entry) => entry.id === encounter.id) || (encounter.appointment ?? []).some((ref) => appointmentIds.has(ref.reference?.split('/').pop() ?? '')); /** A fallback read for a missed event; the socket carries the day between reads. */ export const DAY_FALLBACK_READ_MS = 5 * 60_000;