import type { CodeableConcept, Location } from 'fhir/r4'; export const LOCATION_PHYSICAL_TYPE_SYSTEM = 'http://terminology.hl7.org/CodeSystem/location-physical-type'; export const ROOM_PHYSICAL_TYPE_CODE = 'ro'; /** The CodeableConcept to stamp on a new exam-room Location. */ export const roomPhysicalType = (): CodeableConcept => ({ coding: [{ system: LOCATION_PHYSICAL_TYPE_SYSTEM, code: ROOM_PHYSICAL_TYPE_CODE, display: 'Room' }], }); /** True when the Location is an exam room (physicalType code `ro`), regardless of coding system. */ export const isRoomLocation = (location: Location): boolean => (location.physicalType?.coding ?? []).some((c) => c.code === ROOM_PHYSICAL_TYPE_CODE); export const SITE_PHYSICAL_TYPE_CODE = 'si'; export const VIRTUAL_PHYSICAL_TYPE_CODE = 'vi'; /** * Virtual carries OUR system. R4 location-physical-type has no vi, so the datastore refuses * that coding; every predicate here matches on the CODE, so the system is free to differ. */ export const VIRTUAL_PHYSICAL_TYPE_SYSTEM = 'https://akashichealthrecords.com/fhir/CodeSystem/location-physical-type'; /** The CodeableConcept to stamp on the org's virtual (telehealth) Location. */ export const virtualPhysicalType = (): CodeableConcept => ({ coding: [{ system: VIRTUAL_PHYSICAL_TYPE_SYSTEM, code: VIRTUAL_PHYSICAL_TYPE_CODE, display: 'Virtual' }], }); /** True when the Location is the virtual/telehealth one (physicalType code `vi`), regardless of coding system. */ export const isVirtualLocation = (location: Location): boolean => (location.physicalType?.coding ?? []).some((c) => c.code === VIRTUAL_PHYSICAL_TYPE_CODE); /** * True when the Location is a bookable SITE. * * THE DEFECT THIS NOW AVOIDS, measured on staging 2026-08-20 and not hypothetical. This used to be an * allowlist - no `physicalType` at all, or an explicit `si`. But the Location editor offers a * *"Physical form of the location"* dropdown over the **whole** HL7 value set, so an admin naming their * clinic a **Building** is using the control exactly as presented. That Location was then a site (no), * a room (no) and virtual (no) **in NO bucket**, and the admin Locations page builds its three sections * from exactly those three predicates. **It vanished from the only screen that manages it, from every * schedule and staff-assignment picker, and from booking.** * **5 of the 9 Locations at the two real practices were in that state** - `bu` on West Burlington * Hospital, Chiropractic Office, Baldwin Clinic and Roberts Clinic; `area` on Hospital. Four of Badger's * six. * * SO IT FAILS TOWARD SHOWING. Anything that is not a room and not the virtual location is a site. **A * Location a practice cannot see is a Location they cannot fix**, and that is the worse direction. * * THIS IS THE BLOCKLIST DELETED, AND THAT REVERSAL IS DELIBERATE - read before undoing it. * `filterOutRooms` was replaced by an allowlist because *"the moment a virtual telehealth Location existed * it would have appeared in every booking, schedule and org picker, fail-open, with no feature flag."* * **That exact hole is closed by name here**: virtual is excluded explicitly, not by omission, and a * test asserts it. What the allowlist bought beyond that was protection against a FUTURE Location kind - * paid for by hiding kinds that already exist. The input side is narrowed instead (see * `SUPPORTED_PHYSICAL_TYPE_CODES`), so a new kind cannot be created through the product, and the * exhaustiveness test below fails if anyone adds one that lands nowhere. */ export const isSiteLocation = (location: Location): boolean => !isRoomLocation(location) && !isVirtualLocation(location); /** * The physical forms the product actually models, and therefore the only ones its editor offers. * * THE INPUT AND THE BUCKETS HAVE TO AGREE, which is why this lives beside the predicates rather than in * the form. The defect above was precisely a disagreement between them: the editor offered fifteen values * and the buckets understood three. * Adding a code here without giving it a bucket reinstates the defect - the exhaustiveness test is what * stops that. */ export const SUPPORTED_PHYSICAL_TYPE_CODES: readonly string[] = [ SITE_PHYSICAL_TYPE_CODE, ROOM_PHYSICAL_TYPE_CODE, VIRTUAL_PHYSICAL_TYPE_CODE, ]; /** * Narrow a fetched `location-physical-type` value set to what the editor should offer. * * A Location already carrying an unsupported code keeps it: the select is bound to `field.value` and * only writes on change, so opening such a record shows an empty dropdown and saving without touching it * preserves what was there. **Nothing is silently rewritten**, and choosing a supported value fixes the * record. That is why this narrows the OPTIONS rather than normalising the DATA. */ export const supportedPhysicalTypeOptions = (options: T[]): T[] => options .filter((option) => option.code !== undefined && SUPPORTED_PHYSICAL_TYPE_CODES.includes(option.code)) .map((option) => option.code === VIRTUAL_PHYSICAL_TYPE_CODE ? { ...option, system: VIRTUAL_PHYSICAL_TYPE_SYSTEM } : option); /** * Sites, for site/booking/schedule pickers. * * THE ALLOWLIST THIS COMMENT USED TO DESCRIBE IS GONE - see `isSiteLocation` for why, and read that * before reinstating it. In short: it was written to stop a virtual Location leaking into every picker *, and it did - but it also hid every Location whose physical form was anything other than * `si`, which on real data meant **5 of 9**, including four of one practice's six clinics. * Virtual is now excluded BY NAME rather than by omission, so hole stays closed while a * Building stops disappearing. */ export const onlySites = (locations: Location[]): Location[] => locations.filter(isSiteLocation); // `filterOutRooms` (the blocklist) was DELETED in once its last call site migrated. Do not // reintroduce it: "everything that is not a room" silently admits every Location kind invented later. /** Only the exam rooms - for the Rooms surface and (future) room pickers. */ export const onlyRooms = (locations: Location[]): Location[] => locations.filter(isRoomLocation); /** * Only the virtual (telehealth) locations - for the Telehealth section of the admin Locations page. * * This bucket has to exist precisely BECAUSE `onlySites` excludes virtual: without it a virtual * location is in no bucket at all and is invisible on the only screen that manages it. */ export const onlyVirtual = (locations: Location[]): Location[] => locations.filter(isVirtualLocation); /** The id of the site a room is partOf, if any. */ export const getRoomSiteId = (location: Location): string | undefined => location.partOf?.reference?.match(/^Location\/([^/]+)/)?.[1];