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:
@@ -431,79 +431,55 @@ class AutomationUnitedUcardClaimSubmit:
|
|||||||
|
|
||||||
def _select_search_result(self):
|
def _select_search_result(self):
|
||||||
"""
|
"""
|
||||||
When the Subscriber ID + DOB search matches multiple coverage records
|
After the Subscriber ID + DOB search, the site does one of two things:
|
||||||
("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.
|
|
||||||
|
|
||||||
Confirmed against the live site (2026-09-13): a results table with
|
1. Multiple coverage records exist ("Duplicate records have been
|
||||||
columns Name / Group / Coverage End Date / Address, a radio button
|
found") — a results table appears with a radio button in each
|
||||||
in the first cell of each row, and CANCEL / SELECT pill buttons
|
row (Name / Group / Coverage End Date / Address columns) and
|
||||||
below the table.
|
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:
|
try:
|
||||||
result_rows = WebDriverWait(self.driver, 15).until(
|
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:
|
except TimeoutException:
|
||||||
# A single unambiguous match may skip the results table entirely
|
result_rows = None
|
||||||
# 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"
|
|
||||||
|
|
||||||
if not result_rows:
|
if result_rows:
|
||||||
return "ERROR: No matching patient records found"
|
result = self._select_last_row_and_click_select(len(result_rows))
|
||||||
|
if result is not True:
|
||||||
total_rows = len(result_rows)
|
|
||||||
result = self._select_row_and_wait_for_member_link(-1, "last", total_rows, member_link_timeout=8)
|
|
||||||
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)
|
return result # propagate a hard ERROR (radio/select button not found)
|
||||||
|
else:
|
||||||
|
print("[UnitedUcard Claim] No insurance history/radio shown — single unambiguous match")
|
||||||
|
|
||||||
if total_rows < 2:
|
result = self._click_member_link_or_advance()
|
||||||
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:
|
if result is True:
|
||||||
return self._click_next("Patient Information step")
|
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
|
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,
|
Clicks the radio button in the LAST row of the insurance history
|
||||||
since a prior failed SELECT may have altered the DOM), clicks SELECT,
|
table, then clicks SELECT.
|
||||||
then waits up to `member_link_timeout` seconds for the "Member
|
|
||||||
Details" name link to appear.
|
|
||||||
|
|
||||||
Returns True (link found & clicked), "NO_MEMBER_LINK" (link never
|
Returns True, or an "ERROR: ..." string for a hard failure.
|
||||||
appeared — likely an inactive record), or an "ERROR: ..." string for
|
|
||||||
a hard failure (radio/select button not found).
|
|
||||||
"""
|
"""
|
||||||
try:
|
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:
|
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:
|
try:
|
||||||
radio = row.find_element(By.CSS_SELECTOR, "input[type='radio']")
|
radio = row.find_element(By.CSS_SELECTOR, "input[type='radio']")
|
||||||
@@ -511,7 +487,7 @@ class AutomationUnitedUcardClaimSubmit:
|
|||||||
try:
|
try:
|
||||||
radio = row.find_element(By.XPATH, ".//*[@role='radio' or contains(@class,'radio')]")
|
radio = row.find_element(By.XPATH, ".//*[@role='radio' or contains(@class,'radio')]")
|
||||||
except Exception as e:
|
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)
|
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio)
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
@@ -519,7 +495,7 @@ class AutomationUnitedUcardClaimSubmit:
|
|||||||
radio.click()
|
radio.click()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.driver.execute_script("arguments[0].click();", radio)
|
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):
|
# Confirmed against the live site (2026-09-13):
|
||||||
# <input type="button" class="btn btn-sm btn-primary pull-right margin-left-10"
|
# <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)")
|
print("[UnitedUcard Claim] Clicked SELECT (fallback selector)")
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
except TimeoutException:
|
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
|
return True
|
||||||
# patient's name as a link. Clicking it does NOT navigate away —
|
|
||||||
# it replaces the table with a confirmation card (avatar initials +
|
def _click_member_link_or_advance(self, timeout=15):
|
||||||
# name, Group Number, Subscriber ID, DOB, Address) on the same
|
"""
|
||||||
# Patient Information page, and enables the bottom "Next" button.
|
After SELECT (or immediately after Search, for a single
|
||||||
# If the selected record is inactive, this link never appears.
|
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:
|
try:
|
||||||
member_link = WebDriverWait(self.driver, member_link_timeout).until(
|
member_link = WebDriverWait(self.driver, timeout).until(
|
||||||
EC.element_to_be_clickable((By.XPATH,
|
EC.element_to_be_clickable((By.XPATH,
|
||||||
"//*[contains(text(),'Member Details')]/following::table[1]//a[1]"))
|
"//*[contains(text(),'Member Details')]/following::table[1]//a[1]"))
|
||||||
)
|
)
|
||||||
@@ -562,8 +547,16 @@ class AutomationUnitedUcardClaimSubmit:
|
|||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
return True
|
return True
|
||||||
except TimeoutException:
|
except TimeoutException:
|
||||||
print(f"[UnitedUcard Claim] Member Details link did not appear after selecting {row_label} row")
|
pass
|
||||||
return "NO_MEMBER_LINK"
|
|
||||||
|
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=""):
|
def _click_radio_by_id(self, element_id, label=""):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user