@openmrs/esm-patient-tests-app 11.3.1-pre.9277 → 11.3.1-pre.9280
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/dist/6231.js +1 -1
- package/dist/6231.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-tests-app.js +1 -1
- package/dist/openmrs-esm-patient-tests-app.js.buildmanifest.json +8 -8
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/test-results/individual-results-table-tablet/helper.tsx +8 -2
- package/src/test-results/loadPatientTestData/helpers.test.ts +834 -0
- package/src/test-results/loadPatientTestData/helpers.ts +114 -0
- package/src/test-results/loadPatientTestData/loadPatientData.ts +66 -11
- package/src/test-results/overview/common-datatable.component.tsx +1 -1
- package/src/test-results/overview/useOverviewData.ts +22 -10
- package/src/types.ts +9 -1
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
type ObsMetaInfo,
|
|
8
8
|
type OBSERVATION_INTERPRETATION,
|
|
9
9
|
} from '@openmrs/esm-patient-common-lib';
|
|
10
|
+
import type { FHIRObservationResource } from '../../types';
|
|
11
|
+
import { type ReferenceRanges } from '../grouped-timeline/reference-range-helpers';
|
|
10
12
|
|
|
11
13
|
const PAGE_SIZE = 300;
|
|
12
14
|
const CHUNK_PREFETCH_COUNT = 1;
|
|
@@ -151,6 +153,118 @@ export function exist(...args: any[]): boolean {
|
|
|
151
153
|
return true;
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Extracts reference ranges from FHIR Observation referenceRange field.
|
|
158
|
+
* Handles different range types: normal, treatment, and absolute.
|
|
159
|
+
*/
|
|
160
|
+
export function extractObservationReferenceRanges(
|
|
161
|
+
resource: FHIRObservationResource | ObsRecord,
|
|
162
|
+
): ReferenceRanges | undefined {
|
|
163
|
+
if (!resource.referenceRange || resource.referenceRange.length === 0) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const ranges: ReferenceRanges = {
|
|
168
|
+
units: resource.valueQuantity?.unit,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
resource.referenceRange.forEach((range) => {
|
|
172
|
+
const rangeType = range.type?.coding?.[0]?.code;
|
|
173
|
+
const rangeSystem = range.type?.coding?.[0]?.system;
|
|
174
|
+
|
|
175
|
+
if (rangeSystem === 'http://terminology.hl7.org/CodeSystem/referencerange-meaning') {
|
|
176
|
+
if (rangeType === 'normal') {
|
|
177
|
+
ranges.hiNormal = range.high?.value;
|
|
178
|
+
ranges.lowNormal = range.low?.value;
|
|
179
|
+
} else if (rangeType === 'treatment') {
|
|
180
|
+
ranges.hiCritical = range.high?.value;
|
|
181
|
+
ranges.lowCritical = range.low?.value;
|
|
182
|
+
}
|
|
183
|
+
} else if (rangeSystem === 'http://fhir.openmrs.org/ext/obs/reference-range' && rangeType === 'absolute') {
|
|
184
|
+
ranges.hiAbsolute = range.high?.value;
|
|
185
|
+
ranges.lowAbsolute = range.low?.value;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Only return if we found at least one range value
|
|
190
|
+
if (
|
|
191
|
+
ranges.hiNormal !== undefined ||
|
|
192
|
+
ranges.lowNormal !== undefined ||
|
|
193
|
+
ranges.hiCritical !== undefined ||
|
|
194
|
+
ranges.lowCritical !== undefined ||
|
|
195
|
+
ranges.hiAbsolute !== undefined ||
|
|
196
|
+
ranges.lowAbsolute !== undefined
|
|
197
|
+
) {
|
|
198
|
+
return ranges;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Extracts and maps FHIR Observation interpretation to OBSERVATION_INTERPRETATION.
|
|
206
|
+
* Supports both interpretation codes (e.g., "LL", "N", "H") and display values (e.g., "Critically Low", "Normal").
|
|
207
|
+
*/
|
|
208
|
+
export function extractObservationInterpretation(
|
|
209
|
+
resource: FHIRObservationResource | ObsRecord,
|
|
210
|
+
): OBSERVATION_INTERPRETATION | undefined {
|
|
211
|
+
if (!resource.interpretation || resource.interpretation.length === 0) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const interpretation = resource.interpretation[0];
|
|
216
|
+
const code = interpretation.coding?.[0]?.code;
|
|
217
|
+
const display = interpretation.coding?.[0]?.display || interpretation.text;
|
|
218
|
+
|
|
219
|
+
// Map FHIR interpretation codes (HL7 v3 ObservationInterpretation codes)
|
|
220
|
+
if (code) {
|
|
221
|
+
switch (code.toUpperCase()) {
|
|
222
|
+
case 'LL':
|
|
223
|
+
return 'CRITICALLY_LOW';
|
|
224
|
+
case 'HH':
|
|
225
|
+
return 'CRITICALLY_HIGH';
|
|
226
|
+
case 'L':
|
|
227
|
+
return 'LOW';
|
|
228
|
+
case 'H':
|
|
229
|
+
return 'HIGH';
|
|
230
|
+
case 'N':
|
|
231
|
+
return 'NORMAL';
|
|
232
|
+
case 'LU':
|
|
233
|
+
return 'OFF_SCALE_LOW';
|
|
234
|
+
case 'HU':
|
|
235
|
+
return 'OFF_SCALE_HIGH';
|
|
236
|
+
default:
|
|
237
|
+
// Fall through to display mapping
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Map FHIR interpretation display values
|
|
243
|
+
if (display) {
|
|
244
|
+
const normalized = display.trim().toLowerCase();
|
|
245
|
+
switch (normalized) {
|
|
246
|
+
case 'critically low':
|
|
247
|
+
return 'CRITICALLY_LOW';
|
|
248
|
+
case 'critically high':
|
|
249
|
+
return 'CRITICALLY_HIGH';
|
|
250
|
+
case 'low':
|
|
251
|
+
return 'LOW';
|
|
252
|
+
case 'high':
|
|
253
|
+
return 'HIGH';
|
|
254
|
+
case 'normal':
|
|
255
|
+
return 'NORMAL';
|
|
256
|
+
case 'off scale low':
|
|
257
|
+
return 'OFF_SCALE_LOW';
|
|
258
|
+
case 'off scale high':
|
|
259
|
+
return 'OFF_SCALE_HIGH';
|
|
260
|
+
default:
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
154
268
|
export const assessValue =
|
|
155
269
|
(meta: ObsMetaInfo) =>
|
|
156
270
|
(value: string): OBSERVATION_INTERPRETATION => {
|
|
@@ -7,13 +7,21 @@ import {
|
|
|
7
7
|
type ObsMetaInfo,
|
|
8
8
|
} from '@openmrs/esm-patient-common-lib';
|
|
9
9
|
import {
|
|
10
|
+
addUserDataToCache,
|
|
11
|
+
assessValue,
|
|
12
|
+
extractMetaInformation,
|
|
13
|
+
extractObservationReferenceRanges,
|
|
14
|
+
extractObservationInterpretation,
|
|
10
15
|
getEntryConceptClassUuid,
|
|
11
16
|
getUserDataFromCache,
|
|
12
17
|
loadObsEntries,
|
|
13
18
|
loadPresentConcepts,
|
|
14
|
-
extractMetaInformation,
|
|
15
|
-
addUserDataToCache,
|
|
16
19
|
} from './helpers';
|
|
20
|
+
import {
|
|
21
|
+
selectReferenceRange,
|
|
22
|
+
formatReferenceRange,
|
|
23
|
+
type ReferenceRanges,
|
|
24
|
+
} from '../grouped-timeline/reference-range-helpers';
|
|
17
25
|
|
|
18
26
|
function parseSingleObsData(
|
|
19
27
|
testConceptNameMap: Record<ConceptUuid, string>,
|
|
@@ -23,6 +31,11 @@ function parseSingleObsData(
|
|
|
23
31
|
return (entry: ObsRecord) => {
|
|
24
32
|
entry.conceptClass = getEntryConceptClassUuid(entry);
|
|
25
33
|
|
|
34
|
+
// Extract observation-level reference ranges from FHIR Observation referenceRange field
|
|
35
|
+
const observationRanges = extractObservationReferenceRanges(entry);
|
|
36
|
+
// Extract observation-level interpretation from FHIR Observation interpretation field
|
|
37
|
+
const observationInterpretation = extractObservationInterpretation(entry);
|
|
38
|
+
|
|
26
39
|
if (entry.hasMember) {
|
|
27
40
|
// is a panel
|
|
28
41
|
entry.members = new Array(entry.hasMember.length);
|
|
@@ -31,17 +44,59 @@ function parseSingleObsData(
|
|
|
31
44
|
});
|
|
32
45
|
} else {
|
|
33
46
|
// is a single test
|
|
34
|
-
entry.meta = metaInfomation[entry.conceptClass];
|
|
35
|
-
}
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
// Extract value FIRST before computing interpretation
|
|
49
|
+
if (entry.valueQuantity) {
|
|
50
|
+
entry.value = String(entry.valueQuantity.value);
|
|
51
|
+
delete entry.valueQuantity;
|
|
52
|
+
} else if (entry.valueCodeableConcept) {
|
|
53
|
+
entry.value = entry.valueCodeableConcept.coding?.[0]?.display;
|
|
54
|
+
delete entry.valueCodeableConcept;
|
|
55
|
+
} else if (entry.valueString) {
|
|
56
|
+
entry.value = entry.valueString;
|
|
57
|
+
delete entry.valueString;
|
|
58
|
+
}
|
|
41
59
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
const conceptMeta = metaInfomation[entry.conceptClass];
|
|
61
|
+
|
|
62
|
+
// Node-level (concept-level) reference ranges
|
|
63
|
+
const nodeRanges: ReferenceRanges = {
|
|
64
|
+
hiAbsolute: conceptMeta.hiAbsolute,
|
|
65
|
+
hiCritical: conceptMeta.hiCritical,
|
|
66
|
+
hiNormal: conceptMeta.hiNormal,
|
|
67
|
+
lowAbsolute: conceptMeta.lowAbsolute,
|
|
68
|
+
lowCritical: conceptMeta.lowCritical,
|
|
69
|
+
lowNormal: conceptMeta.lowNormal,
|
|
70
|
+
units: conceptMeta.units,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Merge observation-level and concept-level ranges (observation takes precedence)
|
|
74
|
+
const selectedRanges = selectReferenceRange(observationRanges, nodeRanges);
|
|
75
|
+
|
|
76
|
+
// Create merged meta with observation-level ranges taking precedence
|
|
77
|
+
const mergedMeta: ObsMetaInfo = {
|
|
78
|
+
...conceptMeta,
|
|
79
|
+
// Update meta with merged ranges
|
|
80
|
+
hiAbsolute: selectedRanges?.hiAbsolute ?? conceptMeta.hiAbsolute,
|
|
81
|
+
hiCritical: selectedRanges?.hiCritical ?? conceptMeta.hiCritical,
|
|
82
|
+
hiNormal: selectedRanges?.hiNormal ?? conceptMeta.hiNormal,
|
|
83
|
+
lowAbsolute: selectedRanges?.lowAbsolute ?? conceptMeta.lowAbsolute,
|
|
84
|
+
lowCritical: selectedRanges?.lowCritical ?? conceptMeta.lowCritical,
|
|
85
|
+
lowNormal: selectedRanges?.lowNormal ?? conceptMeta.lowNormal,
|
|
86
|
+
units: selectedRanges?.units ?? conceptMeta.units,
|
|
87
|
+
// Update range string with merged ranges
|
|
88
|
+
range: selectedRanges ? formatReferenceRange(selectedRanges, selectedRanges.units) : conceptMeta.range,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Always update assessValue to use merged ranges (computed after mergedMeta to avoid unsafe cast)
|
|
92
|
+
// This ensures assessValue is computed even when only concept-level ranges exist
|
|
93
|
+
mergedMeta.assessValue = assessValue(mergedMeta);
|
|
94
|
+
|
|
95
|
+
entry.meta = mergedMeta;
|
|
96
|
+
|
|
97
|
+
// Use observation-level interpretation if available, otherwise compute using merged ranges
|
|
98
|
+
entry.interpretation =
|
|
99
|
+
observationInterpretation ?? (mergedMeta.assessValue ? mergedMeta.assessValue(entry.value) : 'NORMAL');
|
|
45
100
|
}
|
|
46
101
|
|
|
47
102
|
entry.name = testConceptNameMap[entry.conceptClass];
|
|
@@ -77,7 +77,7 @@ const CommonDataTable: React.FC<CommonDataTableProps> = ({ title, data, descript
|
|
|
77
77
|
{rows.map((row, i) => (
|
|
78
78
|
<TypedTableRow
|
|
79
79
|
key={row.id}
|
|
80
|
-
interpretation={data[i]?.
|
|
80
|
+
interpretation={data[i]?.interpretation as OBSERVATION_INTERPRETATION}
|
|
81
81
|
{...getRowProps({ row })}
|
|
82
82
|
>
|
|
83
83
|
{row.cells.map((cell) => {
|
|
@@ -23,24 +23,36 @@ export type OverviewPanelEntry = [string, string, Array<OverviewPanelData>, Date
|
|
|
23
23
|
|
|
24
24
|
export function parseSingleEntry(entry: ObsRecord, type: string, panelName: string): Array<OverviewPanelData> {
|
|
25
25
|
if (type === 'Test') {
|
|
26
|
+
// Use observation-level interpretation set during data loading
|
|
27
|
+
const interpretation = entry.interpretation || 'NORMAL';
|
|
26
28
|
return [
|
|
27
29
|
{
|
|
28
30
|
id: entry.id,
|
|
29
31
|
name: panelName,
|
|
30
32
|
range: entry.meta?.range || '--',
|
|
31
|
-
interpretation
|
|
32
|
-
value:
|
|
33
|
+
interpretation,
|
|
34
|
+
value: {
|
|
35
|
+
value: entry.value,
|
|
36
|
+
interpretation,
|
|
37
|
+
},
|
|
33
38
|
},
|
|
34
39
|
];
|
|
35
40
|
} else {
|
|
36
|
-
return entry.members.map((
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
return entry.members.map((member) => {
|
|
42
|
+
// Use observation-level interpretation set during data loading
|
|
43
|
+
const interpretation = member.interpretation || 'NORMAL';
|
|
44
|
+
return {
|
|
45
|
+
id: member.id,
|
|
46
|
+
key: member.id,
|
|
47
|
+
name: member.name,
|
|
48
|
+
range: member.meta?.range || '--',
|
|
49
|
+
interpretation,
|
|
50
|
+
value: {
|
|
51
|
+
value: member.value,
|
|
52
|
+
interpretation,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
});
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
|
package/src/types.ts
CHANGED
|
@@ -67,6 +67,14 @@ export interface FHIRObservationResource {
|
|
|
67
67
|
hasMember?: Array<{
|
|
68
68
|
reference: string;
|
|
69
69
|
}>;
|
|
70
|
+
interpretation?: Array<{
|
|
71
|
+
coding: Array<{
|
|
72
|
+
code: string;
|
|
73
|
+
display: string;
|
|
74
|
+
system?: string;
|
|
75
|
+
}>;
|
|
76
|
+
text?: string;
|
|
77
|
+
}>;
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
export interface Concept {
|
|
@@ -107,7 +115,7 @@ export interface ConceptMeta {
|
|
|
107
115
|
range: string;
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
export interface ObsRecord extends FHIRObservationResource {
|
|
118
|
+
export interface ObsRecord extends Omit<FHIRObservationResource, 'interpretation'> {
|
|
111
119
|
conceptUuid: string;
|
|
112
120
|
relatedObs: Array<ObsRecord>;
|
|
113
121
|
meta: ConceptMeta;
|