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:
2026-09-20 23:40:50 -04:00
co-authored by Claude Sonnet 5
parent 7ec82902d9
commit c719b20dd0
2 changed files with 61 additions and 9 deletions
@@ -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()']"))