fix: BCBS MA — identify patient by member ID + DOB, prevent overwriting subscriber

- Added getPatientByInsuranceIdAndDob to storage
- Processor uses insuranceId + DOB as unique key instead of insuranceId alone
- Dependent with same subscriber ID but different DOB gets a new patient record
  (bypasses createOrUpdatePatientByInsuranceId which would overwrite subscriber)
- Name extraction anchored to "Relationship:" to scope Patient Info column only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-01 23:51:11 -04:00
parent 87d7ce9ed9
commit 3ac185b0ec
3 changed files with 87 additions and 31 deletions

View File

@@ -63,10 +63,45 @@ async function processBcbsMaResult(
? seleniumResult.lastName.trim() ? seleniumResult.lastName.trim()
: (formLastName ?? ""); : (formLastName ?? "");
await createOrUpdatePatientByInsuranceId({ insuranceId, firstName, lastName, dob: formDob, userId });
const normalizedInsuranceId = insuranceId.replace(/\s+/g, ""); const normalizedInsuranceId = insuranceId.replace(/\s+/g, "");
const patient = await storage.getPatientByInsuranceId(normalizedInsuranceId);
// Identify patient by member ID + DOB — not member ID alone.
// Dependents (e.g. Maria) share the subscriber's member ID but have a different DOB.
const dobDate = formDob ? new Date(formDob) : null;
let patient = dobDate
? await storage.getPatientByInsuranceIdAndDob(normalizedInsuranceId, dobDate)
: await storage.getPatientByInsuranceId(normalizedInsuranceId);
if (patient) {
// Existing patient found by insuranceId + DOB — update name
log("bcbs-ma-processor", `Patient matched by insuranceId+DOB id=${patient.id}`);
await storage.updatePatient(patient.id, { firstName, lastName });
} else {
// No match — create a brand new patient record.
// Do NOT use createOrUpdatePatientByInsuranceId here because it looks up by
// insuranceId alone and would overwrite the subscriber (e.g. Hugo) instead of
// creating a new record for the dependent (e.g. Maria).
log("bcbs-ma-processor", "No patient matched by insuranceId+DOB — creating new patient record");
try {
await storage.createPatient({
firstName,
lastName,
insuranceId: normalizedInsuranceId,
dateOfBirth: dobDate ?? undefined,
gender: "",
phone: "",
userId,
} as any);
} catch (createErr: any) {
log("bcbs-ma-processor", `createPatient failed: ${createErr?.message}`, createErr);
throw createErr;
}
patient = dobDate
? await storage.getPatientByInsuranceIdAndDob(normalizedInsuranceId, dobDate)
: await storage.getPatientByInsuranceId(normalizedInsuranceId);
}
if (!patient?.id) { if (!patient?.id) {
output.patientUpdateStatus = "Patient not found; no update performed"; output.patientUpdateStatus = "Patient not found; no update performed";
return output; return output;

View File

@@ -10,6 +10,7 @@ export interface IStorage {
// Patient methods // Patient methods
getPatient(id: number): Promise<Patient | undefined>; getPatient(id: number): Promise<Patient | undefined>;
getPatientByInsuranceId(insuranceId: string): Promise<Patient | null>; getPatientByInsuranceId(insuranceId: string): Promise<Patient | null>;
getPatientByInsuranceIdAndDob(insuranceId: string, dob: Date): Promise<Patient | null>;
getAllPatients(): Promise<Patient[]>; getAllPatients(): Promise<Patient[]>;
getRecentPatients(limit: number, offset: number): Promise<Patient[]>; getRecentPatients(limit: number, offset: number): Promise<Patient[]>;
getPatientsByIds(ids: number[]): Promise<Patient[]>; getPatientsByIds(ids: number[]): Promise<Patient[]>;
@@ -60,6 +61,19 @@ export const patientsStorage: IStorage = {
}); });
}, },
async getPatientByInsuranceIdAndDob(insuranceId: string, dob: Date): Promise<Patient | null> {
const startOfDay = new Date(dob);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(dob);
endOfDay.setHours(23, 59, 59, 999);
return db.patient.findFirst({
where: {
insuranceId,
dateOfBirth: { gte: startOfDay, lte: endOfDay },
},
});
},
async getRecentPatients(limit: number, offset: number): Promise<Patient[]> { async getRecentPatients(limit: number, offset: number): Promise<Patient[]> {
return db.patient.findMany({ return db.patient.findMany({
skip: offset, skip: offset,

View File

@@ -424,19 +424,24 @@ class AutomationBCBSMAEligibilityCheck:
else: else:
eligibility = "Unknown" eligibility = "Unknown"
# Extract first/last name from DOM — scope to "Patient Information" column only # Extract first/last name from Patient Information column only.
# to avoid picking up the duplicate values in the Subscriber Information column. # "Relationship:" is unique to the Patient column (not in Subscriber column).
# Use it as anchor: grab text from "Relationship:" up to "Member ID:" (Subscriber starts there).
import re
first_name = self.first_name first_name = self.first_name
last_name = self.last_name last_name = self.last_name
try: try:
# Find the "Patient Information" header, then search within its parent container # Slice out just the Patient Information section
patient_section = self.driver.find_element(By.XPATH, patient_match = re.search(
"//*[normalize-space(text())='Patient Information']/ancestor::*[3]" r"Relationship:(.+?)(?=Member ID:|Subscriber Information|Plan Name:)",
page_text,
re.DOTALL
) )
section_text = patient_section.text if patient_match:
print(f"[BCBS MA step2] Patient section text:\n{section_text[:300]}") patient_text = patient_match.group(1)
print(f"[BCBS MA step2] Patient section:\n{patient_text[:200]}")
lines = section_text.split("\n") lines = patient_text.split("\n")
capturing_last = False capturing_last = False
for line in lines: for line in lines:
stripped = line.strip() stripped = line.strip()
@@ -447,7 +452,7 @@ class AutomationBCBSMAEligibilityCheck:
first_name = val first_name = val
elif "Last Name:" in stripped and not last_name: elif "Last Name:" in stripped and not last_name:
val = stripped.split("Last Name:", 1)[1].strip() val = stripped.split("Last Name:", 1)[1].strip()
val = val.split("SSN:")[0].split("Date of Birth:")[0].split("Member ID:")[0].strip() val = val.split("SSN:")[0].split("Date of Birth:")[0].strip()
if val: if val:
last_name = val last_name = val
capturing_last = True capturing_last = True
@@ -457,6 +462,8 @@ class AutomationBCBSMAEligibilityCheck:
capturing_last = False capturing_last = False
if first_name and last_name and not capturing_last: if first_name and last_name and not capturing_last:
break break
else:
print("[BCBS MA step2] Patient section not found in page text")
print(f"[BCBS MA step2] Extracted — First: '{first_name}', Last: '{last_name}'") print(f"[BCBS MA step2] Extracted — First: '{first_name}', Last: '{last_name}'")
except Exception as e: except Exception as e: