feat: CCA pre-auth — select first member in multi-record popup, retry code/tooth/billed fills, stop instead of submitting incomplete lines

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 00:56:36 -04:00
co-authored by Claude Sonnet 5
parent 8c0fcb4267
commit a5a15d86bc
@@ -363,6 +363,20 @@ class AutomationCCAPreAuth:
if not verified: if not verified:
return "ERROR: 'Verify Eligibility' button not found" return "ERROR: 'Verify Eligibility' button not found"
# Multiple records popup: pick the first member (top Select)
try:
sel_btn = WebDriverWait(self.driver, 12).until(
EC.element_to_be_clickable((By.ID, "buttonSelectMember0"))
)
print("[CCA PreAuth step2] Multiple records popup — clicking top Select")
try:
sel_btn.click()
except Exception:
self.driver.execute_script("arguments[0].click();", sel_btn)
time.sleep(2)
except TimeoutException:
print("[CCA PreAuth step2] No multiple-records popup")
print("[CCA PreAuth step2] Waiting for eligibility result...") print("[CCA PreAuth step2] Waiting for eligibility result...")
try: try:
WebDriverWait(self.driver, 25).until( WebDriverWait(self.driver, 25).until(
@@ -681,6 +695,7 @@ class AutomationCCAPreAuth:
# ── Code: click td[2] to activate → find ServicesCODE by ID globally ── # ── Code: click td[2] to activate → find ServicesCODE by ID globally ──
code_filled = False code_filled = False
for _ca in range(1, 4):
for xpath in [ for xpath in [
f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[2]", f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[2]",
f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[2]", f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[2]",
@@ -699,90 +714,88 @@ class AutomationCCAPreAuth:
print(f"[CCA PreAuth step3] Row {idx+1}: code={code!r} entered") print(f"[CCA PreAuth step3] Row {idx+1}: code={code!r} entered")
code_filled = True code_filled = True
break break
except Exception: except Exception as ce:
print(f"[CCA PreAuth step3] Row {idx+1}: code attempt {_ca}/3 {ce.__class__.__name__}")
continue continue
if code_filled:
break
time.sleep(1.5)
if not code_filled: if not code_filled:
print(f"[CCA PreAuth step3] Row {idx+1}: Code fill failed — skipping row") print(f"[CCA PreAuth step3] Row {idx+1}: Code fill failed")
continue return (f"ERROR: failed to fill code for service line {idx+1} "
f"(code={code}) — pre-auth NOT submitted")
# ── Tooth: click td[4] in Services grid → wait for input → clear → type ── # ── Tooth: click td[4] → wait for input → clear → type (retry on stale/not-ready) ──
if tooth: if tooth:
try:
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
tooth_done = False
time.sleep(0.8) # grid re-renders the row after the code is entered
for _ta in range(1, 5):
try:
tooth_cell = None tooth_cell = None
tooth_cell_xpaths = [ for xpath in [
f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[4]", f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[4]",
f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[4]", f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[4]",
f"//div[@id='Services']//div[contains(@class,'e-content')]//tr[{idx+1}]/td[4]", f"//div[@id='Services']//div[contains(@class,'e-content')]//tr[{idx+1}]/td[4]",
] ]:
# Re-find cell fresh each attempt to avoid StaleElement
for xpath in tooth_cell_xpaths:
try: try:
tooth_cell = self.driver.find_element(By.XPATH, xpath) tooth_cell = self.driver.find_element(By.XPATH, xpath)
break break
except Exception: except Exception:
continue continue
if not tooth_cell:
if tooth_cell: raise Exception("tooth cell not found")
self.driver.execute_script( self.driver.execute_script(
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", tooth_cell) "arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", tooth_cell)
if _ta >= 3:
ActionChains(self.driver).double_click(tooth_cell).perform()
else:
tooth_cell.click() tooth_cell.click()
time.sleep(0.3) time.sleep(0.4)
# Wait until an input becomes active/focused inside the cell # Fresh lookup every time: prefer the pre-rendered ServicesTOOTH input
tooth_input = None tooth_input = None
for _ in range(10): for _ in range(8):
cands = [e for e in self.driver.find_elements(By.ID, "ServicesTOOTH")
if e.is_displayed()]
if cands:
tooth_input = cands[0]
break
active = self.driver.switch_to.active_element active = self.driver.switch_to.active_element
tag = active.tag_name.lower() if active else "" if active.tag_name.lower() == "input":
if tag == "input":
tooth_input = active tooth_input = active
break break
# Try clicking again to trigger edit mode time.sleep(0.25)
try: if not tooth_input:
tooth_cell = self.driver.find_element(By.XPATH, tooth_cell_xpaths[0]) raise Exception("tooth input not active")
except Exception:
pass
try:
tooth_cell.click()
except Exception:
pass
time.sleep(0.2)
if tooth_input: try:
# Clear → Type → Verify (with JS fallback)
tooth_input.click()
tooth_input.send_keys(Keys.CONTROL + "a") tooth_input.send_keys(Keys.CONTROL + "a")
tooth_input.send_keys(Keys.DELETE) tooth_input.send_keys(Keys.DELETE)
time.sleep(0.1)
for ch in str(tooth): for ch in str(tooth):
tooth_input.send_keys(ch) tooth_input.send_keys(ch)
time.sleep(0.05) time.sleep(0.05)
except Exception:
pass
time.sleep(0.2) time.sleep(0.2)
# Verify value was entered; fall back to JS inject if empty if (tooth_input.get_attribute("value") or "").strip() != str(tooth):
entered = tooth_input.get_attribute("value") or ""
if entered.strip() == "":
self.driver.execute_script( self.driver.execute_script(
"arguments[0].value = arguments[1];" "arguments[0].focus();arguments[0].value = arguments[1];"
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));" "arguments[0].dispatchEvent(new Event('input',{bubbles:true}));"
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));", "arguments[0].dispatchEvent(new Event('change',{bubbles:true}));",
tooth_input, str(tooth)) tooth_input, str(tooth))
time.sleep(0.2) time.sleep(0.2)
print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered") print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered")
else: tooth_done = True
# Fallback: JS inject directly into active element break
active = self.driver.switch_to.active_element except Exception as te:
self.driver.execute_script( print(f"[CCA PreAuth step3] Row {idx+1}: tooth attempt {_ta}/4: {te.__class__.__name__}")
"arguments[0].value = arguments[1];" time.sleep(1.0)
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));" if not tooth_done:
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));", return (f"ERROR: failed to fill tooth for service line {idx+1} "
active, str(tooth)) f"(code={code}) — pre-auth NOT submitted")
time.sleep(0.2)
print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered via JS fallback")
else:
print(f"[CCA PreAuth step3] Row {idx+1}: Tooth cell not found — skipping")
except Exception as e:
print(f"[CCA PreAuth step3] Row {idx+1}: Tooth fill failed: {e} — skipping tooth")
# ── Billed Amount: click TD cell to activate edit mode (turn yellow), # ── Billed Amount: click TD cell to activate edit mode (turn yellow),
# wait for input, delete default 0.00, type amount ── # wait for input, delete default 0.00, type amount ──
@@ -807,38 +820,52 @@ class AutomationCCAPreAuth:
billed_cell.click() billed_cell.click()
time.sleep(0.3) time.sleep(0.3)
# Wait until the input activates (cell turns yellow = edit mode) # Activate the editor and type. "element not interactable" happens when the
# grid hasn't finished the previous row, so re-click and retry.
from selenium.webdriver.common.action_chains import ActionChains
js_set = (
"arguments[0].focus();arguments[0].value = arguments[1];"
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));"
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));")
billed_input = None billed_input = None
for _ in range(10): for attempt in range(1, 5):
try: try:
billed_input = WebDriverWait(self.driver, 1).until( cands = [e for e in self.driver.find_elements(By.ID, "ServicesBILLED_AMOUNT")
EC.element_to_be_clickable((By.ID, "ServicesBILLED_AMOUNT"))) if e.is_displayed()]
if not cands:
raise Exception("no visible ServicesBILLED_AMOUNT input")
inp = cands[0]
try:
inp.send_keys(Keys.CONTROL + "a")
inp.send_keys(Keys.DELETE)
for ch in str(billed_str):
inp.send_keys(ch)
time.sleep(0.05)
except Exception as ke:
print(f"[CCA PreAuth step3] Row {idx+1}: typing failed ({ke.__class__.__name__}), using JS")
self.driver.execute_script(js_set, inp, str(billed_str))
time.sleep(0.2)
if (inp.get_attribute("value") or "").strip() in ("", "0.00", "0"):
self.driver.execute_script(js_set, inp, str(billed_str))
time.sleep(0.2)
billed_input = inp
break break
except Exception: except Exception as ae:
pass print(f"[CCA PreAuth step3] Row {idx+1}: billed attempt {attempt}/4: {ae.__class__.__name__}")
time.sleep(0.8)
try: try:
billed_cell = self.driver.find_element(By.XPATH, billed_cell_xpaths[0]) billed_cell = self.driver.find_element(By.XPATH, billed_cell_xpaths[0])
self.driver.execute_script(
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", billed_cell)
if attempt >= 2:
ActionChains(self.driver).double_click(billed_cell).perform()
else:
billed_cell.click() billed_cell.click()
time.sleep(0.5)
except Exception: except Exception:
pass pass
time.sleep(0.2)
if billed_input: if billed_input:
billed_input.send_keys(Keys.DELETE)
time.sleep(0.3)
for ch in str(billed_str):
billed_input.send_keys(ch)
time.sleep(0.05)
time.sleep(0.2)
# Verify; fallback to JS if empty
entered = billed_input.get_attribute("value") or ""
if entered.strip() == "":
self.driver.execute_script(
"arguments[0].value = arguments[1];"
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));"
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));",
billed_input, str(billed_str))
time.sleep(0.2)
print(f"[CCA PreAuth step3] Row {idx+1}: billed={billed_str!r} entered") print(f"[CCA PreAuth step3] Row {idx+1}: billed={billed_str!r} entered")
else: else:
print(f"[CCA PreAuth step3] Row {idx+1}: Billed Amount input not activated — skipping") print(f"[CCA PreAuth step3] Row {idx+1}: Billed Amount input not activated — skipping")