import type { Observation } from 'fhir/r4'; const LOINC = 'http://loinc.org'; const UCUM = 'http://unitsofmeasure.org'; const VITAL_SIGNS_CATEGORY = [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/observation-category', code: 'vital-signs', display: 'Vital Signs' }], }]; export interface VitalsInput { systolic?: number; diastolic?: number; heartRate?: number; respRate?: number; temperature?: number; spo2?: number; height?: number; weight?: number; } // The simple (single-value) vitals that drive the form grid - US customary units. export const SIMPLE_VITALS: { key: keyof VitalsInput; label: string; loinc: string; loincDisplay: string; unitCode: string; unitDisplay: string }[] = [ { key: 'heartRate', label: 'Heart rate', loinc: '8867-4', loincDisplay: 'Heart rate', unitCode: '/min', unitDisplay: '/min' }, { key: 'respRate', label: 'Resp. rate', loinc: '9279-1', loincDisplay: 'Respiratory rate', unitCode: '/min', unitDisplay: '/min' }, { key: 'temperature', label: 'Temp', loinc: '8310-5', loincDisplay: 'Body temperature', unitCode: '[degF]', unitDisplay: '°F' }, { key: 'spo2', label: 'SpO₂', loinc: '2708-6', loincDisplay: 'Oxygen saturation', unitCode: '%', unitDisplay: '%' }, { key: 'height', label: 'Height', loinc: '8302-2', loincDisplay: 'Body height', unitCode: '[in_i]', unitDisplay: 'in' }, { key: 'weight', label: 'Weight', loinc: '29463-7', loincDisplay: 'Body weight', unitCode: '[lb_av]', unitDisplay: 'lb' }, ]; const isNum = (v?: number): v is number => typeof v === 'number' && !Number.isNaN(v); export const computeBmi = (heightIn?: number, weightLb?: number): number | undefined => { if (!isNum(heightIn) || !isNum(weightLb) || heightIn <= 0) return undefined; return Math.round((703 * weightLb / (heightIn * heightIn)) * 10) / 10; }; const base = (loinc: string, display: string, patientId: string, encounterId: string | undefined, nowIso: string): Observation => ({ resourceType: 'Observation', status: 'final', category: VITAL_SIGNS_CATEGORY, code: { coding: [{ system: LOINC, code: loinc, display }] }, subject: { reference: `Patient/${patientId}` }, ...(encounterId && { encounter: { reference: `Encounter/${encounterId}` } }), effectiveDateTime: nowIso, }); const qty = (value: number, unitDisplay: string, unitCode: string) => ({ value, unit: unitDisplay, system: UCUM, code: unitCode }); export const buildVitalObservations = (values: VitalsInput, patientId: string, encounterId: string | undefined, nowIso: string): Observation[] => { const out: Observation[] = []; // Blood pressure panel - only when both sides entered. if (isNum(values.systolic) && isNum(values.diastolic)) { out.push({ ...base('85354-9', 'Blood pressure panel', patientId, encounterId, nowIso), component: [ { code: { coding: [{ system: LOINC, code: '8480-6', display: 'Systolic blood pressure' }] }, valueQuantity: qty(values.systolic, 'mmHg', 'mm[Hg]') }, { code: { coding: [{ system: LOINC, code: '8462-4', display: 'Diastolic blood pressure' }] }, valueQuantity: qty(values.diastolic, 'mmHg', 'mm[Hg]') }, ], }); } for (const v of SIMPLE_VITALS) { const value = values[v.key]; if (isNum(value)) out.push({ ...base(v.loinc, v.loincDisplay, patientId, encounterId, nowIso), valueQuantity: qty(value, v.unitDisplay, v.unitCode) }); } const bmi = computeBmi(values.height, values.weight); if (isNum(bmi)) out.push({ ...base('39156-5', 'Body mass index (BMI)', patientId, encounterId, nowIso), valueQuantity: qty(bmi, 'kg/m²', 'kg/m2') }); return out; }; /** Where the person entering readings says they came from. Only "measured here" can make them this practice's own. */ export type VitalSource = 'measured-here' | 'from-another-record'; /** One reading as the native vital route takes it: typed fields only; the server owns codes, units and the record. */ export interface NativeVitalEntry { kind: 'blood-pressure' | 'heart-rate' | 'resp-rate' | 'temperature' | 'spo2' | 'height' | 'weight'; value?: number; systolic?: number; diastolic?: number; effectiveDateTime: string; encounterId?: string; source: VitalSource; } const NATIVE_KIND: Record<(typeof SIMPLE_VITALS)[number]['key'], NativeVitalEntry['kind'] | undefined> = { heartRate: 'heart-rate', respRate: 'resp-rate', temperature: 'temperature', spo2: 'spo2', height: 'height', weight: 'weight', systolic: undefined, diastolic: undefined, }; /** The entries to send, one per reading. A blood pressure needs both halves; BMI is shown, never sent. */ export const buildNativeVitalEntries = ( values: VitalsInput, encounterId: string | undefined, nowIso: string, source: VitalSource, ): { field: keyof VitalsInput | 'bloodPressure'; entry: NativeVitalEntry }[] => { const common = { effectiveDateTime: nowIso, source, ...(encounterId ? { encounterId } : {}) }; const out: { field: keyof VitalsInput | 'bloodPressure'; entry: NativeVitalEntry }[] = []; if (isNum(values.systolic) && isNum(values.diastolic)) { out.push({ field: 'bloodPressure', entry: { kind: 'blood-pressure', systolic: values.systolic, diastolic: values.diastolic, ...common } }); } for (const v of SIMPLE_VITALS) { const value = values[v.key]; const kind = NATIVE_KIND[v.key]; if (kind && isNum(value)) out.push({ field: v.key, entry: { kind, value, ...common } }); } return out; };