// - reading the readiness facts off FHIR. The half that has to be right for the sentence above // it to mean anything. // // EVERY FACT HERE HAS A SAFE DIRECTION AND THEY ARE NOT ALL THE SAME. Reporting a working person as // broken sends an administrator on a wasted trip; reporting a broken person as working leaves a // practitioner invisible to booking behind a page that says they are fine. The second is the failure // this card exists to end, so where a fact cannot be established the answer is not "fine". // // IT DERIVES NO BOOKING VERDICT. That comes from `utils/booking/resolveBookability.ts` via // `getProviderReadiness`, and is handed in - see `staffReadiness.ts`'s header for what the second // implementation of that question already cost this codebase. import type { Practitioner, PractitionerRole } from 'fhir/r4'; import type { BookabilityBlocker } from '../booking/bookabilityReasons'; import { isValidNpi } from '../npi'; import { holdsClinicalRole } from '../staffRoleCatalog'; import type { StaffFacts } from './staffReadiness'; const NPI_SYSTEM = 'http://hl7.org/fhir/sid/us-npi'; // **I GOT THIS WRONG ONCE AND THE CORRECTION IS WORTH MORE THAN THE CODE.** I read absence off // `PractitionerRole.notAvailable`, reasoning that FHIR R4 has no such field on `Schedule`. True of the // SPEC, wrong about this PRODUCT: the schedule carries its not-available periods in an Akashic // extension (`SCHEDULE_NOT_AVAILABLE_EXTENSION_URL`), there are already two editors for them - Manage // Schedule and Edit Availability - and `getScheduleAvailabilityEvents` already skips any day they // cover, so booking honours them today. // **Spencer's ruling was exact and I checked the spec instead of the codebase.** A field's absence // from a standard says nothing about whether the product models it. // // SO `onLeave` IS HANDED IN, not derived here. It belongs to the SCHEDULE reader, and deriving it a // second time from a resource nothing writes is the drift this module already warns about one field up. interface Input { practitioner: Practitioner; roles: PractitionerRole[]; /** From `getProviderReadiness`. NEVER re-derived here. */ booking: { staffBookable: boolean; blockers: readonly BookabilityBlocker[]; basis?: 'confirmed' | 'checking' | 'unavailable' }; // `now` LIVED HERE and is gone: this module stopped deriving absence, and it was the only fact // that needed a clock. A required parameter nothing reads is one every caller has to invent. /** Whether the invitation has been accepted, where the caller knows. See the default below. */ inviteAccepted?: boolean; /** * Is this person AWAY today? Read off their SCHEDULES' not-available periods by the caller - * `getScheduleAvailability(schedule).notAvailable` is where the product stores them, and the two * existing editors are Manage Schedule and Edit Availability. * NOT derived here from `PractitionerRole.notAvailable`: nothing in the product writes that, and * a second derivation of "is this person away" is the drift `staffReadiness` was already rewritten * to remove one field over. */ onLeave?: boolean; /** Whether any of this person's schedule blocks names a Location, read by the caller. */ scheduleHasLocation?: boolean; } export function staffFactsFrom({ practitioner, roles, booking, inviteAccepted, onLeave, scheduleHasLocation, }: Input): StaffFacts { // ANY active role keeps the person here. Someone holding two roles with one switched off has not // left the practice, and "deactivated" is a sentence about a real human's job. const activeRoles = roles.filter((r) => r.active !== false); return { // DEFAULTS TRUE where the caller does not know. Invitation state is not on the FHIR resources // this reads, and inventing a `false` would put "has not signed in" on every existing staff // member in the practice - a page-wide false alarm is how a page stops being read. The caller // supplies it when it has it. inviteAccepted: inviteAccepted ?? true, hasRole: activeRoles.length > 0, // Location moved onto the Schedule; the role carries it only for blocks saved before that. hasLocation: activeRoles.some((r) => (r.location ?? []).length > 0) || scheduleHasLocation === true, // ONE predicate, shared with the booking resolver, the setup chain, the staff record's tab gate // and the reassign picker. It had been written out four times before it was extracted. isClinical: holdsClinicalRole(roles), // A MALFORMED NPI IS NOT AN NPI. A typo'd one is WORSE than an absent one for identity // matching ( measured three false-positive pairs in the seed data alone), so reporting // it as present would hide the very gap this line exists to surface. hasNpi: (practitioner.identifier ?? []).some( (id) => id.system === NPI_SYSTEM && !!id.value && isValidNpi(id.value), ), // MIRRORS THE SENDER EXACTLY, and the precision is the point. `scheduleChangeNotice.ts` picks // the first `telecom` whose `system` is `email` and whose value contains an `@`, and returns // `skipped-no-address` otherwise. Anything looser here - "has any telecom", "has a phone" - // would report somebody reachable whom the product will silently decline to write to, which is // the exact false-green this step exists to end. // // NOT the Cognito account's sign-in address. Those differ, the person signs in perfectly well, // and that difference is why this was invisible until a CloudWatch read found it. hasContact: (practitioner.telecom ?? []).some( (point) => point.system === 'email' && typeof point.value === 'string' && point.value.includes('@'), ), // Spencer, 2026-08-14: deactivation means GONE. active: activeRoles.length > 0, //...and AWAY is a different fact, read off the person's SCHEDULES by the caller. Defaults // FALSE where the caller does not know: a wrong "on leave" tells a practice a working clinician // is absent, which is the louder error, and the readiness line's job is to name a gap rather // than invent one. onLeave: onLeave ?? false, booking, }; }