// The share carries a reference only; each viewer reads the item on its own route and authorization. import type { CarePlan, DiagnosticReport, DocumentReference, ImagingStudy, Observation } from 'fhir/r4'; import { environment } from '../../environments/environment'; import { CHANGED_HERE_TEXT, changedHere, isWithdrawnReturned, WITHDRAWN_RESULT_TEXT, withheldText } from '../../utils/returnedPublication'; // A returned result withdrawn here is said to be withdrawn, never summarised as what it once said. const shownWords = (item: { id?: string; status?: string; extension?: DiagnosticReport['extension'] }, words: string | undefined) => withheldText(item) ?? (isWithdrawnReturned(item) ? WITHDRAWN_RESULT_TEXT : words); // A returned result this practice changed after the laboratory returned it says so wherever its words are shown. const changedHereOf = (item: { id?: string; status?: string; extension?: DiagnosticReport['extension'] }) => changedHere(item) && !withheldText(item) && !isWithdrawnReturned(item) ? { changedHere: CHANGED_HERE_TEXT } : {}; import { bloodPressureReading } from '../../utils/observationPanelValue'; /** Studies, results, plans and documents - the kinds the picker offers and the server accepts. */ export const SHAREABLE_TYPES: readonly string[] = ['DiagnosticReport', 'Observation', 'ImagingStudy', 'CarePlan', 'DocumentReference']; const ID = /^[A-Za-z0-9.-]{1,64}$/; export const isShareableReference = (value: string | undefined | null): value is string => { if (typeof value !== 'string') return false; const [type, id, ...rest] = value.split('/'); return rest.length === 0 && SHAREABLE_TYPES.includes(type) && ID.test(id ?? ''); }; /** The ordinary read route for a shown item, or nothing for a reference that cannot be shown. */ export const readUrlFor = (reference: string): string | undefined => { if (!isShareableReference(reference)) return undefined; const base = reference.startsWith('DocumentReference/') ? environment.fhirFoundationApi : environment.fhirClinicalApi; return base + '/' + reference; }; export interface ShownItemSummary { title: string; detail?: string; /** Said beside a returned result this practice changed after the laboratory returned it. */ changedHere?: string; source?: string; when?: string; } export type ShownResource = DiagnosticReport | Observation | ImagingStudy | CarePlan | DocumentReference; const conceptText = (concept?: { text?: string; coding?: { display?: string }[] }) => concept?.text || concept?.coding?.find((c) => c.display)?.display || undefined; const observationValue = (o: Observation): string | undefined => { const pressure = bloodPressureReading(o); if (pressure) return pressure; if (o.valueQuantity?.value !== undefined) { return [o.valueQuantity.value, o.valueQuantity.unit].filter((part) => part !== undefined && part !== '').join(' '); } return o.valueString ?? conceptText(o.valueCodeableConcept); }; /** What the banner and the stage say about an item. Only fields the record holds; nothing inferred. */ export const summariseShownItem = (item: ShownResource): ShownItemSummary => { switch (item.resourceType) { case 'DiagnosticReport': return { title: conceptText(item.code) ?? 'Report', detail: shownWords(item, item.conclusion), ...changedHereOf(item), source: item.performer?.[0]?.display, when: item.effectiveDateTime ?? item.issued, }; case 'Observation': return { title: conceptText(item.code) ?? 'Result', detail: shownWords(item, observationValue(item)), ...changedHereOf(item), source: item.performer?.[0]?.display, when: item.effectiveDateTime ?? item.issued, }; case 'ImagingStudy': return { title: item.description ?? conceptText(item.procedureCode?.[0]) ?? 'Imaging study', source: item.series?.[0]?.performer?.[0]?.actor?.display, when: item.started, }; case 'CarePlan': return { title: item.title ?? 'Care plan', detail: item.description, source: item.author?.display, when: item.period?.start ?? item.created, }; case 'DocumentReference': return { title: item.description ?? conceptText(item.type) ?? 'Document', source: item.custodian?.display ?? item.author?.[0]?.display, when: item.date, }; } }; /** "Cedar Valley Imaging, Sep 2" - the source and the day, whichever the record holds. */ export const provenanceLine = (summary: ShownItemSummary): string | undefined => { const day = summary.when && !Number.isNaN(new Date(summary.when).getTime()) ? new Date(summary.when).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : undefined; return [summary.source, day].filter(Boolean).join(', ') || undefined; };