Skip to content

Commit 9703cdb

Browse files
committed
NRL-1472 add validation for structured document contentTypes
1 parent 1ffb4da commit 9703cdb

File tree

6 files changed

+98
-17
lines changed

6 files changed

+98
-17
lines changed

layer/nrlf/consumer/fhir/r4/model.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,19 @@ class NRLFormatCode(Coding):
246246
Field(description="The system URL for the NRLF Format Code."),
247247
]
248248
code: Annotated[
249-
Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"],
249+
Literal[
250+
"urn:nhs-ic:record-contact",
251+
"urn:nhs-ic:unstructured",
252+
"urn:nhs-ic:structured",
253+
],
250254
Field(description="The code representing the format of the document."),
251255
]
252256
display: Annotated[
253-
Literal["Contact details (HTTP Unsecured)", "Unstructured Document"],
257+
Literal[
258+
"Contact details (HTTP Unsecured)",
259+
"Unstructured Document",
260+
"Structured Document",
261+
],
254262
Field(description="The display text for the code."),
255263
]
256264

layer/nrlf/core/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,3 +674,16 @@ def coding_value(self):
674674
"https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability"
675675
)
676676
CONTENT_FORMAT_CODE_URL = "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode"
677+
CONTENT_FORMAT_CODE_MAP = {
678+
"urn:nhs-ic:record-contact": "Contact details (HTTP Unsecured)",
679+
"urn:nhs-ic:unstructured": "Unstructured Document",
680+
"urn:nhs-ic:structured": "Structured Document",
681+
}
682+
683+
ATTACHMENT_CONTENT_TYPES = {
684+
"application/pdf",
685+
"text/html",
686+
"application/json",
687+
"application/fhir+json",
688+
"application/json+fhir",
689+
}

layer/nrlf/core/tests/test_validators.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,38 @@ def test_validate_content_format_invalid_code_for_unstructured_document():
11571157
}
11581158

11591159

1160+
def test_validate_content_format_invalid_code_for_structured_document():
1161+
validator = DocumentReferenceValidator()
1162+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
1163+
1164+
document_ref_data["content"][0]["format"] = {
1165+
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode",
1166+
"code": "urn:nhs-ic:record-contact",
1167+
"display": "Contact details (HTTP Unsecured)",
1168+
}
1169+
1170+
result = validator.validate(document_ref_data)
1171+
1172+
assert result.is_valid is False
1173+
assert result.resource.id == "Y05868-99999-99999-999999"
1174+
assert len(result.issues) == 1
1175+
assert result.issues[0].model_dump(exclude_none=True) == {
1176+
"severity": "error",
1177+
"code": "business-rule",
1178+
"details": {
1179+
"coding": [
1180+
{
1181+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
1182+
"code": "UNPROCESSABLE_ENTITY",
1183+
"display": "Unprocessable Entity",
1184+
}
1185+
]
1186+
},
1187+
"diagnostics": "Invalid content format code: urn:nhs-ic:record-contact format code must be 'urn:nhs-ic:structured' for Structured Document attachments.",
1188+
"expression": ["content[0].format.code"],
1189+
}
1190+
1191+
11601192
def test_validate_content_format_invalid_code_for_contact_details():
11611193
validator = DocumentReferenceValidator()
11621194
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")

layer/nrlf/core/validators.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from nrlf.consumer.fhir.r4.model import RequestQueryCategory
88
from nrlf.core.codes import SpineErrorConcept
99
from nrlf.core.constants import (
10+
ATTACHMENT_CONTENT_TYPES,
1011
CATEGORY_ATTRIBUTES,
12+
CONTENT_FORMAT_CODE_MAP,
1113
ODS_SYSTEM,
1214
PRACTICE_SETTING_VALUE_SET_URL,
1315
REQUIRED_CREATE_FIELDS,
@@ -483,6 +485,21 @@ def _validate_content_format(self, model: DocumentReference):
483485
diagnostics=f"Invalid content format code: {content.format.code} format code must be 'urn:nhs-ic:unstructured' for Unstructured Document attachments.",
484486
field=f"content[{i}].format.code",
485487
)
488+
elif (
489+
content.attachment.contentType
490+
in {
491+
"application/json",
492+
"application/fhir+json",
493+
"application/json+fhir",
494+
}
495+
and content.format.code != "urn:nhs-ic:structured"
496+
):
497+
self.result.add_error(
498+
issue_code="business-rule",
499+
error_code="UNPROCESSABLE_ENTITY",
500+
diagnostics=f"Invalid content format code: {content.format.code} format code must be 'urn:nhs-ic:structured' for Structured Document attachments.",
501+
field=f"content[{i}].format.code",
502+
)
486503

