Skip to content

Commit 1d83892

Browse files
authored
fix: filter out observations without a value (#1851)
1 parent b73cc93 commit 1d83892

File tree

4 files changed

+86
-20
lines changed

4 files changed

+86
-20
lines changed

opal/services/fhir/ips.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@
2727
from fhir.resources.R4B.reference import Reference
2828

2929

30+
def _clean_observations(observations: list[Observation]) -> list[Observation]:
31+
"""
32+
Clean up observations.
33+
34+
Remove those:
35+
- without a category
36+
- without a value and therefore a dataAbsentReason
37+
""" # noqa: DOC201
38+
return [
39+
observation for observation in observations if observation.category and observation.dataAbsentReason is None
40+
]
41+
42+
3043
def build_patient_summary( # noqa: PLR0913, PLR0917
3144
patient: Patient,
3245
conditions: list[Condition],
@@ -52,18 +65,13 @@ def build_patient_summary( # noqa: PLR0913, PLR0917
5265
# If a language is configured for IPS, use it to generate the bundle
5366
# Otherwise, default to the system's primary language (assuming clinical data is typically saved in this language)
5467
ips_language = settings.IPS_LANGUAGE or settings.LANGUAGES[0][0]
55-
56-
observations_with_category = [observation for observation in observations if observation.category]
68+
cleaned_observations = _clean_observations(observations)
5769

5870
vital_signs = [
59-
observation
60-
for observation in observations_with_category
61-
if observation.category[0].coding[0].code == 'vital-signs'
71+
observation for observation in cleaned_observations if observation.category[0].coding[0].code == 'vital-signs'
6272
]
6373
labs = [
64-
observation
65-
for observation in observations_with_category
66-
if observation.category[0].coding[0].code == 'laboratory'
74+
observation for observation in cleaned_observations if observation.category[0].coding[0].code == 'laboratory'
6775
]
6876

6977
# Translates static strings in the right language for the bundle

opal/services/fhir/tests/fixtures/observations.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,51 @@
376376
]
377377
}
378378
}
379+
},
380+
{
381+
"fullUrl": "urn:uuid:9efb0a09-01e2-4792-8085-439698be315e",
382+
"resource": {
383+
"resourceType": "Observation",
384+
"id": "9efb0a09-01e2-4792-8085-439698be315e",
385+
"meta": {
386+
"versionId": "1",
387+
"lastUpdated": "2025-10-30T11:47:52-05:00"
388+
},
389+
"status": "final",
390+
"category": [
391+
{
392+
"coding": [
393+
{
394+
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
395+
"code": "vital-signs"
396+
}
397+
]
398+
}
399+
],
400+
"code": {
401+
"coding": [
402+
{
403+
"system": "http://loinc.org",
404+
"code": "3151-8",
405+
"display": "Inhaled oxygen flow rate"
406+
}
407+
]
408+
},
409+
"subject": {
410+
"reference": "Patient/9ef97180-cd66-4fb3-a903-f1b78bd693a5",
411+
"type": "Patient"
412+
},
413+
"effectiveDateTime": "2025-10-10T10:27:03-05:00",
414+
"dataAbsentReason": {
415+
"coding": [
416+
{
417+
"system": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
418+
"code": "unknown",
419+
"display": "Unknown"
420+
}
421+
]
422+
}
423+
}
379424
}
380425
]
381426
}

