import type { Appointment, Patient, Organization, Reference } from "fhir/r4"; import type { PractitionerWithRoles } from "../services/PractitionerService.tsx"; /** The Organization ids from a patient's generalPractitioner list. * * An org is appended when the patient BOOKS an appointment there AND an active clinical authorization * exists, or the moment staff record that authorization in person - never on login. The comment here * used to say "on each cross-org login", which was true before the server moved the write to the * booking and attestation seams; read that way it makes a patient with no account look unauthorizable. * * Filters out non-Organization references (e.g. Practitioner) and de-duplicates, preserving first-seen * order. The entries it skips include the patient's own outside physician, which is a contained local * reference in this same field. */ export const extractOrganizationIds = (patient?: Patient | null): string[] => { const refs = patient?.generalPractitioner ?? []; const ids: string[] = []; for (const ref of refs) { const match = ref.reference?.match(/^Organization\/(.+)$/); if (match && !ids.includes(match[1])) ids.push(match[1]); } return ids; }; /** Puts the entry org (the site the patient logged in through) first; leaves the rest in place. */ export const sortEntryOrgFirst = (orgs: Organization[], entryOrgId?: string): Organization[] => { if (!entryOrgId) return orgs; const entry = orgs.filter(o => o.id === entryOrgId); const rest = orgs.filter(o => o.id !== entryOrgId); return [...entry, ...rest]; }; /** * An organization the server could not read, degraded to its identity alone. * * The list endpoint does this deliberately rather than omitting the row: its own comment says a * practice silently missing from a patient's list would have the patient conclude the practice is * gone. So it sends back what it knows - the resource type and the id - and leaves the rendering to * the client. * * The client rendered it as `organization.name || "Organization"`: a card titled "Organization", * carrying the same actions as a real one. Not a missing card and not an error, but a practice that * looks like it exists and has no name, indistinguishable from one that loaded - on the screen whose * whole job is to be the patient's true map of their own care. That is the denial rule broken in the * direction it warns about, a failure rendering as data. * * THE TEST IS THE ABSENCE OF A NAME, and it is the server's own degrade shape rather than a guess: it * returns `{ resourceType, id }` and nothing else. A real practice on this list always has a name - * it is what the patient knows it by, and one without would be unusable here whatever the cause. * An id is required too, because a row with no id is not a row at all. */ export const isUnreadableOrganization = (org: Organization): boolean => Boolean(org.id) && (!org.name || Boolean(org.meta?.tag?.some( (tag) => tag.system === UNREADABLE_ORGANIZATION_TAG.system && tag.code === UNREADABLE_ORGANIZATION_TAG.code, ))); /** The server's mark on a practice it could not read; the stub may still carry the name the record holds. */ export const UNREADABLE_ORGANIZATION_TAG = { system: 'https://akashichealthrecords.com/fhir/CodeSystem/read-status', code: 'unreadable', } as const; /** * The practice a reference names: the patient's organization it points at, else its own display. * The display is a copy taken when the reference was written, so a renamed practice keeps its old name * there. Unknown stays undefined, so a row names nobody rather than a guess. Shared by the home queue * and the task list so one task names one practice on both. */ export const organizationNameOf = ( reference: Reference | undefined, organizations: Organization[], ): string | undefined => { const ref = reference?.reference; const id = ref?.startsWith("Organization/") ? ref.slice("Organization/".length) : undefined; const current = id ? organizations.find((candidate) => candidate.id === id)?.name?.trim() : undefined; return current || reference?.display?.trim() || undefined; }; /** The appointment's participant of one resource type, whole, for when its display has to be resolved. */ export const participantActor = (appointment: Appointment, resourceType: string): Reference | undefined => appointment.participant?.find((participant) => participant.actor?.reference?.startsWith(`${resourceType}/`), )?.actor; /** * The practice behind an appointment: its own Organization participant, else the organization its * clinician's PractitionerRole names. Nothing rather than the portal's own practice - a guessed mark * puts the wrong practice's colour on a visit. */ export const appointmentOrgRef = ( appointment: Appointment, practitioners: readonly PractitionerWithRoles[], ): Reference | undefined => { const own = participantActor(appointment, "Organization"); if (own?.display || own?.reference) return own; const practitionerId = appointment.participant ?.find((participant) => participant.actor?.reference?.startsWith("Practitioner/")) ?.actor?.reference?.slice("Practitioner/".length); if (!practitionerId) return undefined; const roles = practitioners.find((entry) => entry.practitioner.id === practitionerId)?.roles ?? []; // The role the booking names settles a clinician who works at more than one practice; without // it, their roles must agree or the practice is unknown. const bookedRoleId = participantActor(appointment, "PractitionerRole")?.reference?.slice("PractitionerRole/".length); const booked = bookedRoleId ? roles.find((role) => role.id === bookedRoleId)?.organization : undefined; if (booked?.reference || booked?.display) return booked; const named = roles.map((role) => role.organization).filter((organization) => organization?.reference || organization?.display); return new Set(named.map((organization) => organization?.reference || organization?.display)).size === 1 ? named[0] : undefined; };