Skip to content

Commit c2f2340

Browse files
Merge pull request #831 from NHSDigital/feature/made14-NRL-1279-supplier-data-tests
[NRL-1279] Add supplier samples test data and automated validation tests
2 parents 968d39b + cc19856 commit c2f2340

22 files changed

+2466
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import os
2+
3+
import pytest
4+
5+
from nrlf.consumer.fhir.r4.model import DocumentReference as ConsumerDocumentReference
6+
from nrlf.core.validators import DocumentReferenceValidator
7+
from nrlf.producer.fhir.r4.model import DocumentReference as ProducerDocumentReference
8+
9+
10+
def sample_pointer_files() -> list[str]:
11+
return [f for f in os.listdir("./tests/data/samples") if f.endswith(".json")]
12+
13+
14+
def load_sample_pointer_data(pointer_file: str) -> str:
15+
with open(f"./tests/data/samples/{pointer_file}", "r") as f:
16+
return f.read()
17+
18+
19+
@pytest.mark.parametrize("sample_pointer_file", sample_pointer_files())
20+
def test_sample_pointer_as_consumer(sample_pointer_file: str):
21+
sample_pointer_data = load_sample_pointer_data(sample_pointer_file)
22+
23+
docref = ConsumerDocumentReference.model_validate_json(sample_pointer_data)
24+
result = DocumentReferenceValidator().validate(data=docref)
25+
26+
assert result.is_valid
27+
28+
29+
@pytest.mark.parametrize("sample_pointer_file", sample_pointer_files())
30+
def test_sample_pointer_as_producer(sample_pointer_file: str):
31+
sample_pointer_data = load_sample_pointer_data(sample_pointer_file)
32+
33+
docref = ProducerDocumentReference.model_validate_json(sample_pointer_data)
34+
result = DocumentReferenceValidator().validate(data=docref)
35+
36+
assert result.is_valid

