Skip to content

Commit 889a2a4

Browse files
committed
Merge branch 'develop' of github.com:NHSDigital/NRLF into feature/eema1-NRL-477-validateDocStatusAndDailyBuildAndFormatCode
2 parents cef4616 + 26407d4 commit 889a2a4

File tree

18 files changed

+129
-53
lines changed

18 files changed

+129
-53
lines changed

api/consumer/searchDocumentReference/search_document_reference.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from nrlf.core.logger import LogReference, logger
1010
from nrlf.core.model import ConnectionMetadata, ConsumerRequestParams
1111
from nrlf.core.response import Response, SpineErrorResponse
12-
from nrlf.core.validators import validate_category, validate_type_system
12+
from nrlf.core.validators import validate_category, validate_type
1313

1414

1515
@request_handler(params=ConsumerRequestParams)
@@ -46,8 +46,7 @@ def handler(
4646
base_url = f"https://{config.ENVIRONMENT}.api.service.nhs.uk/"
4747
self_link = f"{base_url}record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{params.nhs_number}"
4848

49-
# TODO - Add checks for the type code as well as system
50-
if not validate_type_system(params.type, metadata.pointer_types):
49+
if not validate_type(params.type, metadata.pointer_types):
5150
logger.log(
5251
LogReference.CONSEARCH002,
5352
type=params.type,

api/consumer/searchPostDocumentReference/search_post_document_reference.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from nrlf.core.logger import LogReference, logger
1010
from nrlf.core.model import ConnectionMetadata, ConsumerRequestParams
1111
from nrlf.core.response import Response, SpineErrorResponse
12-
from nrlf.core.validators import validate_category, validate_type_system
12+
from nrlf.core.validators import validate_category, validate_type
1313

1414

1515
@request_handler(body=ConsumerRequestParams)
@@ -50,7 +50,7 @@ def handler(
5050
base_url = f"https://{config.ENVIRONMENT}.api.service.nhs.uk/"
5151
self_link = f"{base_url}record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{body.nhs_number}"
5252

53-
if not validate_type_system(body.type, metadata.pointer_types):
53+
if not validate_type(body.type, metadata.pointer_types):
5454
logger.log(
5555
LogReference.CONPOSTSEARCH002,
5656
type=body.type,

api/consumer/swagger.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ info:
3636
* [End of Life Care Coordination Summary](http://snomed.info/sct/861421000000109)
3737
* [Emergency health care plan](http://snomed.info/sct/887701000000100)
3838
* [Lloyd George record folder](http://snomed.info/sct/16521000000101)
39-
* [Advanced care plan](http://snomed.info/sct/736366004)
39+
* [Advance care plan](http://snomed.info/sct/736366004)
4040
* [Treatment escalation plan](http://snomed.info/sct/735324008)
4141
* [Summary record]("http://snomed.info/sct|824321000000109")
4242
* [Personalised Care and Support Plan]("http://snomed.info/sct|2181441000000107")
@@ -1519,8 +1519,8 @@ components:
15191519
SNOMED_CODES_LLOYD_GEORGE_RECORD_FOLDER:
15201520
summary: Lloyd George record folder
15211521
value: http://snomed.info/sct|16521000000101
1522-
SNOMED_CODES_ADVANCED_CARE_PLAN:
1523-
summary: Advanced care plan
1522+
SNOMED_CODES_ADVANCE_CARE_PLAN:
1523+
summary: Advance care plan
15241524
value: http://snomed.info/sct|736366004
15251525
SNOMED_CODES_TREATMENT_ESCALATION_PLAN:
15261526
summary: Treatment escalation plan

api/producer/searchDocumentReference/search_document_reference.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from nrlf.core.logger import LogReference, logger
77
from nrlf.core.model import ConnectionMetadata, ProducerRequestParams
88
from nrlf.core.response import Response, SpineErrorResponse
9-
from nrlf.core.validators import validate_category, validate_type_system
9+
from nrlf.core.validators import validate_category, validate_type
1010
from nrlf.producer.fhir.r4.model import Bundle, DocumentReference
1111

1212

@@ -48,7 +48,7 @@ def handler(
4848
expression="subject:identifier",
4949
)
5050

51-
if not validate_type_system(params.type, metadata.pointer_types):
51+
if not validate_type(params.type, metadata.pointer_types):
5252
logger.log(
5353
LogReference.PROSEARCH002,
5454
type=params.type,

api/producer/searchPostDocumentReference/search_post_document_reference.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from nrlf.core.logger import LogReference, logger
77
from nrlf.core.model import ConnectionMetadata, ProducerRequestParams
88
from nrlf.core.response import Response, SpineErrorResponse
9-
from nrlf.core.validators import validate_category, validate_type_system
9+
from nrlf.core.validators import validate_category, validate_type
1010
from nrlf.producer.fhir.r4.model import Bundle, DocumentReference
1111

1212

@@ -42,7 +42,7 @@ def handler(
4242
expression="subject:identifier",
4343
)
4444

45-
if not validate_type_system(body.type, metadata.pointer_types):
45+
if not validate_type(body.type, metadata.pointer_types):
4646
logger.log(
4747
LogReference.PROPOSTSEARCH002,
4848
type=body.type,

api/producer/swagger.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ info:
3636
* [End of Life Care Coordination Summary](http://snomed.info/sct/861421000000109)
3737
* [Emergency health care plan](http://snomed.info/sct/887701000000100)
3838
* [Lloyd George record folder](http://snomed.info/sct/16521000000101)
39-
* [Advanced care plan](http://snomed.info/sct/736366004)
39+
* [Advance care plan](http://snomed.info/sct/736366004)
4040
* [Treatment escalation plan](http://snomed.info/sct/735324008)
4141
* [Summary record]("http://snomed.info/sct|824321000000109")
4242
* [Personalised Care and Support Plan]("http://snomed.info/sct|2181441000000107")
@@ -2054,8 +2054,8 @@ components:
20542054
SNOMED_CODES_LLOYD_GEORGE_RECORD_FOLDER:
20552055
summary: Lloyd George record folder
20562056
value: http://snomed.info/sct|16521000000101
2057-
SNOMED_CODES_ADVANCED_CARE_PLAN:
2058-
summary: Advanced care plan
2057+
SNOMED_CODES_ADVANCE_CARE_PLAN:
2058+
summary: Advance care plan
20592059
value: http://snomed.info/sct|736366004
20602060
SNOMED_CODES_TREATMENT_ESCALATION_PLAN:
20612061
summary: Treatment escalation plan

layer/nrlf/core/constants.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class PointerTypes(Enum):
6060
CONTINGENCY_PLAN = "http://snomed.info/sct|325691000000100"
6161
EOL_CARE_PLAN = "http://snomed.info/sct|736373009"
6262
LLOYD_GEORGE_FOLDER = "http://snomed.info/sct|16521000000101"
63-
ADVANCED_CARE_PLAN = "http://snomed.info/sct|736366004"
63+
ADVANCE_CARE_PLAN = "http://snomed.info/sct|736366004"
6464
TREATMENT_ESCALATION_PLAN = "http://snomed.info/sct|735324008"
6565
SUMMARY_RECORD = "http://snomed.info/sct|824321000000109"
6666
PERSONALISED_CARE_AND_SUPPORT_PLAN = "http://snomed.info/sct|2181441000000107"
@@ -139,8 +139,8 @@ def coding_value(self):
139139
PointerTypes.LLOYD_GEORGE_FOLDER.value: {
140140
"display": "Lloyd George record folder",
141141
},
142-
PointerTypes.ADVANCED_CARE_PLAN.value: {
143-
"display": "Advanced care plan",
142+
PointerTypes.ADVANCE_CARE_PLAN.value: {
143+
"display": "Advance care plan",
144144
},
145145
PointerTypes.TREATMENT_ESCALATION_PLAN.value: {
146146
"display": "Treatment escalation plan",
@@ -169,7 +169,7 @@ def coding_value(self):
169169
PointerTypes.CONTINGENCY_PLAN.value: Categories.CARE_PLAN.value,
170170
PointerTypes.EOL_CARE_PLAN.value: Categories.CARE_PLAN.value,
171171
PointerTypes.LLOYD_GEORGE_FOLDER.value: Categories.CARE_PLAN.value,
172-
PointerTypes.ADVANCED_CARE_PLAN.value: Categories.CARE_PLAN.value,
172+
PointerTypes.ADVANCE_CARE_PLAN.value: Categories.CARE_PLAN.value,
173173
PointerTypes.TREATMENT_ESCALATION_PLAN.value: Categories.CARE_PLAN.value,
174174
PointerTypes.PERSONALISED_CARE_AND_SUPPORT_PLAN.value: Categories.CARE_PLAN.value,
175175
#

layer/nrlf/core/tests/test_validators.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from nrlf.core.validators import (
1414
DocumentReferenceValidator,
1515
ValidationResult,
16-
validate_type_system,
16+
validate_type,
1717
)
1818
from nrlf.producer.fhir.r4.model import (
1919
DocumentReference,
@@ -23,28 +23,37 @@
2323
from nrlf.tests.data import load_document_reference_json
2424

2525

26-
def test_validate_type_system_valid():
26+
def test_validate_type_valid():
2727
type_ = RequestQueryType(root=PointerTypes.MENTAL_HEALTH_PLAN.value)
2828
pointer_types = [
2929
PointerTypes.MENTAL_HEALTH_PLAN.value,
3030
PointerTypes.EOL_CARE_PLAN.value,
3131
]
32-
assert validate_type_system(type_, pointer_types) is True
32+
assert validate_type(type_, pointer_types) is True
3333

3434

35-
def test_validate_type_system_invalid():
35+
def test_validate_type_invalid_system():
3636
type_ = RequestQueryType(root="http://snomed.info/invalid|736373009")
3737
pointer_types = [
3838
PointerTypes.EOL_CARE_PLAN.value,
3939
PointerTypes.EOL_CARE_PLAN.value,
4040
]
41-
assert validate_type_system(type_, pointer_types) is False
41+
assert validate_type(type_, pointer_types) is False
4242

4343

44-
def test_validate_type_system_empty():
44+
def test_validate_type_invalid_code():
45+
type_ = RequestQueryType(root=PointerTypes.MRA_UPPER_LIMB_ARTERY.value)
46+
pointer_types = [
47+
PointerTypes.MENTAL_HEALTH_PLAN.value,
48+
PointerTypes.EOL_CARE_PLAN.value,
49+
]
50+
assert validate_type(type_, pointer_types) is False
51+
52+
53+
def test_validate_type_empty():
4554
type_ = None
4655
pointer_types: list[str] = []
47-
assert validate_type_system(type_, pointer_types) is True
56+
assert validate_type(type_, pointer_types) is True
4857

4958

5059
def test_validation_result_reset():

layer/nrlf/core/validators.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,14 @@
2424
from nrlf.producer.fhir.r4 import model as producer_model
2525

2626

27-
def validate_type_system(
28-
type_: Optional[RequestQueryType], pointer_types: List[str]
29-
) -> bool:
27+
def validate_type(type_: Optional[RequestQueryType], pointer_types: List[str]) -> bool:
3028
"""
3129
Validates if the given type system is present in the list of pointer types.
3230
"""
3331
if not type_:
3432
return True
3533

36-
type_system = type_.root.split("|", 1)[0]
37-
pointer_type_systems = [
38-
pointer_type.split("|", 1)[0] for pointer_type in pointer_types
39-
]
40-
41-
return type_system in pointer_type_systems
34+
return type_.root in pointer_types
4235

4336

4437
# TODO - Validate category is in set permissions once permissioning by category is done.

reports/find_invalid_pointers.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from datetime import datetime, timedelta, timezone
2+
from typing import Any
3+
4+
import boto3
5+
import fire
6+
7+
from nrlf.consumer.fhir.r4.model import DocumentReference
8+
from nrlf.core.logger import logger
9+
from nrlf.core.validators import DocumentReferenceValidator
10+
11+
dynamodb = boto3.client("dynamodb")
12+
paginator = dynamodb.get_paginator("scan")
13+
14+
logger.setLevel("ERROR")
15+
16+
17+
def _validate_document(document: str):
18+
docref = DocumentReference.model_validate_json(document)
19+
20+
validator = DocumentReferenceValidator()
21+
result = validator.validate(data=docref)
22+
23+
if not result.is_valid:
24+
raise RuntimeError("Failed to validate document: " + str(result.issues))
25+
26+
27+
def _find_invalid_pointers(table_name: str) -> dict[str, float | int]:
28+
"""
29+
Find pointers in the given table that are invalid.
30+
Parameters:
31+
- table_name: The name of the pointers table to use.
32+
"""
33+
34+
print(f"Finding invalid pointers in table {table_name}....") # noqa
35+
36+
params: dict[str, Any] = {
37+
"TableName": table_name,
38+
"PaginationConfig": {"PageSize": 50},
39+
}
40+
41+
invalid_pointers = []
42+
total_scanned_count = 0
43+
44+
start_time = datetime.now(tz=timezone.utc)
45+
46+
for page in paginator.paginate(**params):
47+
for item in page["Items"]:
48+
pointer_id = item.get("id", {}).get("S")
49+
document = item.get("document", {}).get("S", "")
50+
try:
51+
_validate_document(document)
52+
except Exception as exc:
53+
invalid_pointers.append((pointer_id, exc))
54+
55+
total_scanned_count += page["ScannedCount"]
56+
57+
if total_scanned_count % 1000 == 0:
58+
print(".", end="", flush=True) # noqa
59+
60+
if total_scanned_count % 100000 == 0:
61+
print( # noqa
62+
f"scanned={total_scanned_count} invalid={len(invalid_pointers)}"
63+
)
64+
65+
end_time = datetime.now(tz=timezone.utc)
66+
67+
print(" Done") # noqa
68+
69+
print("Writing invalid_pointers to file ./invalid_pointers.txt ...") # noqa
70+
with open("invalid_pointers.txt", "w") as f:
71+
for _id, err in invalid_pointers:
72+
f.write(f"{_id}: {err}\n")
73+
74+
return {
75+
"invalid_pointers": len(invalid_pointers),
76+
"scanned_count": total_scanned_count,
77+
"took-secs": timedelta.total_seconds(end_time - start_time),
78+
}
79+
80+
81+
if __name__ == "__main__":
82+
fire.Fire(_find_invalid_pointers)

0 commit comments

Comments
 (0)