opal/services/fhir/tests/test_fhir.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def test_patient_observations(self, fhir_connector: FHIRConnector, mocker: Mocke
388388

389389
observations = fhir_connector.patient_observations('test-patient-uuid')
390390

391-
assert len(observations) == 9
391+
assert len(observations) == 10
392392
assert observations[0].id == '59ace158-3be6-11f0-9645-fa163e09c13a'
393393
assert observations[1].id == '59acedd7-3be6-11f0-9645-fa163e09c13a'
394394
fhir_connector.session.get.assert_called_once_with(
@@ -403,7 +403,7 @@ def test_patient_observations_no_category(self, fhir_connector: FHIRConnector, m
403403

404404
observations = fhir_connector.patient_observations('test-patient-uuid')
405405

406-
assert len(observations) == 9
406+
assert len(observations) == 10
407407
assert observations[8].id == 'a083c331-bd33-4372-8c4d-8c329d354607'
408408
assert observations[8].category is None
409409

opal/services/fhir/tests/test_ips.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ def test_build_patient_summary() -> None:
7878
assert summary.type == 'document'
7979
assert summary.identifier.system == 'urn:oid:2.16.724.4.8.10.200.10'
8080

81-
observations_with_category = [observation for observation in observations if observation.category]
81+
observations_with_category_value = ips._clean_observations(observations)
8282

8383
# Composition, Patient, Device and the resources
8484
assert len(summary.entry) == (
8585
3
8686
+ len(conditions)
8787
+ len(medication_requests)
8888
+ len(allergies)
89-
+ len(observations_with_category)
89+
+ len(observations_with_category_value)
9090
+ len(immunizations)
9191
)
9292

@@ -106,16 +106,16 @@ def test_build_patient_summary_composition() -> None:
106106
# Verify all 7 IPS sections
107107
assert len(composition.section) == 7
108108

109-
observations_with_category = [observation for observation in observations if observation.category]
109+
observations_with_category_value = ips._clean_observations(observations)
110110

111111
vital_signs = [
112112
observation
113-
for observation in observations_with_category
113+
for observation in observations_with_category_value
114114
if observation.category[0].coding[0].code == 'vital-signs'
115115
]
116116
labs = [
117117
observation
118-
for observation in observations_with_category
118+
for observation in observations_with_category_value
119119
if observation.category[0].coding[0].code == 'laboratory'
120120
]
121121

@@ -209,7 +209,7 @@ def test_build_patient_summary_resources_included() -> None:
209209
expected_ids.update(condition.id for condition in conditions)
210210
expected_ids.update(medication_request.id for medication_request in medication_requests)
211211
expected_ids.update(allergy.id for allergy in allergies)
212-
expected_ids.update(observation.id for observation in observations if observation.category)
212+
expected_ids.update(observation.id for observation in ips._clean_observations(observations))
213213
expected_ids.update(immunization.id for immunization in immunizations)
214214

215215
assert resource_ids == expected_ids
@@ -225,10 +225,23 @@ def test_build_patient_summary_resources_observations_without_category() -> None
225225
_prepare_build_patient_summary()
226226
)
227227

228-
# Skip the first three entries (Composition, Patient, Device)
229228
resource_ids = {entry.resource.id for entry in summary.entry}
230229

231-
observations_without_category = [observation for observation in observations if not observation.category]
230+
observations_without_category = [observation.id for observation in observations if not observation.category]
231+
232+
assert len(observations_without_category) == 1
233+
assert 'a083c331-bd33-4372-8c4d-8c329d354607' in observations_without_category
234+
assert 'a083c331-bd33-4372-8c4d-8c329d354607' not in resource_ids
235+
236+
237+
def test_build_patient_summary_resources_observations_without_data_absent_reason() -> None:
238+
"""Observations with a dataAbsentReason are not included in the patient summary Bundle."""
239+
summary, _patient, _conditions, _medication_requests, _allergies, observations, _immunizations = (
240+
_prepare_build_patient_summary()
241+
)
242+
243+
resource_ids = {entry.resource.id for entry in summary.entry}
244+
observations_without_value = [observation.id for observation in observations if observation.dataAbsentReason]
232245

233-
for observation in observations_without_category:
234-
assert observation.id not in resource_ids
246+
assert len(observations_without_value) == 1
247+
assert '9efb0a09-01e2-4792-8085-439698be315e' not in resource_ids

0 commit comments

Comments
 (0)