/** * WHAT A PATIENT IS TOLD WHEN THE SERVICE STEP HAS NOTHING ON IT. * * MEASURED 2026-09-15 at a practice configured end to end: two active services, both attached to a * seated practitioner, both on a schedule block whose horizon covered that day. The patient was told * " hasn't added any bookable services yet. Please contact the office to schedule an * appointment." The wire returned both services, active. The sentence named the wrong subject, and the * only thing it offered was a telephone call to a practice that had done nothing wrong. * * THREE STATES REACH ONE EMPTY LIST AND THEY ARE NOT THE SAME FACT: * * nothing to book from no schedule block is open to patient self-booking at all * nothing listed on it blocks exist, and none of them carries a self-bookable service * the first-visit gate the practice requires a new patient consultation first, and no block * offering one is open to this patient * * Only the middle one is what the old sentence described, and it was said in all three. The third is * the one that produced the measurement above: the catalogue was full, and the gate had pinned the * choice to a consultation that no open block offered. * * WRITTEN FOR THE PATIENT, NOT THE OFFICE. `BLOCKER_COPY` says the same causes in an administrator's * words - "this schedule block", "self-booking is switched off" - which is right on the screen where * somebody can go and change them, and wrong here. A patient cannot act on a schedule block. */ /** Which of the three emptinesses the flow is in. Decided by the caller, which holds the inputs. */ export type NothingToOffer = /** No schedule block at this practice is open to patient self-booking. */ | 'NO_SELF_BOOKABLE_SCHEDULE' /** Blocks are open, and none of them lists a service a patient may book. */ | 'NO_SELF_BOOKABLE_SERVICE' /** A first visit must be a new patient consultation, and no open block offers one. */ | 'FIRST_VISIT_NOT_OFFERED'; /** * The practice's name where there is one, and "This practice" where there is not - never an empty gap * and never the word "organization", which is ours and not theirs. */ const named = (practiceName: string | undefined): string => { const trimmed = practiceName?.trim(); return trimmed ? trimmed : 'This practice'; }; /** * ONE SENTENCE PLUS ONE REMEDY, AND THE REMEDY IS THE SAME IN ALL THREE because it honestly is: none of * these is something the patient can route around inside the flow. What changes is that they are no * longer told something false on the way to the telephone. */ export const describeNothingToOffer = ( state: NothingToOffer, practiceName: string | undefined, ): string => { const who = named(practiceName); switch (state) { case 'FIRST_VISIT_NOT_OFFERED': // Names the gate AND the gap, because the patient is otherwise told a practice they can see // has a full catalogue offers nothing. The gate is the practice's own policy and saying so // is what makes the emptiness make sense. return `Your first visit with ${who} has to be a new patient consultation, and none is ` + 'open for online booking right now. Please contact the office to arrange it.'; case 'NO_SELF_BOOKABLE_SCHEDULE': // "No times" rather than "no services": the catalogue is not what is missing, and sending // somebody to ask about services they can already see is what the old sentence did. return `${who} has no appointment times open for online booking right now. ` + 'Please contact the office to schedule an appointment.'; case 'NO_SELF_BOOKABLE_SERVICE': default: // The one case the original sentence described, kept close to its original words. return `${who} hasn't made any services available for online booking yet. ` + 'Please contact the office to schedule an appointment.'; } };