Skip to content

Commit aa6b26e

Browse files
NRL-1050 Add validation for types and type-category mappings
1 parent 0ae824c commit aa6b26e

File tree

2 files changed

+123
-7
lines changed

2 files changed

+123
-7
lines changed

layer/nrlf/core/constants.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,51 @@ def coding_value(self):
113113
},
114114
}
115115

116+
TYPE_ATTRIBUTES = {
117+
PointerTypes.MENTAL_HEALTH_PLAN.value: {
118+
"display": "Mental health crisis plan",
119+
},
120+
PointerTypes.EMERGENCY_HEALTHCARE_PLAN.value: {
121+
"display": "Emergency health care plan",
122+
},
123+
PointerTypes.EOL_COORDINATION_SUMMARY.value: {
124+
"display": "End of Life Care Coordination Summary",
125+
},
126+
PointerTypes.RESPECT_FORM.value: {
127+
"display": "ReSPECT (Recommended Summary Plan for Emergency Care and Treatment) form",
128+
},
129+
PointerTypes.NEWS2_CHART.value: {
130+
"display": "Royal College of Physicians NEWS2 (National Early Warning Score 2) chart",
131+
},
132+
PointerTypes.CONTINGENCY_PLAN.value: {
133+
"display": "Contingency plan",
134+
},
135+
PointerTypes.EOL_CARE_PLAN.value: {
136+
"display": "End of life care plan",
137+
},
138+
PointerTypes.LLOYD_GEORGE_FOLDER.value: {
139+
"display": "Lloyd George record folder",
140+
},
141+
PointerTypes.ADVANCED_CARE_PLAN.value: {
142+
"display": "Advanced care plan",
143+
},
144+
PointerTypes.TREATMENT_ESCALATION_PLAN.value: {
145+
"display": "Treatment escalation plan",
146+
},
147+
PointerTypes.SUMMARY_RECORD.value: {
148+
"display": "Summary record",
149+
},
150+
PointerTypes.PERSONALISED_CARE_AND_SUPPORT_PLAN.value: {
151+
"display": "Personalised Care and Support Plan",
152+
},
153+
PointerTypes.MRA_UPPER_LIMB_ARTERY.value: {
154+
"display": "MRA Upper Limb Artery",
155+
},
156+
PointerTypes.MRI_AXILLA_BOTH.value: {
157+
"display": "MRI Axilla Both",
158+
},
159+
}
160+
116161
TYPE_CATEGORIES = {
117162
#
118163
# Care plans

layer/nrlf/core/validators.py

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
from pydantic import ValidationError
66

77
from nrlf.core.codes import SpineErrorConcept
8-
from nrlf.core.constants import CATEGORY_ATTRIBUTES, ODS_SYSTEM, REQUIRED_CREATE_FIELDS
8+
from nrlf.core.constants import (
9+
CATEGORY_ATTRIBUTES,
10+
ODS_SYSTEM,
11+
REQUIRED_CREATE_FIELDS,
12+
TYPE_ATTRIBUTES,
13+
TYPE_CATEGORIES,
14+
)
915
from nrlf.core.errors import ParseError
1016
from nrlf.core.logger import LogReference, logger
1117
from nrlf.core.types import DocumentReference, OperationOutcomeIssue, RequestQueryType
@@ -117,8 +123,10 @@ def validate(self, data: Dict[str, Any] | DocumentReference):
117123
self._validate_identifiers(resource)
118124
self._validate_relates_to(resource)
119125
self._validate_ssp_asid(resource)
126+
self._validate_type(resource)
120127
self._validate_category(resource)
121128
self._validate_author(resource)
129+
self._validate_type_category_mapping(resource)
122130
if resource.content[0].extension:
123131
self._validate_content_extension(resource)
124132

@@ -337,6 +345,50 @@ def _validate_ssp_asid(self, model: DocumentReference):
337345
)
338346
return
339347

