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
+134 -107
View File
@@ -363,6 +363,20 @@ class AutomationCCAPreAuth:
if not verified:
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...")
try:
WebDriverWait(self.driver, 25).until(
@@ -681,108 +695,107 @@ class AutomationCCAPreAuth:
# ── Code: click td[2] to activate → find ServicesCODE by ID globally ──
code_filled = False
for xpath in [
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-content')]//tr[{idx+1}]/td[2]",
]:
try:
code_cell = self.driver.find_element(By.XPATH, xpath)
self.driver.execute_script(
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", code_cell)
code_cell.click()
time.sleep(0.5)
code_input = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.ID, "ServicesCODE")))
code_input.send_keys(code)
time.sleep(1.5)
print(f"[CCA PreAuth step3] Row {idx+1}: code={code!r} entered")
code_filled = True
for _ca in range(1, 4):
for xpath in [
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-content')]//tr[{idx+1}]/td[2]",
]:
try:
code_cell = self.driver.find_element(By.XPATH, xpath)
self.driver.execute_script(
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", code_cell)
code_cell.click()
time.sleep(0.5)
code_input = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.ID, "ServicesCODE")))
code_input.send_keys(code)
time.sleep(1.5)
print(f"[CCA PreAuth step3] Row {idx+1}: code={code!r} entered")
code_filled = True
break
except Exception as ce:
print(f"[CCA PreAuth step3] Row {idx+1}: code attempt {_ca}/3 {ce.__class__.__name__}")
continue
if code_filled:
break
except Exception:
continue
time.sleep(1.5)
if not code_filled:
print(f"[CCA PreAuth step3] Row {idx+1}: Code fill failed — skipping row")
continue
print(f"[CCA PreAuth step3] Row {idx+1}: Code fill failed")
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:
try:
from selenium.webdriver.common.action_chains import ActionChains
tooth_cell = None
tooth_cell_xpaths = [
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-content')]//tr[{idx+1}]/td[4]",
]
# Re-find cell fresh each attempt to avoid StaleElement
for xpath in tooth_cell_xpaths:
try:
tooth_cell = self.driver.find_element(By.XPATH, xpath)
break
except Exception:
continue
if tooth_cell:
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
for xpath in [
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-content')]//tr[{idx+1}]/td[4]",
]:
try:
tooth_cell = self.driver.find_element(By.XPATH, xpath)
break
except Exception:
continue
if not tooth_cell:
raise Exception("tooth cell not found")
self.driver.execute_script(
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", tooth_cell)
tooth_cell.click()
time.sleep(0.3)
if _ta >= 3:
ActionChains(self.driver).double_click(tooth_cell).perform()
else:
tooth_cell.click()
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
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
tag = active.tag_name.lower() if active else ""
if tag == "input":
if active.tag_name.lower() == "input":
tooth_input = active
break
# Try clicking again to trigger edit mode
try:
tooth_cell = self.driver.find_element(By.XPATH, tooth_cell_xpaths[0])
except Exception:
pass
try:
tooth_cell.click()
except Exception:
pass
time.sleep(0.2)
time.sleep(0.25)
if not tooth_input:
raise Exception("tooth input not active")
if tooth_input:
# Clear → Type → Verify (with JS fallback)
tooth_input.click()
try:
tooth_input.send_keys(Keys.CONTROL + "a")
tooth_input.send_keys(Keys.DELETE)
time.sleep(0.1)
for ch in str(tooth):
tooth_input.send_keys(ch)
time.sleep(0.05)
time.sleep(0.2)
# Verify value was entered; fall back to JS inject if empty
entered = tooth_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}));",
tooth_input, str(tooth))
time.sleep(0.2)
print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered")
else:
# Fallback: JS inject directly into active element
active = self.driver.switch_to.active_element
except Exception:
pass
time.sleep(0.2)
if (tooth_input.get_attribute("value") or "").strip() != str(tooth):
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('change',{bubbles:true}));",
active, str(tooth))
tooth_input, str(tooth))
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")
print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered")
tooth_done = True
break
except Exception as te:
print(f"[CCA PreAuth step3] Row {idx+1}: tooth attempt {_ta}/4: {te.__class__.__name__}")
time.sleep(1.0)
if not tooth_done:
return (f"ERROR: failed to fill tooth for service line {idx+1} "
f"(code={code}) — pre-auth NOT submitted")
# ── Billed Amount: click TD cell to activate edit mode (turn yellow),
# wait for input, delete default 0.00, type amount ──
@@ -807,38 +820,52 @@ class AutomationCCAPreAuth:
billed_cell.click()
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
for _ in range(10):
for attempt in range(1, 5):
try:
billed_input = WebDriverWait(self.driver, 1).until(
EC.element_to_be_clickable((By.ID, "ServicesBILLED_AMOUNT")))
cands = [e for e in self.driver.find_elements(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
except Exception:
pass
try:
billed_cell = self.driver.find_element(By.XPATH, billed_cell_xpaths[0])
billed_cell.click()
except Exception:
pass
time.sleep(0.2)
except Exception as ae:
print(f"[CCA PreAuth step3] Row {idx+1}: billed attempt {attempt}/4: {ae.__class__.__name__}")
time.sleep(0.8)
try:
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()
time.sleep(0.5)
except Exception:
pass
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")
else:
print(f"[CCA PreAuth step3] Row {idx+1}: Billed Amount input not activated — skipping")