feat: United Ucard claim searches by name+DOB and fills tooth number/surface when provided
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1537,6 +1537,8 @@ export function ClaimForm({
|
||||
insuranceProvider: "United Ucard",
|
||||
appointmentId: appointmentIdToUse,
|
||||
insuranceSiteKey: "UNITED_UCARD",
|
||||
firstName: patient?.firstName ?? "",
|
||||
lastName: patient?.lastName ?? "",
|
||||
claimId: createdClaim.id,
|
||||
claimFiles: claimFilesMeta,
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ service lines / attach files / submit / save confirmation PDF) are left as
|
||||
TODO stubs — fill these in once we know the exact DOM for the claim
|
||||
submission flow on the UHC Dental provider dashboard.
|
||||
"""
|
||||
from selenium.common.exceptions import WebDriverException, TimeoutException
|
||||
from selenium.common.exceptions import WebDriverException, TimeoutException, NoSuchElementException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
@@ -382,12 +382,12 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
|
||||
def step3_fill_patient_information(self):
|
||||
"""
|
||||
Fill the Patient Information step: Date of Service, Subscriber ID,
|
||||
Date of Birth, then click Search to look up the member.
|
||||
Fill the Patient Information step: Date of Service, choose the
|
||||
First Name + Last Name + DOB search, then First/Last Name, Date of Birth, then click Search to look up the member.
|
||||
|
||||
Confirmed against the live site (2026-09-13):
|
||||
<input type="date" class="date-input date-of-service" placeholder="mm/dd/yyyy">
|
||||
<input type="text" class="subscriber-id">
|
||||
<input type="text" class="member-fname"> / <input type="text" class="member-lname">
|
||||
<input type="date" class="date-input date-of-birth" placeholder="mm/dd/yyyy">
|
||||
<button class="search-button member-search">Search</button>
|
||||
"""
|
||||
@@ -402,12 +402,30 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
return "ERROR: Date of Service field not found on Patient Information step"
|
||||
|
||||
try:
|
||||
subscriber_id_field = self.driver.find_element(By.CSS_SELECTOR, "input.subscriber-id")
|
||||
subscriber_id_field.clear()
|
||||
subscriber_id_field.send_keys(self.memberId)
|
||||
print(f"[UnitedUcard Claim] Entered Subscriber ID: {self.memberId}")
|
||||
name_radio = WebDriverWait(self.driver, 10).until(
|
||||
EC.presence_of_element_located((By.CSS_SELECTOR, "input.searchname[name='member-search'][value='name']"))
|
||||
)
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", name_radio)
|
||||
try:
|
||||
name_radio.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", name_radio)
|
||||
print("[UnitedUcard Claim] Selected 'First Name + Last Name + DOB' search")
|
||||
except TimeoutException:
|
||||
return "ERROR: 'First Name + Last Name + DOB' radio not found on Patient Information step"
|
||||
|
||||
try:
|
||||
fname_field = WebDriverWait(self.driver, 10).until(
|
||||
EC.element_to_be_clickable((By.CSS_SELECTOR, "input.member-fname"))
|
||||
)
|
||||
fname_field.clear()
|
||||
fname_field.send_keys(self.firstName)
|
||||
lname_field = self.driver.find_element(By.CSS_SELECTOR, "input.member-lname")
|
||||
lname_field.clear()
|
||||
lname_field.send_keys(self.lastName)
|
||||
print(f"[UnitedUcard Claim] Entered name: {self.firstName} {self.lastName}")
|
||||
except Exception as e:
|
||||
return f"ERROR: Subscriber ID field not found on Patient Information step: {e}"
|
||||
return f"ERROR: First/Last Name fields not found on Patient Information step: {e}"
|
||||
|
||||
try:
|
||||
dob_field = self.driver.find_element(By.CSS_SELECTOR, "input.date-of-birth")
|
||||
@@ -646,6 +664,16 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
except TimeoutException:
|
||||
return "ERROR: 'Add a Procedure' button not found"
|
||||
|
||||
def _wait_visible(self, element_id, timeout=10):
|
||||
"""Return the element once visible, or None if it never appears
|
||||
(the portal only shows tooth fields for certain procedure codes)."""
|
||||
try:
|
||||
return WebDriverWait(self.driver, timeout).until(
|
||||
lambda d: next((e for e in d.find_elements(By.ID, element_id) if e.is_displayed()), None)
|
||||
)
|
||||
except TimeoutException:
|
||||
return None
|
||||
|
||||
def _fill_and_save_procedure(self, line: dict, idx: int):
|
||||
"""
|
||||
Fill one procedure row (Procedure Code, Date of Service, Procedure
|
||||
@@ -690,6 +718,28 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
except Exception as e:
|
||||
return f"ERROR: Procedure Fee field not found for line {idx + 1}: {e}"
|
||||
|
||||
# Tooth Number / Surface are driven by the app's claim data: only wait
|
||||
# for and fill them when the service line actually has a value.
|
||||
tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
|
||||
if tooth:
|
||||
tooth_field = self._wait_visible("toothNum")
|
||||
if tooth_field:
|
||||
tooth_field.clear()
|
||||
tooth_field.send_keys(tooth)
|
||||
print(f"[UnitedUcard Claim] Entered Tooth Number: {tooth}")
|
||||
else:
|
||||
print(f"[UnitedUcard Claim] Tooth Number field did not appear for {procedure_code}; skipped")
|
||||
|
||||
surface = str(line.get("toothSurface") or line.get("surface") or "").strip().upper()
|
||||
if surface:
|
||||
surface_field = self._wait_visible("toothSurf")
|
||||
if surface_field:
|
||||
surface_field.clear()
|
||||
surface_field.send_keys(surface)
|
||||
print(f"[UnitedUcard Claim] Entered Tooth Surface: {surface}")
|
||||
else:
|
||||
print(f"[UnitedUcard Claim] Tooth Surface field did not appear for {procedure_code}; skipped")
|
||||
|
||||
try:
|
||||
save_btn = WebDriverWait(self.driver, 10).until(
|
||||
EC.element_to_be_clickable((By.XPATH, "//*[@onclick='saveProcCode()']"))
|
||||
|
||||
Reference in New Issue
Block a user