import type { Condition, MedicationRequest } from "fhir/r4"; import type { RecordEvent } from "./patientRecordStream"; import { medicationNameOf } from "./medicationName"; /** * EVERYTHING ABOUT ONE CONDITION - the card's third verify point, and the one no other portal does. * * Filtering the stream to a condition already answers *"show me everything"*. This answers the * question underneath it: **where does this condition stand?** - what was diagnosed and when, what is * being taken for it now, when it was last looked at, and how much of the record it accounts for. * * EVERY LINE IS COUNTED OR QUOTED, NEVER INFERRED. The deck also draws *"Last HbA1c 6.8% falling"*. * That is a claim about a SERIES - it needs this result matched against every earlier one by code, and * a loose match produces a confident sentence about the wrong measurement. It is not built, and its * absence is the honest state rather than an omission to tidy up later. * * Shape only: no clock, no fetch, no render. */ export interface ConditionOverview { name: string; /** "Diagnosed 12 March 2021 by Dr. Reyes - Riverside Family Medicine". */ diagnosed?: string; /** "Metformin 1000mg - Twice daily" - one per medicine still being taken FOR this condition. */ takingNow: string[]; /** "7 August 2026 - Dr. Reyes" - the newest thing in the record about it. */ lastReviewed?: string; /** "23 items - 2 practices - 5 years". All three counted from the events themselves. */ footprint: string; } const dayLabel = (when?: string): string | undefined => { if (!when) return undefined; const parts = /^(\d{4})-(\d{2})-(\d{2})/.exec(when); // Read from the digits when it is date-only: re-parsed as a UTC instant it lands a day earlier in // Iowa, which on a diagnosis date is the wrong day of somebody's life. const at = parts ? new Date(Number(parts[1]), Number(parts[2]) - 1, Number(parts[3])) : new Date(when); return Number.isNaN(at.getTime()) ? undefined : at.toLocaleDateString(undefined, { day: "numeric", month: "long", year: "numeric" }); }; const yearOf = (when: string): number | undefined => { const year = Number(when.slice(0, 4)); return Number.isNaN(year) || year === 0 ? undefined : year; }; /** * HOW LONG THE RECORD RUNS FOR THIS CONDITION, in years, from the events themselves. * * Years apart, NOT a duration: "5 years" from 2021 to 2026 is what a patient means, and computing it * from a millisecond difference gives 4 whenever the anniversary has not come round yet - a number that * is arithmetically right and reads as wrong to the person whose record it is. */ const spanOf = (events: readonly RecordEvent[]): string | undefined => { const years = events .map((event) => event.when) .filter((when): when is string => Boolean(when)) .map(yearOf) .filter((year): year is number => year !== undefined); if (years.length === 0) return undefined; const span = Math.max(...years) - Math.min(...years); if (span === 0) return undefined; return `${span} year${span === 1 ? "" : "s"}`; }; export const buildConditionOverview = (input: { /** The name to lead with. Always available - it is what the reader tapped. */ name: string; condition?: Condition; /** Every event the About filter kept. */ events: readonly RecordEvent[]; /** ACTIVE requests only; the caller has already applied that filter, which is where it belongs. */ medications: readonly MedicationRequest[]; /** The condition's own id, so a medicine can be matched to it. */ conditionId?: string; }): ConditionOverview => { const { name, condition, events, medications, conditionId } = input; const diagnosedOn = dayLabel(condition?.onsetDateTime ?? condition?.recordedDate); const diagnosedBy = condition?.recorder?.display?.trim() || condition?.asserter?.display?.trim(); // The deck also names the PRACTICE on this line. A Condition has no service provider, and the // encounter it references is a visit rather than an organisation - so the only way to fill it would // be to guess from the events, which is a different claim wearing the same words. // A MEDICINE BELONGS HERE BECAUSE IT NAMES THE CONDITION, never because it is in the filtered set. // The set also holds medicines a patient happened to be given at a visit ABOUT this condition, and // listing one of those under "Taking now for your diabetes" is a claim the record does not make. const takingNow = medications .filter((request) => (request.reasonReference ?? []).some( (reference) => reference.reference?.split("/").pop() === conditionId, ), ) .map((request) => [ medicationNameOf(request), request.dosageInstruction?.[0]?.text?.trim(), ] .filter(Boolean) .join(" · "), ) .filter((line) => line.length > 0); // The newest DATED event. An undated one is real and is not evidence of when anything happened. const newest = [...events] .filter((event) => event.when) .sort((a, b) => b.when!.localeCompare(a.when!))[0]; const practices = new Set( events.map((event) => event.organizationId ?? event.organizationName).filter(Boolean), ).size; const footprint = [ `${events.length} item${events.length === 1 ? "" : "s"}`, practices > 1 ? `${practices} practices` : undefined, spanOf(events), ] .filter(Boolean) .join(" · "); return { name, diagnosed: diagnosedOn ? [`Diagnosed ${diagnosedOn}`, diagnosedBy && `by ${diagnosedBy}`].filter(Boolean).join(" ") : undefined, takingNow, lastReviewed: newest ? [dayLabel(newest.when), newest.clinicianName].filter(Boolean).join(" · ") : undefined, footprint, }; };