Skip to content

Commit

Permalink
Merge branch 'main' into PRMP-1292
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-start-nhs committed Dec 20, 2024
2 parents c0b822f + 2bc003e commit 863797b
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 58 deletions.
19 changes: 14 additions & 5 deletions lambdas/enums/snomed_codes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
from enum import StrEnum
from enum import Enum

from pydantic import BaseModel

class SnomedCodesType(StrEnum):
LLOYD_GEORGE = "16521000000101"

class SnomedCode(BaseModel):
code: str
display_name: str

class SnomedCodesCategory(StrEnum):
CARE_PLAN = "734163000"

class SnomedCodes(Enum):
LLOYD_GEORGE = SnomedCode(
code="16521000000101", display_name="Lloyd George record folder"
)
CARE_PLAN = SnomedCode(code="734163000", display_name="Care plan")
GENERAL_MEDICAL_PRACTICE = SnomedCode(
code="1060971000000108", display_name="General practice service"
)
27 changes: 21 additions & 6 deletions lambdas/models/nrl_fhir_document_reference.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Optional

from enums.snomed_codes import SnomedCode, SnomedCodes
from fhir.resources.R4B.documentreference import DocumentReference
from models.nrl_sqs_message import NrlAttachment
from pydantic import BaseModel, ConfigDict
Expand All @@ -10,9 +11,11 @@ class FhirDocumentReference(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
nhs_number: str
custodian: str
snomed_code_doc_type: str = "None"
snomed_code_category: str = "None"
snomed_code_category_display: str = "Care plan"
snomed_code_doc_type: SnomedCode = SnomedCodes.LLOYD_GEORGE.value
snomed_code_category: SnomedCode = SnomedCodes.CARE_PLAN.value
snomed_code_practice_setting: SnomedCode = (
SnomedCodes.GENERAL_MEDICAL_PRACTICE.value
)
attachment: Optional[NrlAttachment] = NrlAttachment()

def build_fhir_dict(self):
Expand All @@ -36,7 +39,8 @@ def build_fhir_dict(self):
"coding": [
{
"system": snomed_url,
"code": self.snomed_code_doc_type,
"code": self.snomed_code_doc_type.code,
"display": self.snomed_code_doc_type.display_name,
}
]
},
Expand All @@ -45,8 +49,8 @@ def build_fhir_dict(self):
"coding": [
{
"system": snomed_url,
"code": self.snomed_code_category,
"display": self.snomed_code_category_display,
"code": self.snomed_code_category.code,
"display": self.snomed_code_category.display_name,
}
]
}
Expand All @@ -71,5 +75,16 @@ def build_fhir_dict(self):
},
}
],
"context": {
"practiceSetting": {
"coding": [
{
"system": snomed_url,
"code": self.snomed_code_practice_setting.code,
"display": self.snomed_code_practice_setting.display_name,
}
]
}
},
}
return DocumentReference(**structure_json)
12 changes: 8 additions & 4 deletions lambdas/models/nrl_sqs_message.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from enums.snomed_codes import SnomedCodesCategory, SnomedCodesType
from enums.snomed_codes import SnomedCode, SnomedCodes
from pydantic import AliasGenerator, BaseModel, ConfigDict
from pydantic.alias_generators import to_camel

Expand All @@ -17,12 +17,16 @@ class NrlAttachment(BaseModel):

class NrlSqsMessage(BaseModel):
model_config = ConfigDict(
alias_generator=AliasGenerator(serialization_alias=to_camel)
alias_generator=AliasGenerator(serialization_alias=to_camel),
use_enum_values=True,
)

