Skip to content

Commit 9ac13dc

Browse files
committed
Merge branch 'master' into VED-169-Delta-Stream-multi-rec
# Conflicts: # delta_backend/src/delta.py
2 parents f491b6b + de82aea commit 9ac13dc

File tree

94 files changed

+13936
-1294
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+13936
-1294
lines changed

.github/workflows/sonarcloud.yml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ jobs:
1313
if: ${{ !contains(github.event.pull_request.labels.*.name, 'dependency') }}
1414
runs-on: ubuntu-latest
1515

16-
1716
steps:
1817
- uses: actions/checkout@v4
1918
with:
@@ -26,10 +25,10 @@ jobs:
2625
run: |
2726
aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
2827
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
29-
28+
3029
- name: Initialize Unit Test Failure Tracker
3130
run: echo "false" > test_failed.txt
32-
31+
3332
- name: Run unittest with filenameprocessor-coverage
3433
id: filenameprocessor
3534
continue-on-error: true
@@ -38,23 +37,20 @@ jobs:
3837
poetry run coverage run --source=filenameprocessor -m unittest discover -s filenameprocessor || echo "filenameprocessor tests failed" >> failed_tests.txt
3938
poetry run coverage xml -o sonarcloud-coverage-filenameprocessor-coverage.xml
4039
41-
4240
- name: Run unittest with recordprocessor-coverage
4341
id: recordprocessor
4442
continue-on-error: true
4543
run: |
4644
poetry run coverage run --source=recordprocessor -m unittest discover -s recordprocessor || echo "recordprocessor tests failed" >> failed_tests.txt
4745
poetry run coverage xml -o sonarcloud-coverage-recordprocessor-coverage.xml
4846
49-
5047
- name: Run unittest with recordforwarder-coverage
5148
id: recordforwarder
5249
continue-on-error: true
5350
run: |
5451
PYTHONPATH=$(pwd)/backend:$(pwd)/backend/tests poetry run coverage run --source=backend -m unittest discover -s backend/tests -p "*batch*.py" || echo "recordforwarder tests failed" >> failed_tests.txt
5552
poetry run coverage xml -o sonarcloud-coverage-recordforwarder-coverage.xml
5653
57-
5854
- name: Run unittest with coverage-ack-lambda
5955
id: acklambda
6056
continue-on-error: true
@@ -70,7 +66,7 @@ jobs:
7066
pip install poetry==1.8.4 mypy-boto3-dynamodb==1.35.54 boto3==1.26.165 coverage botocore==1.29.165 jmespath==1.0.1 python-dateutil==2.9.0 urllib3==1.26.20 s3transfer==0.6.2 typing-extensions==4.12.2
7167
poetry run coverage run --source=delta_backend -m unittest discover -s delta_backend || echo "delta tests failed" >> failed_tests.txt
7268
poetry run coverage xml -o sonarcloud-coverage-delta.xml
73-
69+
7470
- name: Run unittest with coverage-fhir-api
7571
id: fhirapi
7672
continue-on-error: true
@@ -79,6 +75,14 @@ jobs:
7975
poetry run coverage run --source=backend -m unittest discover -s backend || echo "fhir-api tests failed" >> failed_tests.txt
8076
poetry run coverage xml -o sonarcloud-coverage.xml
8177
78+
- name: Run unittest with coverage-mesh-processor
79+
id: meshprocessor
80+
continue-on-error: true
81+
run: |
82+
pip install poetry==1.8.4 moto==4.2.11 coverage redis botocore==1.35.49 simplejson responses structlog fhir.resources jsonpath_ng pydantic==1.10.13 requests aws-lambda-typing cffi pyjwt boto3-stubs-lite[dynamodb]~=1.26.90 python-stdnum==1.20
83+
poetry run coverage run --source=mesh_processor -m unittest discover -s mesh_processor || echo "mesh_processor tests failed" >> failed_tests.txt
84+
poetry run coverage xml -o sonarcloud-mesh_processor-coverage.xml
85+
8286
- name: Run Test Failure Summary
8387
id: check_failure
8488
run: |
@@ -98,5 +102,5 @@ jobs:
98102
- name: SonarCloud Scan
99103
uses: SonarSource/sonarqube-scan-action@master
100104
env:
101-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
102-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
105+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
106+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ build-proxy:
3838
scripts/build_proxy.sh
3939

