Skip to content

Commit eb32cee

Browse files
committed
21 fail
1 parent b6277bf commit eb32cee

16 files changed

+738
-267
lines changed

delta_backend/src/delta.py

Lines changed: 30 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import logging
88
from botocore.exceptions import ClientError
99
from log_firehose import FirehoseLogger
10-
from Converter import Converter
11-
from helpers.delta_data import DeltaData
12-
from helpers.mappings import OperationName, EventName
10+
from helpers.db_processor import DbProcessor
11+
from helpers.mappings import OperationName, EventName, ActionFlag
12+
from helpers.record_processor import RecordProcessor
13+
from helpers.sqs_utils import send_message
1314

1415
failure_queue_url = os.environ["AWS_SQS_QUEUE_URL"]
1516
delta_table_name = os.environ["DELTA_TABLE_NAME"]
@@ -18,22 +19,24 @@
1819
logger = logging.getLogger()
1920
logger.setLevel("INFO")
2021
firehose_logger = FirehoseLogger()
21-
delta_data = DeltaData(delta_table_name, delta_source)
2222

2323

24-
def send_message(record):
25-
# Create a message
26-
message_body = record
27-
# Use boto3 to interact with SQS
28-
sqs_client = boto3.client("sqs")
29-
try:
30-
# Send the record to the queue
31-
print(f"Sending record to DLQ: {message_body}")
32-
sqs_client.send_message(QueueUrl=failure_queue_url, MessageBody=json.dumps(message_body))
33-
logger.info("Record saved successfully to the DLQ")
34-
except ClientError as e:
35-
logger.error(f"Error sending record to DLQ: {e}")
24+
# def send_message(record):
25+
# # Create a message
26+
# message_body = record
27+
# # Use boto3 to interact with SQS
28+
# sqs_client = boto3.client("sqs")
29+
# try:
30+
# # Send the record to the queue
31+
# print(f"Sending record to DLQ: {message_body}")
32+
# sqs_client.send_message(QueueUrl=failure_queue_url, MessageBody=json.dumps(message_body))
33+
# logger.info("Record saved successfully to the DLQ")
34+
# except ClientError as e:
35+
# logger.error(f"Error sending record to DLQ: {e}")
3636

37+
def get_vaccine_type(patientsk) -> str:
38+
parsed = [str.strip(str.lower(s)) for s in patientsk.split("#")]
39+
return parsed[0]
3740

3841
def handler(event, context):
3942
logger.info("Starting Delta Handler")
@@ -45,81 +48,17 @@ def handler(event, context):
4548
try:
4649
dynamodb = boto3.resource("dynamodb", region_name="eu-west-2")
4750
delta_table = dynamodb.Table(delta_table_name)
48-
49-
# Converting ApproximateCreationDateTime directly to string will give Unix timestamp
50-
# I am converting it to isofformat for filtering purpose. This can be changed accordingly
51-
51+
db_processor = DbProcessor(delta_table, delta_source, logger)
52+
record_processor = RecordProcessor(delta_table,
53+
delta_source,
54+
log_data,
55+
db_processor,
56+
firehose_logger,
57+
firehose_log,
58+
logger)
5259
for record in event["Records"]:
53-
start = time.time()
54-
log_data["date_time"] = str(datetime.now())
55-
intrusion_check = False
56-
approximate_creation_time = datetime.utcfromtimestamp(record["dynamodb"]["ApproximateCreationDateTime"])
57-
expiry_time = approximate_creation_time + timedelta(days=30)
58-
expiry_time_epoch = int(expiry_time.timestamp())
59-
error_records = []
60-
response = str()
61-
imms_id = str()
62-
operation = str()
63-
if record["eventName"] != EventName.DELETE_PHYSICAL:
64-
new_image = record["dynamodb"]["NewImage"]
65-
imms_id = new_image["PK"]["S"].split("#")[1]
66-
supplier_system = new_image["SupplierSystem"]["S"]
67-
if supplier_system not in ("DPSFULL", "DPSREDUCED"):
68-
response, error_records = delta_data.write_to_db(new_image, imms_id, approximate_creation_time, expiry_time_epoch)
69-
else:
70-
operation_outcome["statusCode"] = "200"
71-
operation_outcome["statusDesc"] = "Record from DPS skipped"
72-
log_data["operation_outcome"] = operation_outcome
73-
firehose_log["event"] = log_data
74-
firehose_logger.send_log(firehose_log)
75-
logger.info(f"Record from DPS skipped for {imms_id}")
76-
return {"statusCode": 200, "body": f"Record from DPS skipped for {imms_id}"}
77-
else:
78-
operation = OperationName.DELETE_PHYSICAL
79-
new_image = record["dynamodb"]["Keys"]
80-
logger.info(f"Record to delta:{new_image}")
81-
imms_id = new_image["PK"]["S"].split("#")[1]
82-
response = delta_table.put_item(
83-
Item={
84-
"PK": str(uuid.uuid4()),
85-
"ImmsID": imms_id,
86-
"Operation": OperationName.DELETE_PHYSICAL,
87-
"VaccineType": "default",
88-
"SupplierSystem": "default",
89-
"DateTimeStamp": approximate_creation_time.isoformat(),
90-
"Source": delta_source,
91-
"Imms": "",
92-
"ExpiresAt": expiry_time_epoch,
93-
}
94-
)
95-
end = time.time()
96-
log_data["time_taken"] = f"{round(end - start, 5)}s"
97-
operation_outcome = {"record": imms_id, "operation_type": operation}
98-
if response["ResponseMetadata"]["HTTPStatusCode"] == 200:
99-
if error_records:
100-
log = f"Partial success: successfully synced into delta, but issues found within record {imms_id}"
101-
operation_outcome["statusCode"] = "207"
102-
operation_outcome["statusDesc"] = (
103-
f"Partial success: successfully synced into delta, but issues found within record {json.dumps(error_records)}"
104-
)
105-
else:
106-
log = f"Record Successfully created for {imms_id}"
107-
operation_outcome["statusCode"] = "200"
108-
operation_outcome["statusDesc"] = "Successfully synched into delta"
109-
log_data["operation_outcome"] = operation_outcome
110-
firehose_log["event"] = log_data
111-
firehose_logger.send_log(firehose_log)
112-
logger.info(log)
113-
return {"statusCode": 200, "body": "Records processed successfully"}
114-
else:
115-
log = f"Record NOT created for {imms_id}"
116-
operation_outcome["statusCode"] = "500"
117-
operation_outcome["statusDesc"] = "Exception"
118-
log_data["operation_outcome"] = operation_outcome
119-
firehose_log["event"] = log_data
120-
firehose_logger.send_log(firehose_log)
121-
logger.info(log)
122-
return {"statusCode": 500, "body": "Records not processed successfully"}
60+
record_processor.process_record(record)
61+
12362

