import type { Extension, HealthcareService, PractitionerRole, Reference } from 'fhir/r4'; // Telehealth booking S2. Whether a service may be DELIVERED BY VIDEO. // // Deliberately shaped as a near-twin of `serviceSelfBookable.ts` - ONE extension URL on the same // carriers, where the carrier location determines the level: // HealthcareService.extension org DEFAULT (absent NOT eligible) // PractitionerRole.healthcareService[i].extension practitioner OVERRIDE (absent inherit default) // Schedule.actor[i].extension block OVERRIDE (deferred to ) // // IT DIFFERS FROM ITS SIBLING IN TWO WAYS, AND BOTH MATTER. // // 1. THE DEFAULT IS INVERTED. `service-patient-self-bookable` is absent TRUE; this is absent FALSE. // Eligibility cannot be inferred - there is no derivable property of a HealthcareService that says a // consultation is deliverable by video and a procedure is not. Only the practitioner offering it knows. // Spencer, 2026-08-01: "You CAN do a consultation via video, you can't perform surgery via video." // Defaulting ON would surface surgery, wound care and every in-person procedure as bookable telehealth // the moment an org enables the feature - a patient-visible clinical error created by a default rather // than by anyone's decision. // // 2. IT IS NOT AUDIENCE-SCOPED. Self-bookable gates PATIENTS only - staff book straight through it by // design, because it is curation. Telehealth eligibility gates EVERYONE: a receptionist cannot book // surgery-by-video either, because the constraint is clinical possibility, not curation. must // therefore NOT copy the `NOT_SELF_BOOKABLE_*` blockers' audience handling when it wires this into // `resolveBookability`. // // Nothing here is a security boundary. Like its sibling, this is client-side curation the server // preserves but does not enforce (see the api's appointment-booking-flags.ts header for that decision on // self-bookable). Whether telehealth eligibility SHOULD be server-enforced is an open question on card // 111 - it reads more like clinical safety than curation - and it is deliberately not answered here. export const SERVICE_TELEHEALTH_ELIGIBLE_EXTENSION_URL = 'https://akashichealthrecords.com/fhir/StructureDefinition/service-telehealth-eligible'; // Tri-state form value for the per-service practitioner control (Default / Eligible / Not eligible). export type TelehealthEligibleChoice = 'default' | 'eligible' | 'not-eligible'; const readBoolExt = (extensions: Extension[] | undefined): boolean | undefined => { const ext = extensions?.find((e) => e.url === SERVICE_TELEHEALTH_ELIGIBLE_EXTENSION_URL); return ext ? ext.valueBoolean : undefined; }; // Org default carried on the HealthcareService. Absent FALSE - see (1) in the header. export const getServiceTelehealthEligibleDefault = (service?: HealthcareService | null): boolean => readBoolExt(service?.extension) ?? false; // The explicit override (or undefined = inherit) carried on a service Reference - practitioner level. export const getRefTelehealthEligibleOverride = (ref?: Reference): boolean | undefined => readBoolExt(ref?.extension); // Tri-state form value <-> stored override boolean|undefined. export const overrideToTelehealthChoice = ( override: boolean | undefined, ): TelehealthEligibleChoice => override === undefined ? 'default' : override ? 'eligible' : 'not-eligible'; export const telehealthChoiceToOverride = ( choice: TelehealthEligibleChoice, ): boolean | undefined => (choice === 'default' ? undefined : choice === 'eligible'); // Rebuild a service Reference's extension array applying the chosen override, PRESERVING any other ref // extensions - notably the self-bookable override, which lives on the same Reference. Dropping it here // would silently reset a practitioner's self-booking curation every time they touched telehealth. // override === undefined removes the extension (inherit); true/false writes an explicit valueBoolean. export const applyRefTelehealthEligible = ( ref: Reference, override: boolean | undefined, ): Reference => { const others = (ref.extension ?? []).filter( (e) => e.url !== SERVICE_TELEHEALTH_ELIGIBLE_EXTENSION_URL, ); const next: Extension[] = override === undefined ? others : [...others, { url: SERVICE_TELEHEALTH_ELIGIBLE_EXTENSION_URL, valueBoolean: override }]; const result: Reference = { ...ref }; if (next.length) { result.extension = next; } else { delete result.extension; } return result; }; /** * Set the org default on the HealthcareService itself, preserving every other extension. * * THE SIBLING ABOVE WRITES THE PRACTITIONER'S OVERRIDE; THIS WRITES THE PRACTICE'S DEFAULT, and until * now only the override had a writer. The default could be READ by four call sites and set by none, so * the flag arrived on services by some other route and the administrator responsible for the catalogue * could neither see it nor change it - while it decided whether a patient could book the service at all. * * STORED EXPLICITLY EVEN WHEN FALSE, following the self-bookable default beside it in the service * editor. Absent and false resolve identically, so this changes no behaviour; it changes whether the * record says somebody decided. That is the whole complaint this answers, and "nobody has said" and * "somebody said no" should not be the same bytes. * * Preserves the others for the reason `applyRefTelehealthEligible` gives: the duration, self-bookable, * price and appointment-cap extensions live on this same array, and dropping them here would reset four * unrelated decisions every time somebody touched video. */ export const applyServiceTelehealthEligible = ( service: HealthcareService, eligible: boolean, ): HealthcareService => ({ ...service, extension: [ ...(service.extension ?? []).filter( (e) => e.url !== SERVICE_TELEHEALTH_ELIGIBLE_EXTENSION_URL, ), { url: SERVICE_TELEHEALTH_ELIGIBLE_EXTENSION_URL, valueBoolean: eligible }, ], }); // Full resolution for a (service, practitionerRole): practitioner override wins, else the org default. // The block-level carrier is deferred to, which also applies the org-level telehealth policy // on top of this. export const resolveServiceTelehealthEligible = ( service: HealthcareService | undefined, practitionerRole?: PractitionerRole, ): boolean => { const ref = (practitionerRole?.healthcareService ?? []).find( (r) => r.reference?.split('/').pop() === service?.id, ); const practitionerOverride = getRefTelehealthEligibleOverride(ref); if (practitionerOverride !== undefined) { return practitionerOverride; } return getServiceTelehealthEligibleDefault(service); };