import type { CommunicationRequest, Extension } from 'fhir/r4'; /** * - AN ARRIVING REFERRAL IN THE FRONT-DESK QUEUE, as the client sees it. * * Spencer, 2026-08-23: *"It's just referral triage... they're doing it from within the front desk message * system. The message system makes sense here so we can leverage '[receptionist name] is reading it' and * '[receptionist name] triaged it' system we already have there."* * * SO THIS IS A WORK ITEM WEARING A THREAD'S CLOTHES, and every rule below follows from that one fact. * It lives in the pool because the pool has claim/release and claim expiry - not because it is a * conversation. It has no messages, no author and **no reply**: the ruling makes the reply affordance * *absent*, not disabled. * * AND IT HAS NO PATIENT, WHICH IS WHAT BREAKS EVERY EXISTING RULE. `counterpartyReference` resolves a * pool thread's other party to the PATIENT - that is fix - so a referral item resolves to * `undefined` and falls through to the last-resort label. Left alone this ships a row reading **"Unknown"** * with an empty conversation beside it: the exact defect was raised for, re-created by a new * arrival path. The naming rule lives HERE, with the rest of the referral's client knowledge, rather * than as a fourth inline special case in a panel. * * NOTHING HERE EVER NAMES THE PATIENT, and that is a disclosure rule rather than a style one. The * receiving practice has no Patient record for the person on an arriving referral and a referral grants * nobody anything, so a name would disclose a patient identity to a desk with no relationship to them. * The server refuses to deliver a notice that names them; this side simply never composes one. */ /** * Older marker urls stay readable, newest first - the same shape as the pool-key and read-receipt * extensions, and for the same reason: a shell cached by a previous build must still be recognisable. */ const REFERRAL_SUBJECT_EXTENSION_URLS = [ 'https://akashichealthrecords.com/fhir/StructureDefinition/thread-referral-subject', 'https://akashic.health/fhir/StructureDefinition/thread-referral-subject', ]; /** What the desk needs in order to see an arriving referral and act on it. */ export interface ReferralThreadSubject { /** The referral's ServiceRequest id. */ referralId: string; /** The row's name - the sending practice. Composed by the server; never sliced from the summary. */ label: string; /** The paragraph the receptionist reads once the item is open. */ summary: string; } /** * The thread record as the server describes it, narrowed to what this module reads. * * Every field optional, because the overwhelming majority of pool threads are patient conversations * and always will be. A thread that says nothing about a subject is a patient thread - that is the * server's backward-compatible default and this mirrors it. */ export interface ThreadSubjectFields { subjectType?: 'patient' | 'referral' | null; subjectId?: string | null; subjectLabel?: string | null; subjectSummary?: string | null; } const isReferralSubjectExtension = (extension: Extension | undefined): boolean => !!extension?.url && REFERRAL_SUBJECT_EXTENSION_URLS.includes(extension.url); const childValue = (extension: Extension | undefined, url: string): string | undefined => { const child = extension?.extension?.find((candidate) => candidate.url === url) as | (Extension & { valueString?: string }) | undefined; return child?.valueString || undefined; }; /** The referral this thread is about, or `undefined` for every ordinary conversation. */ export const getReferralSubject = ( request: CommunicationRequest | undefined, ): ReferralThreadSubject | undefined => { const extension = request?.extension?.find(isReferralSubjectExtension); const referralId = childValue(extension, 'referralId'); if (!referralId) return undefined; return { referralId, // A missing label degrades to a TRUE generic rather than to an empty row. An unlabelled item // still has to be findable and claimable - a blank row reads as a half-loaded screen and gets // reloaded rather than worked. label: childValue(extension, 'label') || 'Referral from another practice', summary: childValue(extension, 'summary') || 'A referral arrived. Claim it to take it on.', }; }; /** True when this row is an arriving referral rather than a conversation with anybody. */ export const isReferralThreadRequest = (request: CommunicationRequest | undefined): boolean => !!getReferralSubject(request); /** * Stamp (or clear) the referral marker on a thread shell from the thread the server described. * * IT CLEARS AS WELL AS SETS, and the clearing direction is the load-bearing one - exactly as * `withThreadPoolKey` records. This runs over shells read back out of the local cache, so a marker left * behind by an older build has to be able to go away. A stamp-only version is indistinguishable from * correct until the day it is wrong. */ export const withReferralSubject = ( request: CommunicationRequest, thread: ThreadSubjectFields | undefined, ): CommunicationRequest => { const others = (request.extension || []).filter( (extension) => !isReferralSubjectExtension(extension), ); const isReferral = thread?.subjectType === 'referral' && !!thread.subjectId; if (!isReferral) { return others.length === (request.extension || []).length ? request : { ...request, extension: others }; } const children: Extension[] = [ { url: 'referralId', valueString: thread.subjectId as string }, ...(thread.subjectLabel ? [{ url: 'label', valueString: thread.subjectLabel }] : []), ...(thread.subjectSummary ? [{ url: 'summary', valueString: thread.subjectSummary }] : []), ]; return { ...request, extension: [...others, { url: REFERRAL_SUBJECT_EXTENSION_URLS[0], extension: children }], }; }; /** * The name of a row in the front desk's list. * * IT NAMES THE SENDING PRACTICE, which the server composed. Every row in this queue is a referral, so * "Referral" alone distinguishes nothing in a list of twenty - the practice it came from is the one fact * that tells two of them apart at a glance. */ export const referralThreadLabel = ( request: CommunicationRequest | undefined, ): string | undefined => getReferralSubject(request)?.label;