import { restBaseUrl } from '@openmrs/esm-framework'; import { type PatientData, type ObsRecord, type ConceptUuid, type ConceptRecord, type ObsMetaInfo, type OBSERVATION_INTERPRETATION, } from '@openmrs/esm-patient-common-lib'; import type { FHIRObservationResource } from '../../types'; import { type ReferenceRanges } from '../grouped-timeline/reference-range-helpers'; const PAGE_SIZE = 300; const CHUNK_PREFETCH_COUNT = 1; const retrieveFromIterator = (iteratorOrIterable: IterableIterator, length: number): Array => { const iterator = iteratorOrIterable[Symbol.iterator](); return Array.from({ length }, () => iterator.next().value); }; const PATIENT_DATA_CACHE_SIZE = 5; let patientResultsDataCache: Record = {}; /** * Adds given user testresults data to a cache * * @param patientUuid * @param data {PatientData} * @param indicator UUID of the newest observation */ export function addUserDataToCache(patientUuid: string, data: PatientData, indicator: string) { patientResultsDataCache[patientUuid] = [data, Date.now(), indicator]; const currentStateEntries = Object.entries(patientResultsDataCache); if (currentStateEntries.length > PATIENT_DATA_CACHE_SIZE) { currentStateEntries.sort(([, [, dateA]], [, [, dateB]]) => dateB - dateA); patientResultsDataCache = Object.fromEntries(currentStateEntries.slice(0, PATIENT_DATA_CACHE_SIZE)); } } async function getLatestObsUuid(patientUuid: string): Promise { const request = fhirObservationRequests({ patient: patientUuid, category: 'laboratory', _sort: '-_date', _summary: 'data', _format: 'json', _count: '1', }); const result = await request.next().value; return result?.entry?.[0]?.resource?.id; } /** * Retrieves cached user testresults data * Checks the indicator against the backend while doing so * * @param { string } patientUuid * @param { PatientData } data * @param { string } indicator UUID of the newest observation */ export function getUserDataFromCache(patientUuid: string): [PatientData | undefined, Promise] { const cacheEntry = patientResultsDataCache[patientUuid]; const [data, , indicator] = cacheEntry || []; return [ data, !!data && indicator ? getLatestObsUuid(patientUuid).then((obsUuid) => obsUuid !== indicator) : Promise.resolve(true), ]; } /** * Iterator * @param queries */ function* fhirObservationRequests(queries: Record) { const fhirPathname = `${window.openmrsBase}/ws/fhir2/R4/Observation`; const path = fhirPathname + '?' + Object.entries(queries) .map(([q, v]) => q + '=' + v) .join('&'); const pathWithPageOffset = (offset) => path + '&_getpagesoffset=' + offset * PAGE_SIZE; let offsetCounter = 0; while (true) { yield fetch(pathWithPageOffset(offsetCounter++)).then((res) => res.json()); } } /** * Load all patient testresult observations in parallel * * @param { string } patientUuid * @returns { Promise> } */ export const loadObsEntries = async (patientUuid: string): Promise> => { const requests = fhirObservationRequests({ patient: patientUuid, category: 'laboratory', _sort: '-_date', _summary: 'data', _format: 'json', _count: '' + PAGE_SIZE, }); let responses = await Promise.all(retrieveFromIterator(requests, CHUNK_PREFETCH_COUNT)); const total = responses[0]?.total ?? 0; if (total > CHUNK_PREFETCH_COUNT * PAGE_SIZE) { const missingRequestsCount = Math.ceil(total / PAGE_SIZE) - CHUNK_PREFETCH_COUNT; responses = [...responses, ...(await Promise.all(retrieveFromIterator(requests, missingRequestsCount)))]; } return responses.slice(0, Math.ceil(total / PAGE_SIZE)).flatMap((res) => res?.entry?.map((e) => e.resource) ?? []); }; export const getEntryConceptClassUuid = (entry: ObsRecord | FHIRObservationResource): string => entry?.code?.coding?.[0]?.code ?? ''; const conceptCache: Record> = {}; /** * fetch all concepts for all given observation entries */ export function loadPresentConcepts(entries: Array): Promise> { const conceptUuids = [...new Set(entries.map(getEntryConceptClassUuid).filter(Boolean))]; return Promise.allSettled( conceptUuids.map( (conceptUuid) => conceptCache[conceptUuid] || (conceptCache[conceptUuid] = fetch(`${window.openmrsBase}${restBaseUrl}/concept/${conceptUuid}?v=full`) .then((res) => { if (!res.ok) { throw new Error(`Failed to fetch concept ${conceptUuid}: ${res.statusText}`); } return res.json(); }) .catch((error) => { // Remove failed promise from cache so it can be retried delete conceptCache[conceptUuid]; throw error; })), ), ).then((results) => results .filter((result): result is PromiseFulfilledResult => result.status === 'fulfilled') .map((result) => result.value), ); } /** * returns true if no value is null or undefined * * @param args any * @returns {boolean} */ export function exist(...args: any[]): boolean { for (const y of args) { if (y === null || y === undefined) { return false; } } return true; } /** * Extracts reference ranges from FHIR Observation referenceRange field. * Handles different range types: normal, treatment, and absolute. */ export function extractObservationReferenceRanges( resource: FHIRObservationResource | ObsRecord, ): ReferenceRanges | undefined { if (!resource.referenceRange || resource.referenceRange.length === 0) { return undefined; } const ranges: ReferenceRanges = { units: resource.valueQuantity?.unit, }; resource.referenceRange.forEach((range) => { const rangeType = range.type?.coding?.[0]?.code; const rangeSystem = range.type?.coding?.[0]?.system; if (rangeSystem === 'http://terminology.hl7.org/CodeSystem/referencerange-meaning') { if (rangeType === 'normal') { ranges.hiNormal = range.high?.value; ranges.lowNormal = range.low?.value; } else if (rangeType === 'treatment') { ranges.hiCritical = range.high?.value; ranges.lowCritical = range.low?.value; } } else if (rangeSystem === 'http://fhir.openmrs.org/ext/obs/reference-range' && rangeType === 'absolute') { ranges.hiAbsolute = range.high?.value; ranges.lowAbsolute = range.low?.value; } }); // Only return if we found at least one range value if ( ranges.hiNormal !== undefined || ranges.lowNormal !== undefined || ranges.hiCritical !== undefined || ranges.lowCritical !== undefined || ranges.hiAbsolute !== undefined || ranges.lowAbsolute !== undefined ) { return ranges; } return undefined; } /** * Extracts and maps FHIR Observation interpretation to OBSERVATION_INTERPRETATION. * Supports both interpretation codes (e.g., "LL", "N", "H") and display values (e.g., "Critically Low", "Normal"). */ export function extractObservationInterpretation( resource: FHIRObservationResource | ObsRecord, ): OBSERVATION_INTERPRETATION | undefined { if (!resource.interpretation || resource.interpretation.length === 0) { return undefined; } const interpretation = resource.interpretation[0]; const code = interpretation.coding?.[0]?.code; const display = interpretation.coding?.[0]?.display || interpretation.text; // Map FHIR interpretation codes (HL7 v3 ObservationInterpretation codes) if (code) { switch (code.toUpperCase()) { case 'LL': return 'CRITICALLY_LOW'; case 'HH': return 'CRITICALLY_HIGH'; case 'L': return 'LOW'; case 'H': return 'HIGH'; case 'N': return 'NORMAL'; case 'LU': return 'OFF_SCALE_LOW'; case 'HU': return 'OFF_SCALE_HIGH'; default: // Fall through to display mapping break; } } // Map FHIR interpretation display values if (display) { const normalized = display.trim().toLowerCase(); switch (normalized) { case 'critically low': return 'CRITICALLY_LOW'; case 'critically high': return 'CRITICALLY_HIGH'; case 'low': return 'LOW'; case 'high': return 'HIGH'; case 'normal': return 'NORMAL'; case 'off scale low': return 'OFF_SCALE_LOW'; case 'off scale high': return 'OFF_SCALE_HIGH'; default: return undefined; } } return undefined; } export const assessValue = (meta: ObsMetaInfo) => (value: string): OBSERVATION_INTERPRETATION => { if (isNaN(parseFloat(value))) { return 'NORMAL'; } const numericValue = parseFloat(value); if (exist(meta.hiAbsolute) && numericValue > meta.hiAbsolute) { return 'OFF_SCALE_HIGH'; } if (exist(meta.hiCritical) && numericValue > meta.hiCritical) { return 'CRITICALLY_HIGH'; } if (exist(meta.hiNormal) && numericValue > meta.hiNormal) { return 'HIGH'; } if (exist(meta.lowAbsolute) && numericValue < meta.lowAbsolute) { return 'OFF_SCALE_LOW'; } if (exist(meta.lowCritical) && numericValue < meta.lowCritical) { return 'CRITICALLY_LOW'; } if (exist(meta.lowNormal) && numericValue < meta.lowNormal) { return 'LOW'; } return 'NORMAL'; }; export function extractMetaInformation(concepts: Array): Record { return Object.fromEntries( concepts.map( ({ uuid, hiAbsolute, hiCritical, hiNormal, lowAbsolute, lowCritical, lowNormal, units, datatype: { display: datatype }, }) => { const meta: ObsMetaInfo = { hiAbsolute, hiCritical, hiNormal, lowAbsolute, lowCritical, lowNormal, units, datatype, }; if (exist(hiNormal, lowNormal)) { meta.range = `${lowNormal} – ${hiNormal}`; } meta.assessValue = assessValue(meta); return [uuid, meta]; }, ), ); }