Skip to content

Commit 25c4b3b

Browse files
committed
more unit tests for snomed terms
1 parent 51227e9 commit 25c4b3b

File tree

3 files changed

+307
-3
lines changed

3 files changed

+307
-3
lines changed

delta_backend/src/ConversionLayout.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ def _extract_dose_unit_code(immunization) -> str:
5252
def _extract_dose_unit_term(immunization) -> str:
5353
dose_quantity = immunization.get("doseQuantity", {})
5454
return dose_quantity.get("unit", "")
55-
56-
55+
5756
def _get_first_snomed_code(coding_container: dict) -> str:
5857
codings = coding_container.get("coding", [])
5958
for coding in codings:
@@ -281,7 +280,7 @@ def _extract_dose_sequence(immunization) -> str:
281280
"expression": {
282281
"expressionName": "Not Empty",
283282
"expressionType": "DOSESEQUENCE",
284-
"expressionRule": ""
283+
"expressionRule": _extract_dose_sequence
285284
}
286285
},
287286
{
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import copy
2+
import json
3+
import unittest
4+
from tests.utils_for_converter_tests import ValuesForTests
5+
from Converter import Converter
6+
7+
class TestPersonSNOMEDTermsToFlatJson(unittest.TestCase):
8+
9+
def setUp(self):
10+
self.request_json_data = copy.deepcopy(ValuesForTests.json_data)
11+
12+
def _set_snomed_codings(self, target_path: str, codings: list[dict], extension_url: str = None):
13+
"""Helper to insert coding entries into self.request_json_data at the desired FHIR path"""
14+
if target_path in {"vaccineCode", "site", "route"}:
15+
self.request_json_data[target_path] = {"coding": codings}
16+
elif target_path == "reasonCode":
17+
self.request_json_data["reasonCode"] = [{"coding": codings}]
18+
elif target_path == "extension":
19+
self.request_json_data["extension"] = [{
20+
"url": extension_url or "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
21+
"valueCodeableConcept": {
22+
"coding": codings
23+
}
24+
}]
25+
26+
def _run_snomed_test(self, flat_field_name, expected_snomed_code):
27+
"""Helper function to run the test"""
28+
self.converter = Converter(json.dumps(self.request_json_data))
29+
flat_json = self.converter.runConversion(self.request_json_data, False, True)
30+
self.assertEqual(flat_json.get(flat_field_name), expected_snomed_code)
31+
32+
def test_vaccination_procedure_term_text_present(self):
33+
# Scenario 1: `text` field is present
34+
self._set_snomed_codings(
35+
target_path="extension",
36+
codings=[
37+
{
38+
"code": "dummy",
39+
"system": "http://snomed.info/sct"
40+
}
41+
],
42+
extension_url="https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure"
43+
)
44+
self.request_json_data["extension"][0]["valueCodeableConcept"]["text"] = "Procedure term from text"
45+
self._run_snomed_test("VACCINATION_PROCEDURE_TERM", "Procedure term from text")
46+
47+
def test_vaccination_procedure_term_from_text(self):
48+
"""Test when valueCodeableConcept.text is present — it takes priority."""
49+
self.request_json_data["extension"][0]["valueCodeableConcept"]["text"] = "Procedure term from text"
50+
self._run_snomed_test("VACCINATION_PROCEDURE_TERM", "Procedure term from text")
51+
52+
def test_vaccination_procedure_term_from_extension_value_string(self):
53+
"""Test fallback to extension.valueString when text is missing."""
54+
self.request_json_data["extension"][0]["valueCodeableConcept"].pop("text", None) # Remove text
55+
self._run_snomed_test("VACCINATION_PROCEDURE_TERM", "1 - Test Value string 123456 COVID19 vaccination")
56+
57+
def test_vaccination_procedure_term_from_display_fallback(self):
58+
"""Test fallback to display when no extension valueString is present."""
59+
coding = self.request_json_data["extension"][0]["valueCodeableConcept"]["coding"][0]
60+
coding.pop("extension", None) # Remove all extensions
61+
self.request_json_data["extension"][0]["valueCodeableConcept"].pop("text", None) # Remove text
62+
self._run_snomed_test("VACCINATION_PROCEDURE_TERM",
63+
"Administration of first dose of severe acute respiratory syndrome coronavirus 2 vaccine (procedure)")
64+
65+
def test_vaccination_procedure_term_null_when_nothing_matches(self):
66+
"""Test null return when no text, no extension.valueString, and no display."""
67+
coding = self.request_json_data["extension"][0]["valueCodeableConcept"]["coding"][0]
68+
coding.pop("extension", None)
69+
coding.pop("display", None)
70+
self.request_json_data["extension"][0]["valueCodeableConcept"].pop("text", None)
71+
self._run_snomed_test("VACCINATION_PROCEDURE_TERM", "")
72+
73+
def test_vaccination_procedure_term_skips_non_sct_systems(self):
74+
"""Test that only the first SNOMED SCT system coding is used."""
75+
# Add a dummy non-SCT coding before the valid one
76+
self.request_json_data["extension"][0]["valueCodeableConcept"]["text"] = None # force to fallback path
77+
codings = self.request_json_data["extension"][0]["valueCodeableConcept"]["coding"]
78+
codings.insert(0, {
79+
"system": "http://not-snomed",
80+
"code": "IGNORE",
81+
"display": "Ignore this"
82+
})
83+
self._run_snomed_test("VACCINATION_PROCEDURE_TERM", "1 - Test Value string 123456 COVID19 vaccination")
84+
85+
def test_vaccine_product_term_from_text(self):
86+
"""Test when vaccineCode.text is present — it takes priority."""
87+
self.request_json_data["vaccineCode"]["text"] = "Preferred vaccine product text"
88+
self._run_snomed_test("VACCINE_PRODUCT_TERM", "Preferred vaccine product text")
89+
90+
def test_vaccine_product_term_from_extension_value_string(self):
91+
"""Test fallback to coding.extension.valueString when text is missing."""
92+
self.request_json_data["vaccineCode"].pop("text", None) # Remove text
93+
94+
# Modify first SNOMED coding
95+
sct_coding = next(
96+
c for c in self.request_json_data["vaccineCode"]["coding"]
97+
if c.get("system") == "http://snomed.info/sct"
98+
)
99+
sct_coding["extension"] = [{
100+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay",
101+
"valueString": "Extension value from vaccine code"
102+
}]
103+
self._run_snomed_test("VACCINE_PRODUCT_TERM", "Extension value from vaccine code")
104+
105+
def test_vaccine_product_term_from_display_fallback(self):
106+
"""Test fallback to display when text and extension.valueString are missing."""
107+
self.request_json_data["vaccineCode"].pop("text", None)
108+
sct_coding = next(
109+
c for c in self.request_json_data["vaccineCode"]["coding"]
110+
if c.get("system") == "http://snomed.info/sct"
111+
)
112+
sct_coding.pop("extension", None)
113+
sct_coding["display"] = "Display fallback for vaccine"
114+
self._run_snomed_test("VACCINE_PRODUCT_TERM", "Display fallback for vaccine")
115+
116+
def test_vaccine_product_term_returns_empty_when_no_data(self):
117+
"""Test returns empty string when no text, no valueString, no display."""
118+
self.request_json_data["vaccineCode"].pop("text", None)
119+
sct_coding = next(
120+
c for c in self.request_json_data["vaccineCode"]["coding"]
121+
if c.get("system") == "http://snomed.info/sct"
122+
)
123+
sct_coding.pop("extension", None)
124+
sct_coding.pop("display", None)
125+
self._run_snomed_test("VACCINE_PRODUCT_TERM", "")
126+
127+
def test_vaccine_product_term_skips_non_sct_codings(self):
128+
"""Test ignores non-SNOMED codings and uses first valid SNOMED coding."""
129+
self.request_json_data["vaccineCode"].pop("text", None)
130+
131+
# Insert a non-SNOMED coding before SNOMED ones
132+
self.request_json_data["vaccineCode"]["coding"].insert(0, {
133+
"system": "http://not-snomed",
134+
"code": "IGNORE",
135+
"display": "Wrong system display"
136+
})
137+
138+
sct_coding = next(
139+
c for c in self.request_json_data["vaccineCode"]["coding"]
140+
if c.get("system") == "http://snomed.info/sct"
141+
)
142+
sct_coding["extension"] = [{
143+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay",
144+
"valueString": "Valid SNOMED vaccine product"
145+
}]
146+
self._run_snomed_test("VACCINE_PRODUCT_TERM", "Valid SNOMED vaccine product")
147+
148+
def test_site_of_vaccination_term_from_text(self):
149+
"""Test when site.text is present — takes highest priority."""
150+
self.request_json_data["site"]["text"] = "Left arm from text"
151+
self._run_snomed_test("SITE_OF_VACCINATION_TERM", "Left arm from text")
152+
153+
def test_site_of_vaccination_term_from_extension_value_string(self):
154+
"""Test fallback to extension.valueString when text is missing."""
155+
self.request_json_data["site"].pop("text", None)
156+
157+
sct_coding = next(
158+
c for c in self.request_json_data["site"]["coding"]
159+
if c.get("system") == "http://snomed.info/sct"
160+
)
161+
sct_coding["extension"] = [{
162+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay",
163+
"valueString": "Extension site description"
164+
}]
165+
self._run_snomed_test("SITE_OF_VACCINATION_TERM", "Extension site description")
166+
167+
def test_site_of_vaccination_term_from_display(self):
168+
"""Test fallback to display when text and extension are missing."""
169+
self.request_json_data["site"].pop("text", None)
170+
sct_coding = next(
171+
c for c in self.request_json_data["site"]["coding"]
172+
if c.get("system") == "http://snomed.info/sct"
173+
)
174+
sct_coding.pop("extension", None)
175+
sct_coding["display"] = "Left upper arm (display)"
176+
self._run_snomed_test("SITE_OF_VACCINATION_TERM", "Left upper arm (display)")
177+
178+
def test_site_of_vaccination_term_returns_empty_when_no_valid_data(self):
179+
"""Test when no text, no extension, no display."""
180+
self.request_json_data["site"].pop("text", None)
181+
sct_coding = next(
182+
c for c in self.request_json_data["site"]["coding"]
183+
if c.get("system") == "http://snomed.info/sct"
184+
)
185+
sct_coding.pop("extension", None)
186+
sct_coding.pop("display", None)
187+
self._run_snomed_test("SITE_OF_VACCINATION_TERM", "")
188+
189+
def test_site_of_vaccination_term_skips_non_sct_systems(self):
190+
"""Test ignores codings with non-SNOMED systems."""
191+
self.request_json_data["site"].pop("text", None)
192+
193+
# Add a non-SNOMED coding first
194+
self.request_json_data["site"]["coding"].insert(0, {
195+
"system": "http://not-snomed",
196+
"code": "XYZ",
197+
"display": "Invalid display"
198+
})
199+
200+
sct_coding = next(
201+
c for c in self.request_json_data["site"]["coding"]
202+
if c.get("system") == "http://snomed.info/sct"
203+
)
204+
sct_coding["extension"] = [{
205+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay",
206+
"valueString": "Valid SCT site term"
207+
}]
208+
self._run_snomed_test("SITE_OF_VACCINATION_TERM", "Valid SCT site term")
209+
210+
def test_route_of_vaccination_term_from_text(self):
211+
"""Test when route.text is present — takes highest priority."""
212+
self.request_json_data["route"]["text"] = "Oral route from text"
213+
self._run_snomed_test("ROUTE_OF_VACCINATION_TERM", "Oral route from text")
214+
215+
def test_route_of_vaccination_term_from_extension_value_string(self):
216+
"""Test fallback to extension.valueString when text is missing."""
217+
self.request_json_data["route"].pop("text", None)
218+
219+
sct_coding = next(
220+
c for c in self.request_json_data["route"]["coding"]
221+
if c.get("system") == "http://snomed.info/sct"
222+
)
223+
sct_coding["extension"] = [{
224+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay",
225+
"valueString": "Intramuscular route from extension"
226+
}]
227+
self._run_snomed_test("ROUTE_OF_VACCINATION_TERM", "Intramuscular route from extension")
228+
229+
def test_route_of_vaccination_term_from_display(self):
230+
"""Test fallback to display when text and extension are missing."""
231+
self.request_json_data["route"].pop("text", None)
232+
sct_coding = next(
233+
c for c in self.request_json_data["route"]["coding"]
234+
if c.get("system") == "http://snomed.info/sct"
235+
)
236+
sct_coding.pop("extension", None)
237+
sct_coding["display"] = "Intranasal route"
238+
self._run_snomed_test("ROUTE_OF_VACCINATION_TERM", "Intranasal route")
239+
240+
def test_route_of_vaccination_term_returns_empty_when_no_valid_data(self):
241+
"""Test returns empty string when no text, extension, or display."""
242+
self.request_json_data["route"].pop("text", None)
243+
sct_coding = next(
244+
c for c in self.request_json_data["route"]["coding"]
245+
if c.get("system") == "http://snomed.info/sct"
246+
)
247+
sct_coding.pop("extension", None)
248+
sct_coding.pop("display", None)
249+
self._run_snomed_test("ROUTE_OF_VACCINATION_TERM", "")
250+
251+
def test_route_of_vaccination_term_skips_non_sct_systems(self):
252+
"""Test that non-SNOMED codings are ignored."""
253+
self.request_json_data["route"].pop("text", None)
254+
255+
# Add a non-SNOMED coding first
256+
self.request_json_data["route"]["coding"].insert(0, {
257+
"system": "http://not-snomed",
258+
"code": "999",
259+
"display": "Invalid non-SCT display"
260+
})
261+
262+
sct_coding = next(
263+
c for c in self.request_json_data["route"]["coding"]
264+
if c.get("system") == "http://snomed.info/sct"
265+
)
266+
sct_coding["extension"] = [{
267+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay",
268+
"valueString": "Correct route from SCT"
269+
}]
270+
self._run_snomed_test("ROUTE_OF_VACCINATION_TERM", "Correct route from SCT")
271+
272+
def test_dose_unit_term_when_unit_exists(self):
273+
"""Test returns doseQuantity.unit when present."""
274+
self.request_json_data["doseQuantity"] = {
275+
"value": 0.5,
276+
"unit": "milliliter",
277+
"system": "http://unitsofmeasure.org",
278+
"code": "ml"
279+
}
280+
self._run_snomed_test("DOSE_UNIT_TERM", "milliliter")
281+
282+
def test_dose_unit_term_returns_empty_when_dose_quantity_absent(self):
283+
"""Test returns empty string when doseQuantity is missing."""
284+
self.request_json_data.pop("doseQuantity", None)
285+
self._run_snomed_test("DOSE_UNIT_TERM", "")
286+
287+
def test_dose_unit_term_returns_empty_when_unit_missing(self):
288+
"""Test returns empty string when doseQuantity.unit is missing."""
289+
self.request_json_data["doseQuantity"] = {
290+
"value": 0.5,
291+
"system": "http://unitsofmeasure.org",
292+
"code": "ml"
293+
# unit is missing
294+
}
295+
self._run_snomed_test("DOSE_UNIT_TERM", "")

delta_backend/tests/utils_for_converter_tests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ class ValuesForTests:
3636
"system": "http://snomed.info/sct",
3737
"code": "13246814444444",
3838
"display": "Administration of first dose of severe acute respiratory syndrome coronavirus 2 vaccine (procedure)",
39+
"extension": [
40+
{
41+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay",
42+
"valueString": "1 - Test Value string 123456 COVID19 vaccination"
43+
},
44+
{
45+
"url": "http://hl7.org/fhir/StructureDefinition/coding-sctdescid",
46+
"valueId": "1 - 5306706018"
47+
}
48+
],
3949
}
4050
]
4151
},

0 commit comments

Comments
 (0)