11from common .clients import logger
22from typing import Dict , Any
3- from pds_details import pds_get_patient_id
4- from ieds_db_operations import ieds_check_exist , ieds_update_patient_id
3+ from pds_details import pds_get_patient_id , pds_get_patient_details , normalize_name_from_pds
4+ from ieds_db_operations import (
5+ ieds_check_exist ,
6+ ieds_update_patient_id ,
7+ extract_patient_resource_from_item ,
8+ get_items_from_patient_id ,
9+ )
510import json
611import ast
712
@@ -53,10 +58,125 @@ def process_nhs_number(nhs_number: str) -> Dict[str, Any]:
5358 logger .info ("Update patient ID from %s to %s" , nhs_number , new_nhs_number )
5459
5560 if ieds_check_exist (nhs_number ):
56- response = ieds_update_patient_id (nhs_number , new_nhs_number )
61+ # Fetch PDS details for demographic comparison
62+ try :
63+ pds_details = pds_get_patient_details (nhs_number )
64+ except Exception :
65+ logger .exception ("process_nhs_number: failed to fetch PDS details, aborting update" )
66+ return {
67+ "status" : "error" ,
68+ "message" : "Failed to fetch PDS details for demographic comparison" ,
69+ "nhs_number" : nhs_number ,
70+ }
71+
72+ # Get IEDS items for this patient id and compare demographics
73+ try :
74+ items = get_items_from_patient_id (nhs_number )
75+ except Exception :
76+ logger .exception ("process_nhs_number: failed to fetch IEDS items, aborting update" )
77+ return {
78+ "status" : "error" ,
79+ "message" : "Failed to fetch IEDS items for demographic comparison" ,
80+ "nhs_number" : nhs_number ,
81+ }
82+
83+ # If at least one IEDS item matches demographics, proceed with update
84+ match_found = False
85+ for item in items :
86+ try :
87+ if demographics_match (pds_details , item ):
88+ match_found = True
89+ break
90+ except Exception :
91+ logger .exception ("process_nhs_number: error while comparing demographics for item: %s" , item )
92+
93+ if not match_found :
94+ logger .info ("process_nhs_number: No IEDS items matched PDS demographics. Skipping update for %s" , nhs_number )
95+ response = {
96+ "status" : "success" ,
97+ "message" : "No IEDS items matched PDS demographics; update skipped" ,
98+ }
99+ else :
100+ response = ieds_update_patient_id (nhs_number , new_nhs_number )
57101 else :
58102 logger .info ("No IEDS record found for: %s" , nhs_number )
59103 response = {"status" : "success" , "message" : f"No records returned for ID: { nhs_number } " }
60104
61105 response ["nhs_number" ] = nhs_number
62106 return response
107+
108+ def extract_normalized_name_from_patient (patient : dict ) -> str | None :
109+ """Return a normalized 'given family' name string from a Patient resource or None."""
110+ if not patient :
111+ return None
112+ name = patient .get ("name" )
113+ if not name :
114+ return None
115+ try :
116+ name_entry = name [0 ] if isinstance (name , list ) else name
117+ given = name_entry .get ("given" )
118+ given_str = None
119+ if isinstance (given , list ) and given :
120+ given_str = given [0 ]
121+ elif isinstance (given , str ):
122+ given_str = given
123+ family = name_entry .get ("family" )
124+ parts = [p for p in [given_str , family ] if p ]
125+ return " " .join (parts ).strip ().lower () if parts else None
126+ except Exception :
127+ return None
128+
129+
130+ def demographics_match (pds_details : dict , ieds_item : dict ) -> bool :
131+ """Compare PDS patient details to an IEDS item (FHIR Patient resource).
132+
133+ Parameters:
134+ - pds_details: dict returned by PDS (patient details)
135+ - ieds_item: dict representing a single IEDS item containing a FHIR Patient resource
136+
137+ Returns True if name, birthDate and gender match (when present in both sources).
138+ If required fields are missing or unparsable on the IEDS side the function returns False.
139+ """
140+ try :
141+ # extract pds values
142+ pds_name = normalize_name_from_pds (pds_details ) if isinstance (pds_details , dict ) else None
143+ pds_gender = pds_details .get ("gender" ) if isinstance (pds_details , dict ) else None
144+ pds_birth = pds_details .get ("birthDate" ) if isinstance (pds_details , dict ) else None
145+
146+ patient = extract_patient_resource_from_item (ieds_item )
147+ if not patient :
148+ logger .debug ("demographics_match: no patient resource in item" )
149+ return False
150+
151+ # normalize incoming patient name
152+ incoming_name = extract_normalized_name_from_patient (patient )
153+
154+ incoming_gender = patient .get ("gender" )
155+ incoming_birth = patient .get ("birthDate" )
156+
157+ def _norm_str (x ):
158+ return str (x ).strip ().lower () if x is not None else None
159+
160+ # Compare birthDate (strict if both present)
161+ if pds_birth and incoming_birth :
162+ if str (pds_birth ).strip () != str (incoming_birth ).strip ():
163+ logger .debug ("demographics_match: birthDate mismatch %s != %s" , pds_birth , incoming_birth )
164+ return False
165+
166+ # Compare gender (case-insensitive)
167+ if pds_gender and incoming_gender :
168+ if _norm_str (pds_gender ) != _norm_str (incoming_gender ):
169+ logger .debug ("demographics_match: gender mismatch %s != %s" , pds_gender , incoming_gender )
170+ return False
171+
172+ # Compare names if both present (normalized)
173+ if pds_name and incoming_name :
174+ if _norm_str (pds_name ) != _norm_str (incoming_name ):
175+ logger .debug ("demographics_match: name mismatch %s != %s" , pds_name , incoming_name )
176+ return False
177+
178+ # If we reached here, all present fields matched (or were not present to compare)
179+ return True
180+ except Exception :
181+ logger .exception ("demographics_match: comparison failed with exception" )
182+ return False
0 commit comments