import type { Communication, Extension, Reference } from 'fhir/r4'; /** * - THE WAY OUT OF "awaiting your reply" THAT IS NOT A MESSAGE. * * Spencer, 2026-08-26: *"Is there a mechanism for the practitioner to dismiss the message if they * don't want to reply so it's no longer counted?"* There was not, for patient threads. * * WHAT IT COSTS WITHOUT ONE * *Awaiting reply* is derived from who spoke last, so a patient's **"Thank you, that helps."** leaves the * practitioner owing a reply **forever** - or typing *"You're welcome"* to clear their own dashboard, * **which fires a notification at the patient.** The product was training clinicians to send empty * messages, and each one costs a patient an interruption. * And a count nobody can get to zero stops being read at all. **That is the failure that matters** - * the badge exists to say *there is work here*, and a permanent floor of unclearable threads is exactly * how a signal becomes wallpaper. * * IT MARKS A MESSAGE, NOT A THREAD, AND THAT CHOICE DOES THE WORK * The dismissal lands on the **specific `Communication`** the practitioner was looking at. Everything * awkward about a thread-level flag then disappears: * * - **A new inbound message makes the thread owed again on its own.** It is a different resource with no * dismissal on it, so the derivation reads it and the thread is owed. **Nothing expires, nothing goes * stale, and there is no way to permanently silence a patient by pressing a button once.** * * - **The read-then-write race cannot happen.** The Captain raised it against a thread-level marker: if a * patient message arrives between render and click, a "seen as of now" flag would clear a message the * practitioner never saw - the thread leaves the owed count while the patient waits on a reply that is * counted nowhere. Here the dismissal is written to the message it names, the newer message carries * none, and the thread stays owed. **No conditional write and no refusal are needed; the shape of the * data refuses it.** * * - **It is per person, so a pooled thread cannot be made to vanish for the whole desk.** The mark names * who made it, exactly as a read receipt does. * * **NOT `closed`.** That term exists on the collaboration path and is read-only - it stops the PATIENT * writing too. Somebody who says *"thanks"* and then remembers one more thing must not find the door * locked. * **The patient is told nothing and receives no notification.** A dismissal is the practitioner's own * bookkeeping; surfacing it would read as *"your message was ignored"*, which is worse than the empty * reply it replaces. * * It deliberately mirrors the read-receipt extension next door in `CommunicationService` - same * carrier, same per-reference shape, same persistence path. **A second storage mechanism for "something * this person did to this message" is how the two come to disagree.** */ export const NO_REPLY_NEEDED_EXTENSION_URL = 'https://akashichealthrecords.com/fhir/extensions/no-reply-needed'; interface DismissalChildExtension extends Extension { valueReference?: Reference; valueInstant?: string; } interface DismissalExtension extends Extension { extension?: DismissalChildExtension[]; } const dismissalsOn = (message: Communication): DismissalExtension[] => (message.extension ?? []).filter( (extension): extension is DismissalExtension => extension.url === NO_REPLY_NEEDED_EXTENSION_URL, ); /** * Has THIS person marked THIS message as needing no reply? * * Per reference, never "has anybody dismissed it". A colleague's bookkeeping is not mine, and on a * pooled thread one receptionist deciding no answer is needed must not clear the desk's board for * everybody - that would be a second, quieter way to make a shared thread disappear, which the claim * semantics already exist to handle properly. */ export const isDismissedBy = ( message: Communication | undefined, staffReference: string | undefined, ): boolean => { if (!message || !staffReference) return false; return dismissalsOn(message).some((dismissal) => dismissal.extension?.some( (child) => child.url === 'dismissedBy' && child.valueReference?.reference === staffReference, ), ); }; /** * The message with this person's dismissal on it. Immutable, and idempotent. * * Returns the message UNCHANGED when they have already dismissed it, rather than appending a second * mark. A double-click is not two decisions, and a list of duplicates would make an audit of *"who * decided this needed no answer, and when"* read as though it had been decided repeatedly. */ export const markNoReplyNeeded = ( message: Communication, staffReference: string, at: string, ): Communication => { if (isDismissedBy(message, staffReference)) return message; const dismissal: DismissalExtension = { url: NO_REPLY_NEEDED_EXTENSION_URL, extension: [ { url: 'dismissedBy', valueReference: { reference: staffReference } }, { url: 'dismissedAt', valueInstant: at }, ], }; return { ...message, extension: [...(message.extension ?? []), dismissal] }; };