import type { FieldTimeline, FieldTimelineEntry } from './buildFieldTimeline'; import { PROFILE_HISTORY, readFieldValue } from './profileHistoryFields'; /** * One verified demographic edit from `GET Patient/{id}/demographic-history`: the field this practice * changed, its new value (absent when cleared), when, and who. Never a previous value. */ export interface DemographicHistoryRow { field: string; value?: unknown; recorded: string; author: { reference: string; display?: string }; } /** The Patient field a history binding reads, or undefined for one the route never covers. */ const fieldOf = (path: string): string | undefined => { if (path === PROFILE_HISTORY.name) return 'name'; if (path === PROFILE_HISTORY.phone || path === PROFILE_HISTORY.email) return 'telecom'; if (path.startsWith('profile:address:')) return 'address'; if (path.startsWith('profile:')) return undefined; return path.split(/[.[]/)[0]; }; const sameValue = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b); /** * A field's timeline from verified edits alone. `rows` newest-first. The oldest entry stands without * a "before": what preceded this practice's first edit is not ours to show. */ export function demographicTimeline(rows: DemographicHistoryRow[], path: string, label: string): FieldTimeline { const field = fieldOf(path); const values = rows .filter((row) => row.field === field) .map((row) => ({ row, value: readFieldValue({ resourceType: 'Patient', [row.field]: row.value }, path) })); const entries: FieldTimelineEntry[] = values .filter((current, index) => index === values.length - 1 || !sameValue(current.value, values[index + 1].value)) .map(({ row, value }) => ({ value, whenIso: row.recorded, ...(row.author.display ? { who: row.author.display } : { whoConfidence: 'unknown' as const }), })); return { path, label, changed: entries.length > 0, entries }; }