import type { ReactNode } from "react"; import type { Patient } from "fhir/r4"; import { FhirHumanNameDisplay } from "../../../../utils/fhirHumanName.tsx"; import { getPatientBandLine } from "../../../../components/PatientProfileCard/PatientProfileCard"; import { formatFhirHumanName } from "../../../../utils/fhirHumanName.tsx"; import PatientMark from "../../ReceptionistHomePage/PatientMark"; import { useFhirAllergyIntolerances } from "../../../../hooks/useFhirAllergyIntolerances"; import { ownedRead } from "../../../../hooks/usePatientRecordStream"; import { allergyRows } from "../../../../utils/patientHealthSummary"; import PresenceDot from "../../../../components/PresenceDot/PresenceDot"; import { practiceHasNoCareRelationship } from "../../../../utils/careRelationship"; import { usePatientRecordStream } from "../../../../hooks/usePatientRecordStream"; import { isAsOfIncomplete } from "../../../../utils/searchTruncation"; import { availableRecordEmpty, formatCutoff, useChartRecordStanding, type RecordFreshness } from "../../../../hooks/useRecordAccessStanding"; import { CONSENT_INSTRUMENT_DEFINITIONS } from "../../../../config/consentInstruments"; /** * WHOSE CHART IS THIS? - the staff chart's persistent answer. * * MEASURED IN THE LIVE APP: on `.../clinical/conditions`, and on all thirty-two clinical destinations, * **the patient's name appeared nowhere on the page.** The identity block lives on the Profile section, so * it leaves with it - and the only remaining identifier was a UUID in the address bar. * * THAT IS A WRONG-PATIENT RISK, NOT A LABELLING GAP. A clinician reading, adding or editing clinical * data had nothing on screen naming the person. The failure mode is the wrong chart, and it is silent: * **nothing on the page could contradict a mistaken belief about who was open.** Charts are reached from a * list of similar rows, and real practices have families, juniors and repeated surnames. * * THIS IS THE HUMAN HALF OF. That card made the app refuse to load a patient who disagrees * with what the surface declared. **A guard nobody can corroborate by looking at the screen is the whole * of our protection** - this is what lets a person check it. */ export interface PatientChartIdentityProps { /** The loaded patient. */ patient: Patient | undefined; /** * THE ID FROM THE ROUTE, and the reason this component takes two things that look like one. * * It renders ONLY when the loaded patient is the patient the URL names. Not "whatever the patient * context happens to hold" - exists because that context can go stale, and a header that * confidently displays the WRONG name is worse than one that displays none: it manufactures exactly the * false confidence the rest of this work removes. */ routePatientId: string | undefined; /** * The organization whose chart this is - passed IN rather than read from a context. * * This component is deliberately presentational: its own tests render it bare, so reaching for the auth * context here would make every caller provide an auth provider merely to display a name. The page * above already holds the organization, and handing it down costs one prop. * * Optional, so a caller that does not have one simply gets no care-relationship line - which is the * correct silence rather than a claim made without evidence. */ organizationId?: string; /** The visit this chart is open inside, when it is: a chip and a verb change, nothing else moves. */ visit?: { label: string }; /** The allergy pill, handed in so the band itself stays presentational. */ allergies?: ReactNode; /** The band's own actions, right-aligned after the patient's facts. */ actions?: ReactNode; } // Speaks only once the record's reads were answered with a 403: a care team can grant the record with no // general practitioner named, so the missing link alone proves nothing. const RefusedRecordLine = () => { const { refused } = usePatientRecordStream(); if (!refused) return null; return ( This practice cannot read this patient's clinical record. Check their {CONSENT_INSTRUMENT_DEFINITIONS['hipaa-authorization'].name} and care relationship on the Profile section. ); }; // After a reauthorization, a list not yet read under it is never shown as complete. const freshnessNote = (freshness: RecordFreshness | undefined): string => freshness === "updating" ? " (updating after reauthorization)" : freshness === "not-updated" ? " (could not be updated after reauthorization)" : ""; // Allergies ride the band on every section; another patient's list or a failed read never reads as none. export const PatientChartAllergies = () => { const allergies = useFhirAllergyIntolerances(); const { standing } = useChartRecordStanding(); const read = ownedRead(allergies, allergies.allergyIntolerancesList); // A refusal is an answer, not a failed load. if (allergies.error && allergies.refused) { return Allergies not available to you; } if (allergies.error) { return Allergies could not be loaded; } if (read.loading) return null; const names = allergyRows(read.list).map((row) => row.title); // Served as of a cutoff and not in full: what is missing is unknown, so it is never "none". const incomplete = isAsOfIncomplete(allergies.allergyIntolerancesBundle); if (incomplete && names.length === 0) { return Allergies could not be read in full; } return names.length > 0 ? ( {names.join(" ยท ")}{incomplete ? " (list may be incomplete)" : ""}{freshnessNote(allergies.freshness)} ) : ( {availableRecordEmpty("No allergies recorded", standing, allergies.freshness)} ); }; // WHICH RECORD THIS IS, while the patient's HIPAA Authorization for this practice is not current: the record as it // stood at the withdrawal, said on every section, or that the standing could not be checked (with Retry). export const PatientChartRecordStanding = () => { const { standing, retry } = useChartRecordStanding(); if (standing.kind === "withdrawn") { return ( {standing.at ? `Record available through ${formatCutoff(standing.at)}. Newer information is not available to this practice.` : "The patient withdrew this practice's record authorization. The record shown is as it stood then; newer information is not available to this practice."} ); } if (standing.kind === "unknown") { return ( This patient's record authorization could not be checked, so whether this is their current record is not known.{" "} ); } return null; }; // New messages and orders need the patient's current authorization: while it is withdrawn they are off, and say why. // Old conversations and orders stay readable; the server decides every other refusal. export const PatientChartNewActions = ({ onMessage, onOrder }: { onMessage: () => void; onOrder: () => void }) => { const { standing } = useChartRecordStanding(); const withdrawn = standing.kind === "withdrawn"; return ( <> {withdrawn && ( New messages and orders need the patient's current authorization. )} ); }; export const PatientChartIdentity = ({ patient, routePatientId, organizationId, visit, allergies, actions }: PatientChartIdentityProps) => { const noCareRelationship = practiceHasNoCareRelationship(patient, organizationId); // Fails closed: a name here is a claim about which chart is open, made only when the route and the loaded patient agree. if (!patient || !routePatientId || patient.id !== routePatientId) return null; return (
{/* An h2: the page's only h1 belongs to the WordPress theme. */}

{getPatientBandLine(patient)}
{allergies} {visit && (