import { Annotation, CodeableConcept, Observation, ObservationReferenceRange } from "fhir/r4"; import { formatDateForDisplay } from "../../lib/utils"; import { StatusBadge } from "../ui/status"; import { CHANGED_HERE_TEXT, changedHere, withheldText } from "../../utils/returnedPublication"; import "./ObservationResultPanel.css"; const ABOVE = new Set(["H", "HH", "HU", ">"]); const BELOW = new Set(["L", "LL", "LU", "<"]); export const isLaboratoryResult = (observation: Observation): boolean => (observation.category ?? []).some((category) => (category.coding ?? []).some((coding) => coding.code === "laboratory")); export const resultName = (observation: Observation): string => observation.code?.text || observation.code?.coding?.[0]?.display || "Result"; const conceptText = (concept: CodeableConcept): string => concept.text || (concept.coding ?? []).map((coding) => coding.display || coding.code).filter(Boolean).join(", "); const rangeText = (range: ObservationReferenceRange): string => { if (range.text) return range.text; const unit = range.high?.unit ?? range.low?.unit ?? ""; const { low, high } = { low: range.low?.value, high: range.high?.value }; const span = low !== undefined && high !== undefined ? `${low}–${high}` : low !== undefined ? `${low} or above` : high !== undefined ? `up to ${high}` : ""; return span ? `${span}${unit ? ` ${unit}` : ""}` : ""; }; // The flag words only for a direction the lab coded; a normal result carries no chip. const flagOf = (observation: Observation): { words: string; tone: "bad" | "warn" } | undefined => { const concept = observation.interpretation?.[0]; if (!concept) return undefined; const codes = (concept.coding ?? []).map((coding) => coding.code ?? ""); if (codes.some((code) => ABOVE.has(code))) return { words: "Above the usual range", tone: "bad" }; if (codes.some((code) => BELOW.has(code))) return { words: "Below the usual range", tone: "bad" }; if (codes.includes("N")) return undefined; const words = conceptText(concept); return words ? { words, tone: "warn" } : undefined; }; const whenOf = (observation: Observation): string | undefined => observation.effectiveDateTime ?? observation.effectivePeriod?.start ?? observation.issued; // Results filed against an order carry only the test's name, so the name is the key when no code is. const codeKey = (observation: Observation): string | undefined => { const coding = observation.code?.coding?.find((each) => each.code); if (coding) return `${coding.system ?? ""}|${coding.code}`; return observation.code?.text ? `text|${observation.code.text.trim().toLowerCase()}` : undefined; }; const authorOf = (note: Annotation): string | undefined => note.authorString ?? note.authorReference?.display; interface Point { value: number; when: string; current: boolean } // Earlier numeric values of the same coded test, oldest first, ending with this one. const trendOf = (observation: Observation, history: Observation[]): Point[] => { const key = codeKey(observation); if (!key || observation.valueQuantity?.value === undefined) return []; const points = history .filter((each) => each.id !== observation.id && codeKey(each) === key && each.valueQuantity?.value !== undefined && whenOf(each)) .filter((each) => each.status !== "entered-in-error" && each.status !== "cancelled") .map((each) => ({ value: each.valueQuantity!.value!, when: whenOf(each)!, current: false })) .filter((each) => !whenOf(observation) || each.when <= whenOf(observation)!) .sort((a, b) => a.when.localeCompare(b.when)) .slice(-4); return points.length ? [...points, { value: observation.valueQuantity.value, when: whenOf(observation) ?? "", current: true }] : []; }; const Spark = ({ points }: { points: Point[] }) => { const values = points.map((point) => point.value); const [min, max] = [Math.min(...values), Math.max(...values)]; const x = (index: number) => 10 + (280 * index) / (points.length - 1); const y = (value: number) => (max === min ? 45 : 80 - (70 * (value - min)) / (max - min)); const last = points[points.length - 1]; return (
    {points.map((point, index) => (
  1. {point.value} {point.current ? now : index === 0 ? {new Date(point.when).getFullYear()} : null}
  2. ))}
); }; interface ObservationResultPanelProps { observation: Observation; history?: Observation[]; } export const ObservationResultPanel = ({ observation, history = [] }: ObservationResultPanelProps) => { const performer = observation.performer?.find((each) => each.display)?.display; const when = whenOf(observation); const quantity = observation.valueQuantity; const value = quantity?.value !== undefined ? String(quantity.value) : observation.valueString ?? (observation.valueCodeableConcept ? conceptText(observation.valueCodeableConcept) : undefined); const range = observation.referenceRange?.map(rangeText).find(Boolean); const flag = flagOf(observation); const trend = trendOf(observation, history); const notes = (observation.note ?? []).filter((note) => note.text); return (
{performer || when ? (

{[performer, when && formatDateForDisplay(when)].filter(Boolean).join(" · ")}

) : null} {observation.status !== "final" ? : null}
{value ?? withheldText(observation) ?? "No value recorded"} {value !== undefined && quantity?.unit ? {quantity.unit} : null} {range ? Usual range {range} : null} {value !== undefined && changedHere(observation) ? {CHANGED_HERE_TEXT} : null} {flag ? {flag.words} : null}
{trend.length ? (

Over time

) : null} {notes.map((note, index) => (

{authorOf(note) ? `What ${authorOf(note)} said` : "Note"}

{note.text} {note.time ? {formatDateForDisplay(note.time)} : null}
))}
); };