Skip to content

Commit 5fc09d4

Browse files
committed
[NRL-1631] WIP - Add logs for create/upsert of unexpected dupe pointers
1 parent f310c3a commit 5fc09d4

File tree

7 files changed

+109
-4
lines changed

7 files changed

+109
-4
lines changed

api/producer/createDocumentReference/create_document_reference.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from nrlf.core.constants import (
55
PERMISSION_AUDIT_DATES_FROM_PAYLOAD,
66
PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL,
7+
TYPES_WITH_MULTIPLES,
78
)
89
from nrlf.core.decorators import request_handler
910
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
@@ -255,6 +256,23 @@ def handler(
255256
logger.log(LogReference.PROCREATE999)
256257
return NRLResponse.RESOURCE_SUPERSEDED(resource_id=result.resource.id)
257258

259+
pointer_type = core_model.type
260+
if pointer_type not in TYPES_WITH_MULTIPLES:
261+
patient_number = core_model.nhs_number
262+
pointer_custodian = core_model.custodian
263+
existing_pointers_count = repository.count_by_nhs_number(
264+
patient_number, [pointer_type]
265+
)
266+
267+
if existing_pointers_count > 0:
268+
logger.log(
269+
LogReference.PROCREATE012,
270+
pointer_type=pointer_type,
271+
patient_number=patient_number,
272+
existing_pointers_count=existing_pointers_count,
273+
pointer_custodian=pointer_custodian,
274+
)
275+
258276
logger.log(LogReference.PROCREATE009, pointer_id=result.resource.id)
259277
repository.create(core_model)
260278
logger.log(LogReference.PROCREATE999)

api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,3 +1699,52 @@ def test__set_create_time_fields_when_no_date_but_perms():
16991699
},
17001700
"date": test_time,
17011701
}
1702+
1703+
1704+
@mock_aws
1705+
@mock_repository
1706+
def test_upsert_logs_for_unexpected_multi_pointer(
1707+
repository: DocumentPointerRepository,
1708+
):
1709+
doc_ref = load_document_reference("Y05868-736253002-Valid")
1710+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
1711+
repository.create(doc_pointer)
1712+
1713+
event = create_test_api_gateway_event(
1714+
headers=create_headers(),
1715+
body=doc_ref.model_dump_json(exclude_none=True),
1716+
)
1717+
1718+
result = handler(event, create_mock_context())
1719+
body = result.pop("body")
1720+
1721+
assert result == {
1722+
"statusCode": "200",
1723+
"headers": {
1724+
"Location": "/producer/FHIR/R4/DocumentReference/Y05868-99999-99999-999999",
1725+
**default_response_headers(),
1726+
},
1727+
"isBase64Encoded": False,
1728+
}
1729+
1730+
parsed_body = json.loads(body)
1731+
1732+
assert parsed_body == {
1733+
"resourceType": "OperationOutcome",
1734+
"issue": [
1735+
{
1736+
"severity": "information",
1737+
"code": "informational",
1738+
"details": {
1739+
"coding": [
1740+
{
1741+
"code": "RESOURCE_UPDATED",
1742+
"display": "Resource updated",
1743+
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
1744+
}
1745+
]
1746+
},
1747+
"diagnostics": "The document has been updated",
1748+
}
1749+
],
1750+
}

api/producer/upsertDocumentReference/upsert_document_reference.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from nrlf.core.constants import (
33
PERMISSION_AUDIT_DATES_FROM_PAYLOAD,
44
PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL,
5+
TYPES_WITH_MULTIPLES,
56
)
67
from nrlf.core.decorators import request_handler
78
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
@@ -262,6 +263,23 @@ def handler(
262263
logger.log(LogReference.PROUPSERT999)
263264
return NRLResponse.RESOURCE_SUPERSEDED(resource_id=saved_model.id)
264265

266+
pointer_type = core_model.type
267+
if pointer_type not in TYPES_WITH_MULTIPLES:
268+
patient_number = core_model.nhs_number
269+
pointer_custodian = core_model.custodian
270+
existing_pointers_count = repository.count_by_nhs_number(
271+
patient_number, [pointer_type]
272+
)
273+
274+
if existing_pointers_count > 0:
275+
logger.log(
276+
LogReference.PROCREATE012,
277+
pointer_type=pointer_type,
278+
patient_number=patient_number,
279+
existing_pointers_count=existing_pointers_count,
280+
pointer_custodian=pointer_custodian,
281+
)
282+
265283
logger.log(LogReference.PROUPSERT009, pointer_id=result.resource.id)
266284
saved_model = repository.create(core_model)
267285
logger.log(LogReference.PROUPSERT999)

layer/nrlf/core/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ def coding_value(self):
200200
PointerTypes.SHARED_CARE_RECORD.value: Categories.RECORD_HEADINGS.value,
201201
}
202202

