Skip to content

Commit 02fade0

Browse files
Akol125mfjarvis
authored andcommitted
VED-350: Generic Validation Dose Unit Code (#734)
* VED-350: generic validation for require system when code is present
1 parent 98d1589 commit 02fade0

File tree

5 files changed

+79
-6
lines changed

5 files changed

+79
-6
lines changed

backend/src/models/fhir_immunization_pre_validators.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def validate(self):
8585
self.pre_validate_route_coding_display,
8686
self.pre_validate_dose_quantity_value,
8787
self.pre_validate_dose_quantity_code,
88+
self.pre_validate_dose_quantity_system,
89+
self.pre_validate_dose_quantity_system_and_code,
8890
self.pre_validate_dose_quantity_unit,
8991
self.pre_validate_reason_code_codings,
9092
self.pre_validate_reason_code_coding_codes,
@@ -799,9 +801,6 @@ def pre_validate_route_coding_display(self, values: dict) -> dict:
799801
except (KeyError, IndexError):
800802
pass
801803

802-
# TODO: need to validate that doseQuantity.system is "http://unitsofmeasure.org"?
803-
# Check with Martin
804-
805804
def pre_validate_dose_quantity_value(self, values: dict) -> dict:
806805
"""
807806
Pre-validate that, if doseQuantity.value (legacy CSV field name: DOSE_AMOUNT) exists,
@@ -818,6 +817,17 @@ def pre_validate_dose_quantity_value(self, values: dict) -> dict:
818817
except KeyError:
819818
pass
820819

820+
def pre_validate_dose_quantity_system(self, values: dict) -> dict:
821+
"""
822+
Pre-validate that if doseQuantity.system exists then it is a non-empty string:
823+
If system exists, it must be a non-empty string.
824+
"""
825+
try:
826+
field_value = values["doseQuantity"]["system"]
827+
PreValidation.for_string(field_value, "doseQuantity.system")
828+
except KeyError:
829+
pass
830+
821831
def pre_validate_dose_quantity_code(self, values: dict) -> dict:
822832
"""
823833
Pre-validate that, if doseQuantity.code (legacy CSV field name: DOSE_UNIT_CODE) exists,
@@ -829,6 +839,21 @@ def pre_validate_dose_quantity_code(self, values: dict) -> dict:
829839
except KeyError:
830840
pass
831841

842+
def pre_validate_dose_quantity_system_and_code(self, values: dict) -> dict:
843+
"""
844+
Pre-validate doseQuantity.code and doseQuantity.system:
845+
1. If code exists, system MUST also exist (FHIR SimpleQuantity rule).
846+
"""
847+
dose_quantity = values.get("doseQuantity", {})
848+
code = dose_quantity.get("code")
849+
system = dose_quantity.get("system")
850+
851+
PreValidation.require_system_when_code_present(
852+
code, system, "doseQuantity.code", "doseQuantity.system"
853+
)
854+
855+
return values
856+
832857
def pre_validate_dose_quantity_unit(self, values: dict) -> dict:
833858
"""
834859
Pre-validate that, if doseQuantity.unit (legacy CSV field name: DOSE_UNIT_TERM) exists,

backend/src/models/utils/pre_validator_utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,19 @@ def for_integer_or_decimal(field_value: Union[int, Decimal], field_location: str
194194
or type(field_value) is Decimal # pylint: disable=unidiomatic-typecheck
195195
):
196196
raise TypeError(f"{field_location} must be a number")
197+
198+
@staticmethod
199+
def require_system_when_code_present(
200+
code_value:str,
201+
system_value:str,
202+
code_location:str,
203+
system_location:str,
204+
) -> None:
205+
"""
206+
If code is present (non-empty), system must also be present (non-empty).
207+
"""
208+
if code_value is not None and system_value is None:
209+
raise ValueError(f"If {code_location} is present, {system_location} must also be present")
197210

198211
@staticmethod
199212
def for_unique_list(

backend/tests/test_immunization_pre_validator.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,11 +1140,36 @@ def test_pre_validate_dose_quantity_value(self):
11401140
Decimal("1.123456789"), # 9 decimal place
11411141
],
11421142
)
1143+
def test_pre_validate_dose_quantity_system(self):
1144+
"""Test pre_validate_dose_quantity_system accepts valid values and rejects invalid values"""
11431145

1146+
system_location = "doseQuantity.system"
1147+
ValidatorModelTests.test_string_value(self, system_location, valid_strings_to_test=["http://unitsofmeasure.org"])
1148+
11441149
def test_pre_validate_dose_quantity_code(self):
11451150
"""Test pre_validate_dose_quantity_code accepts valid values and rejects invalid values"""
1146-
field_location = "doseQuantity.code"
1147-
ValidatorModelTests.test_string_value(self, field_location, valid_strings_to_test=["ABC123"])
1151+
1152+
code_location = "doseQuantity.code"
1153+
ValidatorModelTests.test_string_value(self, code_location, valid_strings_to_test=["ABC123"])
1154+
1155+
def test_pre_validate_dose_quantity_system_and_code(self):
1156+
"""Test pre_validate_dose_quantity_system_and_code accepts valid values and rejects invalid values"""
1157+
1158+
field_location = "doseQuantity"
1159+
_test_valid_values_accepted(
1160+
self,
1161+
valid_json_data=deepcopy(self.json_data),
1162+
field_location=field_location,
1163+
valid_values_to_test=ValidValues.valid_dose_quantity,
1164+
)
1165+
1166+
_test_invalid_values_rejected(
1167+
self,
1168+
valid_json_data=deepcopy(self.json_data),
1169+
field_location=field_location,
1170+
invalid_value=InvalidValues.invalid_dose_quantity,
1171+
expected_error_message="If doseQuantity.code is present, doseQuantity.system must also be present"
1172+
)
11481173

11491174
def test_pre_validate_dose_quantity_unit(self):
11501175
"""Test pre_validate_dose_quantity_unit accepts valid values and rejects invalid values"""

backend/tests/utils/generic_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def test_invalid_values_rejected(
7272
invalid_json_data = parse(field_location).update(valid_json_data, invalid_value)
7373

7474
# Test that correct error type is raised
75-
with test_instance.assertRaises(ValueError or TypeError) as error:
75+
with test_instance.assertRaises((ValueError, TypeError)) as error:
7676
test_instance.validator.validate(invalid_json_data)
7777

7878
full_error_message = str(error.exception)

backend/tests/utils/values_for_tests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ class ValidValues:
5555
# Not a valid snomed code, but is valid coding format for format testing
5656
snomed_coding_element = {"system": "http://snomed.info/sct", "code": "ABC123", "display": "test"}
5757

58+
valid_dose_quantity = [
59+
{"value": 3, "unit": "milliliter", "system": "http://unitsofmeasure.org", "code": "ml"},
60+
{"value": 2, "unit": "ml", "system": "http://snomed.info/sct", "code": "258773002"},
61+
{"value": 4, "unit": "ml", "system": "http://snomed.info/sct"},
62+
{"value": 5, "unit": "ml" }
63+
]
64+
5865
manufacturer_resource_id_Man1 = {"resourceType": "Manufacturer", "id": "Man1"}
5966

6067
practitioner_resource_id_Pract1 = {"resourceType": "Practitioner", "id": "Pract1"}
@@ -377,3 +384,6 @@ class InvalidValues:
377384
{"use": "official", "given": ["Florence"]},
378385
{"family": "Nightingale", "given": ""},
379386
]
387+
388+
invalid_dose_quantity = {"value": 2, "unit": "ml", "code": "258773002"}
389+

0 commit comments

Comments
 (0)