import type { Address, Patient } from 'fhir/r4'; import type { IValueSet } from '../../services/ValueSetService'; import { readDemographics } from '../../components/FhirPatientDisplay/demographics'; import { telecomSlots, telecomUseSlots } from '../../components/FhirPatientDisplay/telecomSlots'; import { formatFhirHumanName } from '../fhirHumanName'; import { interpreterNeededLabel } from '../interpreterNeeded'; import { enrolledLabel } from '../tribalAffiliation'; import { formatPhoneNumber } from '../phoneNumber'; import { getAtPath } from './getAtPath'; /** * History bindings for profile fields that have no fixed position in a Patient: answers kept in * extensions (whose order in `Patient.extension` shifts whenever another one is added), repeats, and * the formatted name. Each is read by what it means, the same way the profile reads it, so a field's * history never picks up another extension that happens to sit at its old index. */ export const PROFILE_HISTORY = { name: 'profile:name', languages: 'profile:languages', preferredLanguage: 'profile:preferredLanguage', interpreterNeeded: 'profile:interpreterNeeded', interpreterAnswer: 'profile:interpreterAnswer', races: 'profile:races', detailedRaces: 'profile:detailedRaces', ethnicities: 'profile:ethnicities', detailedEthnicities: 'profile:detailedEthnicities', tribes: 'profile:tribes', enrolled: 'profile:enrolled', phone: 'profile:phone', email: 'profile:email', } as const; /** Address rows are shown by index, the same way How to reach you lists them. */ export const profileAddressHistory = (index: number): string => `profile:address:${index}`; /** "1200 Gear Avenue, West Burlington, IA 52655". */ export const formatProfileAddress = (address: Address): string => { const region = [address.state, address.postalCode].filter(Boolean).join(' '); return [...(address.line ?? []), address.city, region].filter(Boolean).join(', '); }; const ADDRESS_HISTORY = /^profile:address:(\d+)$/; const phoneHistory = (patient: Patient): string[] | undefined => { const values = telecomSlots(patient.telecom, 'phone'); const uses = telecomUseSlots(patient.telecom, 'phone'); const slots = ([0, 1] as const) .filter((index) => values[index]) .map((index) => { const formatted = formatPhoneNumber(values[index]); return uses[index] ? `${formatted} ${uses[index]}` : formatted; }); return slots.length ? slots : undefined; }; const emailHistory = (patient: Patient): string[] | undefined => { const values = telecomSlots(patient.telecom, 'email').filter(Boolean); return values.length ? values : undefined; }; // An empty answer is absent, so a cleared list and a never-given one read alike as "(not set)". const displays = (items: IValueSet[]): string[] | undefined => items.length ? items.map((item) => item.display || item.code) : undefined; const preferredLanguage = (patient: Patient): string | undefined => { const entries = patient.communication ?? []; const language = (entries.find((entry) => entry.preferred) ?? entries[0])?.language; return language?.coding?.[0]?.display || language?.text || language?.coding?.[0]?.code || undefined; }; const READERS: Record unknown> = { [PROFILE_HISTORY.name]: (patient) => formatFhirHumanName(patient.name, { emptyText: '' }) || undefined, [PROFILE_HISTORY.languages]: (patient) => displays(readDemographics(patient).languages), [PROFILE_HISTORY.preferredLanguage]: preferredLanguage, // The patient's profile reads no answer as No, so its history does too; staff see the answer itself. [PROFILE_HISTORY.interpreterNeeded]: (patient) => interpreterNeededLabel(patient) ?? 'No', [PROFILE_HISTORY.interpreterAnswer]: (patient) => interpreterNeededLabel(patient), [PROFILE_HISTORY.races]: (patient) => displays(readDemographics(patient).races), [PROFILE_HISTORY.detailedRaces]: (patient) => displays(readDemographics(patient).detailedRaces), [PROFILE_HISTORY.ethnicities]: (patient) => displays(readDemographics(patient).ethnicities), [PROFILE_HISTORY.detailedEthnicities]: (patient) => displays(readDemographics(patient).detailedEthnicities), [PROFILE_HISTORY.tribes]: (patient) => displays(readDemographics(patient).tribes), [PROFILE_HISTORY.enrolled]: (patient) => { const demographics = readDemographics(patient); return enrolledLabel(demographics.tribes.length > 0, demographics.enrolled) || undefined; }, [PROFILE_HISTORY.phone]: phoneHistory, [PROFILE_HISTORY.email]: emailHistory, }; /** One version's value for a history binding: a profile meaning, or a plain path into the resource. */ export function readFieldValue(resource: unknown, path: string): unknown { const read = READERS[path]; if (read) return resource && typeof resource === 'object' ? read(resource as Patient) : undefined; const addressMatch = path.match(ADDRESS_HISTORY); if (addressMatch && resource && typeof resource === 'object') { const address = (resource as Patient).address?.[Number(addressMatch[1])]; return address ? formatProfileAddress(address) : undefined; } return getAtPath(resource, path); }