import type { AllergyIntolerance, Condition, Device, Immunization, ImmunizationRecommendation, MedicationRequest, MedicationStatement, } from "fhir/r4"; import { medicationNameOf } from "./medicationName"; import { requestMeaning, statementMeaning, statementSource } from "./medicationMeaning"; /** * WHAT IS TRUE NOW - the `Now` lens, and the state half of the approved record (, step 5). * * THE ORGANISING RULE: a record holds two kinds of thing and they want opposite treatments. **EVENTS * happened**, so each opens onto what it produced. **STATE is true now**, so it belongs on ONE page with * no dates to navigate. The deck says what this page is for in one line: *"If a doctor asked you * today, this is your answer."* * * SO THE ONLY RULE THAT REALLY MATTERS HERE IS *STILL TRUE*. A resolved condition, a cancelled * prescription or a refuted allergy printed under a heading that says **now** is not a stale row - it is * a **clinical misstatement**, and this is the page a patient reads out to somebody treating them. * Every list below filters on the resource's own status. Never on recency, and never on "we have it". * * AND IT NEVER CLAIMS COMPLETENESS. The deck draws *"Vaccinations - up to date"*; we hold no schedule * and no recommendation for most patients, so that phrase is a claim we cannot support. The heading * carries a COUNT of what is recorded, which is true, instead of a judgement, which would not be. * * Shape only: no clock, no fetch, no render. */ export interface SummaryRow { id: string; /** The bold line. "Penicillin" - "Metformin 1000mg" - "Type 2 diabetes". */ title: string; /** The quieter line under it. "Severe - rash and swelling" - "Twice daily". */ detail?: string; /** Set only for something a clinician must not miss - it is what earns the red edge. */ critical?: boolean; /** Where the row goes, when it opens onto something. */ to?: string; } /** ACTIVE, RECURRENCE AND RELAPSE ARE ALL "NOW". Only `resolved` and `inactive` are not. */ const CURRENT_CLINICAL_STATUS = new Set(["active", "recurrence", "relapse"]); const clinicalStatusOf = ( resource: { clinicalStatus?: { coding?: { code?: string }[]; text?: string } }, ): string | undefined => resource.clinicalStatus?.coding?.[0]?.code?.toLowerCase() ?? resource.clinicalStatus?.text?.trim().toLowerCase(); /** * AN ABSENT STATUS IS TREATED AS CURRENT, and that direction is deliberate. * * FHIR makes `clinicalStatus` optional, and plenty of imported records carry none. The two ways to be * wrong are not symmetrical: **omitting a real allergy from the page a patient reads out to somebody * treating them is the dangerous one**, and showing one that has since been ruled out is a conversation. * We only ever drop what the record explicitly says is over. */ const isCurrent = (resource: { clinicalStatus?: { coding?: { code?: string }[]; text?: string }; }): boolean => { const status = clinicalStatusOf(resource); return !status || CURRENT_CLINICAL_STATUS.has(status); }; const textOf = (concept?: { text?: string; coding?: { display?: string }[] }): string | undefined => concept?.text?.trim() || concept?.coding?.[0]?.display?.trim() || undefined; /** "March 2021" - a month and a year, because that is how a patient says when something started. */ const monthAndYear = (when?: string): string | undefined => { if (!when) return undefined; const parts = /^(\d{4})-(\d{2})/.exec(when); if (parts) { // Read from the digits, never re-parsed: a UTC midnight formatted in a western timezone lands // in the previous month, so January becomes December in Iowa. return new Date(Number(parts[1]), Number(parts[2]) - 1, 1).toLocaleDateString(undefined, { month: "long", year: "numeric", }); } const at = new Date(when); return Number.isNaN(at.getTime()) ? undefined : at.toLocaleDateString(undefined, { month: "long", year: "numeric" }); }; /** * ALLERGIES. `criticality: high` means *"could kill this person"*; `severity` on a single reaction is a * different and weaker statement. Both are read, and the stronger one wins. */ export const allergyRows = (allergies: readonly AllergyIntolerance[]): SummaryRow[] => allergies .filter(isCurrent) .map((allergy) => { const reaction = allergy.reaction?.[0]; const severity = allergy.criticality === "high" ? "Severe" : reaction?.severity ? reaction.severity.charAt(0).toUpperCase() + reaction.severity.slice(1) : undefined; const manifestation = reaction?.manifestation ?.map((entry) => textOf(entry)) .filter(Boolean) .join(" and "); return { id: allergy.id ?? "", title: textOf(allergy.code) ?? "Allergy", detail: [severity, manifestation].filter(Boolean).join(" · ") || undefined, // Red is reserved for the two things that change what somebody does: a high criticality, // or a severe reaction. A moderate rash is real and is not this. critical: allergy.criticality === "high" || reaction?.severity === "severe", }; }); /** Conditions a patient still has. They OPEN - a condition is the `About` facet's own subject. */ export const conditionRows = (conditions: readonly Condition[]): SummaryRow[] => conditions .filter(isCurrent) .map((condition) => { const since = monthAndYear(condition.onsetDateTime ?? condition.recordedDate); const who = condition.recorder?.display?.trim() || condition.asserter?.display?.trim(); return { id: condition.id ?? "", title: textOf(condition.code) ?? "Condition", detail: [since && `Since ${since}`, who].filter(Boolean).join(" · ") || undefined, // The NAME travels with the id. The stream matches on the id, and shows the name on // the chip - including when the record holds nothing about this condition yet, which is // exactly when a raw id would otherwise be all the reader got. to: condition.id ? `/dashboard/patient?about=${encodeURIComponent(condition.id)}&name=${encodeURIComponent(textOf(condition.code) ?? "")}` : undefined, }; }); /** * CURRENT PRESCRIPTIONS AND REPORTED USE, each saying which it is. * * Only what the shared classifier calls current: an active formal order is a recorded prescription, an active * statement is reported use. A proposal, a plan, an unknown status or a stopped one is not printed here as now; * it stays in the full medication views with its own words. A prescription is never said to be taken. */ export const medicationRows = ( requests: readonly MedicationRequest[], statements: readonly MedicationStatement[] = [], ): SummaryRow[] => [ ...requests .filter((request) => requestMeaning(request).group === "current") .map((request, index) => ({ id: request.id ? `MedicationRequest/${request.id}` : `MedicationRequest#unidentified-${index}`, title: medicationNameOf(request) ?? "Medicine", detail: ["Prescription", request.dosageInstruction?.[0]?.text?.trim(), request.requester?.display?.trim()] .filter(Boolean) .join(" · "), })), ...statements .filter((statement) => statementMeaning(statement).group === "current") .map((statement, index) => { const source = statementSource(statement); const who = source.who === "patient" ? "reported by the patient" : source.who === "unknown" ? "source not recorded" : `reported by ${source.name || (source.who === "helper" ? "someone caring for them" : "a clinician")}`; return { id: statement.id ? `MedicationStatement/${statement.id}` : `MedicationStatement#unidentified-${index}`, title: medicationNameOf(statement) ?? "Medicine", detail: ["Reported use", statement.dosage?.[0]?.text?.trim(), who].filter(Boolean).join(" · "), }; }), ]; /** * VACCINATIONS. * * `status: completed` only - an `entered-in-error` record is a correction, and a `not-done` one says * the opposite of what this list means. */ export const immunizationRows = (immunizations: readonly Immunization[]): SummaryRow[] => immunizations .filter((immunization) => immunization.status === "completed") .map((immunization) => ({ id: immunization.id ?? "", title: textOf(immunization.vaccineCode) ?? "Vaccination", detail: monthAndYear(immunization.occurrenceDateTime), })) // Most recent first: the question a patient is answering is "when did I last have one". .reverse(); /** * DEVICES - what is fitted or in use now, the deck's "Pacemaker · Medtronic Azure XT DR". * * `status: active` only: an inactive device is one that was removed, and one entered in error was never * there. The detail is the maker, the model and what the device record says about its safety (an MRI * condition), each only where the record carries it. */ export const deviceRows = (devices: readonly Device[]): SummaryRow[] => devices .filter((device) => device.status === "active") .map((device) => { const named = device.deviceName?.[0]?.name?.trim(); const kind = textOf(device.type); const model = [device.manufacturer?.trim(), device.modelNumber?.trim()].filter(Boolean).join(" "); const second = model || (kind && named && named !== kind ? named : undefined); const safety = (device.safety ?? []).map(textOf).filter(Boolean).join(" · "); return { id: device.id ?? "", title: [kind ?? named ?? "A device", second].filter(Boolean).join(" · "), detail: safety || undefined, }; }); /** What a medicines section says while either family is still from before a reauthorization; undefined when fresh. */ export const medicinesNotCurrent = (freshness: string | undefined): string | undefined => freshness === "updating" ? "Medicines are being read again after reauthorization, so this section may not be current." : freshness === "not-updated" ? "Medicines could not be read again after reauthorization, so this section may not be current." : undefined; export interface SummarySection { key: string; /** "Allergies" - the mono section heading. */ label: string; rows: SummaryRow[]; /** * WHAT IS DUE - Spencer, 2026-08-26: fold the leftover in, then retire the destination. * * THE HONEST VERSION OF THE DECK'S "up to date". That phrase was refused because we hold no * schedule; this says the same kind of thing ONLY when a recommendation actually exists, and says * nothing at all otherwise. Silence here still never means "you are up to date". */ due?: string; /** Rows that open onto something get the chevron treatment; the rest are plain statements. */ opens?: boolean; /** What an empty, fully read section says, when "None recorded" would claim more than the section asks. */ emptyTitle?: string; } /** * The whole page, in the deck's order: allergies, conditions, medicines, vaccinations. * * ALLERGIES LEAD, and the order is clinical rather than alphabetical: it is what somebody treating * this patient needs first, and it is the reason the page exists in a form that can be read out loud. * A SECTION WITH NOTHING IN IT IS STILL SHOWN. *"No allergies recorded"* is a different and more * useful answer than a missing heading, which reads as though the question was never asked. */ /** * WHAT IS DUE, in the patients words. * * ONLY WHAT THE RECOMMENDATION ITSELF SAYS. A `forecastStatus` of `due` or `overdue` is a claim * somebody made; anything else `complete`, `immune`, `contraindicated`, or a status we do not * recognise is NOT a nudge, and inventing one on a vaccination screen is how a patient books an * appointment they did not need. * And no count when nothing is due. Silence is not "up to date"; it is "nothing was recommended". */ const DUE_NOW = new Set(["due", "overdue"]); export const vaccinationsDue = ( recommendations: readonly ImmunizationRecommendation[], ): string | undefined => { const due = recommendations .flatMap((recommendation) => recommendation.recommendation ?? []) .filter((entry) => { const status = entry.forecastStatus?.coding?.[0]?.code?.toLowerCase() ?? entry.forecastStatus?.text?.trim().toLowerCase(); return Boolean(status && DUE_NOW.has(status)); }); if (due.length === 0) return undefined; return `${due.length} due`; }; export const healthSummary = (input: { allergies: readonly AllergyIntolerance[]; conditions: readonly Condition[]; medications: readonly MedicationRequest[]; /** Reported use: read alongside the prescriptions, since either alone is not the medicines section. */ medicationStatements: readonly MedicationStatement[]; immunizations: readonly Immunization[]; recommendations?: readonly ImmunizationRecommendation[]; /** * Devices, when their read answered. A patient with none is not asked the question - the deck draws * the section only for a record that has one - but a failed read still shows it, so the failure is * said where it belongs. */ devices?: readonly Device[]; devicesFailed?: boolean; }): SummarySection[] => [ { key: "allergies", label: "Allergies", rows: allergyRows(input.allergies) }, { key: "conditions", label: "Conditions", rows: conditionRows(input.conditions), opens: true }, { key: "medications", label: "Current prescriptions and reported use", rows: medicationRows(input.medications, input.medicationStatements), emptyTitle: "No current prescriptions or reported use found", }, { key: "immunizations", label: "Vaccinations", rows: immunizationRows(input.immunizations), due: vaccinationsDue(input.recommendations ?? []), }, ...((input.devices && deviceRows(input.devices).length > 0) || input.devicesFailed ? [{ key: "devices", label: "Devices", rows: deviceRows(input.devices ?? []) }] : []), ];