Skip to content

Commit 958d699

Browse files
authored
VED-904-2: Add logic to uplift legacy identifiers (#1022)
* Add logic to uplift legacy identifiers * Move constants to relevant file * Add row update assertion to unit tests * Add local_id assertion to unit tests
1 parent 6566cc3 commit 958d699

File tree

3 files changed

+175
-118
lines changed

3 files changed

+175
-118
lines changed

lambdas/recordprocessor/src/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
"LOCATION_CODE_TYPE_URI",
5050
]
5151

52+
TPP_V2_SUPPLIER_IDENTIFIER_SYSTEM = "YGA"
53+
TPP_V5_SUPPLIER_IDENTIFIER_SYSTEM = "https://tpp-uk.com/Id/ve/vacc"
54+
EMIS_V2_SUPPLIER_IDENTIFIER_SYSTEM = "YGJ"
55+
EMIS_V5_SUPPLIER_IDENTIFIER_SYSTEM = "https://emishealth.com/identifiers/vacc"
56+
5257

5358
class FileStatus:
5459
"""File status constants"""

lambdas/recordprocessor/src/process_row.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
"""Function to process a single row of a csv file"""
22

33
from common.clients import logger
4-
from constants import Diagnostics
4+
from constants import (
5+
EMIS_V2_SUPPLIER_IDENTIFIER_SYSTEM,
6+
EMIS_V5_SUPPLIER_IDENTIFIER_SYSTEM,
7+
TPP_V2_SUPPLIER_IDENTIFIER_SYSTEM,
8+
TPP_V5_SUPPLIER_IDENTIFIER_SYSTEM,
9+
Diagnostics,
10+
)
511
from convert_to_fhir_imms_resource import convert_to_fhir_imms_resource
612
from utils_for_recordprocessor import create_diagnostics_dictionary
713

@@ -14,6 +20,18 @@ def process_row(target_disease: list, allowed_operations: set, row: dict) -> dic
1420
"""
1521
action_flag = (row.get("ACTION_FLAG") or "").upper()
1622
unique_id_uri = row.get("UNIQUE_ID_URI")
23+
24+
# This code the relevant constants can be safely removed once DPS carries out it's data migration to update legacy
25+
# identifiers as it should become redundant. However, it may be worth keeping in case legacy format identifiers are
26+
# received for some reason. Please see issue VED-904 for more information.
27+
if unique_id_uri == TPP_V2_SUPPLIER_IDENTIFIER_SYSTEM:
28+
unique_id_uri = TPP_V5_SUPPLIER_IDENTIFIER_SYSTEM
29+
row["UNIQUE_ID_URI"] = TPP_V5_SUPPLIER_IDENTIFIER_SYSTEM
30+
31+
if unique_id_uri == EMIS_V2_SUPPLIER_IDENTIFIER_SYSTEM:
32+
unique_id_uri = EMIS_V5_SUPPLIER_IDENTIFIER_SYSTEM
33+
row["UNIQUE_ID_URI"] = EMIS_V5_SUPPLIER_IDENTIFIER_SYSTEM
34+
1735
unique_id = row.get("UNIQUE_ID")
1836
local_id = f"{unique_id}^{unique_id_uri}"
1937

lambdas/recordprocessor/tests/test_process_row.py

Lines changed: 151 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -27,145 +27,143 @@
2727
s3_client = boto3_client("s3", region_name=REGION_NAME)
2828
ROW_DETAILS = MockFieldDictionaries.all_fields
2929
Allowed_Operations = {"CREATE", "UPDATE", "DELETE"}
30-
31-
32-
@mock_s3
33-
@patch.dict("os.environ", MOCK_ENVIRONMENT_DICT)
34-
class TestProcessRow(unittest.TestCase):
35-
"""Tests for process_row"""
36-
37-
def setUp(self) -> None:
38-
GenericSetUp(s3_client)
39-
40-
def tearDown(self) -> None:
41-
GenericTearDown(s3_client)
42-
43-
def test_process_row_success(self):
44-
"""
45-
Test that process_row gives the expected output.
46-
These tests check that the row is valid and matches the expected output.
47-
"""
48-
# set the expected output from 'process_row' in case of success
49-
expected_result = {
50-
"resourceType": "Immunization",
51-
"status": "completed",
52-
"protocolApplied": [
30+
expected_successful_result = {
31+
"resourceType": "Immunization",
32+
"status": "completed",
33+
"protocolApplied": [
34+
{
35+
"targetDisease": [
5336
{
54-
"targetDisease": [
37+
"coding": [
5538
{
56-
"coding": [
57-
{
58-
"system": "http://snomed.info/sct",
59-
"code": "55735004",
60-
"display": "Respiratory syncytial virus infection (disorder)",
61-
}
62-
]
39+
"system": "http://snomed.info/sct",
40+
"code": "55735004",
41+
"display": "Respiratory syncytial virus infection (disorder)",
6342
}
64-
],
65-
"doseNumberPositiveInt": 1,
43+
]
6644
}
6745
],
68-
"reasonCode": [{"coding": [{"system": "http://snomed.info/sct", "code": "1037351000000105"}]}],
69-
"recorded": "2024-09-04",
70-
"identifier": [{"value": "RSV_002", "system": "https://www.ravs.england.nhs.uk/"}],
71-
"patient": {"reference": "#Patient1"},
72-
"contained": [
73-
{
74-
"id": "Patient1",
75-
"resourceType": "Patient",
76-
"birthDate": "2008-02-17",
77-
"gender": "male",
78-
"address": [{"postalCode": "WD25 0DZ"}],
79-
"identifier": [
80-
{
81-
"system": "https://fhir.nhs.uk/Id/nhs-number",
82-
"value": "9732928395",
83-
}
84-
],
85-
"name": [{"family": "PEEL", "given": ["PHYLIS"]}],
86-
},
87-
{
88-
"resourceType": "Practitioner",
89-
"id": "Practitioner1",
90-
"name": [{"family": "O'Reilly", "given": ["Ellena"]}],
91-
},
92-
],
93-
"vaccineCode": {
94-
"coding": [
95-
{
96-
"system": "http://snomed.info/sct",
97-
"code": "42223111000001107",
98-
"display": "Quadrivalent influenza vaccine (split virion, inactivated)",
99-
}
100-
]
101-
},
102-
"manufacturer": {"display": "Sanofi Pasteur"},
103-
"expirationDate": "2024-09-15",
104-
"lotNumber": "BN92478105653",
105-
"extension": [
46+
"doseNumberPositiveInt": 1,
47+
}
48+
],
49+
"reasonCode": [{"coding": [{"system": "http://snomed.info/sct", "code": "1037351000000105"}]}],
50+
"recorded": "2024-09-04",
51+
"identifier": [{"value": "RSV_002", "system": "https://www.ravs.england.nhs.uk/"}],
52+
"patient": {"reference": "#Patient1"},
53+
"contained": [
54+
{
55+
"id": "Patient1",
56+
"resourceType": "Patient",
57+
"birthDate": "2008-02-17",
58+
"gender": "male",
59+
"address": [{"postalCode": "WD25 0DZ"}],
60+
"identifier": [
10661
{
107-
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
108-
"valueCodeableConcept": {
109-
"coding": [
110-
{
111-
"system": "http://snomed.info/sct",
112-
"code": "956951000000104",
113-
"display": "RSV vaccination in pregnancy (procedure)",
114-
}
115-
]
116-
},
62+
"system": "https://fhir.nhs.uk/Id/nhs-number",
63+
"value": "9732928395",
11764
}
11865
],
119-
"occurrenceDateTime": "2024-09-04T18:33:25+00:00",
120-
"primarySource": True,
121-
"site": {
122-
"coding": [
123-
{
124-
"system": "http://snomed.info/sct",
125-
"code": "368209003",
126-
"display": "Right arm",
127-
}
128-
]
129-
},
130-
"route": {
66+
"name": [{"family": "PEEL", "given": ["PHYLIS"]}],
67+
},
68+
{
69+
"resourceType": "Practitioner",
70+
"id": "Practitioner1",
71+
"name": [{"family": "O'Reilly", "given": ["Ellena"]}],
72+
},
73+
],
74+
"vaccineCode": {
75+
"coding": [
76+
{
77+
"system": "http://snomed.info/sct",
78+
"code": "42223111000001107",
79+
"display": "Quadrivalent influenza vaccine (split virion, inactivated)",
80+
}
81+
]
82+
},
83+
"manufacturer": {"display": "Sanofi Pasteur"},
84+
"expirationDate": "2024-09-15",
85+
"lotNumber": "BN92478105653",
86+
"extension": [
87+
{
88+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
89+
"valueCodeableConcept": {
13190
"coding": [
13291
{
13392
"system": "http://snomed.info/sct",
134-
"code": "1210999013",
135-
"display": "Intradermal use",
93+
"code": "956951000000104",
94+
"display": "RSV vaccination in pregnancy (procedure)",
13695
}
13796
]
13897
},
139-
"doseQuantity": {
140-
"value": Decimal("0.3"),
141-
"unit": "Inhalation - unit of product usage",
98+
}
99+
],
100+
"occurrenceDateTime": "2024-09-04T18:33:25+00:00",
101+
"primarySource": True,
102+
"site": {
103+
"coding": [
104+
{
142105
"system": "http://snomed.info/sct",
143-
"code": "2622896019",
144-
},
145-
"performer": [
146-
{
147-
"actor": {
148-
"type": "Organization",
149-
"identifier": {
150-
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
151-
"value": "RVVKC",
152-
},
153-
}
154-
},
155-
{"actor": {"reference": "#Practitioner1"}},
156-
],
157-
"location": {
106+
"code": "368209003",
107+
"display": "Right arm",
108+
}
109+
]
110+
},
111+
"route": {
112+
"coding": [
113+
{
114+
"system": "http://snomed.info/sct",
115+
"code": "1210999013",
116+
"display": "Intradermal use",
117+
}
118+
]
119+
},
120+
"doseQuantity": {
121+
"value": Decimal("0.3"),
122+
"unit": "Inhalation - unit of product usage",
123+
"system": "http://snomed.info/sct",
124+
"code": "2622896019",
125+
},
126+
"performer": [
127+
{
128+
"actor": {
129+
"type": "Organization",
158130
"identifier": {
159-
"value": "RJC02",
160131
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
161-
}
162-
},
132+
"value": "RVVKC",
133+
},
134+
}
135+
},
136+
{"actor": {"reference": "#Practitioner1"}},
137+
],
138+
"location": {
139+
"identifier": {
140+
"value": "RJC02",
141+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
163142
}
143+
},
144+
}
145+
164146

147+
@mock_s3
148+
@patch.dict("os.environ", MOCK_ENVIRONMENT_DICT)
149+
class TestProcessRow(unittest.TestCase):
150+
"""Tests for process_row"""
151+
152+
def setUp(self) -> None:
153+
GenericSetUp(s3_client)
154+
155+
def tearDown(self) -> None:
156+
GenericTearDown(s3_client)
157+
158+
def test_process_row_success(self):
159+
"""
160+
Test that process_row gives the expected output.
161+
These tests check that the row is valid and matches the expected output.
162+
"""
165163
# call 'process_row' with required details
166164
imms_fhir_resource = process_row(TargetDiseaseElements.RSV, Allowed_Operations, ROW_DETAILS)
167165
# validate if the response with expected result
168-
self.assertDictEqual(imms_fhir_resource["fhir_json"], expected_result)
166+
self.assertDictEqual(imms_fhir_resource["fhir_json"], expected_successful_result)
169167

170168
def test_process_row_invalid_action_flag(self):
171169
"""
@@ -243,6 +241,42 @@ def test_process_row_missing_unique_id_uri(self):
243241
)
244242
self.assertEqual(response["diagnostics"]["statusCode"], 400)
245243