203+
#
204+
# Pointer types that can have multiple pointers for a single patient
205+
TYPES_WITH_MULTIPLES = [
206+
PointerTypes.MRA_UPPER_LIMB_ARTERY.value,
207+
PointerTypes.MRI_AXILLA_BOTH.value,
208+
PointerTypes.APPOINTMENT.value,
209+
]
210+
203211
PRACTICE_SETTING_VALUE_SET_URL = (
204212
"https://fhir.nhs.uk/England/ValueSet/England-PracticeSetting"
205213
)

layer/nrlf/core/log_references.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ class LogReference(Enum):
270270
PROCREATE011 = _Reference(
271271
"INFO", "Preserved .date field when creating new document reference"
272272
)
273+
PROCREATE012 = _Reference(
274+
"WARN", "Existing pointers found for patient during create operation"
275+
)
273276
PROCREATE999 = _Reference(
274277
"INFO", "Successfully completed producer createDocumentReference"
275278
)
@@ -329,6 +332,9 @@ class LogReference(Enum):
329332
PROUPSERT011 = _Reference(
330333
"INFO", "Preserved .date field when creating new document reference for upsert"
331334
)
335+
PROUPSERT012 = _Reference(
336+
"WARN", "Existing pointers found for patient during upsert operation"
337+
)
332338
PROUPSERT999 = _Reference(
333339
"INFO", "Successfully completed producer upsertDocumentReference"
334340
)

layer/nrlf/core/logger.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from datetime import datetime
3+
from typing import Any
34

45
from aws_lambda_powertools import Logger as PowertoolsLogger
56
from aws_lambda_powertools.logging.formatter import LambdaPowertoolsFormatter
@@ -9,7 +10,7 @@
910

1011

1112
class SplunkFormatter(LambdaPowertoolsFormatter):
12-
def __init__(self, *args, **kwargs):
13+
def __init__(self, *args: Any, **kwargs: Any) -> None:
1314
super().__init__(*args, **kwargs)
1415
self.splunk_index = os.getenv("SPLUNK_INDEX", "aws_recordlocator_dev")
1516

@@ -30,7 +31,7 @@ def serialize(self, log: LogRecord) -> str:
3031

3132

3233
class Logger(PowertoolsLogger):
33-
def log(self, code: LogReference, **kwargs):
34+
def log(self, code: LogReference, **kwargs: Any) -> None:
3435
kwargs["log_reference"] = code.name
3536
match code.value.level:
3637
case "DEBUG":
@@ -45,6 +46,11 @@ def log(self, code: LogReference, **kwargs):
4546
self.critical(code.value.message, stacklevel=3, **kwargs)
4647
case "EXCEPTION":
4748
self.exception(code.value.message, **kwargs)
49+
case _:
50+
self.warning(
51+
f"Unhandled log level: {code.value.level} - {code.value.message}",
52+
**kwargs,
53+
)
4854

4955

5056
logger = Logger(logger_formatter=SplunkFormatter())

layer/nrlf/tests/events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import Dict, List, Optional
2+
from typing import Any, Dict, List, Optional
33
from unittest.mock import Mock
44

55

@@ -46,7 +46,7 @@ def create_test_api_gateway_event(
4646
query_string_parameters: Optional[Dict[str, str]] = None,
4747
path_parameters: Optional[Dict[str, str]] = None,
4848
body: Optional[str] = None,
49-
):
49+
) -> Dict[str, Any]:
5050
return {
5151
"resource": "/",
5252
"path": "/",

0 commit comments

Comments
 (0)