// Who belongs on a picker whose axis means "patients who saw this clinician". // // MEASURED 2026-09-14, Marketing workspace at Great River Health Center: the campaign composer's // Practitioner axis opened on TWENTY-SIX options, beginning "Any practitioner - Generic One - // Zztabclose Adminone - Platform Manager - Marketing One". An office manager, a platform manager and // the practice's own marketer were offered on an axis that means "patients who saw this clinician". // // NOTHING WAS DISCLOSED AND NOTHING WAS WRONG IN THE RESULT - the axis is intersected with a // qualifying appointment, so picking the marketer resolves to an audience of zero, correctly. The cost // is that a six-clinician practice is handed a twenty-six item control, the list implies those people // see patients, and the marketer choosing an audience cannot tell which four of the twenty-six are the // honest answers. // // WHY IT IS THIS PREDICATE AND NOT A FILTER INVENTED HERE. Every staff member has a `Practitioner` // resource - that is the spine, deliberately: the work identity exists for anyone the practice // employs and is not a claim that they see patients. The rule for which of them a PATIENT can land on // is already written down in `appointments/appointment-booking-flow.md`: *"The patient can only ever // land on a practitioner who actually holds the matched org-role and has a bookable schedule. // HR/marketing have no bookable specialty, so they never appear."* And it is already implemented once, // as `holdsClinicalRole`, which the booking readiness sentence calls `isProvider` and which the // provider setup chain, the prescriber list, the schedule editor and the staff facts all read. This // screen was the outlier, not the place to author a tenth answer. // // THE SCHEDULE HALF IS DELIBERATELY NOT ASKED HERE. Bookability answers "can a patient book them // NOW"; this axis asks "could a patient have been seen by them", which is a longer-lived question - a // clinician who has stopped taking new appointments still has patients who saw them last year, and // narrowing the axis by today's schedule would silently hide that audience. import type { PractitionerWithRoles } from '../../../services/PractitionerService'; import { holdsClinicalRole } from '../../../utils/staffRoleCatalog'; export type PractitionerAxisState = /** Options to offer. */ | { kind: 'ready'; options: PractitionerWithRoles[] } /** The list never arrived. The axis cannot be used and the rest of the audience still works. */ | { kind: 'could-not-load' } /** The list arrived and nobody at this practice sees patients. A true answer about the practice. */ | { kind: 'nobody-sees-patients' } /** The list is still being read. */ | { kind: 'loading' } /** This seat may not read practitioners; a boundary, not a fault. */ | { kind: 'not-permitted' }; /** * THE TWO EMPTIES ARE DIFFERENT ANSWERS AND MUST NOT COLLAPSE. Before this filter existed, an empty * list could only mean the load failed, so the screen said so. Filtering makes a SECOND empty * reachable - a practice whose staff are all non-clinical - and reporting that as "we could not load * the practitioner list" would be a false report of our own failure about a practice that is simply * young. An absence must never be left to carry a message it cannot distinguish. */ export const practitionerAxis = ( practitionersList: readonly PractitionerWithRoles[], read: { loading?: boolean; denied?: boolean } = {}, ): PractitionerAxisState => { if (read.denied) return { kind: 'not-permitted' }; if (practitionersList.length === 0) return read.loading ? { kind: 'loading' } : { kind: 'could-not-load' }; const options = practitionersList.filter((entry) => holdsClinicalRole(entry.roles)); if (options.length === 0) return { kind: 'nobody-sees-patients' }; return { kind: 'ready', options }; };