fix: handle DDMA site redesign in eligibility check flow

DDMA now often skips the family-members results table and navigates
straight to the single-patient detail page, and labels the patient's
name "Subscriber name:" instead of "Name:". Update step1/step2 to
detect the detail-page URL correctly (it's nested under the search
path, so a plain substring check couldn't tell them apart), extract
the name from the new label, and fall back to parsing "Coverage is:"
for eligibility status when there's no results row to read it from.
Also fix a status-text substring bug ("active" matching inside
"inactive") and stop the backend from collapsing an unknown
eligibility result into INACTIVE. Skip name extraction entirely when
the caller already knows the patient (existing-patient recheck).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 21:20:38 -04:00
co-authored by Claude Sonnet 5
parent b860fd213f
commit f8235787db
2 changed files with 75 additions and 27 deletions
@@ -366,16 +366,26 @@ class AutomationDeltaDentalMAEligibilityCheck:
search_btn.click()
print("[DDMA step1] Clicked Search button")
# Wait for the Members results table to render
# Wait for either the Members results table to render, or (when the
# Subscriber ID + DOB match exactly one family member) DDMA to skip
# the results list and navigate straight to the patient detail page.
# Note: the detail page URL is nested UNDER the search page path
# (".../check-patient-benefits-search/check-patient-benefit-details"),
# so "check-patient-benefits-search" alone can't distinguish them —
# only the detail page's own "-benefit-details" segment can.
try:
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH, "//table//tbody//tr"))
lambda d: d.find_elements(By.XPATH, "//table//tbody//tr")
or "check-patient-benefit-details" in d.current_url.lower()
)
time.sleep(2)
except TimeoutException:
print("[DDMA step1] Warning: No results table appeared within timeout")
return "ERROR: INVALID SEARCH CRITERIA"
if "check-patient-benefit-details" in self.driver.current_url.lower():
print(f"[DDMA step1] Search navigated directly to detail page: {self.driver.current_url}")
print("[DDMA step1] Search completed successfully")
return "Success"
@@ -390,26 +400,40 @@ class AutomationDeltaDentalMAEligibilityCheck:
try:
import re
# Wait for results table, then pause for full render
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
)
time.sleep(2) # Let the row content fully render after table appears
except TimeoutException:
print("[DDMA step2] Warning: Results table not found within timeout")
eligibilityText = "unknown"
foundMemberId = self.memberId or ""
patientName = ""
# If the caller already knows this patient (e.g. an existing patient record
# being rechecked), use that name directly — no need to scrape/parse it off
# the DDMA page, only the eligibility status is actually needed.
patientName = f"{self.firstName} {self.lastName}".strip() if self.firstName and self.lastName else ""
known_patient_name = bool(patientName)
# The Members table lists the whole family sharing this subscriber ID —
# find the row matching this patient's DOB (falls back to last name, then first row).
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
print(f"[DDMA step2] Found {len(rows)} family member row(s)")
# DDMA now sometimes skips the family-members results table entirely and
# navigates straight to the single-patient detail page (when Subscriber ID +
# DOB match exactly one person). In that case there's no members table to
# parse here — skip straight to the detail-page fallback extraction below.
already_on_detail_page = "check-patient-benefit-details" in self.driver.current_url.lower()
rows = []
target_row = None
if not already_on_detail_page:
# Wait for results table, then pause for full render
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
)
time.sleep(2) # Let the row content fully render after table appears
except TimeoutException:
print("[DDMA step2] Warning: Results table not found within timeout")
# The Members table lists the whole family sharing this subscriber ID —
# find the row matching this patient's DOB (falls back to last name, then first row).
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
print(f"[DDMA step2] Found {len(rows)} family member row(s)")
else:
print(f"[DDMA step2] Already on detail page: {self.driver.current_url}")
if self.dateOfBirth and rows:
try:
dob_parts = self.dateOfBirth.split("-")
@@ -436,17 +460,17 @@ class AutomationDeltaDentalMAEligibilityCheck:
if target_row is not None:
try:
cells = target_row.find_elements(By.XPATH, "./td")
if cells:
if cells and not known_patient_name:
patientName = cells[0].text.strip()
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'")
if len(cells) >= 2 and cells[1].text.strip():
foundMemberId = cells[1].text.strip()
if len(cells) >= 7:
status_text = cells[6].text.strip().lower()
if "active" in status_text:
eligibilityText = "active"
elif "inactive" in status_text or "termed" in status_text:
if "inactive" in status_text or "termed" in status_text:
eligibilityText = "inactive"
elif "active" in status_text:
eligibilityText = "active"
else:
eligibilityText = status_text
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
@@ -463,10 +487,10 @@ class AutomationDeltaDentalMAEligibilityCheck:
else:
print("[DDMA step2] Warning: No family member rows found in search results")
# Wait for the detail page to load (URL moves away from the search page)
# Wait for the detail page to load (URL gains the "-benefit-details" segment)
try:
WebDriverWait(self.driver, 15).until(
lambda d: "check-patient-benefits-search" not in d.current_url
lambda d: "check-patient-benefit-details" in d.current_url.lower()
)
print(f"[DDMA step2] Navigated to detail page: {self.driver.current_url}")
except Exception:
@@ -522,11 +546,13 @@ class AutomationDeltaDentalMAEligibilityCheck:
# forward past accessibility text to the first plausible name value.
for i, line in enumerate(page_lines):
# Case 1: "Name : Value" or "Name: Value" on the same line
# (DDMA's detail page labels this "Subscriber name:", so that
# prefix must be accepted rather than excluded)
same_line = re.match(
r'^(?:member\s+)?name\s*[:\-]\s*(.+)$', line, re.IGNORECASE
r'^(?:member\s+|subscriber\s+)?name\s*[:\-]\s*(.+)$', line, re.IGNORECASE
)
if same_line and not re.search(
r'(provider|group|subscriber|plan)\s+name', line, re.IGNORECASE
r'(provider|group|plan)\s+name', line, re.IGNORECASE
):
candidate = same_line.group(1).strip()
if looks_like_name(candidate):
@@ -535,7 +561,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
break
# Case 2: "Name" or "Name:" alone on a line — scan forward for value
elif re.match(r'^(?:member\s+)?name\s*:?\s*$', line, re.IGNORECASE):
elif re.match(r'^(?:member\s+|subscriber\s+)?name\s*:?\s*$', line, re.IGNORECASE):
for j in range(i + 1, min(i + 6, len(page_lines))):
candidate = page_lines[j].strip()
if looks_like_name(candidate):
@@ -554,6 +580,27 @@ class AutomationDeltaDentalMAEligibilityCheck:
if not patientName:
print("[DDMA step2] Could not extract patient name from detail page")
# Fallback: refine eligibility status from the detail page's "Coverage is:"
# label if the family-table row extraction didn't set it (e.g. DDMA
# navigated straight to the detail page, skipping the members table).
if eligibilityText == "unknown":
try:
page_text = self.driver.execute_script("return document.body.innerText;")
coverage_match = re.search(
r'coverage\s+is\s*[:\-]?\s*([A-Za-z]+)', page_text, re.IGNORECASE
)
if coverage_match:
status_text = coverage_match.group(1).strip().lower()
if "inactive" in status_text or "termed" in status_text:
eligibilityText = "inactive"
elif "active" in status_text:
eligibilityText = "active"
else:
eligibilityText = status_text
print(f"[DDMA step2] Extracted eligibility from 'Coverage is:' label: '{eligibilityText}'")
except Exception as e:
print(f"[DDMA step2] Could not extract eligibility from 'Coverage is:' label: {e}")
# Wait for the page to be fully ready before capturing PDF
try:
WebDriverWait(self.driver, 30).until(