Skip to content

Commit 565a765

Browse files
committed
add gp identifier
1 parent 44d469c commit 565a765

7 files changed

Lines changed: 177 additions & 50 deletions

File tree

infrastructure/instance/modules/mns_publisher/mns_publisher_lambda.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,9 @@ resource "aws_lambda_function" "mns_publisher_lambda" {
191191

192192
environment {
193193
variables = {
194-
SPLUNK_FIREHOSE_NAME = var.splunk_firehose_stream_name
195-
"IMMUNIZATION_ENV" = var.resource_scope,
196-
"IMMUNIZATION_BASE_PATH" = var.sub_environment
194+
SPLUNK_FIREHOSE_NAME = var.splunk_firehose_stream_name
195+
IMMUNIZATION_ENV = var.resource_scope,
196+
IMMUNIZATION_BASE_PATH = var.sub_environment
197197
}
198198
}
199199

lambdas/id_sync/src/id_sync.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
"""
22
- Parses the incoming AWS event into `AwsLambdaEvent` and iterate its `records`.
33
- Delegate each record to `process_record` and collect `nhs_number` from each result.
4-
- If any record has status == "error" raise `PdsSyncException` with aggregated nhs_numbers.
5-
- Any unexpected error is wrapped into `PdsSyncException(message="Error processing id_sync event")`.
4+
- If any record has status == "error" raise `IdSyncException` with aggregated nhs_numbers.
5+
- Any unexpected error is wrapped into `IdSyncException(message="Error processing id_sync event")`.
66
"""
77

88
from typing import Any, Dict
99

10-
from common.api_clients.errors import PdsSyncException
1110
from common.aws_lambda_event import AwsLambdaEvent
1211
from common.clients import STREAM_NAME, logger
1312
from common.log_decorator import logging_decorator
13+
from exceptions.id_sync_exception import IdSyncException
1414
from record_processor import process_record
1515

1616

@@ -34,7 +34,7 @@ def handler(event_data: Dict[str, Any], _context) -> Dict[str, Any]:
3434
error_count += 1
3535

3636
if error_count > 0:
37-
raise PdsSyncException(
37+
raise IdSyncException(
3838
message=f"Processed {len(records)} records with {error_count} errors",
3939
)
4040

@@ -43,10 +43,10 @@ def handler(event_data: Dict[str, Any], _context) -> Dict[str, Any]:
4343
logger.info("id_sync handler completed: %s", response)
4444
return response
4545

46-
except PdsSyncException as e:
46+
except IdSyncException as e:
4747
logger.exception(f"id_sync error: {e.message}")
4848
raise
4949
except Exception:
5050
msg = "Error processing id_sync event"
5151
logger.exception(msg)
52-
raise PdsSyncException(message=msg)
52+
raise IdSyncException(message=msg)

lambdas/id_sync/tests/test_id_sync.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
with patch("common.log_decorator.logging_decorator") as mock_decorator:
55
mock_decorator.return_value = lambda f: f # Pass-through decorator
6-
from common.api_clients.errors import PdsSyncException
6+
from exceptions.id_sync_exception import IdSyncException
77
from id_sync import handler
88

99

@@ -93,7 +93,7 @@ def test_handler_error_single_record(self):
9393
}
9494

9595
# Call handler
96-
with self.assertRaises(PdsSyncException) as exception_context:
96+
with self.assertRaises(IdSyncException) as exception_context:
9797
handler(self.single_sqs_event, None)
9898

9999
exception = exception_context.exception
@@ -117,7 +117,7 @@ def test_handler_mixed_success_error(self):
117117
]
118118

119119
# Call handler
120-
with self.assertRaises(PdsSyncException) as exception_context:
120+
with self.assertRaises(IdSyncException) as exception_context:
121121
handler(self.multi_sqs_event, None)
122122

123123
error = exception_context.exception
@@ -139,7 +139,7 @@ def test_handler_all_records_fail(self):
139139
]
140140

141141
# Call handler
142-
with self.assertRaises(PdsSyncException) as exception_context:
142+
with self.assertRaises(IdSyncException) as exception_context:
143143
handler(self.multi_sqs_event, None)
144144
exception = exception_context.exception
145145
# Assertions
@@ -187,7 +187,7 @@ def test_handler_aws_lambda_event_exception(self):
187187
self.mock_aws_lambda_event.side_effect = Exception("AwsLambdaEvent creation failed")
188188

189189
# Call handler
190-
with self.assertRaises(PdsSyncException) as exception_context:
190+
with self.assertRaises(IdSyncException) as exception_context:
191191
handler(self.single_sqs_event, None)
192192