nhs_number: str
snomed_code_doc_type: str = SnomedCodesType.LLOYD_GEORGE
snomed_code_category: str = SnomedCodesCategory.CARE_PLAN
snomed_code_doc_type: SnomedCode = SnomedCodes.LLOYD_GEORGE.value
snomed_code_category: SnomedCode = SnomedCodes.CARE_PLAN.value
snomed_code_practice_setting: SnomedCode = (
SnomedCodes.GENERAL_MEDICAL_PRACTICE.value
)
description: str = ""
attachment: Optional[NrlAttachment] = None
action: str
4 changes: 3 additions & 1 deletion lambdas/services/bulk_upload_metadata_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ def csv_to_staging_metadata(csv_file_path: str) -> list[StagingMetadata]:
logger.info("Parsing bulk upload metadata")

patients = {}
with open(csv_file_path, mode="r") as csv_file_handler:
with open(
csv_file_path, mode="r", encoding="utf-8", errors="replace"
) as csv_file_handler:
csv_reader: Iterable[dict] = csv.DictReader(csv_file_handler)
for row in csv_reader:
file_metadata = MetadataFile.model_validate(row)
Expand Down
8 changes: 4 additions & 4 deletions lambdas/services/document_deletion_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from enums.lambda_error import LambdaError
from enums.nrl_sqs_upload import NrlActionTypes
from enums.s3_lifecycle_tags import S3LifecycleTags
from enums.snomed_codes import SnomedCodesCategory, SnomedCodesType
from enums.snomed_codes import SnomedCodes
from enums.supported_document_types import SupportedDocumentTypes
from models.document_reference import DocumentReference
from models.nrl_sqs_message import NrlSqsMessage
Expand Down Expand Up @@ -91,13 +91,13 @@ def send_sqs_message_to_remove_pointer(self, nhs_number: str):
delete_nrl_message = NrlSqsMessage(
nhs_number=nhs_number,
action=NrlActionTypes.DELETE,
snomed_code_doc_type=SnomedCodesType.LLOYD_GEORGE,
snomed_code_category=SnomedCodesCategory.CARE_PLAN,
snomed_code_doc_type=SnomedCodes.LLOYD_GEORGE.value,
snomed_code_category=SnomedCodes.CARE_PLAN.value,
)
sqs_group_id = f"NRL_delete_{uuid.uuid4()}"
nrl_queue_url = os.environ["NRL_SQS_QUEUE_URL"]
self.sqs_service.send_message_fifo(
queue_url=nrl_queue_url,
message_body=delete_nrl_message.model_dump_json(),
message_body=delete_nrl_message.model_dump_json(exclude_unset=True),
group_id=sqs_group_id,
)
13 changes: 8 additions & 5 deletions lambdas/services/nrl_api_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import uuid

import requests
from requests import HTTPError
from enums.snomed_codes import SnomedCode
from requests.adapters import HTTPAdapter
from requests.exceptions import ConnectionError, HTTPError, Timeout
from urllib3 import Retry
from utils.audit_logging_setup import LoggingService
from utils.exceptions import NrlApiException
Expand Down Expand Up @@ -46,7 +47,7 @@ def create_new_pointer(self, body, retry_on_expired: bool = True):
)
response.raise_for_status()
logger.info("Successfully created new pointer")
except HTTPError as e:
except (ConnectionError, Timeout, HTTPError) as e:
logger.error(e.response.content)
if e.response.status_code == 401 and retry_on_expired:
self.headers["Authorization"] = (
Expand All @@ -56,14 +57,16 @@ def create_new_pointer(self, body, retry_on_expired: bool = True):
else:
raise NrlApiException("Error while creating new NRL Pointer")

def get_pointer(self, nhs_number, record_type=None, retry_on_expired: bool = True):
def get_pointer(
self, nhs_number, record_type: SnomedCode = None, retry_on_expired: bool = True
):
try:
self.set_x_request_id()
params = {
"subject:identifier": f"https://fhir.nhs.uk/Id/nhs-number|{nhs_number}"
}
if record_type:
params["type"] = f"http://snomed.info/sct|{record_type}"
params["type"] = f"http://snomed.info/sct|{record_type.code}"
response = self.session.get(
url=self.endpoint, params=params, headers=self.headers
)
Expand All @@ -79,7 +82,7 @@ def get_pointer(self, nhs_number, record_type=None, retry_on_expired: bool = Tru
else:
raise NrlApiException("Error while getting NRL Pointer")

def delete_pointer(self, nhs_number, record_type):
def delete_pointer(self, nhs_number, record_type: SnomedCode = None):
search_results = self.get_pointer(nhs_number, record_type).get("entry", [])
for entry in search_results:
self.set_x_request_id()
Expand Down
26 changes: 13 additions & 13 deletions lambdas/tests/unit/handlers/test_manage_nrl_pointer_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json

import pytest
from enums.snomed_codes import SnomedCodes
from handlers.manage_nrl_pointer_handler import lambda_handler
from models.nrl_sqs_message import NrlAttachment, NrlSqsMessage
from utils.exceptions import NrlApiException


Expand All @@ -14,18 +14,18 @@ def mock_service(mocker):


def build_test_sqs_message(action="create"):
SQS_Message = {
"nhs_number": "123456789",
"snomed_code_doc_type": "16521000000101",
"snomed_code_category": "734163000",
"action": action,
"attachment": {
"contentType": "application/pdf",
"url": "https://example.org/my-doc.pdf",
},
}
doc_details = NrlAttachment(
url="https://example.org/my-doc.pdf",
)
sqs_message = NrlSqsMessage(
nhs_number="123456789",
action=action,
snomed_code_doc_type=SnomedCodes.LLOYD_GEORGE.value,
snomed_code_category=SnomedCodes.CARE_PLAN.value,
attachment=doc_details,
).model_dump_json()
return {
"body": json.dumps(SQS_Message),
"body": sqs_message,
"eventSource": "aws:sqs",
}

Expand Down
13 changes: 8 additions & 5 deletions lambdas/tests/unit/services/test_document_deletion_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest
from enums.s3_lifecycle_tags import S3LifecycleTags
from enums.snomed_codes import SnomedCodes
from enums.supported_document_types import SupportedDocumentTypes
from services.document_deletion_service import DocumentDeletionService
from tests.unit.conftest import (
Expand Down Expand Up @@ -227,12 +228,14 @@ def test_send_sqs_message_to_remove_pointer(mocker, mock_deletion_service):

expected_message_body = (
'{{"nhs_number":"{}",'
'"snomed_code_doc_type":"16521000000101",'
'"snomed_code_category":"734163000",'
'"description":"",'
'"attachment":null,'
'"snomed_code_doc_type":{},'
'"snomed_code_category":{},'
'"action":"delete"}}'
).format(TEST_NHS_NUMBER)
).format(
TEST_NHS_NUMBER,
SnomedCodes.LLOYD_GEORGE.value.model_dump_json(),
SnomedCodes.CARE_PLAN.value.model_dump_json(),
)

mock_deletion_service.send_sqs_message_to_remove_pointer(TEST_NHS_NUMBER)

Expand Down
23 changes: 12 additions & 11 deletions lambdas/tests/unit/services/test_nrl_api_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from enums.snomed_codes import SnomedCodes
from requests import Response
from services.nrl_api_service import NrlApiService
from tests.unit.conftest import FAKE_URL, TEST_NHS_NUMBER
Expand Down Expand Up @@ -43,12 +44,12 @@ def test_get_end_user_ods_code(nrl_service):


def test_get_pointer_with_record_type(mocker, nrl_service):
mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value
mocker.patch("uuid.uuid4", return_value="test_uuid")

mock_params = {
"subject:identifier": f"https://fhir.nhs.uk/Id/nhs-number|{TEST_NHS_NUMBER}",
"type": f"http://snomed.info/sct|{mock_type}",
"type": f"http://snomed.info/sct|{mock_type.code}",
}
mock_headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
Expand All @@ -64,11 +65,11 @@ def test_get_pointer_with_record_type(mocker, nrl_service):


def test_get_pointer_with_record_type_no_retry(mocker, nrl_service):
mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value
mocker.patch("uuid.uuid4", return_value="test_uuid")
mock_params = {
"subject:identifier": f"https://fhir.nhs.uk/Id/nhs-number|{TEST_NHS_NUMBER}",
"type": f"http://snomed.info/sct|{mock_type}",
"type": f"http://snomed.info/sct|{mock_type.code}",
}
mock_headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
Expand All @@ -90,11 +91,11 @@ def test_get_pointer_with_record_type_no_retry(mocker, nrl_service):


def test_get_pointer_with_record_type_with_retry(mocker, nrl_service):
mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value
mocker.patch("uuid.uuid4", return_value="test_uuid")
mock_params = {
"subject:identifier": f"https://fhir.nhs.uk/Id/nhs-number|{TEST_NHS_NUMBER}",
"type": f"http://snomed.info/sct|{mock_type}",
"type": f"http://snomed.info/sct|{mock_type.code}",
}
mock_headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
Expand All @@ -120,7 +121,7 @@ def test_get_pointer_raise_error(nrl_service):
response.status_code = 400
response._content = b"{}"

mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value

nrl_service.session.get.return_value = response
pytest.raises(NrlApiException, nrl_service.get_pointer, TEST_NHS_NUMBER, mock_type)
Expand All @@ -129,7 +130,7 @@ def test_get_pointer_raise_error(nrl_service):


def test_delete_pointer_with_record_type_no_record(mocker, nrl_service):
mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value
mocker.patch("uuid.uuid4", return_value="test_uuid")

nrl_response = {
Expand All @@ -145,7 +146,7 @@ def test_delete_pointer_with_record_type_no_record(mocker, nrl_service):


def test_delete_pointer_with_record_type_one_record(mocker, nrl_service):
mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value
mocker.patch("uuid.uuid4", return_value="test_uuid")
mock_pointer_id = "ODSCODE-1111bfb1-1111-2222-3333-4444555c666f"
mock_headers = {
Expand Down Expand Up @@ -176,7 +177,7 @@ def test_delete_pointer_with_record_type_one_record(mocker, nrl_service):


def test_delete_pointer_with_record_type_more_than_one_record(mocker, nrl_service):
mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value
mocker.patch("uuid.uuid4", return_value="test_uuid")
mock_pointer_id = "ODSCODE-1111bfb1-1111-2222-3333-4444555c666"

Expand Down Expand Up @@ -209,7 +210,7 @@ def test_delete_pointer_not_raise_error(mocker, nrl_service):
response = Response()
response.status_code = 400
response._content = b"{}"
mock_type = 11111111
mock_type = SnomedCodes.LLOYD_GEORGE.value
nrl_response = {
"resourceType": "Bundle",
"type": "searchset",
Expand Down
4 changes: 2 additions & 2 deletions lambdas/tests/unit/utils/test_lloyd_george_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def test_validate_name_with_wrong_first_name(mocker, mock_pds_patient):
validate_patient_name_using_full_name_history(
lg_file_patient_name, mock_pds_patient
)
assert mock_validate_name.call_count == 2
assert mock_validate_name.call_count == 3


def test_validate_name_with_wrong_family_name(mocker, mock_pds_patient):
Expand All @@ -344,7 +344,7 @@ def test_validate_name_with_wrong_family_name(mocker, mock_pds_patient):
validate_patient_name_using_full_name_history(
lg_file_patient_name, mock_pds_patient
)
assert mock_validate_name.call_count == 2
assert mock_validate_name.call_count == 3


def test_validate_name_with_historical_name(mocker, mock_pds_patient):
Expand Down
2 changes: 0 additions & 2 deletions lambdas/utils/lloyd_george_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,6 @@ def validate_patient_name_using_full_name_history(
)

for name in pds_patient_details.name:
if name.use == "usual":
continue
historic_first_name_in_pds: str = name.given[0]
historic_family_name_in_pds = name.family
if validate_patient_name(
Expand Down

0 comments on commit 863797b

Please sign in to comment.