244+
def test_process_row_successfully_uplifts_legacy_tpp_uri(self):
245+
"""
246+
Test that process_row gives the expected output.
247+
These tests check that the row is valid and matches the expected output.
248+
"""
249+
legacy_tpp_row = deepcopy(ROW_DETAILS)
250+
legacy_tpp_row["UNIQUE_ID_URI"] = "YGA"
251+
252+
expected_successful_result_tpp = deepcopy(expected_successful_result)
253+
expected_successful_result_tpp["identifier"][0]["system"] = "https://tpp-uk.com/Id/ve/vacc"
254+
255+
# call 'process_row' with required details
256+
imms_fhir_resource = process_row(TargetDiseaseElements.RSV, Allowed_Operations, legacy_tpp_row)
257+
# validate if the response with expected result
258+
self.assertDictEqual(imms_fhir_resource["fhir_json"], expected_successful_result_tpp)
259+
self.assertEqual(legacy_tpp_row["UNIQUE_ID_URI"], "https://tpp-uk.com/Id/ve/vacc")
260+
self.assertEqual(imms_fhir_resource["local_id"], "RSV_002^https://tpp-uk.com/Id/ve/vacc")
261+
262+
def test_process_row_successfully_uplifts_legacy_emis_uri(self):
263+
"""
264+
Test that process_row gives the expected output.
265+
These tests check that the row is valid and matches the expected output.
266+
"""
267+
legacy_emis_row = deepcopy(ROW_DETAILS)
268+
legacy_emis_row["UNIQUE_ID_URI"] = "YGJ"
269+
270+
expected_successful_result_emis = deepcopy(expected_successful_result)
271+
expected_successful_result_emis["identifier"][0]["system"] = "https://emishealth.com/identifiers/vacc"
272+
273+
# call 'process_row' with required details
274+
imms_fhir_resource = process_row(TargetDiseaseElements.RSV, Allowed_Operations, legacy_emis_row)
275+
# validate if the response with expected result
276+
self.assertDictEqual(imms_fhir_resource["fhir_json"], expected_successful_result_emis)
277+
self.assertEqual(legacy_emis_row["UNIQUE_ID_URI"], "https://emishealth.com/identifiers/vacc")
278+
self.assertEqual(imms_fhir_resource["local_id"], "RSV_002^https://emishealth.com/identifiers/vacc")
279+
246280

247281
if __name__ == "__main__":
248282
unittest.main()

0 commit comments

Comments
 (0)