import type { Practitioner } from 'fhir/r4'; /** * WHETHER A PATIENT MAY OPEN A BRAND-NEW THREAD WITH THIS PRACTITIONER. * * Spencer, 2026-09-04: a practitioner may say whether patients can direct message them, and it "would only * be for patients sending a brand new message to the practitioner on their own, if a practitioner messages * the patient then the patient should still be able to reply back." * * THE SERVER IS THE GATE; THIS IS THE EXPLANATION. `Communication/thread` refuses a patient-initiated * thread with somebody who has turned this off, and it would refuse one this file wrongly offered. What * this decides is what the patient is TOLD - and Spencer ruled that they are told plainly rather than shown * a door that then refuses: * * "When the toggle is set to not allowed patients should see a message saying '[practitioner name] does * not currently accept new messages' and disable the click event on them. If they already have a message * thread with the practitioner then this doesn't apply and clicking the practitioner would open up the * existing thread like normal." * * SO THE ROW HAS THREE STATES, NOT TWO, and the middle one is the one that gets lost: a practitioner who * declines NEW threads but is already in a conversation with this patient stays fully clickable. Greying * out every listing of them satisfies the toggle and breaks the ruling - the patient can still post a reply * but can no longer reach the thread to write it in. * * READ FROM THE PROJECTED PAYLOAD. The patient's copy of a Practitioner is reduced to a directory view and * `extension` is an open container that is dropped whole; this one url is named back into it server-side * precisely so this rule can be answered here. If that naming is ever removed, every practitioner reads as * accepting - which is the failure direction chosen deliberately below. */ export const PATIENT_INITIATED_MESSAGES_EXTENSION = 'https://akashichealthrecords.com/fhir/extensions/patient-initiated-messages'; /** * May a patient start a new conversation with this practitioner? * * FAILS OPEN, matching its server twin exactly - the two must agree, and if they disagree the server wins * and the patient sees a door that refuses. Only an explicit `valueBoolean === false` closes it; absent, * malformed, or the wrong value type all read as open, which is also the default this ships with. */ export const allowsPatientInitiatedMessages = ( practitioner: Practitioner | undefined | null, ): boolean => { const setting = (practitioner?.extension ?? []).find( (extension) => extension.url === PATIENT_INITIATED_MESSAGES_EXTENSION, ); return setting?.valueBoolean !== false; }; /** The sentence the patient sees in place of a door. Named so the copy has one home. */ export const declinesNewMessagesNotice = (practitionerName: string | undefined): string => `${practitionerName?.trim() || 'This person'} does not currently accept new messages`; /** * The three states a practitioner's row can be in on the patient's provider picker. * * NAMED AS A RULE BECAUSE THE MIDDLE ONE IS THE ONE THAT GETS LOST. `open` and `declined` are the pair * anybody would write from the toggle alone; `existing` is the exception Spencer attached to it, and a * build that greys out every listing of a practitioner who declines satisfies the toggle and breaks the * ruling - the patient can still post a reply but can no longer reach the thread to write it in. * * A rule that cannot be stated on its own cannot be tested on its own, and this one has to be. */ export type PatientPickerRowState = 'open' | 'existing' | 'declined'; export const patientPickerRowState = ( practitioner: Practitioner | undefined | null, hasExistingThread: boolean, ): PatientPickerRowState => { if (allowsPatientInitiatedMessages(practitioner)) return 'open'; // Checked SECOND and deliberately: an existing conversation outranks the setting, because the setting // governs starting one and this one has already started. return hasExistingThread ? 'existing' : 'declined'; };