feat: United Ucard name+DOB eligibility search via chat, duplicate-record handling, Product ID as member ID
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -394,48 +394,183 @@ class AutomationUnitedUcardEligibilityCheck:
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter Service Date: {e}"
|
||||
|
||||
# Member Date of Birth
|
||||
try:
|
||||
dob_digits = re.sub(r"\D", "", self._format_dob(self.dateOfBirth))
|
||||
_fill_field("eligMemDob", dob_digits, "Member DOB")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter Member Date of Birth: {e}"
|
||||
# Attempt 1: Subscriber ID + DOB (default "Search By" radio).
|
||||
# Skipped if we have no member ID.
|
||||
if self.memberId:
|
||||
err = self._search_by_subscriber_id(_fill_field)
|
||||
if err:
|
||||
return err
|
||||
if self._search_succeeded():
|
||||
print("[UnitedUcard step1] Eligibility search submitted (Subscriber ID)")
|
||||
return "Success"
|
||||
print("[UnitedUcard step1] Subscriber ID + DOB search did not return a result")
|
||||
else:
|
||||
print("[UnitedUcard step1] No memberId provided; going straight to name search")
|
||||
|
||||
# Subscriber ID
|
||||
try:
|
||||
_fill_field("eligSubsId", self.memberId, "Subscriber ID")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter Subscriber ID: {e}"
|
||||
# Attempt 2: fall back to First Name + Last Name + DOB.
|
||||
if not (self.firstName and self.lastName and self.dateOfBirth):
|
||||
return "ERROR: Subscriber ID + DOB search failed and first name / last name / DOB are not all available for a name search"
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# Click SEARCH button
|
||||
try:
|
||||
search_btn = WebDriverWait(self.driver, 10).until(
|
||||
EC.presence_of_element_located((By.XPATH,
|
||||
"//input[@type='button' and @value='search']"))
|
||||
)
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", search_btn)
|
||||
time.sleep(0.3)
|
||||
try:
|
||||
search_btn.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", search_btn)
|
||||
print("[UnitedUcard step1] Clicked SEARCH button")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not click SEARCH button: {e}"
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
# TODO: confirm what the results look like (same page update? new
|
||||
# tab? modal?) once we see it, then extend step2() accordingly.
|
||||
print("[UnitedUcard step1] Eligibility search submitted")
|
||||
return "Success"
|
||||
print(f"[UnitedUcard step1] Falling back to name search: {self.firstName} {self.lastName}, DOB={self.dateOfBirth}")
|
||||
err = self._search_by_name(_fill_field)
|
||||
if err:
|
||||
return err
|
||||
if self._search_succeeded():
|
||||
print("[UnitedUcard step1] Eligibility search submitted (Name)")
|
||||
return "Success"
|
||||
return "ERROR: Eligibility search returned no result by Subscriber ID + DOB or by Name + DOB"
|
||||
|
||||
except Exception as e:
|
||||
print(f"[UnitedUcard step1] Exception: {e}")
|
||||
return f"ERROR:STEP1 - {e}"
|
||||
|
||||
def _click_search(self):
|
||||
search_btn = WebDriverWait(self.driver, 10).until(
|
||||
EC.presence_of_element_located((By.XPATH,
|
||||
"//input[@type='button' and @value='search']"))
|
||||
)
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", search_btn)
|
||||
time.sleep(0.3)
|
||||
try:
|
||||
search_btn.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", search_btn)
|
||||
print("[UnitedUcard step1] Clicked SEARCH button")
|
||||
time.sleep(3)
|
||||
|
||||
def _select_search_by(self, value):
|
||||
"""Click the 'Search By' radio (value 'searchbyname' or the subscriber ID one)."""
|
||||
radio = WebDriverWait(self.driver, 10).until(
|
||||
EC.presence_of_element_located((By.CSS_SELECTOR, f"input[type='radio'][name='searchby'][value='{value}']"))
|
||||
)
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio)
|
||||
try:
|
||||
radio.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", radio)
|
||||
time.sleep(1)
|
||||
print(f"[UnitedUcard step1] Selected Search By radio: {value}")
|
||||
|
||||
def _search_by_subscriber_id(self, _fill_field):
|
||||
"""Returns an error string, or None once SEARCH has been clicked."""
|
||||
try:
|
||||
dob_digits = re.sub(r"\D", "", self._format_dob(self.dateOfBirth))
|
||||
_fill_field("eligMemDob", dob_digits, "Member DOB")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter Member Date of Birth: {e}"
|
||||
try:
|
||||
_fill_field("eligSubsId", self.memberId, "Subscriber ID")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter Subscriber ID: {e}"
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
self._click_search()
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not click SEARCH button: {e}"
|
||||
return None
|
||||
|
||||
def _search_by_name(self, _fill_field):
|
||||
"""Switch to the 'Name' radio and search by first/last name + DOB.
|
||||
Returns an error string, or None once SEARCH has been clicked."""
|
||||
try:
|
||||
self._select_search_by("searchbyname")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not select the Name search radio: {e}"
|
||||
|
||||
# Switching the radio re-renders the form, so re-enter Service Date too.
|
||||
try:
|
||||
from datetime import date
|
||||
_fill_field("serviceDate", date.today().strftime("%m%d%Y"), "Service Date")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter Service Date: {e}"
|
||||
|
||||
for field_id, value, label in (("eligFname", self.firstName, "First Name"),
|
||||
("eligLname", self.lastName, "Last Name")):
|
||||
try:
|
||||
_fill_field(field_id, value, label)
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter {label}: {e}"
|
||||
|
||||
try:
|
||||
dob_digits = re.sub(r"\D", "", self._format_dob(self.dateOfBirth))
|
||||
_fill_field("eligMemDob", dob_digits, "Member DOB")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not enter Member Date of Birth: {e}"
|
||||
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
self._click_search()
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not click SEARCH button: {e}"
|
||||
return None
|
||||
|
||||
def _handle_duplicate_records(self):
|
||||
"""If the portal lists duplicate member records ("Duplicate records have been
|
||||
found."), select the Active one (falling back to the last row) and resubmit.
|
||||
Returns True if a record was selected."""
|
||||
radios = self.driver.find_elements(By.CSS_SELECTOR, "input[type='radio'][name='refineMemDupliGroup']")
|
||||
radios = [r for r in radios if r.is_displayed()]
|
||||
if not radios:
|
||||
return False
|
||||
|
||||
chosen = None
|
||||
for r in radios:
|
||||
try:
|
||||
row_text = r.find_element(By.XPATH, "./ancestor::tr[1]").text.lower()
|
||||
except Exception:
|
||||
row_text = ""
|
||||
if "active" in row_text and "termed" not in row_text and "inactive" not in row_text:
|
||||
chosen = r
|
||||
break
|
||||
if chosen is None:
|
||||
chosen = radios[-1]
|
||||
|
||||
print(f"[UnitedUcard step1] {len(radios)} duplicate records; selecting value={chosen.get_attribute('value')}")
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", chosen)
|
||||
try:
|
||||
chosen.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", chosen)
|
||||
time.sleep(1.5)
|
||||
|
||||
# Click the "select" button (ng-click="refineSearch()") shown below the duplicate list.
|
||||
try:
|
||||
select_btn = WebDriverWait(self.driver, 10).until(
|
||||
EC.element_to_be_clickable((By.XPATH,
|
||||
"//input[@type='button' and @value='select' and contains(@ng-click,'refineSearch')]"))
|
||||
)
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", select_btn)
|
||||
try:
|
||||
select_btn.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", select_btn)
|
||||
print("[UnitedUcard step1] Clicked select button for duplicate record")
|
||||
except Exception as e:
|
||||
print(f"[UnitedUcard step1] Could not click select button: {e}")
|
||||
return True
|
||||
|
||||
def _search_succeeded(self):
|
||||
"""True once the browser lands on the Eligibility Summary page; False as
|
||||
soon as the form shows a "Member not found" error (or after 30s).
|
||||
Duplicate-record lists are resolved along the way."""
|
||||
deadline = time.time() + 30
|
||||
duplicates_handled = False
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
if "eligibility-summary" in self.driver.current_url.lower():
|
||||
return True
|
||||
body = self.driver.find_element(By.TAG_NAME, "body").text.lower()
|
||||
if "member not found" in body:
|
||||
print("[UnitedUcard step1] Portal reported: Member not found")
|
||||
return False
|
||||
if not duplicates_handled and self._handle_duplicate_records():
|
||||
duplicates_handled = True
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"[UnitedUcard step1] Result check error: {e}")
|
||||
time.sleep(0.5)
|
||||
return False
|
||||
|
||||
def step2(self):
|
||||
"""
|
||||
Extract patient/eligibility info from the "Eligibility Summary" page
|
||||
@@ -462,6 +597,13 @@ class AutomationUnitedUcardEligibilityCheck:
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
# Wait for the summary content (not just the page shell) before reading/printing it.
|
||||
try:
|
||||
WebDriverWait(self.driver, 20).until(
|
||||
lambda d: "insurance information" in d.find_element(By.TAG_NAME, "body").text.lower()
|
||||
)
|
||||
except Exception:
|
||||
print("[UnitedUcard step2] WARNING: 'Insurance Information' not visible yet; capturing anyway")
|
||||
time.sleep(2)
|
||||
|
||||
current_url = self.driver.current_url
|
||||
@@ -522,8 +664,21 @@ class AutomationUnitedUcardEligibilityCheck:
|
||||
if not name_extracted:
|
||||
print("[UnitedUcard step2] WARNING: Could not extract patient name from page; using form values")
|
||||
|
||||
# ── Member ID ────────────────────────────────────────────────
|
||||
# The Subscriber ID badge is masked, but "Product ID" is the full ID
|
||||
# (the value the Subscriber ID search field accepts, e.g. "D0033034").
|
||||
# This page looks the same however the search was done (ID or name),
|
||||
# so always prefer it; fall back to the ID we searched with.
|
||||
memberId = self.memberId
|
||||
pid_match = re.search(r'Product ID:\s*([A-Za-z0-9]+)', page_text)
|
||||
if pid_match:
|
||||
memberId = pid_match.group(1)
|
||||
print(f"[UnitedUcard step2] Member ID from Product ID: {memberId}")
|
||||
else:
|
||||
print(f"[UnitedUcard step2] WARNING: Product ID not found on page; using searched ID '{memberId}'")
|
||||
|
||||
# ── Capture PDF ──────────────────────────────────────────────
|
||||
pdf_path = self._capture_pdf(self.memberId)
|
||||
pdf_path = self._capture_pdf(memberId)
|
||||
if not pdf_path:
|
||||
return {"status": "error", "message": "STEP2 FAILED: Could not generate PDF"}
|
||||
|
||||
@@ -539,10 +694,9 @@ class AutomationUnitedUcardEligibilityCheck:
|
||||
"ss_path": pdf_path,
|
||||
"pdf_path": pdf_path,
|
||||
"patientName": patientName,
|
||||
# NOTE: Subscriber ID shown on this page is masked (e.g.
|
||||
# "******444"), so we return the ID we searched with rather
|
||||
# than a scraped-and-truncated value.
|
||||
"memberId": self.memberId,
|
||||
# NOTE: the Subscriber ID badge is masked (e.g. "******444"), so
|
||||
# we return the page's Product ID (falling back to the searched ID).
|
||||
"memberId": memberId,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user