scripts/redact_live_pointers.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import json
2+
import os
3+
from datetime import datetime, timedelta, timezone
4+
from typing import Any
5+
6+
import boto3
7+
import fire
8+
9+
from nrlf.consumer.fhir.r4.model import DocumentReference
10+
from nrlf.core.constants import PointerTypes
11+
from nrlf.core.logger import logger
12+
from nrlf.core.utils import create_fhir_instant
13+
from nrlf.core.validators import DocumentReferenceValidator
14+
15+
dynamodb = boto3.client("dynamodb")
16+
paginator = dynamodb.get_paginator("scan")
17+
18+
logger.setLevel("ERROR")
19+
20+
type_to_name = {pointer_type.value: pointer_type.name for pointer_type in PointerTypes}
21+
22+
23+
def _redact_timestamps(docref: DocumentReference) -> None:
24+
mock_timestamp = create_fhir_instant()
25+
docref.meta.lastUpdated = mock_timestamp
26+
docref.date = mock_timestamp
27+
28+
29+
def _redact_ids(docref: DocumentReference) -> None:
30+
ods_code = docref.custodian.identifier.value
31+
32+
mock_id = "c2a99222-eb50-4451-ad6e-1e951627800e"
33+
docref.subject.identifier.value = "9999999999"
34+
docref.id = f"{ods_code}-{mock_id}"
35+
if docref.masterIdentifier:
36+
docref.masterIdentifier.value = f"mid_{mock_id}"
37+
if docref.relatesTo:
38+
for relates_to in docref.relatesTo:
39+
relates_to.target.identifier.value = f"rel_{mock_id}"
40+
41+
42+
def _redact_content(docref: DocumentReference) -> None:
43+
mock_timestamp = create_fhir_instant()
44+
for content in docref.content:
45+
if content.attachment.url.startswith("ssp://"):
46+
content.attachment.url = "ssp://content.test.local/content"
47+
else:
48+
content.attachment.url = "https://content.test.local/content"
49+
content.attachment.creation = mock_timestamp
50+
51+
52+
def _redact_context(docref: DocumentReference) -> None:
53+
if docref.context.related:
54+
for related in docref.context.related:
55+
related.identifier.value = "012345678910"
56+
57+
mock_timestamp = create_fhir_instant()
58+
if docref.context.period:
59+
if docref.context.period.start:
60+
docref.context.period.start = mock_timestamp
61+
if docref.context.period.end:
62+
docref.context.period.end = mock_timestamp
63+
64+
65+
def _redact_pointers(src_path: str, dest_path: str) -> None:
66+
"""
67+
Redact pointers in .json files in from the source path and write the redacted pointer to the destination path.
68+
Parameters:
69+
- src_path: The path to the source directory containing the pointers.
70+
- dest_path: The path to the destination directory to write the redacted pointers.
71+
"""
72+
src_pointer_files = [f for f in os.listdir(src_path) if f.endswith(".json")]
73+
74+
for src_pointer_file in src_pointer_files:
75+
print("Reading", src_pointer_file)
76+
with open(f"{src_path}/{src_pointer_file}", "r") as f:
77+
pointer_data = f.read()
78+
79+
docref = DocumentReference.model_validate_json(pointer_data)
80+
81+
ods_code = docref.custodian.identifier.value
82+
type_coding = docref.type.coding[0]
83+
pointer_type = type_to_name[f"{type_coding.system}|{type_coding.code}"]
84+
85+
_redact_ids(docref)
86+
_redact_timestamps(docref)
87+
_redact_content(docref)
88+
_redact_context(docref)
89+
90+
month_year = datetime.now().strftime("%b%y")
91+
filename = f"{dest_path}/{ods_code}_{pointer_type}_{month_year}.json"
92+
93+
print("Writing", filename)
94+
with open(filename, "w") as f:
95+
f.write(docref.model_dump_json(indent=2, exclude_unset=True))
96+
97+
98+
if __name__ == "__main__":
99+
fire.Fire(_redact_pointers)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"resourceType": "DocumentReference",
3+
"id": "11X-c2a99222-eb50-4451-ad6e-1e951627800e",
4+
"meta": {
5+
"lastUpdated": "2025-02-10T16:48:27.326Z"
6+
},
7+
"masterIdentifier": {
8+
"system": "urn:ietf:rfc:3986",
9+
"value": "mid_c2a99222-eb50-4451-ad6e-1e951627800e"
10+
},
11+
"status": "current",
12+
"type": {
13+
"coding": [
14+
{
15+
"system": "http://snomed.info/sct",
16+
"code": "736366004",
17+
"display": "Advance care plan"
18+
}
19+
]
20+
},
21+
"category": [
22+
{
23+
"coding": [
24+
{
25+
"system": "http://snomed.info/sct",
26+
"code": "734163000",
27+
"display": "Care plan"
28+
}
29+
]
30+
}
31+
],
32+
"subject": {
33+
"identifier": {
34+
"system": "https://fhir.nhs.uk/Id/nhs-number",
35+
"value": "9999999999"
36+
}
37+
},
38+
"date": "2025-02-10T16:48:27.326Z",
39+
"author": [
40+
{
41+
"identifier": {
42+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
43+
"value": "L85012"
44+
}
45+
}
46+
],
47+
"custodian": {
48+
"identifier": {
49+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
50+
"value": "11X"
51+
}
52+
},
53+
"content": [
54+
{
55+
"attachment": {
56+
"contentType": "application/pdf",
57+
"url": "ssp://content.test.local/content",
58+
"creation": "2025-02-10T16:48:27.326Z"
59+
},
60+
"format": {
61+
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode",
62+
"code": "urn:nhs-ic:unstructured",
63+
"display": "Unstructured Document"
64+
},
65+
"extension": [
66+
{
67+
"valueCodeableConcept": {
68+
"coding": [
69+
{
70+
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability",
71+
"code": "static",
72+
"display": "Static"
73+
}
74+
]
75+
},
76+
"url": "https://fhir.nhs.uk/England/StructureDefinition/Extension-England-ContentStability"
77+
}
78+
]
79+
}
80+
],
81+
"context": {
82+
"period": {
83+
"start": "2025-02-10T16:48:27.326Z"
84+
},
85+
"practiceSetting": {
86+
"coding": [
87+
{
88+
"system": "http://snomed.info/sct",
89+
"code": "1060971000000108",
90+
"display": "General practice service"
91+
}
92+
]
93+
},
94+
"related": [
95+
{
96+
"identifier": {
97+
"system": "https://fhir.nhs.uk/Id/nhsSpineASID",
98+
"value": "012345678910"
99+
}
100+
}
101+
]
102+
}
103+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"resourceType": "DocumentReference",
3+
"id": "11X-c2a99222-eb50-4451-ad6e-1e951627800e",
4+
"meta": {
5+
"lastUpdated": "2025-02-10T16:48:27.327Z"
6+
},
7+
"masterIdentifier": {
8+
"system": "urn:ietf:rfc:3986",
9+
"value": "mid_c2a99222-eb50-4451-ad6e-1e951627800e"
10+
},
11+
"status": "current",
12+
"type": {
13+
"coding": [
14+
{
15+
"system": "http://snomed.info/sct",
16+
"code": "2181441000000107",
17+
"display": "Personalised Care and Support Plan"
18+
}
19+
]
20+
},
21+
"category": [
22+
{
23+
"coding": [
24+
{
25+
"system": "http://snomed.info/sct",
26+
"code": "734163000",
27+
"display": "Care plan"
28+
}
29+
]
30+
}
31+
],
32+
"subject": {
33+
"identifier": {
34+
"system": "https://fhir.nhs.uk/Id/nhs-number",
35+
"value": "9999999999"
36+
}
37+
},
38+
"date": "2025-02-10T16:48:27.327Z",
39+
"author": [
40+
{
41+
"identifier": {
42+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
43+
"value": "L85609"
44+
}
45+
}
46+
],
47+
"custodian": {
48+
"identifier": {
49+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
50+
"value": "11X"
51+
}
52+
},
53+
"content": [
54+
{
55+
"attachment": {
56+
"contentType": "application/pdf",
57+
"url": "ssp://content.test.local/content",
58+
"creation": "2025-02-10T16:48:27.327Z"
59+
},
60+
"format": {
61+
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLFormatCode",
62+
"code": "urn:nhs-ic:unstructured",
63+
"display": "Unstructured Document"
64+
},
65+
"extension": [
66+
{
67+
"valueCodeableConcept": {
68+
"coding": [
69+
{
70+
"system": "https://fhir.nhs.uk/England/CodeSystem/England-NRLContentStability",
71+
"code": "static",
72+
"display": "Static"
73+
}
74+
]
75+
},
76+
"url": "https://fhir.nhs.uk/England/StructureDefinition/Extension-England-ContentStability"
77+
}
78+
]
79+
}
80+
],
81+
"context": {
82+
"period": {
83+
"start": "2025-02-10T16:48:27.327Z"
84+
},
85+
"practiceSetting": {
86+
"coding": [
87+
{
88+
"system": "http://snomed.info/sct",
89+
"code": "1060971000000108",
90+
"display": "General practice service"
91+
}
92+
]
93+
},
94+
"related": [
95+
{
96+
"identifier": {
97+
"system": "https://fhir.nhs.uk/Id/nhsSpineASID",
98+
"value": "012345678910"
99+
}
100+
}
101+
]
102+
}
103+
}

0 commit comments

Comments
 (0)