4040
#Files to loop over in release
41-
_dist_include="pytest.ini poetry.lock poetry.toml pyproject.toml Makefile build/. e2e e2e_batch specification sandbox terraform scripts backend delta_backend ack_backend filenameprocessor recordprocessor"
41+
_dist_include="pytest.ini poetry.lock poetry.toml pyproject.toml Makefile build/. e2e e2e_batch specification sandbox terraform scripts backend delta_backend ack_backend filenameprocessor recordprocessor mesh_processor"
4242

4343

4444
#Create /dist/ sub-directory and copy files into directory

backend/.envrc.default

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
layout pyenv 3.10.16
1+
layout pyenv 3.10.12
22

3-
dotenv
3+
dotenv

backend/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
build
2-
.venv
2+
.venv

backend/src/fhir_service.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@
1919
from models.field_names import FieldNames
2020
from models.errors import InvalidPatientId, CustomValidationError, UnhandledResponseError
2121
from models.fhir_immunization import ImmunizationValidator
22-
from models.utils.generic_utils import nhs_number_mod11_check, get_occurrence_datetime, create_diagnostics, form_json
22+
from models.utils.generic_utils import nhs_number_mod11_check, get_occurrence_datetime, create_diagnostics, form_json, get_contained_patient
2323
from models.constants import Constants
2424
from models.errors import MandatoryError
2525
from pds_service import PdsService
26-
from s_flag_handler import handle_s_flag
2726
from timer import timed
2827
from filter import Filter
2928

