Skip to content

Commit cc0207f

Browse files
Merge branch 'develop' into feature/eema1-NRL-1465-updateAsdfVersions
2 parents 3611eae + 0d552c3 commit cc0207f

14 files changed

+214
-39
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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class PointerTypes(Enum):
6666
PERSONALISED_CARE_AND_SUPPORT_PLAN = "http://snomed.info/sct|2181441000000107"
6767
MRA_UPPER_LIMB_ARTERY = "https://nicip.nhs.uk|MAULR"
6868
MRI_AXILLA_BOTH = "https://nicip.nhs.uk|MAXIB"
69+
APPOINTMENT = "http://snomed.info/sct|749001000000101"
6970

7071
@staticmethod
7172
def list():
@@ -84,6 +85,7 @@ class Categories(Enum):
8485
CLINICAL_NOTE = "http://snomed.info/sct|823651000000106"
8586
DIAGNOSTIC_STUDIES_REPORT = "http://snomed.info/sct|721981007"
8687
DIAGNOSTIC_PROCEDURE = "http://snomed.info/sct|103693007"
88+
RECORD_ARTIFACT = "http://snomed.info/sct|419891008"
8789

8890
@staticmethod
8991
def list():
@@ -112,6 +114,7 @@ def coding_value(self):
112114
Categories.DIAGNOSTIC_PROCEDURE.value: {
113115
"display": "Diagnostic procedure",
114116
},
117+
Categories.RECORD_ARTIFACT.value: {"display": "Record artifact"},
115118
}
116119

117120
TYPE_ATTRIBUTES = {
@@ -157,6 +160,9 @@ def coding_value(self):
157160
PointerTypes.MRI_AXILLA_BOTH.value: {
158161
"display": "MRI Axilla Both",
159162
},
163+
PointerTypes.APPOINTMENT.value: {
164+
"display": "Appointment",
165+
},
160166
}
161167

162168
TYPE_CATEGORIES = {
@@ -182,6 +188,9 @@ def coding_value(self):
182188
# Imaging
183189
PointerTypes.MRA_UPPER_LIMB_ARTERY.value: Categories.DIAGNOSTIC_STUDIES_REPORT.value,
184190
PointerTypes.MRI_AXILLA_BOTH.value: Categories.DIAGNOSTIC_PROCEDURE.value,
191+
#
192+
# Bookings and Referrals
193+
PointerTypes.APPOINTMENT.value: Categories.RECORD_ARTIFACT.value,
185194
}
186195

187196
PRACTICE_SETTING_VALUE_SET_URL = (
@@ -653,6 +662,7 @@ def coding_value(self):
653662
"24291000087104": "Geriatric chronic pain management service",
654663
"1323501000000109": "Special care dentistry service",
655664
"1423561000000102": "Acute oncology service",
665+
"394802001": "General medicine",
656666
}
657667

658668

@@ -664,3 +674,16 @@ def coding_value(self):
664674
"https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability"
665675
)
666676
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: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,40 @@ 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]["attachment"]["contentType"] = "application/json"
1165+
1166+
document_ref_data["content"][0]["format"] = {
1167+
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode",
1168+
"code": "urn:nhs-ic:record-contact",
1169+
"display": "Contact details (HTTP Unsecured)",
1170+
}
1171+
1172+
result = validator.validate(document_ref_data)
1173+
1174+
assert result.is_valid is False
1175+
assert result.resource.id == "Y05868-99999-99999-999999"
1176+
assert len(result.issues) == 1
1177+
assert result.issues[0].model_dump(exclude_none=True) == {
1178+
"severity": "error",
1179+
"code": "business-rule",
1180+
"details": {
1181+
"coding": [
1182+
{
1183+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
1184+
"code": "UNPROCESSABLE_ENTITY",
1185+
"display": "Unprocessable Entity",
1186+
}
1187+
]
1188+
},
1189+
"diagnostics": "Invalid content format code: urn:nhs-ic:record-contact format code must be 'urn:nhs-ic:structured' for Structured Document attachments.",
1190+
"expression": ["content[0].format.code"],
1191+
}
1192+
1193+
11601194
def test_validate_content_format_invalid_code_for_contact_details():
11611195
validator = DocumentReferenceValidator()
11621196
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
@@ -1353,23 +1387,25 @@ def test_validate_content_invalid_content_type():
13531387
}
13541388
]
13551389
},
1356-
"diagnostics": "Invalid contentType: invalid/type. Must be 'application/pdf' or 'text/html'",
1390+
"diagnostics": "Invalid contentType: invalid/type. Must be 'application/pdf', 'text/html' or 'application/fhir+json'",
13571391
"expression": ["content[0].attachment.contentType"],
13581392
}
13591393

13601394

13611395
@pytest.mark.parametrize(
1362-
"format_code, format_display",
1396+
"content_type, format_code, format_display",
13631397
[
1364-
("urn:nhs-ic:record-contact", "Contact details (HTTP Unsecured)"),
1365-
("urn:nhs-ic:unstructured", "Unstructured Document"),
1398+
("text/html", "urn:nhs-ic:record-contact", "Contact details (HTTP Unsecured)"),
1399+
("application/pdf", "urn:nhs-ic:unstructured", "Unstructured Document"),
1400+
("application/json+fhir", "urn:nhs-ic:structured", "Structured Document"),
13661401
],
13671402
)
1368-
def test_validate_nrl_format_code_valid_match(format_code, format_display):
1403+
def test_validate_nrl_format_code_valid_match(
1404+
content_type, format_code, format_display
1405+
):
13691406
validator = DocumentReferenceValidator()
13701407
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
1371-
if format_code == "urn:nhs-ic:record-contact":
1372-
document_ref_data["content"][0]["attachment"]["contentType"] = "text/html"
1408+
document_ref_data["content"][0]["attachment"]["contentType"] = content_type
13731409

13741410
document_ref_data["content"][0]["format"] = {
13751411
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode",
@@ -1383,27 +1419,34 @@ def test_validate_nrl_format_code_valid_match(format_code, format_display):
13831419

13841420

13851421
@pytest.mark.parametrize(
1386-
"format_code, format_display, expected_display",
1422+
"content_type, format_code, format_display, expected_display",
13871423
[
13881424
(
1425+
"application/pdf",
13891426
"urn:nhs-ic:unstructured",
13901427
"Contact details (HTTP Unsecured)",
13911428
"Unstructured Document",
13921429
),
13931430
(
1431+
"text/html",
13941432
"urn:nhs-ic:record-contact",
13951433
"Unstructured Document",
13961434
"Contact details (HTTP Unsecured)",
13971435
),
1436+
(
1437+
"application/fhir+json",
1438+
"urn:nhs-ic:structured",
1439+
"Unstructured Document",
1440+
"Structured Document",
1441+
),
13981442
],
13991443
)
14001444
def test_validate_nrl_format_code_display_mismatch(
1401-
format_code, format_display, expected_display
1445+
content_type, format_code, format_display, expected_display
14021446
):
14031447
validator = DocumentReferenceValidator()
14041448
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
1405-
if format_code == "urn:nhs-ic:record-contact":
1406-
document_ref_data["content"][0]["attachment"]["contentType"] = "text/html"
1449+
document_ref_data["content"][0]["attachment"]["contentType"] = content_type
14071450

14081451
document_ref_data["content"][0]["format"] = {
14091452
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode",

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

resources/fhir/NRLF-FormatCode-ValueSet.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"resourceType": "ValueSet",
33
"id": "NRLF-FormatCode",
44
"url": "https://fhir.nhs.uk/England/ValueSet/England-NRLFormatCode",
5-
"version": "1.0.0",
5+
"version": "1.0.3",
66
"name": "NRLF Format Code",
77
"status": "draft",
8-
"date": "2025-01-28T00:00:00+00:00",
8+
"date": "2025-05-22T00:00:00+00:00",
99
"publisher": "NHS Digital",
1010
"contact": {
1111
"name": "NRL Team at NHS Digital",
@@ -29,6 +29,10 @@
2929
{
3030
"code": "urn:nhs-ic:unstructured",
3131
"display": "Unstructured Document"
32+
},
33+
{
34+
"code": "urn:nhs-ic:structured",
35+
"display": "Structured Document"
3236
}
3337
]
3438
}

resources/fhir/NRLF-PracticeSetting-ValueSet.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"resourceType": "ValueSet",
33
"id": "NRLF-PracticeSetting",
44
"url": "https://fhir.nhs.uk/England/ValueSet/England-NRLPracticeSetting",
5-
"version": "1.1.2",
5+
"version": "1.1.3",
66
"name": "NRLF Record Practice Setting",
77
"status": "draft",
8-
"date": "2025-01-28T00:00:00+00:00",
8+
"date": "2025-05-22T00:00:00+00:00",
99
"publisher": "NHS Digital",
1010
"contact": {
1111
"name": "NRL Team at NHS Digital",
@@ -1881,6 +1881,10 @@
18811881
{
18821882
"code": "1423561000000102",
18831883
"display": "Acute oncology service"
1884+
},
1885+
{
1886+
"code": "394802001",
1887+
"display": "General medicine"
18841888
}
18851889
]
18861890
}

0 commit comments

Comments
 (0)