feat: chatbot screenshot-only eligibility detect, Sun Life/DentaQuest auto-check, chatbot attachment handoff to preauth; fix TuftsSCO preauth Selenium reliability

- Chatbot: submitting with only a screenshot attached (no text) now triggers the same
  "AI Detect & Check Eligibility" flow as the Copy Agent page
- detect-eligibility-info now also extracts the visible insurance payer name and picks
  Tufts SCO auto-check when both Sun Life and DentaQuest are detected, else MassHealth
- Chatbot-attached files are now handed off to both claim and preauth forms unconditionally,
  not just claims
- TuftsSCO preauth Selenium worker: verify-and-retry the Tooth field (typing was silently
  getting reset by duplicate-procedure-code warning banners), add a final re-verification
  pass across all rows, fix acknowledgement-checkbox targeting/verification, and verify
  the "Next step" click actually advances the wizard instead of trusting a blind click
This commit is contained in:
2026-07-25 00:10:05 -04:00
parent 6b9b3c41f2
commit 870bda5950
8 changed files with 237 additions and 724 deletions

View File

@@ -596,6 +596,36 @@ class AutomationTuftsSCOPreAuth:
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Warning: could not fill {label}: {e}")
def _fill_tooth_field(self, idx, tooth, context=""):
"""Fill the Tooth input for row idx, verifying the DOM value against what was typed
and retrying if it didn't stick. Entering the same procedure code on multiple rows
(e.g. one code across several teeth) triggers a duplicate-code warning banner that can
re-render the form and silently reset earlier rows' Tooth values — callers should re-run
this for every row after the whole form is filled, not just once at type-time."""
tooth = str(tooth).strip()
actual = None
for attempt in range(3):
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
if idx >= len(tooth_inputs):
return None
tooth_inp = tooth_inputs[idx]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_inp)
self.driver.execute_script("arguments[0].focus();", tooth_inp)
time.sleep(0.2)
tooth_inp.send_keys(Keys.CONTROL + "a")
tooth_inp.send_keys(Keys.DELETE)
tooth_inp.send_keys(tooth)
time.sleep(0.3)
actual = (tooth_inp.get_attribute("value") or "").strip()
if actual == tooth:
print(f"[TuftsSCO PreAuth step4] tooth[{idx}]{context}: confirmed '{tooth}'")
return actual
print(f"[TuftsSCO PreAuth step4] tooth[{idx}]{context}: attempt {attempt + 1}"
f"expected '{tooth}', field shows {actual!r}, retrying")
print(f"[TuftsSCO PreAuth step4] WARNING: tooth[{idx}]{context} did not stick — "
f"expected '{tooth}', field shows {actual!r} after 3 attempts")
return actual
def _fill_text_input(self, inp, value, label="field"):
try:
inp.click()
@@ -655,17 +685,8 @@ class AutomationTuftsSCOPreAuth:
if tooth:
try:
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
if idx < len(tooth_inputs):
tooth_inp = tooth_inputs[idx]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_inp)
self.driver.execute_script("arguments[0].focus();", tooth_inp)
time.sleep(0.2)
tooth_inp.send_keys(Keys.CONTROL + "a")
tooth_inp.send_keys(Keys.DELETE)
tooth_inp.send_keys(str(tooth))
print(f"[TuftsSCO PreAuth step4] tooth[{idx}]: typed '{tooth}'")
time.sleep(0.3)
self._fill_tooth_field(idx, tooth)
time.sleep(0.1)
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill tooth: {e}")
@@ -714,6 +735,24 @@ class AutomationTuftsSCOPreAuth:
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill billed amount: {e}")
# Adding a row with a procedure code already used by an earlier row triggers a
# duplicate-code warning banner that re-renders the form — this can silently reset
# a previously-confirmed Tooth value on an earlier row. Re-check every row now that
# no more rows will be added (no more duplicate-code banners can fire after this).
print("[TuftsSCO PreAuth step4] Final tooth verification pass...")
for idx, line in enumerate(active_lines):
tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
if not tooth:
continue
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
if idx >= len(tooth_inputs):
continue
actual = (tooth_inputs[idx].get_attribute("value") or "").strip()
if actual == tooth:
continue
print(f"[TuftsSCO PreAuth step4] Final check: tooth[{idx}] regressed to {actual!r}, re-filling '{tooth}'")
self._fill_tooth_field(idx, tooth, context=" (final pass)")
print("[TuftsSCO PreAuth step4] Done")
return "SUCCESS"
@@ -777,27 +816,56 @@ class AutomationTuftsSCOPreAuth:
# ── Step 6: Click "Next step" ──────────────────────────────────────────────
def step6_click_next(self):
"""Click the 'Next step' button."""
"""Click the 'Next step' button and verify the wizard actually advanced to the
acknowledgement page — the URL doesn't change (SPA), so a click that's silently
ignored (e.g. button still disabled from a validation error) would otherwise look
identical to a successful one in the logs."""
try:
print(f"[TuftsSCO PreAuth step6] Current URL: {self.driver.current_url}")
btn = WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@data-testid='next-step-btn'] | "
"//button[@aria-label='Next step']"
))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn)
time.sleep(0.5)
self.driver.execute_script("""
var el = arguments[0];
el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true}));
""", btn)
print("[TuftsSCO PreAuth step6] Clicked 'Next step'")
time.sleep(2)
advanced = False
for attempt in range(3):
btn = WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@data-testid='next-step-btn'] | "
"//button[@aria-label='Next step']"
))
)
disabled = (btn.get_attribute("disabled") is not None) or \
((btn.get_attribute("aria-disabled") or "").lower() == "true")
if disabled:
print(f"[TuftsSCO PreAuth step6] 'Next step' is disabled (attempt {attempt + 1}) — "
f"a form field may still be invalid/empty; waiting before retry")
time.sleep(1.5)
continue
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn)
time.sleep(0.5)
self.driver.execute_script("""
var el = arguments[0];
el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true}));
""", btn)
print(f"[TuftsSCO PreAuth step6] Clicked 'Next step' (attempt {attempt + 1})")
try:
WebDriverWait(self.driver, 6).until(
EC.presence_of_element_located((By.XPATH,
"//label[contains(.,'submitting this')] | "
"//*[contains(@aria-label,'submitting this')]"
))
)
advanced = True
print("[TuftsSCO PreAuth step6] Confirmed wizard advanced to the acknowledgement step")
break
except TimeoutException:
print(f"[TuftsSCO PreAuth step6] Page did not advance after click (attempt {attempt + 1}), retrying")
print(f"[TuftsSCO PreAuth step6] URL after Next: {self.driver.current_url}")
if not advanced:
return "ERROR: step6 failed: 'Next step' click did not advance the wizard after 3 attempts"
return "SUCCESS"
except Exception as e:
print(f"[TuftsSCO PreAuth step6] Exception: {e}")
@@ -805,30 +873,65 @@ class AutomationTuftsSCOPreAuth:
# ── Step 7: Acknowledge + submit ────────────────────────────────────────────
@staticmethod
def _is_checkbox_checked(el):
try:
if el.is_selected():
return True
except Exception:
pass
state = (el.get_attribute("aria-checked") or el.get_attribute("checked") or "").lower()
return state in ("true", "checked")
def step7_submit_preauth(self):
"""On the pre-auth summary page, tick the acknowledgement checkbox then submit."""
try:
print(f"[TuftsSCO PreAuth step7] Current URL: {self.driver.current_url}")
checkbox = WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH,
"//input[@type='checkbox'] | "
"//*[@role='checkbox'] | "
"//label[contains(.,'submitting this')]//input | "
"//*[contains(@aria-label,'submitting this')]"
))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", checkbox)
time.sleep(0.3)
self.driver.execute_script("""
var el = arguments[0];
el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true}));
""", checkbox)
print("[TuftsSCO PreAuth step7] Checked acknowledgement checkbox")
time.sleep(0.5)
# A bare "//input[@type='checkbox']" would match the FIRST checkbox in the DOM,
# which may not be the acknowledgement box (e.g. a row-selection checkbox in the
# service-line table). Try specific, label-anchored selectors before falling back.
checkbox = None
for xpath in [
"//label[contains(.,'submitting this')]//input[@type='checkbox']",
"//*[contains(@aria-label,'submitting this')]",
"//label[contains(translate(., 'ACKNOWLEDGE', 'acknowledge'),'acknowledge')]//input[@type='checkbox']",
"//*[contains(translate(@aria-label, 'ACKNOWLEDGE', 'acknowledge'),'acknowledge')]",
"//input[@type='checkbox']",
"//*[@role='checkbox']",
]:
try:
checkbox = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, xpath))
)
print(f"[TuftsSCO PreAuth step7] Found acknowledgement checkbox via: {xpath}")
break
except Exception:
continue
if checkbox is None:
return "ERROR: step7 failed: could not find acknowledgement checkbox"
for attempt in range(3):
if self._is_checkbox_checked(checkbox):
break
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", checkbox)
time.sleep(0.3)
self.driver.execute_script("""
var el = arguments[0];
el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true}));
""", checkbox)
time.sleep(0.5)
if attempt > 0:
print(f"[TuftsSCO PreAuth step7] Acknowledgement checkbox retry {attempt + 1}")
if self._is_checkbox_checked(checkbox):
print("[TuftsSCO PreAuth step7] Confirmed acknowledgement checkbox is checked")
else:
print("[TuftsSCO PreAuth step7] WARNING: acknowledgement checkbox did not register as checked after 3 attempts")
all_btns = self.driver.find_elements(By.XPATH, "//button")
print(f"[TuftsSCO PreAuth step7] Buttons: {[b.get_attribute('aria-label') or b.text[:40] for b in all_btns]}")