348+
def _validate_type(self, model: DocumentReference):
349+
"""
350+
Validate the type field contains an appropriate coding system, code and display.
351+
"""
352+
logger.log(LogReference.VALIDATOR001, step="type")
353+
354+
if len(model.type.coding) > 1:
355+
self.result.add_error(
356+
issue_code="invalid",
357+
error_code="INVALID_RESOURCE",
358+
diagnostics=f"Invalid type coding length: {len(model.type[0].coding)} Type Coding must only contain a single value",
359+
field=f"type.coding",
360+
)
361+
return
362+
363+
coding = model.type.coding[0]
364+
if coding.system not in ["http://snomed.info/sct", "https://nicip.nhs.uk"]:
365+
self.result.add_error(
366+
issue_code="value",
367+
error_code="INVALID_RESOURCE",
368+
diagnostics=f"Invalid type system: {coding.system} Type system must be either 'http://snomed.info/sct' or 'https://nicip.nhs.uk'",
369+
field="type.coding[0].system",
370+
)
371+
return
372+
373+
type_id = f"{coding.system}|{coding.code}"
374+
if type_id not in TYPE_ATTRIBUTES.keys():
375+
self.result.add_error(
376+
issue_code="value",
377+
error_code="INVALID_RESOURCE",
378+
diagnostics=f"Invalid type code: {coding.code} Type must be a member of the England-NRLRecordCategory value set (https://fhir.nhs.uk/England/CodeSystem/England-NRLRecordType)",
379+
field="type.coding[0].code",
380+
)
381+
return
382+
383+
type_attributes = TYPE_ATTRIBUTES.get(type_id, {})
384+
if coding.display != type_attributes.get("display"):
385+
self.result.add_error(
386+
issue_code="value",
387+
error_code="INVALID_RESOURCE",
388+
diagnostics=f"type code '{coding.code}' must have a display value of '{type_attributes.get('display')}'",
389+
field="type.coding[0].display",
390+
)
391+
340392
def _validate_category(self, model: DocumentReference):
341393
"""
342394
Validate the category field contains an appropriate coding system, code and display.
@@ -364,22 +416,22 @@ def _validate_category(self, model: DocumentReference):
364416
return
365417

366418
coding = model.category[0].coding[0]
367-
if coding.system != "http://snomed.info/sct":
419+
if coding.system not in ["http://snomed.info/sct", "https://nicip.nhs.uk"]:
368420
self.result.add_error(
369421
issue_code="value",
370422
error_code="INVALID_RESOURCE",
371-
diagnostics=f"Invalid category system: {coding.system} Category system must be 'http://snomed.info/sct'",
372-
field=f"category[0].coding[{0}].system",
423+
diagnostics=f"Invalid category system: {coding.system} Category system must be either 'http://snomed.info/sct' or 'https://nicip.nhs.uk'",
424+
field="category[0].coding[0].system",
373425
)
374426
return
375427

376-
category_id = f"http://snomed.info/sct|{coding.code}"
428+
category_id = f"{coding.system}|{coding.code}"
377429
if category_id not in CATEGORY_ATTRIBUTES.keys():
378430
self.result.add_error(
379431
issue_code="value",
380432
error_code="INVALID_RESOURCE",
381433
diagnostics=f"Invalid category code: {coding.code} Category must be a member of the England-NRLRecordCategory value set (https://fhir.nhs.uk/England/CodeSystem/England-NRLRecordCategory)",
382-
field=f"category[0].coding[{0}].code",
434+
field="category[0].coding[0].code",
383435
)
384436
return
385437

@@ -389,7 +441,26 @@ def _validate_category(self, model: DocumentReference):
389441
issue_code="value",
390442
error_code="INVALID_RESOURCE",
391443
diagnostics=f"category code '{coding.code}' must have a display value of '{category_attributes.get('display')}'",
392-
field=f"category[0].coding[{0}].display",
444+
field="category[0].coding[0].display",
445+
)
446+
447+
def _validate_type_category_mapping(self, model: DocumentReference):
448+
"""
449+
Validate the type field contains an appropriate coding system, code and display.
450+
"""
451+
logger.log(LogReference.VALIDATOR001, step="type_category_mapping")
452+
453+
type_coding = model.type.coding[0]
454+
type_id = f"{type_coding.system}|{type_coding.code}"
455+
category_coding = model.category[0].coding[0]
456+
category_id = f"{category_coding.system}|{category_coding.code}"
457+
458+
if not TYPE_CATEGORIES.get(type_id):
459+
self.result.add_error(
460+
issue_code="value",
461+
error_code="INVALID_RESOURCE",
462+
diagnostics=f"type ({type_id}) does not map to the category: {category_id}",
463+
field=f"type.coding[0].display",
393464
)
394465

395466
def _validate_content_extension(self, model: DocumentReference):

0 commit comments

Comments
 (0)