12463
except Exception as e:
12564
operation_outcome["statusCode"] = "500"
@@ -138,3 +77,4 @@ def handler(event, context):
13877
"statusCode": 500,
13978
"body": "Records not processed",
14079
}
80+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import json
2+
import uuid
3+
from Converter import Converter
4+
5+
6+
class DbProcessor:
7+
def __init__(self, delta_table, delta_source, logger):
8+
self.delta_table = delta_table
9+
self.delta_source = delta_source
10+
self.logger = logger
11+
12+
def get_data(self):
13+
return self.data
14+
15+
def remove(self, imms_id, operation,
16+
approximate_creation_time, expiry_time_epoch):
17+
response = self.delta_table.put_item(
18+
Item={
19+
"PK": str(uuid.uuid4()),
20+
"ImmsID": imms_id,
21+
"Operation": operation,
22+
"VaccineType": "default",
23+
"SupplierSystem": "default",
24+
"DateTimeStamp": approximate_creation_time.isoformat(),
25+
"Source": self.delta_source,
26+
"Imms": "",
27+
"ExpiresAt": expiry_time_epoch,
28+
}
29+
)
30+
return response
31+
32+
def write(self, new_image, imms_id, operation, vaccine_type, supplier_system,
33+
approximate_creation_time, expiry_time_epoch):
34+
try:
35+
# vaccine_type = self.get_vaccine_type(new_image["PatientSK"]["S"])
36+
# supplier_system = new_image["SupplierSystem"]["S"]
37+
# if supplier_system not in ("DPSFULL", "DPSREDUCED"):
38+
# operation = new_image["Operation"]["S"]
39+
# if operation == OperationName.CREATE:
40+
# operation = ActionFlag.CREATE
41+
resource_json = json.loads(new_image["Resource"]["S"])
42+
FHIRConverter = Converter(json.dumps(resource_json))
43+
flat_json = FHIRConverter.runConversion(resource_json) # Get the flat JSON
44+
error_records = FHIRConverter.getErrorRecords()
45+
flat_json[0]["ACTION_FLAG"] = operation
46+
response = self.delta_table.put_item(
47+
Item={
48+
"PK": str(uuid.uuid4()),
49+
"ImmsID": imms_id,
50+
"Operation": operation,
51+
"VaccineType": vaccine_type,
52+
"SupplierSystem": supplier_system,
53+
"DateTimeStamp": approximate_creation_time.isoformat(),
54+
"Source": self.delta_source,
55+
"Imms": str(flat_json),
56+
"ExpiresAt": expiry_time_epoch,
57+
}
58+
)
59+
return response, operation, error_records
60+
except Exception as e:
61+
print(f"Error writing to DB: {e}")
62+
return None

delta_backend/src/helpers/delta_data.py

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

delta_backend/src/helpers/mappings.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ class OperationName:
1616
DELETE_LOGICAL = "DELETE"
1717
DELETE_PHYSICAL = "REMOVE"
1818

19-
ActionFlag = OperationName
19+
class ActionFlag:
20+
CREATE = "CREATE"
21+
UPDATE = "UPDATE"
22+
DELETE_LOGICAL = "DELETE"
23+
DELETE_PHYSICAL = "REMOVE"
2024

2125
class VaccineTypes:
2226
"""Vaccine types"""
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import unittest
2+
from helpers.record_processor import RecordProcessor
3+
4+
5+
class TestGetOpOutcome(unittest.TestCase):
6+
def test_get_op_outcome_basic(self):
7+
# Arrange
8+
status_code = 200
9+
status_desc = "Operation successful"
10+
11+
# Act
12+
outcome = RecordProcessor.get_op_outcome(status_code, status_desc)
13+
14+
# Assert
15+
self.assertEqual(outcome["statusCode"], "200")
16+
self.assertEqual(outcome["statusDesc"], "Operation successful")
17+
self.assertNotIn("diagnostics", outcome)
18+
self.assertNotIn("record", outcome)
19+
self.assertNotIn("operation_type", outcome)
20+
21+
def test_get_op_outcome_with_diagnostics(self):
22+
# Arrange
23+
status_code = 500
24+
status_desc = "Operation failed"
25+
diagnostics = "An error occurred during processing"
26+
27+
# Act
28+
outcome = RecordProcessor.get_op_outcome(status_code, status_desc, diagnostics=diagnostics)
29+
30+
# Assert
31+
self.assertEqual(outcome["statusCode"], "500")
32+
self.assertEqual(outcome["statusDesc"], "Operation failed")
33+
self.assertEqual(outcome["diagnostics"], "An error occurred during processing")
34+
self.assertNotIn("record", outcome)
35+
self.assertNotIn("operation_type", outcome)
36+
37+
def test_get_op_outcome_with_record(self):
38+
# Arrange
39+
status_code = 200
40+
status_desc = "Record processed successfully"
41+
record = "12345"
42+
43+
# Act
44+
outcome = RecordProcessor.get_op_outcome(status_code, status_desc, record=record)
45+
46+
# Assert
47+
self.assertEqual(outcome["statusCode"], "200")
48+
self.assertEqual(outcome["statusDesc"], "Record processed successfully")
49+
self.assertEqual(outcome["record"], "12345")
50+
self.assertNotIn("diagnostics", outcome)
51+
self.assertNotIn("operation_type", outcome)
52+
53+
def test_get_op_outcome_with_operation_type(self):
54+
# Arrange
55+
status_code = 200
56+
status_desc = "Operation completed"
57+
operation_type = "CREATE"
58+
59+
# Act
60+
outcome = RecordProcessor.get_op_outcome(status_code, status_desc, operation_type=operation_type)
61+
62+
# Assert
63+
self.assertEqual(outcome["statusCode"], "200")
64+
self.assertEqual(outcome["statusDesc"], "Operation completed")
65+
self.assertEqual(outcome["operation_type"], "CREATE")
66+
self.assertNotIn("diagnostics", outcome)
67+
self.assertNotIn("record", outcome)
68+
69+
def test_get_op_outcome_with_all_fields(self):
70+
# Arrange
71+
status_code = 207
72+
status_desc = "Partial success"
73+
diagnostics = "Some records failed"
74+
record = "67890"
75+
operation_type = "UPDATE"
76+
77+
# Act
78+
outcome = RecordProcessor.get_op_outcome(
79+
status_code, status_desc, diagnostics=diagnostics, record=record, operation_type=operation_type
80+
)
81+
82+
# Assert
83+
self.assertEqual(outcome["statusCode"], "207")
84+
self.assertEqual(outcome["statusDesc"], "Partial success")
85+
self.assertEqual(outcome["diagnostics"], "Some records failed")
86+
self.assertEqual(outcome["record"], "67890")
87+
self.assertEqual(outcome["operation_type"], "UPDATE")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
def get_op_outcome( status_code: int, status_desc: str, diagnostics: str = None, record: str = None, operation_type: str = None) -> dict:
2+
"""
3+
Constructs the operation_outcome dictionary.
4+
5+
Args:
6+
status_code (int): status code e.g., "200", "500"
7+
status_desc (str): Operation's outcome
8+
diagnostics (str, optional): Additional diagnostic information.
9+
record (str, optional): record identifier (e.g., imms_id).
10+
operation_type (str, optional): The type of operation performed.
11+
12+
13+
Returns:
14+
dict: The constructed operation_outcome dictionary.
15+
"""
16+
outcome = { "statusCode": str(status_code), "statusDesc": status_desc}
17+
if diagnostics:
18+
outcome["diagnostics"] = diagnostics
19+
if record:
20+
outcome["record"] = record
21+
if operation_type:
22+
outcome["operation_type"] = operation_type
23+
return outcome

0 commit comments

Comments
 (0)