487504
def _validate_content_extension(self, model: DocumentReference):
488505
"""
@@ -613,28 +630,23 @@ def _validate_practiceSetting(self, model: DocumentReference):
613630

614631
def _validate_content(self, model: DocumentReference):
615632
"""
616-
Validate that the contentType is present and is either 'application/pdf' or 'text/html'.
633+
Validate that the contentType is present and supported.
617634
"""
618635
logger.log(LogReference.VALIDATOR001, step="content")
619636

620-
format_code_display_map = {
621-
"urn:nhs-ic:record-contact": "Contact details (HTTP Unsecured)",
622-
"urn:nhs-ic:unstructured": "Unstructured Document",
623-
}
624-
625637
for i, content in enumerate(model.content):
626-
if content.attachment.contentType not in ["application/pdf", "text/html"]:
638+
if content.attachment.contentType not in ATTACHMENT_CONTENT_TYPES:
627639
self.result.add_error(
628640
issue_code="business-rule",
629641
error_code="UNPROCESSABLE_ENTITY",
630-
diagnostics=f"Invalid contentType: {content.attachment.contentType}. Must be 'application/pdf' or 'text/html'",
642+
diagnostics=f"Invalid contentType: {content.attachment.contentType}. Must be 'application/pdf', 'text/html' or 'application/fhir+json'",
631643
field=f"content[{i}].attachment.contentType",
632644
)
633645

634646
# Validate NRLFormatCode
635647
format_code = content.format.code
636648
format_display = content.format.display
637-
expected_display = format_code_display_map.get(format_code)
649+
expected_display = CONTENT_FORMAT_CODE_MAP.get(format_code)
638650
if expected_display and format_display != expected_display:
639651
self.result.add_error(
640652
issue_code="business-rule",

layer/nrlf/producer/fhir/r4/model.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: swagger.yaml
3-
# timestamp: 2025-02-07T14:10:35+00:00
3+
# timestamp: 2025-05-22T18:53:21+00:00
44

55
from __future__ import annotations
66

@@ -290,11 +290,19 @@ class NRLFormatCode(Coding):
290290
Field(description="The system URL for the NRLF Format Code."),
291291
]
292292
code: Annotated[
293-
Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"],
293+
Literal[
294+
"urn:nhs-ic:record-contact",
295+
"urn:nhs-ic:unstructured",
296+
"urn:nhs-ic:structured",
297+
],
294298
Field(description="The code representing the format of the document."),
295299
]
296300
display: Annotated[
297-
Literal["Contact details (HTTP Unsecured)", "Unstructured Document"],
301+
Literal[
302+
"Contact details (HTTP Unsecured)",
303+
"Unstructured Document",
304+
"Structured Document",
305+
],
298306
Field(description="The display text for the code."),
299307
]
300308

layer/nrlf/producer/fhir/r4/strict_model.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: swagger.yaml
3-
# timestamp: 2025-02-07T14:10:37+00:00
3+
# timestamp: 2025-05-22T18:53:21+00:00
44

55
from __future__ import annotations
66

@@ -261,11 +261,19 @@ class NRLFormatCode(Coding):
261261
Field(description="The system URL for the NRLF Format Code."),
262262
]
263263
code: Annotated[
264-
Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"],
264+
Literal[
265+
"urn:nhs-ic:record-contact",
266+
"urn:nhs-ic:unstructured",
267+
"urn:nhs-ic:structured",
268+
],
265269
Field(description="The code representing the format of the document."),
266270
]
267271
display: Annotated[
268-
Literal["Contact details (HTTP Unsecured)", "Unstructured Document"],
272+
Literal[
273+
"Contact details (HTTP Unsecured)",
274+
"Unstructured Document",
275+
"Structured Document",
276+
],
269277
Field(description="The display text for the code."),
270278
]
271279

0 commit comments

Comments
 (0)