Skip to content

Commit 43b92e5

Browse files
PRMP-749 - Bulk Upload: Reject deceased patients (#404)
* [PRMP-749] Amend Patient model to handle patient decease status from pds response * [PRMP-749] Add test patient for the case of deceased patient, change other test patient to non-deceased * [PRMP-749] Edit bulk upload service to raise failure report for deceased patient * [PRMP-749] add test patient for both informal and formal deceased, amend allowed ods code when PDS FHIR being stubbed * reduce duplication * minor fix * improve test coverage * improve test coverage * [PRMP-749] made recommended changes --------- Co-authored-by: Steph Torres <stephane.torres1@nhs.net>
1 parent 9ffeb70 commit 43b92e5

25 files changed

+1077
-372
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from enum import StrEnum
2+
3+
4+
class DeathNotificationStatus(StrEnum):
5+
INFORMAL = "1"
6+
FORMAL = "2"
7+
REMOVED = "U"

lambdas/models/pds_models.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import date
22
from typing import Optional, Tuple
33

4+
from enums.death_notification_status import DeathNotificationStatus
45
from pydantic import BaseModel, ConfigDict
56
from pydantic.alias_generators import to_camel
67
from utils.audit_logging_setup import LoggingService
@@ -64,6 +65,11 @@ class GeneralPractitioner(BaseModel):
6465
identifier: GPIdentifier
6566

6667

68+
class Extension(BaseModel):
69+
url: str
70+
extension: list[dict] = []
71+
72+
6773
class PatientDetails(BaseModel):
6874
model_config = conf
6975

@@ -76,6 +82,8 @@ class PatientDetails(BaseModel):
7682
restricted: bool
7783
general_practice_ods: str = ""
7884
active: Optional[bool] = None
85+
deceased: bool = False
86+
death_notification_status: Optional[DeathNotificationStatus] = None
7987

8088

8189
class Patient(BaseModel):
@@ -87,6 +95,8 @@ class Patient(BaseModel):
8795
name: list[Name]
8896
meta: Meta
8997
general_practitioner: list[GeneralPractitioner] = []
98+
deceased_date_time: str = ""
99+
extension: list[Extension] = []
90100

91101
def get_security(self) -> Security:
92102
security = self.meta.security[0] if self.meta.security[0] else None
@@ -150,9 +160,42 @@ def get_is_active_status(self) -> bool:
150160
gp_ods = self.get_active_ods_code_for_gp()
151161
return bool(gp_ods)
152162

163+
def get_death_notification_status(self) -> Optional[DeathNotificationStatus]:
164+
if not self.deceased_date_time:
165+
return None
166+
167+
for extension_wrapper in self.extension:
168+
if (
169+
extension_wrapper.url
170+
== "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-DeathNotificationStatus"
171+
):
172+
return self.parse_death_notification_status_extension(extension_wrapper)
173+
174+
return None
175+
176+
@staticmethod
177+
def parse_death_notification_status_extension(
178+
extension_wrapper: Extension,
179+
) -> Optional[DeathNotificationStatus]:
180+
try:
181+
for nested_extension in extension_wrapper.extension:
182+
if nested_extension["url"] == "deathNotificationStatus":
183+
status_code = nested_extension["valueCodeableConcept"]["coding"][0][
184+
"code"
185+
]
186+
return DeathNotificationStatus(status_code)
187+
188+
except (KeyError, IndexError, ValueError) as e:
189+
logger.info(
190+
f"Failed to parse death_notification_status for patient due to error: {str(e)}. "
191+
"Will fill the value as None."
192+
)
193+
return None
194+
153195
def get_patient_details(self, nhs_number) -> PatientDetails:
154196
family_name, given_name = self.get_current_family_name_and_given_name()
155197
current_home_address = self.get_current_home_address()
198+
death_notification_status = self.get_death_notification_status()
156199

157200
patient_details = PatientDetails(
158201
givenName=given_name,
@@ -166,12 +209,15 @@ def get_patient_details(self, nhs_number) -> PatientDetails:
166209
restricted=not self.is_unrestricted(),
167210
generalPracticeOds=self.get_active_ods_code_for_gp(),
168211
active=self.get_is_active_status(),
212+
deceased=is_deceased(death_notification_status),
213+
deathNotificationStatus=death_notification_status,
169214
)
170215

171216
return patient_details
172217

173218
def get_minimum_patient_details(self, nhs_number) -> PatientDetails:
174219
family_name, given_name = self.get_current_family_name_and_given_name()
220+
death_notification_status = self.get_death_notification_status()
175221

176222
return PatientDetails(
177223
givenName=given_name,
@@ -183,4 +229,13 @@ def get_minimum_patient_details(self, nhs_number) -> PatientDetails:
183229
nhsNumber=self.id,
184230
superseded=bool(nhs_number == id),
185231
restricted=not self.is_unrestricted(),
232+
deceased=is_deceased(death_notification_status),
233+
deathNotificationStatus=death_notification_status,
186234
)
235+
236+
237+
def is_deceased(death_notification_status: Optional[DeathNotificationStatus]) -> bool:
238+
return (
239+
death_notification_status == DeathNotificationStatus.FORMAL
240+
or death_notification_status == DeathNotificationStatus.INFORMAL
241+
)

lambdas/services/bulk_upload_service.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
BulkUploadException,
1717
DocumentInfectedException,
1818
InvalidMessageException,
19+
PatientDeceasedException,
1920
PatientRecordAlreadyExistException,
2021
PdsTooManyRequestsException,
2122
S3FileNotFoundException,
@@ -111,9 +112,20 @@ def handle_sqs_message(self, message: dict):
111112
)
112113
patient_ods_code = pds_patient_details.general_practice_ods
113114
validate_filename_with_patient_details(file_names, pds_patient_details)
115+
114116
if not allowed_to_ingest_ods_code(patient_ods_code):
115117
raise LGInvalidFilesException("Patient not registered at your practice")
116-
except (LGInvalidFilesException, PatientRecordAlreadyExistException) as error:
118+
119+
if pds_patient_details.deceased:
120+
raise PatientDeceasedException(
121+
f"Patient is deceased - {pds_patient_details.death_notification_status.name}"
122+
)
123+
124+
except (
125+
LGInvalidFilesException,
126+
PatientRecordAlreadyExistException,
127+
PatientDeceasedException,
128+
) as error:
117129
logger.info(
118130
f"Detected issue related to patient number: {staging_metadata.nhs_number}"
119131
)

lambdas/services/mock_data/pds_patient_9000000001_X4S4L_pcse.json

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"gender": "female",
7474
"birthDate": "2010-10-22",
7575
"multipleBirthInteger": 1,
76-
"deceasedDateTime": "2010-10-22T00:00:00+00:00",
7776
"generalPractitioner": [
7877
{
7978
"id": "254406A3",
@@ -127,28 +126,6 @@
127126
}
128127
}
129128
},
130-
{
131-
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-DeathNotificationStatus",
132-
"extension": [
133-
{
134-
"url": "deathNotificationStatus",
135-
"valueCodeableConcept": {
136-
"coding": [
137-
{
138-
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-DeathNotificationStatus",
139-
"version": "1.0.0",
140-
"code": "2",
141-
"display": "Formal - death notice received from Registrar of Deaths"
142-
}
143-
]
144-
}
145-
},
146-
{
147-
"url": "systemEffectiveDate",
148-
"valueDateTime": "2010-10-22T00:00:00+00:00"
149-
}
150-
]
151-
},
152129
{
153130
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSCommunication",
154131
"extension": [

lambdas/services/mock_data/pds_patient_9000000002_H81109_gp.json

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
"gender": "female",
6262
"birthDate": "2010-10-22",
6363
"multipleBirthInteger": 1,
64-
"deceasedDateTime": "2010-10-22T00:00:00+00:00",
6564
"generalPractitioner": [
6665
{
6766
"id": "254406A3",
@@ -115,28 +114,6 @@
115114
}
116115
}
117116
},
118-
{
119-
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-DeathNotificationStatus",
120-
"extension": [
121-
{
122-
"url": "deathNotificationStatus",
123-
"valueCodeableConcept": {
124-
"coding": [
125-
{
126-
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-DeathNotificationStatus",
127-
"version": "1.0.0",
128-
"code": "2",
129-
"display": "Formal - death notice received from Registrar of Deaths"
130-
}
131-
]
132-
}
133-
},
134-
{
135-
"url": "systemEffectiveDate",
136-
"valueDateTime": "2010-10-22T00:00:00+00:00"
137-
}
138-
]
139-
},
140117
{
141118
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSCommunication",
142119
"extension": [

lambdas/services/mock_data/pds_patient_9000000003_H85686_gp.json

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"gender": "female",
7474
"birthDate": "2010-10-22",
7575
"multipleBirthInteger": 1,
76-
"deceasedDateTime": "2010-10-22T00:00:00+00:00",
7776
"generalPractitioner": [
7877
{
7978
"id": "254406A3",
@@ -127,28 +126,6 @@
127126
}
128127
}
129128
},
130-
{
131-
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-DeathNotificationStatus",
132-
"extension": [
133-
{
134-
"url": "deathNotificationStatus",
135-
"valueCodeableConcept": {
136-
"coding": [
137-
{
138-
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-DeathNotificationStatus",
139-
"version": "1.0.0",
140-
"code": "2",
141-
"display": "Formal - death notice received from Registrar of Deaths"
142-
}
143-
]
144-
}
145-
},
146-
{
147-
"url": "systemEffectiveDate",
148-
"valueDateTime": "2010-10-22T00:00:00+00:00"
149-
}
150-
]
151-
},
152129
{
153130
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSCommunication",
154131
"extension": [

lambdas/services/mock_data/pds_patient_9000000004_M85143_gp.json

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"gender": "female",
6262
"birthDate": "2010-10-22",
6363
"multipleBirthInteger": 1,
64-
"deceasedDateTime": "2010-10-22T00:00:00+00:00",
64+
6565
"generalPractitioner": [
6666
{
6767
"id": "254406A3",
@@ -115,28 +115,6 @@
115115
}
116116
}
117117
},
118-
{
119-
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-DeathNotificationStatus",
120-
"extension": [
121-
{
122-
"url": "deathNotificationStatus",
123-
"valueCodeableConcept": {
124-
"coding": [
125-
{
126-
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-DeathNotificationStatus",
127-
"version": "1.0.0",
128-
"code": "2",
129-
"display": "Formal - death notice received from Registrar of Deaths"
130-
}
131-
]
132-
}
133-
},
134-
{
135-
"url": "systemEffectiveDate",
136-
"valueDateTime": "2010-10-22T00:00:00+00:00"
137-
}
138-
]
139-
},
140118
{
141119
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSCommunication",
142120
"extension": [

lambdas/services/mock_data/pds_patient_9000000005_not_active.json

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"gender": "female",
7474
"birthDate": "2010-10-22",
7575
"multipleBirthInteger": 1,
76-
"deceasedDateTime": "2010-10-22T00:00:00+00:00",
7776
"generalPractitioner": [
7877
{
7978
"id": "254406A3",
@@ -127,28 +126,6 @@
127126
}
128127
}
129128
},
130-
{
131-
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-DeathNotificationStatus",
132-
"extension": [
133-
{
134-
"url": "deathNotificationStatus",
135-
"valueCodeableConcept": {
136-
"coding": [
137-
{
138-
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-DeathNotificationStatus",
139-
"version": "1.0.0",
140-
"code": "2",
141-
"display": "Formal - death notice received from Registrar of Deaths"
142-
}
143-
]
144-
}
145-
},
146-
{
147-
"url": "systemEffectiveDate",
148-
"valueDateTime": "2010-10-22T00:00:00+00:00"
149-
}
150-
]
151-
},
152129
{
153130
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSCommunication",
154131
"extension": [

lambdas/services/mock_data/pds_patient_9000000025_restricted.json

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,5 @@
5555
"gender": "female",
5656
"birthDate": "2010-10-22",
5757
"multipleBirthInteger": 1,
58-
"deceasedDateTime": "2010-10-22T00:00:00+00:00",
59-
"extension": [
60-
{
61-
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-DeathNotificationStatus",
62-
"extension": [
63-
{
64-
"url": "deathNotificationStatus",
65-
"valueCodeableConcept": {
66-
"coding": [
67-
{
68-
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-DeathNotificationStatus",
69-
"version": "1.0.0",
70-
"code": "2",
71-
"display": "Formal - death notice received from Registrar of Deaths"
72-
}
73-
]
74-
}
75-
},
76-
{
77-
"url": "systemEffectiveDate",
78-
"valueDateTime": "2010-10-22T00:00:00+00:00"
79-
}
80-
]
81-
}
82-
]
58+
"extension": []
8359
}

0 commit comments

Comments
 (0)