/** * "2 of 5 tasks - 3 due by this visit" - ONE VOCABULARY FOR EVERY STAFF WORKSPACE. * * SPENCER'S RULING, 2026-08-15, and it settles two things at once: * * ① **BOTH the receptionist and the practitioner see EVERY task the patient has not completed** - not * just the ones attached to today's visit. *"The practitioner needs to know all of these in order to * give the best advice based on full knowledge of what the patient has and hasn't done."* * ② **But the ones that were due BY THIS VISIT are called out separately**, because they are the ones * that drive a decision at the desk: *"if they're things the patient can complete quickly before * being roomed, or if the appointment should be rescheduled once the tasks are done."* Before the * day it is a nudge to the patient; at check-in it is a choice the front desk has to make. * * **AND WHY "DUE BY THIS VISIT" IS WELL-DEFINED AT ALL** (Spencer, same ruling): a task assigned *"due * by your next appointment"* IS due by this one by the time the day arrives - *"this IS the next * appointment."* Resolving that is card 336's job; this module consumes the answer and never re-derives * it from a date. * * **WHAT THIS CORRECTS.** The count used to mean different things in different places, and one of them * was not a task count at all: * - the front desk counted only the Tasks attached to the appointment - so a patient's other * outstanding work was invisible to the person standing in front of them; * - the practitioner's queue was briefly wired to `POST /R4/patient-readiness`, which counts **five * fixed intake areas** (Profile - Related persons - Medications - Allergies - Notifications) and * would have read "3 of 5 tasks" meaning something else entirely. **Readiness is a separate fact and * deserves its own words** - "profile & history", never "tasks". * The receptionist spec §3.0 lists the shipped line *"Patient profile and history: 2 of 4 complete"* * as a third surface saying the same number differently. It is NOT the same number; §3.0 is right that * the STRING needed unifying and wrong that all three were measuring one thing. * * THREE UNMEASURED STATES, NEVER TWO, and each is a different sentence: * a count - we asked and we know. * `denied` - this caller may not ask about this patient. NOT "their tasks are incomplete". * `unavailable` - we could not find out. NOT "there is nothing to find". * nothing at all - NO TASKS WERE ASSIGNED, distinct from all-complete. Drawing nothing for both makes * an unmet requirement indistinguishable from a met one. */ export interface PatientTaskTally { /** Every task the patient has been given, in the statuses a surface displays. */ total: number; completed: number; /** * How many of the OUTSTANDING ones were due by the visit this row is about. * Outstanding, not assigned: a task due by this visit that is already done needs nobody's attention, * and counting it here would send the desk chasing finished work. * `0` on a row with no appointment (a walk-in), which is correct rather than a gap - there is no * "this visit" for the tasks to have been due by. */ dueHereOutstanding: number; } export type PatientTaskCount = PatientTaskTally | 'denied' | 'unavailable'; /** * ONLY EXCEPTIONS EARN COLOUR, and there are two severities here rather than one. * `complete` - nothing outstanding. * `incomplete` - work is outstanding, but none of it was needed for this visit. A nudge. * `due-here` - outstanding work WAS due by this visit. This is the reschedule decision, and it is the * reason the two are not one tone: a patient who owes a form for next month and one who * owes the intake for the appointment they are standing at are not the same problem. * `unknown` - we could not answer. */ export type TaskCountTone = 'complete' | 'incomplete' | 'due-here' | 'unknown'; export interface TaskCountIndicator { /** "2 of 5 tasks" - always present when anything was assigned. */ label: string; /** The numbers behind `label`, for a row that draws the completed count apart. Absent when unknown. */ completed?: number; total?: number; /** "3 due by this visit" - present only when outstanding work was needed for THIS appointment. */ dueHereLabel?: string; tone: TaskCountTone; /** True while any work is outstanding - the one case a workspace may turn into an action. */ outstanding: boolean; } /** * What a staff row should say about a patient's tasks, or `undefined` when it should say nothing at all. * * `undefined` in, `undefined` out - an unmeasured patient is not a patient with no tasks, and a * surface that renders "0 of 0 tasks" while the batch is still in flight has asserted something it does * not know. */ export const taskCountIndicator = ( count: PatientTaskCount | undefined, ): TaskCountIndicator | undefined => { if (count === undefined) return undefined; if (count === 'denied' || count === 'unavailable') { // ONE SENTENCE FOR BOTH, deliberately. The DISTINCTION matters to us and is preserved in the // type; to the reader of a row "you may not ask" and "we could not find out" are the same // actionable fact - do not trust this space - and spelling out a refusal on screen tells a // clinician about a permission boundary they can do nothing about from here. return { label: 'task status unavailable', tone: 'unknown', outstanding: false }; } // NO TASKS ASSIGNED RENDERS NOTHING. It is not "0 of 0" and it is not "complete": nothing was ever // asked of this patient, and a green tick would claim they had met a requirement that never existed. if (count.total <= 0) return undefined; const outstanding = count.completed < count.total; // Clamped against the total rather than trusted: a stale appointment join could otherwise report // more outstanding here than the patient has left in the world, which reads as a data error to the // one person who cannot investigate it. const dueHere = Math.max(0, Math.min(count.dueHereOutstanding, count.total - count.completed)); return { label: `${count.completed} of ${count.total} tasks`, completed: count.completed, total: count.total, // "3 due by this visit" reads correctly BEFORE the day (a nudge) and ON it (a decision). Not "3 // overdue" - before the appointment they are not late, and a row that cries overdue at booking time // is the kind of alarm a front desk learns to ignore. ...(dueHere > 0 ? { dueHereLabel: `${dueHere} due by this visit` } : {}), tone: !outstanding ? 'complete' : dueHere > 0 ? 'due-here' : 'incomplete', outstanding, }; };