fix: handle no-history single-match case in United Ucard claim search

Only two paths now: click the radio in the last history row when a
table is shown, or click the Member Details link directly when no
history/radio appears at all (single unambiguous match, as seen for
patient Nassim). Removes the first-row retry fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 23:23:48 -04:00
co-authored by Claude Sonnet 5
parent 865b754ae1
commit 8cbdba092d
@@ -431,79 +431,55 @@ class AutomationUnitedUcardClaimSubmit:
def _select_search_result(self):
"""
When the Subscriber ID + DOB search matches multiple coverage records
("Duplicate records have been found"), select the LAST row in the
results table (the default/most-recent coverage record) and click
SELECT. If the "Member Details" name link doesn't show up afterward
(the last row turned out to be an inactive coverage record — seen
for some providers' patients, e.g. provider "JOHN"), fall back to
selecting the FIRST row instead and click SELECT again.
After the Subscriber ID + DOB search, the site does one of two things:
Confirmed against the live site (2026-09-13): a results table with
columns Name / Group / Coverage End Date / Address, a radio button
in the first cell of each row, and CANCEL / SELECT pill buttons
below the table.
1. Multiple coverage records exist ("Duplicate records have been
found") — a results table appears with a radio button in each
row (Name / Group / Coverage End Date / Address columns) and
CANCEL / SELECT pill buttons below it. Select the LAST row (the
default/most-recent coverage record) and click SELECT.
2. A single unambiguous match — no insurance history table and no
radio at all; the page goes straight to a "Member Details"
table with the patient's name as a link (confirmed live for
patient Nassim, 2026-09-15).
Either path ends by clicking the "Member Details" name link, which
replaces the table with a confirmation card and enables the bottom
"Next" button.
"""
try:
result_rows = WebDriverWait(self.driver, 15).until(
lambda d: d.find_elements(By.CSS_SELECTOR, "table tbody tr") or None
lambda d: [r for r in d.find_elements(By.CSS_SELECTOR, "table tbody tr")
if r.find_elements(By.CSS_SELECTOR, "input[type='radio']")] or None
)
except TimeoutException:
# A single unambiguous match may skip the results table entirely
# and advance straight to Claim Information.
try:
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, "//*[contains(text(),'Claim Information')]"))
)
print("[UnitedUcard Claim] Single match — already advanced to Claim Information")
return True
except TimeoutException:
return "ERROR: No search results table found after Search"
result_rows = None
if not result_rows:
return "ERROR: No matching patient records found"
if result_rows:
result = self._select_last_row_and_click_select(len(result_rows))
if result is not True:
return result # propagate a hard ERROR (radio/select button not found)
else:
print("[UnitedUcard Claim] No insurance history/radio shown — single unambiguous match")
total_rows = len(result_rows)
result = self._select_row_and_wait_for_member_link(-1, "last", total_rows, member_link_timeout=8)
result = self._click_member_link_or_advance()
if result is True:
return self._click_next("Patient Information step")
if result != "NO_MEMBER_LINK":
return result # propagate a hard ERROR (radio/select button not found)
if total_rows < 2:
return "ERROR: Member Details link not found after selecting last row"
print("[UnitedUcard Claim] Last row showed no active member — retrying with first row")
try:
WebDriverWait(self.driver, 10).until(
lambda d: d.find_elements(By.CSS_SELECTOR, "table tbody tr") or None
)
except TimeoutException:
return "ERROR: Results table no longer available to retry with first row"
result = self._select_row_and_wait_for_member_link(0, "first", total_rows, member_link_timeout=15)
if result is True:
return self._click_next("Patient Information step")
if result == "NO_MEMBER_LINK":
return "ERROR: Member Details link not found after selecting either the last or first row"
return result
def _select_row_and_wait_for_member_link(self, row_index, row_label, total_rows, member_link_timeout):
def _select_last_row_and_click_select(self, total_rows):
"""
Clicks the radio button in the row at `row_index` (re-located fresh,
since a prior failed SELECT may have altered the DOM), clicks SELECT,
then waits up to `member_link_timeout` seconds for the "Member
Details" name link to appear.
Clicks the radio button in the LAST row of the insurance history
table, then clicks SELECT.
Returns True (link found & clicked), "NO_MEMBER_LINK" (link never
appeared — likely an inactive record), or an "ERROR: ..." string for
a hard failure (radio/select button not found).
Returns True, or an "ERROR: ..." string for a hard failure.
"""
try:
row = self.driver.find_elements(By.CSS_SELECTOR, "table tbody tr")[row_index]
rows = [r for r in self.driver.find_elements(By.CSS_SELECTOR, "table tbody tr")
if r.find_elements(By.CSS_SELECTOR, "input[type='radio']")]
row = rows[-1]
except IndexError:
return f"ERROR: Could not re-locate {row_label} search result row"
return "ERROR: Could not re-locate last search result row"
try:
radio = row.find_element(By.CSS_SELECTOR, "input[type='radio']")
@@ -511,7 +487,7 @@ class AutomationUnitedUcardClaimSubmit:
try:
radio = row.find_element(By.XPATH, ".//*[@role='radio' or contains(@class,'radio')]")
except Exception as e:
return f"ERROR: Could not find radio button in {row_label} search result row: {e}"
return f"ERROR: Could not find radio button in last search result row: {e}"
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio)
time.sleep(0.3)
@@ -519,7 +495,7 @@ class AutomationUnitedUcardClaimSubmit:
radio.click()
except Exception:
self.driver.execute_script("arguments[0].click();", radio)
print(f"[UnitedUcard Claim] Selected {row_label} matching patient record ({total_rows} found)")
print(f"[UnitedUcard Claim] Selected last matching patient record ({total_rows} found)")
# Confirmed against the live site (2026-09-13):
# <input type="button" class="btn btn-sm btn-primary pull-right margin-left-10"
@@ -543,16 +519,25 @@ class AutomationUnitedUcardClaimSubmit:
print("[UnitedUcard Claim] Clicked SELECT (fallback selector)")
time.sleep(2)
except TimeoutException:
return f"ERROR: SELECT button not found after choosing {row_label} patient record"
return "ERROR: SELECT button not found after choosing last patient record"
# After SELECT, a "Member Details" table appears below with the
# patient's name as a link. Clicking it does NOT navigate away —
# it replaces the table with a confirmation card (avatar initials +
# name, Group Number, Subscriber ID, DOB, Address) on the same
# Patient Information page, and enables the bottom "Next" button.
# If the selected record is inactive, this link never appears.
return True
def _click_member_link_or_advance(self, timeout=15):
"""
After SELECT (or immediately after Search, for a single
unambiguous match with no history table), a "Member Details" table
appears with the patient's name as a link. Clicking it does NOT
navigate away — it replaces the table with a confirmation card
(avatar initials + name, Group Number, Subscriber ID, DOB, Address)
on the same Patient Information page, and enables the bottom
"Next" button.
Returns True (link found & clicked, or already advanced to Claim
Information), or an "ERROR: ..." string.
"""
try:
member_link = WebDriverWait(self.driver, member_link_timeout).until(
member_link = WebDriverWait(self.driver, timeout).until(
EC.element_to_be_clickable((By.XPATH,
"//*[contains(text(),'Member Details')]/following::table[1]//a[1]"))
)
@@ -562,8 +547,16 @@ class AutomationUnitedUcardClaimSubmit:
time.sleep(2)
return True
except TimeoutException:
print(f"[UnitedUcard Claim] Member Details link did not appear after selecting {row_label} row")
return "NO_MEMBER_LINK"
pass
try:
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, "//*[contains(text(),'Claim Information')]"))
)
print("[UnitedUcard Claim] Already advanced to Claim Information")
return True
except TimeoutException:
return "ERROR: Member Details link not found after Search"
def _click_radio_by_id(self, element_id, label=""):
try: