Skip to content

Commit 9fe19a1

Browse files
committed
Tests pass
1 parent f8aa653 commit 9fe19a1

15 files changed

+123
-132
lines changed

backend/poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/src/fhir_batch_repository.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import boto3
44
import time
55
import simplejson as json
6+
from clients import logger
67
from dataclasses import dataclass
78
import botocore.exceptions
89
from boto3.dynamodb.conditions import Key, Attr
@@ -34,7 +35,7 @@ def _query_identifier(table, index, pk, identifier, is_present):
3435
return queryresponse
3536

3637
if retries > 6:
37-
print(f"{identifier}: Crossed {retries} retries")
38+
logger.info(f"{identifier}: Crossed {retries} retries")
3839

3940
retries += 1
4041
# Delay time in milliseconds

backend/src/fhir_controller.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from parameter_parser import process_params, process_search_params, create_query_string
3737
import urllib.parse
3838

39+
3940
sqs_client = boto3_client("sqs", region_name="eu-west-2")
4041
queue_url = os.getenv("SQS_QUEUE_URL", "Queue_url")
4142

@@ -392,29 +393,20 @@ def delete_immunization(self, aws_event):
392393
return self.create_response(403, unauthorized.to_operation_outcome())
393394

394395
def search_immunizations(self, aws_event: APIGatewayProxyEventV1) -> dict:
395-
logger.info("SAW: search_immunizations.")
396396

397397
if response := self.authorize_request(aws_event):
398398
return response
399-
logger.info("SAW: Authorised request")
400399
try:
401-
logger.info("SAW: Processing search parameters")
402400
search_params = process_search_params(process_params(aws_event))
403401
except ParameterException as e:
404402
return self._create_bad_request(e.message)
405403
if search_params is None:
406404
raise Exception("Failed to parse parameters.")
407405

408-
logger.info("SAW: fhir_controller. search_params: %s", search_params)
409406
# Check vaxx type permissions- start
410407
try:
411-
logger.info("SAW: fhir_controller. search_immunizations...5")
412-
logger.info("SAW: aws_event=%s", aws_event)
413408
if aws_event.get("headers"):
414-
logger.info("SAW: fhir_controller. search_immunizations...6")
415409
supplier_system = self._identify_supplier_system(aws_event)
416-
logger.info("SAW: Supplier system identified: %s", supplier_system)
417-
logger.info("SAW: Get supplier permissions for: %s", supplier_system)
418410
imms_vax_type_perms = get_supplier_permissions(supplier_system)
419411
if len(imms_vax_type_perms) == 0:
420412
raise UnauthorizedVaxError()
@@ -437,7 +429,6 @@ def search_immunizations(self, aws_event: APIGatewayProxyEventV1) -> dict:
437429
except UnauthorizedVaxError as unauthorized:
438430
return self.create_response(403, unauthorized.to_operation_outcome())
439431
# Check vaxx type permissions on the existing record - end
440-
logger.info("SAW: Searching immunizations...")
441432
result = self.fhir_service.search_immunizations(
442433
search_params.patient_identifier,
443434
vax_type_perm,
@@ -635,7 +626,7 @@ def check_vaccine_type_permissions(self, aws_event):
635626
if len(supplier_system) == 0:
636627
raise UnauthorizedSystemError()
637628
imms_vax_type_perms = get_supplier_permissions(supplier_system)
638-
print(f" update imms = {imms_vax_type_perms}")
629+
logger.info(f" update imms = {imms_vax_type_perms}")
639630
if len(imms_vax_type_perms) == 0:
640631
raise UnauthorizedVaxError()
641632
# Return the values needed for later use
@@ -650,10 +641,8 @@ def check_vaccine_type_permissions(self, aws_event):
650641

651642
@staticmethod
652643
def create_response(status_code, body=None, headers=None):
653-
logger.info("SAW: Creating response with status code: %d", status_code)
654644
if body:
655645
if isinstance(body, dict):
656-
logger.info("SAW: return body : %s", body)
657646
body = json.dumps(body)
658647
if headers:
659648
headers["Content-Type"] = "application/fhir+json"
@@ -668,9 +657,7 @@ def create_response(status_code, body=None, headers=None):
668657

669658
@staticmethod
670659
def _identify_supplier_system(aws_event):
671-
logger.info("SAW: fhir_controller. _identify_supplier_system...1")
672660
headers = aws_event.get("headers", {})
673-
logger.info("SAW: headers=%s", headers)
674661
supplier_system = headers.get("SupplierSystem")
675662
if not supplier_system:
676663
raise UnauthorizedError("SupplierSystem header is missing")

backend/src/fhir_repository.py

Lines changed: 41 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from urllib import response
12
from responses import logger
23
import simplejson as json
34
import os
@@ -21,7 +22,6 @@
2122

2223
from models.utils.validation_utils import get_vaccine_type, check_identifier_system_value
2324

24-
2525
def create_table(table_name=None, endpoint_url=None, region_name="eu-west-2"):
2626
if not table_name:
2727
table_name = os.environ["DYNAMODB_TABLE_NAME"]
@@ -400,86 +400,66 @@ def find_immunizations(self, patient_identifier: str, vaccine_types: list):
400400
import logging
401401
logger = logging.getLogger(__name__)
402402

403-
logger.info("SAW fi...1: find_immunizations called with patient_identifier: '%s', vaccine_types: %s",
404-
patient_identifier, vaccine_types)
405-
406403
# Create the patient PK and log it
407404
patient_pk = _make_patient_pk(patient_identifier)
408-
logger.info("SAW fi...2: patient_pk created: '%s'", patient_pk)
409405

410406
condition = Key("PatientPK").eq(patient_pk)
411407
is_not_deleted = Attr("DeletedAt").not_exists() | Attr("DeletedAt").eq("reinstated")
412408

413-
logger.info("SAW fi...2.1>: is_not_deleted condition created: %s", is_not_deleted)
414-
415-
logger.info("SAW fi...2.1: is_not_deleted condition str: %s", str(is_not_deleted))
416-
logger.info("SAW fi...2.1: is_not_deleted condition dir: %s", dir(is_not_deleted))
417-
logger.info("SAW fi...3: check all patientpk %s", patient_pk)
418-
logger.info("SAW fi...3: check all condition %s", condition)
409+
# # check response without filter
410+
# response = self.table.query(
411+
# IndexName="PatientGSI",
412+
# KeyConditionExpression=condition
413+
# )
414+
# if 'LastEvaluatedKey' in response:
415+
# print("More rows are available. Use LastEvaluatedKey to paginate.")
416+
# else:
417+
# print("No more rows.")
419418

420-
# check response without filter
421-
response = self.table.query(
422-
IndexName="PatientGSI",
423-
KeyConditionExpression=condition
424-
)
425-
if 'LastEvaluatedKey' in response:
426-
print("More rows are available. Use LastEvaluatedKey to paginate.")
427-
else:
428-
print("No more rows.")
419+
raw_items = self.get_all_items(condition,is_not_deleted)
429420

430-
logger.info("SAW fi...3.1: Initial DynamoDB query Count: %s, ScannedCount: %s", response.get("Count", 0), response.get("ScannedCount", 0))
431-
432-
response = self.table.query(
433-
IndexName="PatientGSI",
434-
KeyConditionExpression=condition,
435-
FilterExpression=is_not_deleted,
436-
)
437-
438421
# ✅ Log the raw DynamoDB response
439-
logger.info("SAW fi...4: DynamoDB query response - Count: %s, ScannedCount: %s",
440-
response.get("Count", 0), response.get("ScannedCount", 0))
441-
442-
if "Items" in response:
443-
raw_items = response["Items"]
444-
445-
446-
logger.info("SAW fi...5: total items returned from DynamoDB: %d", len(raw_items))
447-
448-
# log all raw_items["PatientSK"] fields
449-
logger.info("SAW fi...5.1: all PatientSK values: %s",
450-
[item.get("PatientSK", "MISSING") for item in raw_items])
451422

423+
if raw_items:
452424
# Log first few items for debugging
453-
if raw_items:
454-
logger.info("SAW fi...6: sample raw item keys: %s", list(raw_items[0].keys()))
455-
logger.info("SAW fi...7: first few PatientSK values: %s",
456-
[item.get("PatientSK", "MISSING") for item in raw_items[:3]])
457425

458426
# Filter the response to contain only the requested vaccine types
459427
items = [x for x in raw_items if x["PatientSK"].split("#")[0] in vaccine_types]
460428

461-
logger.info("SAW fi...8: after vaccine_types filtering (%s): %d items", vaccine_types, len(items))
462-
463-
if items:
464-
# Log the vaccine types found
465-
found_vaccine_types = [item["PatientSK"].split("#")[0] for item in items]
466-
logger.info("SAW fi...9: found vaccine types: %s", found_vaccine_types)
467-
else:
468-
# Debug why no items matched
469-
all_vaccine_types = [item["PatientSK"].split("#")[0] for item in raw_items]
470-
logger.warning("SAW fi...10: no items matched vaccine_types filter!")
471-
logger.warning("SAW fi...11: requested vaccine_types: %s", vaccine_types)
472-
logger.warning("SAW fi...12: available vaccine_types in data: %s", list(set(all_vaccine_types)))
473-
474429
# Return a list of the FHIR immunization resource JSON items
475430
final_resources = [json.loads(item["Resource"]) for item in items]
476-
logger.info("SAW fi...13: returning %d FHIR resources", len(final_resources))
477431

478432
return final_resources
479433
else:
480-
logger.error("SAW fi...14: No 'Items' key in DynamoDB response!")
481-
logger.error("SAW fi...15: Response keys: %s", list(response.keys()))
482-
raise UnhandledResponseError(message=f"Unhandled error. Query failed", response=response)
434+
logger.warning("no items matched patient_identifier filter!")
435+
return []
436+
437+
def get_all_items(self, condition, is_not_deleted):
438+
"""Query DynamoDB and paginate through all results."""
439+
all_items = []
440+
last_evaluated_key = None
441+
442+
while True:
443+
query_args = {
444+
"IndexName": "PatientGSI",
445+
"KeyConditionExpression": condition,
446+
"FilterExpression": is_not_deleted,
447+
}
448+
if last_evaluated_key:
449+
query_args["ExclusiveStartKey"] = last_evaluated_key
450+
451+
response = self.table.query(**query_args)
452+
if "Items" not in response:
453+
raise UnhandledResponseError(message="No Items in DynamoDB response", response=response)
454+
455+
items = response.get("Items", [])
456+
all_items.extend(items)
457+
458+
last_evaluated_key = response.get("LastEvaluatedKey")
459+
if not last_evaluated_key:
460+
break
461+
462+
return all_items
483463

484464
@staticmethod
485465
def _handle_dynamo_response(response):

backend/src/fhir_service.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -288,42 +288,29 @@ def search_immunizations(
288288
Finds all instances of Immunization(s) for a specified patient which are for the specified vaccine type(s).
289289
Bundles the resources with the relevant patient resource and returns the bundle.
290290
"""
291-
logger.info("SAW: search_immunizations...1 nhs_number: %s, vaccine_types: %s, date_from: %s, date_to: %s",
292-
nhs_number, vaccine_types, date_from, date_to)
293291
# TODO: is disease type a mandatory field? (I assumed it is)
294292
# i.e. Should we provide a search option for getting Patient's entire imms history?
295293

296-
logger.info("SAW: search_immunizations...2 check nhs_number_mod11_check for nhs_number: %s", nhs_number)
297294
if not nhs_number_mod11_check(nhs_number):
298295
return create_diagnostics()
299296

300-
logger.info("SAW: search_immunizations...3 obtain resources for nhs_number: %s, vaccine_types: %s, date_from: %s, date_to: %s",
301-
nhs_number, vaccine_types, date_from, date_to)
302297
# Obtain all resources which are for the requested nhs number and vaccine type(s) and within the date range
303298
resources = [
304299
r
305300
for r in self.immunization_repo.find_immunizations(nhs_number, vaccine_types)
306301
if self.is_valid_date_from(r, date_from) and self.is_valid_date_to(r, date_to)
307302
]
308-
logger.info("SAW: search_immunizations...4 no of resources found: %d", len(resources))
309303
# Create the patient URN for the fullUrl field.
310304
# NOTE: This UUID is assigned when a SEARCH request is received and used only for referencing the patient
311305
# resource from immunisation resources within the bundle. The fullUrl value we are using is a urn (hence the
312306
# FHIR key name of "fullUrl" is somewhat misleading) which cannot be used to locate any externally stored
313307
# patient resource. This is as agreed with VDS team for backwards compatibility with Immunisation History API.
314308
patient_full_url = f"urn:uuid:{str(uuid4())}"
315309

316-
logger.info("SAW: search_immunizations...5 call get_contained_patient")
317310
imms_patient_record = get_contained_patient(resources[-1]) if resources else None
318311

319-
logger.info("SAW: search_immunizations...6 filter resources for search")
320-
# log pretty resources for debug
321-
logger.info("SAW: search_immunizations...7 resources: %s", [
322-
Immunization.parse_obj(r).json(indent=2) for r in resources
323-
])
324-
# Filter and amend the immunization resources for the SEARCH response
312+
# Filter and amend the immunization resources for the SEARCH response
325313
resources_filtered_for_search = [Filter.search(imms, patient_full_url) for imms in resources]
326-
logger.info("SAW: search_immunizations...8 no of items in filtered resources: %d", len(resources_filtered_for_search))
327314
# Add bundle entries for each of the immunization resources
328315
entries = [
329316
BundleEntry(
@@ -335,12 +322,6 @@ def search_immunizations(
335322
for imms in resources_filtered_for_search
336323
]
337324

338-
339-
logger.info("SAW: search_immunizations...9 entries created for bundle: %d", len(entries))
340-
341-
for entry in entries:
342-
logger.debug("SAW: search_immunizations...10 entry: %s", entry.resource.json(indent=2))
343-
344325
# Add patient resource if there is at least one immunization resource
345326
if len(resources) > 0:
346327
entries.append(

backend/src/log_structure.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def function_info(func):
2020
@wraps(func)
2121
def wrapper(*args, **kwargs):
2222
event = args[0] if args else {}
23-
print(f"Event: {event}")
23+
logger.info(f"Event: {event}")
2424
headers = event.get("headers", {})
2525
correlation_id = headers.get("X-Correlation-ID", "X-Correlation-ID not passed")
2626
request_id = headers.get("X-Request-ID", "X-Request-ID not passed")
@@ -40,7 +40,7 @@ def wrapper(*args, **kwargs):
4040
start = time.time()
4141
try:
4242
result = func(*args, **kwargs)
43-
print(f"Result:{result}")
43+
logger.info(f"Result:{result}")
4444
end = time.time()
4545
log_data["time_taken"] = f"{round(end - start, 5)}s"
4646
status = "500"
@@ -56,7 +56,7 @@ def wrapper(*args, **kwargs):
5656
record = result_headers["Location"]
5757
if result.get("body"):
5858
ops_outcome = json.loads(result["body"])
59-
print(f"ops_outcome: {ops_outcome}")
59+
logger.info(f"ops_outcome: {ops_outcome}")
6060
if ops_outcome.get("issue"):
6161
outcome_body = ops_outcome["issue"][0]
6262
status_code = outcome_body["code"]

backend/src/models/utils/permission_checker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from enum import StrEnum
2+
from clients import logger
23

34
class ApiOperationCode(StrEnum):
45
CREATE = "c"
@@ -22,8 +23,9 @@ def _expand_permissions(permissions: list[str]) -> dict[str, list[ApiOperationCo
2223

2324
def validate_permissions(permissions: list[str], operation: ApiOperationCode, vaccine_types: list[str]):
2425
expanded_permissions = _expand_permissions(permissions)
25-
print(f"operation: {operation}, expanded_permissions: {expanded_permissions}, vaccine_types: {vaccine_types}")
26+
logger.info(f"operation: {operation}, expanded_permissions: {expanded_permissions}, vaccine_types: {vaccine_types}")
2627
return all(
2728
operation in expanded_permissions.get(vaccine_type.lower(), [])
2829
for vaccine_type in vaccine_types
2930
)
31+

backend/src/parameter_parser.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ def process_search_params(params: ParamContainer) -> SearchParams:
8585
8686
:raises ParameterException:
8787
"""
88-
logger.info("SAW: process_search_params: %s", params)
8988
# patient.identifier
9089
patient_identifiers = params.get(patient_identifier_key, [])
9190
patient_identifier = patient_identifiers[0] if len(patient_identifiers) == 1 else None
@@ -101,7 +100,6 @@ def process_search_params(params: ParamContainer) -> SearchParams:
101100
f"e.g. \"{patient_identifier_system}|9000000009\"")
102101

103102
patient_identifier = patient_identifier.split("|")[1]
104-
logger.info("SAW: patient_identifier: %s", patient_identifier)
105103

106104
# immunization.target
107105
params[immunization_target_key] = list(set(params.get(immunization_target_key, [])))
@@ -117,7 +115,6 @@ def process_search_params(params: ParamContainer) -> SearchParams:
117115

118116
# date.from
119117
date_froms = params.get(date_from_key, [])
120-
logger.info("SAW: date.from: %s", date_froms)
121118

122119
if len(date_froms) > 1:
123120
raise ParameterException(f"Search parameter {date_from_key} may have one value at most.")
@@ -127,7 +124,6 @@ def process_search_params(params: ParamContainer) -> SearchParams:
127124
if len(date_froms) == 1 else date_from_default
128125
except ValueError:
129126
raise ParameterException(f"Search parameter {date_from_key} must be in format: YYYY-MM-DD")
130-
logger.info("SAW: date.from: %s", date_from)
131127
# date.to
132128
date_tos = params.get(date_to_key, [])
133129

@@ -139,7 +135,6 @@ def process_search_params(params: ParamContainer) -> SearchParams:
139135
if len(date_tos) == 1 else date_to_default
140136
except ValueError:
141137
raise ParameterException(f"Search parameter {date_to_key} must be in format: YYYY-MM-DD")
142-
logger.info("SAW: date.to: %s", date_to)
143138

144139
if date_from and date_to and date_from > date_to:
145140
raise ParameterException(f"Search parameter {date_from_key} must be before {date_to_key}")

0 commit comments

Comments
 (0)