fix: route United Ucard claims correctly and add fee schedule
- AI-column auto-submit and manual "Claim All" dispatchers in claim-form.tsx had no United Ucard branch, so United Ucard claims silently fell through to the MassHealth (MH) submit handler instead of opening the United Ucard site. - Added a United Ucard fee schedule (copied from United SCO as a starting point) and wired it into the frontend price-mapping/mismatch-check code and the backend fee-schedule update route, which previously had no schedule for United Ucard and fell back to MassHealth's prices. - Selenium claim worker: when the member search returns multiple coverage records, it now tries the last row first (existing default) and falls back to the first row only if the "Member Details" link never appears (i.e. the last row was an inactive record), instead of only ever trying one or the other. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -412,14 +412,17 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
except TimeoutException:
|
||||
return "ERROR: Search button not found on Patient Information step"
|
||||
|
||||
return self._select_last_search_result()
|
||||
return self._select_search_result()
|
||||
|
||||
def _select_last_search_result(self):
|
||||
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 (per business rule — the last entry is the current/
|
||||
most-recent coverage record) and click SELECT.
|
||||
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
|
||||
columns Name / Group / Coverage End Date / Address, a radio button
|
||||
@@ -445,14 +448,55 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
if not result_rows:
|
||||
return "ERROR: No matching patient records found"
|
||||
|
||||
last_row = result_rows[-1]
|
||||
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)
|
||||
|
||||
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:
|
||||
radio = last_row.find_element(By.CSS_SELECTOR, "input[type='radio']")
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
|
||||
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).
|
||||
"""
|
||||
try:
|
||||
row = self.driver.find_elements(By.CSS_SELECTOR, "table tbody tr")[row_index]
|
||||
except IndexError:
|
||||
return f"ERROR: Could not re-locate {row_label} search result row"
|
||||
|
||||
try:
|
||||
radio = row.find_element(By.CSS_SELECTOR, "input[type='radio']")
|
||||
except Exception:
|
||||
try:
|
||||
radio = last_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:
|
||||
return f"ERROR: Could not find radio button in last search result row: {e}"
|
||||
return f"ERROR: Could not find radio button in {row_label} search result row: {e}"
|
||||
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio)
|
||||
time.sleep(0.3)
|
||||
@@ -460,7 +504,7 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
radio.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", radio)
|
||||
print(f"[UnitedUcard Claim] Selected last matching patient record ({len(result_rows)} found)")
|
||||
print(f"[UnitedUcard Claim] Selected {row_label} 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"
|
||||
@@ -484,15 +528,16 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
print("[UnitedUcard Claim] Clicked SELECT (fallback selector)")
|
||||
time.sleep(2)
|
||||
except TimeoutException:
|
||||
return "ERROR: SELECT button not found after choosing patient record"
|
||||
return f"ERROR: SELECT button not found after choosing {row_label} 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.
|
||||
try:
|
||||
member_link = WebDriverWait(self.driver, 15).until(
|
||||
member_link = WebDriverWait(self.driver, member_link_timeout).until(
|
||||
EC.element_to_be_clickable((By.XPATH,
|
||||
"//*[contains(text(),'Member Details')]/following::table[1]//a[1]"))
|
||||
)
|
||||
@@ -500,10 +545,10 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
member_link.click()
|
||||
print(f"[UnitedUcard Claim] Clicked member link: {member_name}")
|
||||
time.sleep(2)
|
||||
return True
|
||||
except TimeoutException:
|
||||
return "ERROR: Member Details link not found after selecting patient record"
|
||||
|
||||
return self._click_next("Patient Information step")
|
||||
print(f"[UnitedUcard Claim] Member Details link did not appear after selecting {row_label} row")
|
||||
return "NO_MEMBER_LINK"
|
||||
|
||||
def _click_radio_by_id(self, element_id, label=""):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user