193193
result = exception_context.exception
@@ -208,7 +208,7 @@ def test_handler_process_record_exception(self):
208208
self.mock_process_record.side_effect = Exception("Process record failed")
209209

210210
# Call handler
211-
with self.assertRaises(PdsSyncException) as exception_context:
211+
with self.assertRaises(IdSyncException) as exception_context:
212212
handler(self.single_sqs_event, None)
213213
exception = exception_context.exception
214214
# Assertions
@@ -233,12 +233,12 @@ def test_handler_process_record_missing_nhs_number(self):
233233
}
234234

235235
# Call handler and expect exception
236-
with self.assertRaises(PdsSyncException) as exception_context:
236+
with self.assertRaises(IdSyncException) as exception_context:
237237
handler(self.single_sqs_event, None)
238238

239239
exception = exception_context.exception
240240

241-
self.assertIsInstance(exception, PdsSyncException)
241+
self.assertIsInstance(exception, IdSyncException)
242242
self.assertEqual(exception.message, "Processed 1 records with 1 errors")
243243
self.mock_logger.exception.assert_called_once_with(f"id_sync error: {exception.message}")
244244

@@ -275,7 +275,7 @@ def test_handler_error_count_tracking(self):
275275
]
276276

277277
# Call handler
278-
with self.assertRaises(PdsSyncException) as exception_context:
278+
with self.assertRaises(IdSyncException) as exception_context:
279279
handler(self.multi_sqs_event, None)
280280
exception = exception_context.exception
281281
# Assertions - should track 2 errors out of 4 records

lambdas/mns_publisher/src/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Static constants for the MNS notification creation process
44
SPEC_VERSION = "1.0"
5-
IMMUNISATION_TYPE = "imms-vaccinations-2"
5+
IMMUNISATION_TYPE = "imms-vaccinations-1"
66

77

88
# Fields from the incoming SQS message that forms part of the base schema and filtering attributes for MNS notifications

lambdas/mns_publisher/src/create_notification.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,26 @@ def get_practitioner_details_from_pds(nhs_number: str) -> str | None:
7575
return None
7676

7777
patient_gp = general_practitioners[0]
78-
gp_ods_code = patient_gp.get("identifier", {}).get("value")
78+
patient_gp_identifier = patient_gp.get("identifier", {})
79+
80+
gp_ods_code = patient_gp_identifier.get("value")
7981
if not gp_ods_code:
8082
logger.warning("GP ODS code not found in practitioner details")
8183
return None
8284

85+
# Check if registration is current
86+
period = patient_gp_identifier.get("period", {})
87+
gp_period_end_date = period.get("end", None)
88+
89+
if gp_period_end_date:
90+
# Parse end date (format: YYYY-MM-DD)
91+
end_date = datetime.strptime(gp_period_end_date, "%Y-%m-%d").date()
92+
today = datetime.now().date()
93+
94+
if end_date < today:
95+
logger.warning("GP registration has ended")
96+
return None
97+
8398
return gp_ods_code
8499
except Exception as error:
85100
logger.exception("Failed to get practitioner details from pds", error)

lambdas/mns_publisher/tests/test_create_notification.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class TestGetPractitionerDetailsFromPds(unittest.TestCase):
8686
@patch("create_notification.logger")
8787
def test_get_practitioner_success(self, mock_logger, mock_pds_get):
8888
"""Test successful retrieval of GP ODS code."""
89-
mock_pds_get.return_value = {"generalPractitioner": {"value": "Y12345"}}
89+
mock_pds_get.return_value = {"generalPractitioner": [{"identifier": {"value": "Y12345"}}]}
9090

9191
result = get_practitioner_details_from_pds("9481152782")
9292

@@ -103,7 +103,7 @@ def test_get_practitioner_no_gp_details(self, mock_logger, mock_pds_get):
103103
result = get_practitioner_details_from_pds("9481152782")
104104

105105
self.assertIsNone(result)
106-
mock_logger.warning.assert_called_once_with("No patient details found for NHS number")
106+
mock_logger.warning.assert_called_once_with("No GP details found for patient")
107107

108108
@patch("create_notification.pds_get_patient_details")
109109
@patch("create_notification.logger")
@@ -120,7 +120,7 @@ def test_get_practitioner_gp_is_none(self, mock_logger, mock_pds_get):
120120
@patch("create_notification.logger")
121121
def test_get_practitioner_no_value_field(self, mock_logger, mock_pds_get):
122122
"""Test when value field is missing from generalPractitioner."""
123-
mock_pds_get.return_value = {"generalPractitioner": {"system": "https://fhir.nhs.uk"}}
123+
mock_pds_get.return_value = {"generalPractitioner": [{"identifier": {}}]}
124124

125125
result = get_practitioner_details_from_pds("9481152782")
126126

@@ -131,7 +131,7 @@ def test_get_practitioner_no_value_field(self, mock_logger, mock_pds_get):
131131
@patch("create_notification.logger")
132132
def test_get_practitioner_empty_value(self, mock_logger, mock_pds_get):
133133
"""Test when value is empty string."""
134-
mock_pds_get.return_value = {"generalPractitioner": {"value": ""}}
134+
mock_pds_get.return_value = {"generalPractitioner": [{"identifier": {"value": ""}}]}
135135

136136
result = get_practitioner_details_from_pds("9481152782")
137137

@@ -342,5 +342,85 @@ def test_create_mns_notification_with_update_action(self, mock_get_service_url,
342342
mock_get_gp.assert_called()
343343

344344

345+
@patch("create_notification.pds_get_patient_details")
346+
@patch("create_notification.logger")
347+
def test_get_practitioner_success_no_end_date(self, mock_logger, mock_pds_get):
348+
"""Test successful retrieval when no end date (current registration)."""
349+
mock_pds_get.return_value = {
350+
"generalPractitioner": [{"identifier": {"value": "Y12345", "period": {"start": "2024-01-01"}}}]
351+
}
352+
353+
result = get_practitioner_details_from_pds("9481152782")
354+
355+
self.assertEqual(result, "Y12345")
356+
mock_logger.warning.assert_not_called()
357+
358+
359+
@patch("create_notification.pds_get_patient_details")
360+
@patch("create_notification.logger")
361+
def test_get_practitioner_success_future_end_date(self, mock_logger, mock_pds_get):
362+
"""Test successful retrieval when end date is in the future."""
363+
mock_pds_get.return_value = {
364+
"generalPractitioner": [
365+
{"identifier": {"value": "Y12345", "period": {"start": "2024-01-01", "end": "2030-12-31"}}}
366+
]
367+
}
368+
369+
result = get_practitioner_details_from_pds("9481152782")
370+
371+
self.assertEqual(result, "Y12345")
372+
mock_logger.warning.assert_not_called()
373+
374+
375+
@patch("create_notification.pds_get_patient_details")
376+
@patch("create_notification.logger")
377+
def test_get_practitioner_expired_registration(self, mock_logger, mock_pds_get):
378+
"""Test when GP registration has ended (expired)."""
379+
mock_pds_get.return_value = {
380+
"generalPractitioner": [
381+
{"identifier": {"value": "Y12345", "period": {"start": "2020-01-01", "end": "2023-12-31"}}}
382+
]
383+
}
384+
385+
result = get_practitioner_details_from_pds("9481152782")
386+
387+
self.assertIsNone(result)
388+
mock_logger.warning.assert_called_with(
389+
"GP registration has ended",
390+
extra={"nhs_number": "9481152782", "gp_ods_code": "Y12345", "end_date": "2023-12-31"},
391+
)
392+
393+
394+
@patch("create_notification.pds_get_patient_details")
395+
@patch("create_notification.logger")
396+
def test_get_practitioner_invalid_end_date_format(self, mock_logger, mock_pds_get):
397+
"""Test when end date has invalid format - should still return GP."""
398+
mock_pds_get.return_value = {
399+
"generalPractitioner": [
400+
{"identifier": {"value": "Y12345", "period": {"start": "2024-01-01", "end": "invalid-date"}}}
401+
]
402+
}
403+
404+
result = get_practitioner_details_from_pds("9481152782")
405+
406+
# Should still return GP even with invalid date
407+
self.assertEqual(result, "Y12345")
408+
mock_logger.warning.assert_called_with(
409+
"Invalid end date format in GP registration", extra={"nhs_number": "9481152782", "end_date": "invalid-date"}
410+
)
411+
412+
413+
@patch("create_notification.pds_get_patient_details")
414+
@patch("create_notification.logger")
415+
def test_get_practitioner_no_period_field(self, mock_logger, mock_pds_get):
416+
"""Test when period field is missing entirely."""
417+
mock_pds_get.return_value = {"generalPractitioner": [{"identifier": {"value": "Y12345"}}]}
418+
419+
result = get_practitioner_details_from_pds("9481152782")
420+
421+
self.assertEqual(result, "Y12345")
422+
mock_logger.warning.assert_not_called()
423+
424+
345425
if __name__ == "__main__":
346426
unittest.main()

0 commit comments

Comments
 (0)