import type { Appointment, Coding } from 'fhir/r4'; // ยง6 - the Appointment's location decides the Encounter's class AT CREATION. // // already stamps `class = virtual` server-side when a video session is created, and that STAYS: // it is the backstop for a visit that becomes video without having been booked that way. This makes the // common case right from the start rather than retroactively. Without it, a video visit that is // documented and finished without anyone pressing "start video" is recorded as an in-person visit // forever - and the class is what reporting, billing and the patient's own history read. // // THE VIRTUAL CODING IS BYTE-IDENTICAL TO (`handlers/VideoSession/post.ts`). Two // representations of "virtual" on one resource type is the kind of reporting bug nobody notices until a // count comes out wrong. Change one, change both. export const AMBULATORY_ENCOUNTER_CLASS: Coding = { system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', code: 'AMB', display: 'ambulatory', }; export const VIRTUAL_ENCOUNTER_CLASS: Coding = { system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', code: 'VR', display: 'virtual', }; /** * Is this booking a video visit? True only when the appointment explicitly names the org's virtual * Location among its participants - telehealth is a LOCATION, so that reference IS the fact. * * Fails closed on both halves: no virtual Location configured nothing can be virtual, and the guard * on `virtualLocationId` also stops a stray `Location/undefined` from matching. */ export const appointmentIsVirtual = ( appointment: Appointment | undefined, virtualLocationId: string | undefined, ): boolean => { if (!virtualLocationId) return false; return (appointment?.participant ?? []).some( (participant) => participant.actor?.reference === `Location/${virtualLocationId}`, ); }; /** * Is this encounter a video visit? Read from the Encounter's OWN class, not re-derived. * * The class is the settled answer, and it is settled from two directions: `encounterClassForAppointment` * stamps it at creation from the appointment's virtual Location, and stamps it server-side when a * video session is created - the backstop for a visit that becomes video without having been booked that * way. Asking the appointment again here would re-derive a fact the resource already carries, and would * miss every encounter that took the second path. * * E5 uses this to decide whether "Start video" belongs on the screen at all: it was * unconditional, and on an in-person visit the most prominent control was the one that clinician will * essentially never press. */ export const encounterIsVirtual = (encounter: { class?: Coding } | undefined): boolean => encounter?.class?.code === VIRTUAL_ENCOUNTER_CLASS.code; /** * The class an Encounter should carry, given the appointment it is being started from. * * Ambulatory is the default and must stay so: a walk-in with no appointment, and every practice that has * never touched telehealth, has to be completely unaffected by this. */ export const encounterClassForAppointment = ( appointment: Appointment | undefined, virtualLocationId: string | undefined, ): Coding => appointmentIsVirtual(appointment, virtualLocationId) ? VIRTUAL_ENCOUNTER_CLASS : AMBULATORY_ENCOUNTER_CLASS;