@@ -79,25 +78,23 @@ def get_immunization_by_identifier(
7978

8079
def get_immunization_by_id(self, imms_id: str, imms_vax_type_perms: str) -> Optional[dict]:
8180
"""
82-
Get an Immunization by its ID. Return None if not found. If the patient doesn't have an NHS number,
81+
Get an Immunization by its ID. Return None if it is not found. If the patient doesn't have an NHS number,
8382
return the Immunization without calling PDS or checking S flag.
8483
"""
8584
if not (imms_resp := self.immunization_repo.get_immunization_by_id(imms_id, imms_vax_type_perms)):
8685
return None
8786

8887
# Returns the Immunisation full resource with no obfuscation
8988
resource = imms_resp.get("Resource", {})
90-
imms_filtered_for_read = Filter.read(resource) if resource else {}
91-
9289

9390
return {
9491
"Version": imms_resp.get("Version", ""),
95-
"Resource": Immunization.parse_obj(imms_filtered_for_read),
92+
"Resource": Immunization.parse_obj(resource),
9693
}
9794

9895
def get_immunization_by_id_all(self, imms_id: str, imms: dict) -> Optional[dict]:
9996
"""
100-
Get an Immunization by its ID. Return None if it is not found. If the patient doesn't have an NHS number,
97+
Get an Immunization by its ID. Return None if not found. If the patient doesn't have an NHS number,
10198
return the Immunization without calling PDS or checking S flag.
10299
"""
103100
imms["id"] = imms_id
@@ -264,15 +261,18 @@ def process_patient_for_bundle(patient: dict):
264261
"""
265262

266263
# Remove unwanted top-level fields
267-
fields_to_keep = ["id", "resourceType", "identifier", "birthDate"]
264+
fields_to_keep = {"resourceType", "identifier"}
268265
new_patient = {k: v for k, v in patient.items() if k in fields_to_keep}
269266

270267
# Remove unwanted identifier fields
271-
new_identifiers = []
272-
for identifier in new_patient["identifier"]:
273-
identifier_fields_to_keep = ["system", "value"]
274-
new_identifiers.append({k: v for k, v in identifier.items() if k in identifier_fields_to_keep})
275-
new_patient["identifier"] = new_identifiers
268+
identifier_fields_to_keep = {"system", "value"}
269+
new_patient["identifier"] = [
270+
{k: v for k, v in identifier.items() if k in identifier_fields_to_keep}
271+
for identifier in new_patient.get("identifier", [])
272+
]
273+
274+
if new_patient["identifier"]:
275+
new_patient["id"] = new_patient["identifier"][0].get("value")
276276

277277
return new_patient
278278

@@ -316,25 +316,22 @@ def search_immunizations(
316316
if self.is_valid_date_from(r, date_from) and self.is_valid_date_to(r, date_to)
317317
]
318318

319-
# Check whether the Superseded NHS number present in PDS
320-
if pds_patient := self.pds_service.get_patient_details(nhs_number):
321-
if pds_patient["identifier"][0]["value"] != nhs_number:
322-
return create_diagnostics()
323-
324319
# Create the patient URN for the fullUrl field.
325320
# NOTE: This UUID is assigned when a SEARCH request is received and used only for referencing the patient
326321
# resource from immunisation resources within the bundle. The fullUrl value we are using is a urn (hence the
327322
# FHIR key name of "fullUrl" is somewhat misleading) which cannot be used to locate any externally stored
328323
# patient resource. This is as agreed with VDS team for backwards compatibility with Immunisation History API.
329324
patient_full_url = f"urn:uuid:{str(uuid4())}"
330325

326+
imms_patient_record = get_contained_patient(resources[-1]) if resources else None
327+
331328
# Filter and amend the immunization resources for the SEARCH response
332-
resources_filtered_for_search = [Filter.search(imms, patient_full_url, pds_patient) for imms in resources]
329+
resources_filtered_for_search = [Filter.search(imms, patient_full_url) for imms in resources]
333330

334331
# Add bundle entries for each of the immunization resources
335332
entries = [
336333
BundleEntry(
337-
resource=Immunization.parse_obj(handle_s_flag(imms, pds_patient)),
334+
resource=Immunization.parse_obj(imms),
338335
search=BundleEntrySearch(mode="match"),
339336
fullUrl=f"https://api.service.nhs.uk/immunisation-fhir-api/Immunization/{imms['id']}",
340337
)
@@ -345,7 +342,7 @@ def search_immunizations(
345342
if len(resources) > 0:
346343
entries.append(
347344
BundleEntry(
348-
resource=self.process_patient_for_bundle(pds_patient),
345+
resource=self.process_patient_for_bundle(imms_patient_record),
349346
search=BundleEntrySearch(mode="include"),
350347
fullUrl=patient_full_url,
351348
)

backend/src/filter.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Functions for filtering a FHIR Immunization Resource"""
22

3-
from models.utils.generic_utils import is_actor_referencing_contained_resource, get_contained_practitioner
3+
from models.utils.generic_utils import is_actor_referencing_contained_resource, get_contained_practitioner, get_contained_patient
44
from constants import Urls
55

66

@@ -14,7 +14,7 @@ def remove_reference_to_contained_practitioner(imms: dict) -> dict:
1414

1515
# Remove reference to the contained practitioner from imms[performer]
1616
imms["performer"] = [
17-
x for x in imms["performer"] if not is_actor_referencing_contained_resource(x, contained_practitioner["id"])
17+
x for x in imms["performer"] if not is_actor_referencing_contained_resource(x, contained_practitioner["id"])
1818
]
1919

2020
return imms
@@ -96,25 +96,11 @@ class Filter:
9696
"""Functions for filtering a FHIR Immunization Resource"""
9797

9898
@staticmethod
99-
def read(imms: dict) -> dict:
100-
"""Apply filtering for READ request"""
101-
imms.pop("identifier")
102-
return imms
103-
104-
@staticmethod
105-
def search(imms: dict, patient_full_url: str, bundle_patient: dict = None) -> dict:
99+
def search(imms: dict, patient_full_url: str) -> dict:
106100
"""Apply filtering for an individual FHIR Immunization Resource as part of SEARCH request"""
107101
imms = remove_reference_to_contained_practitioner(imms)
108-
imms.pop("contained")
109-
imms["patient"] = create_reference_to_patient_resource(patient_full_url, bundle_patient)
102+
imms["patient"] = create_reference_to_patient_resource(patient_full_url, get_contained_patient(imms))
110103
imms = add_use_to_identifier(imms)
111-
return imms
104+
imms.pop("contained")
112105

113-
@staticmethod
114-
def s_flag(imms: dict) -> dict:
115-
"""Apply filtering for patients with 'RESTRICTED' flag"""
116-
imms = replace_address_postal_codes(imms)
117-
imms = replace_organization_values(imms)
118-
if imms.get("location"):
119-
imms["location"] = {"identifier": {"system": "urn:iso:std:iso:3166", "value": "GB"}}
120106
return imms

backend/src/s_flag_handler.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)