Files
DentalManagementMH07/apps/SeleniumService/selenium_TuftsSCO_preAuthWorker.py
Gitead 870bda5950 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
2026-07-25 00:10:05 -04:00

1038 lines
51 KiB
Python

"""
Tufts SCO (DentaQuest) Pre-Authorization Worker.
Portal: providers.dentaquest.com
Steps mirror the claim worker exactly, except:
- step3 clicks "Create prior authorization" instead of "Create claim"
- step6/7 target pre-auth submit buttons/text
- step8 extracts a pre-auth number instead of a claim number
"""
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import os
import base64
import re
from dentaquest_browser_manager import get_browser_manager
MEMBERS_URL = "https://providers.dentaquest.com/members"
_SERVICE_DIR = os.path.dirname(os.path.abspath(__file__))
_BACKEND_CWD = os.path.normpath(os.path.join(_SERVICE_DIR, "..", "Backend"))
class AutomationTuftsSCOPreAuth:
def __init__(self, data):
self.headless = False
self.driver = None
claim = data.get("claim", {}) if isinstance(data, dict) else {}
self.memberId = claim.get("memberId", "")
self.dateOfBirth = claim.get("dateOfBirth", "")
self.firstName = claim.get("firstName", "")
self.lastName = claim.get("lastName", "")
self.serviceDate = claim.get("serviceDate", "")
self.serviceLines = claim.get("serviceLines", [])
self.claimFiles = claim.get("claimFiles", [])
self.patientName = claim.get("patientName", "")
self.remarks = claim.get("remarks", "")
self.dentaquest_username = claim.get("dentaquestUsername", "")
self.dentaquest_password = claim.get("dentaquestPassword", "")
self.download_dir = get_browser_manager().download_dir
os.makedirs(self.download_dir, exist_ok=True)
def config_driver(self):
self.driver = get_browser_manager().get_driver(self.headless)
def _force_logout(self):
try:
print("[TuftsSCO PreAuth login] Forcing logout due to credential change...")
browser_manager = get_browser_manager()
try:
self.driver.get("https://providers.dentaquest.com/")
time.sleep(2)
for selector in [
"//button[contains(text(),'Log out') or contains(text(),'Logout') or contains(text(),'Sign out')]",
"//a[contains(text(),'Log out') or contains(text(),'Logout')]",
"//button[@aria-label='Log out' or @aria-label='Logout']",
"//*[contains(@class,'logout') or contains(@class,'signout')]",
]:
try:
btn = WebDriverWait(self.driver, 3).until(EC.element_to_be_clickable((By.XPATH, selector)))
btn.click()
print("[TuftsSCO PreAuth login] Clicked logout button")
time.sleep(2)
break
except TimeoutException:
continue
except Exception as e:
print(f"[TuftsSCO PreAuth login] Could not click logout button: {e}")
try:
self.driver.delete_all_cookies()
print("[TuftsSCO PreAuth login] Cleared all cookies")
except Exception as e:
print(f"[TuftsSCO PreAuth login] Error clearing cookies: {e}")
browser_manager.clear_credentials_hash()
return True
except Exception as e:
print(f"[TuftsSCO PreAuth login] Error during forced logout: {e}")
return False
def _is_maintenance_page(self) -> bool:
try:
body = self.driver.find_element(By.TAG_NAME, "body").text.lower()
markers = ["temporarily unable to service", "maintenance downtime", "capacity problems"]
return any(m in body for m in markers)
except Exception:
return False
def login(self, url):
wait = WebDriverWait(self.driver, 30)
browser_manager = get_browser_manager()
try:
if self.dentaquest_username and browser_manager.credentials_changed(self.dentaquest_username):
self._force_logout()
self.driver.get(url)
time.sleep(2)
try:
current_url = self.driver.current_url
print(f"[TuftsSCO PreAuth login] Current URL: {current_url}")
if "dashboard" in current_url.lower() or "member" in current_url.lower():
try:
WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[TuftsSCO PreAuth login] Already logged in")
return "ALREADY_LOGGED_IN"
except TimeoutException:
pass
except Exception as e:
print(f"[TuftsSCO PreAuth login] Error checking current state: {e}")
self.driver.get(url)
time.sleep(3)
if self._is_maintenance_page():
return "ERROR: DentaQuest portal is in maintenance mode"
current_url = self.driver.current_url.lower()
print(f"[TuftsSCO PreAuth login] After navigation URL: {current_url}")
if "dashboard" in current_url or "member" in current_url:
try:
WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[TuftsSCO PreAuth login] Already on dashboard")
return "ALREADY_LOGGED_IN"
except TimeoutException:
pass
# Dismiss "Authentication flow continued in another tab" modal if present
try:
ok_button = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
"//button[normalize-space(text())='Ok' or normalize-space(text())='OK' "
"or normalize-space(text())='Continue']"
))
)
ok_button.click()
print("[TuftsSCO PreAuth login] Dismissed modal")
time.sleep(3)
try:
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[TuftsSCO PreAuth login] Already authenticated after modal dismiss")
return "ALREADY_LOGGED_IN"
except TimeoutException:
pass
except TimeoutException:
pass
# Check for OTP input on page
try:
WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH,
"//input[@type='tel' or contains(@placeholder,'code') or "
"contains(@aria-label,'Verification') or contains(@name,'otp')]"))
)
print("[TuftsSCO PreAuth login] OTP input found on arrival")
return "OTP_REQUIRED"
except TimeoutException:
pass
# Fill login credentials
try:
username_field = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//input[@type='email' or @name='username' or @id='username' or "
"@name='Email' or @placeholder='Email' or @placeholder='Username' or @type='text']"))
)
username_field.clear()
username_field.send_keys(self.dentaquest_username)
print(f"[TuftsSCO PreAuth login] Entered username")
password_field = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//input[@type='password']"))
)
password_field.clear()
password_field.send_keys(self.dentaquest_password)
print("[TuftsSCO PreAuth login] Entered password")
try:
remember_me = WebDriverWait(self.driver, 3).until(
EC.element_to_be_clickable((By.XPATH,
"//label[.//span[contains(text(),'Remember me')] or "
"contains(translate(text(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'remember me')] | "
"//input[@type='checkbox' and ("
"contains(translate(@name,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'remember') or "
"contains(translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'remember'))]"
))
)
remember_me.click()
print("[TuftsSCO PreAuth login] Checked 'Remember me'")
except Exception:
print("[TuftsSCO PreAuth login] No 'Remember me' found (continuing)")
signin_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@type='submit'] | //input[@type='submit'] | "
"//button[contains(text(),'Sign') or contains(text(),'Log')]"))
)
signin_btn.click()
print("[TuftsSCO PreAuth login] Clicked Sign in")
if self.dentaquest_username:
browser_manager.save_credentials_hash(self.dentaquest_username)
try:
WebDriverWait(self.driver, 30).until(
EC.presence_of_element_located((By.XPATH,
"//input[@type='tel' or contains(@placeholder,'code') or contains(@placeholder,'Code') or "
"contains(@aria-label,'Verification') or contains(@aria-label,'verification') or "
"contains(@name,'otp') or contains(@name,'code')]"
))
)
print("[TuftsSCO PreAuth login] OTP required after sign-in")
return "OTP_REQUIRED"
except TimeoutException:
pass
current_url = self.driver.current_url.lower()
if "dashboard" in current_url or "member" in current_url:
print("[TuftsSCO PreAuth login] Login succeeded without OTP")
return "SUCCESS"
print(f"[TuftsSCO PreAuth login] Unexpected state — URL: {current_url}")
return "SUCCESS"
except Exception as e:
return f"ERROR: Login failed - {e}"
except Exception as e:
return f"ERROR: Login exception - {e}"
# ── Step 1: Search patient ─────────────────────────────────────────────────
def step1_search_patient(self):
"""Navigate to member search and find the patient by Member ID + DOB."""
wait = WebDriverWait(self.driver, 30)
def replace_with_sendkeys(el, value):
el.click()
time.sleep(0.05)
el.send_keys(Keys.CONTROL, "a")
el.send_keys(Keys.BACKSPACE)
el.send_keys(value)
try:
print(f"[TuftsSCO PreAuth step1] Current URL: {self.driver.current_url}")
print(f"[TuftsSCO PreAuth step1] Searching memberId={self.memberId} dob={self.dateOfBirth}")
if self._is_maintenance_page():
return "ERROR: DentaQuest portal is in maintenance mode"
time.sleep(2)
try:
dob_parts = self.dateOfBirth.split("-")
dob_year = dob_parts[0]
dob_month = dob_parts[1].zfill(2)
dob_day = dob_parts[2].zfill(2)
print(f"[TuftsSCO PreAuth step1] Parsed DOB: {dob_month}/{dob_day}/{dob_year}")
except Exception as e:
return f"ERROR: step1 DOB parse failed: {e}"
# Select Location from dropdown
try:
trigger = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
'//button[@data-testid="member-search_location_select-btn"]'))
)
trigger.click()
print("[TuftsSCO PreAuth step1] Clicked location dropdown")
time.sleep(0.5)
first_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, "(//li[@role='option'])[1]"))
)
opt_text = first_option.get_attribute("aria-label") or first_option.text.strip()
first_option.click()
print(f"[TuftsSCO PreAuth step1] Selected location: {opt_text[:60]}")
time.sleep(0.3)
except TimeoutException:
print("[TuftsSCO PreAuth step1] Warning: Location dropdown not found (continuing)")
except Exception as e:
print(f"[TuftsSCO PreAuth step1] Warning: Location select failed: {e}")
# Fill DOB
try:
dob_container = wait.until(EC.presence_of_element_located(
(By.XPATH, "//div[@data-testid='member-search_date-of-birth']")
))
month_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
day_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
year_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
replace_with_sendkeys(month_elem, dob_month)
time.sleep(0.1)
replace_with_sendkeys(day_elem, dob_day)
time.sleep(0.1)
replace_with_sendkeys(year_elem, dob_year)
print(f"[TuftsSCO PreAuth step1] Filled DOB: {dob_month}/{dob_day}/{dob_year}")
except Exception as e:
print(f"[TuftsSCO PreAuth step1] Warning: Could not fill DOB: {e}")
time.sleep(0.3)
# Fill Member ID
if self.memberId:
try:
member_id_input = wait.until(EC.presence_of_element_located(
(By.XPATH, '//input[@placeholder="Search by member ID"]')
))
member_id_input.clear()
member_id_input.send_keys(self.memberId)
print(f"[TuftsSCO PreAuth step1] Entered Member ID: {self.memberId}")
time.sleep(0.2)
except Exception as e:
print(f"[TuftsSCO PreAuth step1] Warning: Could not fill Member ID: {e}")
time.sleep(0.3)
# Click Search
try:
search_btn = wait.until(EC.element_to_be_clickable(
(By.XPATH, '//button[@data-testid="member-search_search-button"]')
))
search_btn.click()
print("[TuftsSCO PreAuth step1] Clicked Search button")
except TimeoutException:
try:
search_btn = self.driver.find_element(By.XPATH, '//button[contains(text(),"Search")]')
search_btn.click()
print("[TuftsSCO PreAuth step1] Clicked Search button (fallback)")
except Exception:
ActionChains(self.driver).send_keys(Keys.RETURN).perform()
print("[TuftsSCO PreAuth step1] Pressed Enter to search")
WebDriverWait(self.driver, 15).until(
EC.any_of(
EC.presence_of_element_located((By.XPATH, "//tbody//tr")),
EC.presence_of_element_located((By.XPATH,
'//*[contains(@data-testid,"no-results") or contains(text(),"No results") '
'or contains(text(),"No member found") or contains(text(),"Nothing was found")]'
)),
)
)
time.sleep(4)
try:
no_results = self.driver.find_element(By.XPATH,
'//*[contains(@data-testid,"no-results") or contains(text(),"No results") '
'or contains(text(),"No member found")]'
)
if no_results and no_results.is_displayed():
return "ERROR: No patient found with given search criteria"
except Exception:
pass
print("[TuftsSCO PreAuth step1] Search completed")
return "SUCCESS"
except Exception as e:
print(f"[TuftsSCO PreAuth step1] Exception: {e}")
return f"ERROR: step1 failed: {e}"
# ── Step 2: Open member information page ───────────────────────────────────
def step2_open_member_page(self):
"""Click patient name link → Member Information page."""
try:
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
)
time.sleep(2)
except TimeoutException:
print("[TuftsSCO PreAuth step2] Warning: Results table not found within timeout")
detail_url = None
for selector in [
"(//table//tbody//tr)[1]//td[1]//a",
"(//tbody//tr)[1]//a[contains(@href,'member-details')]",
"(//tbody//tr)[1]//a[contains(@href,'member')]",
"//a[contains(@href,'member-details')]",
]:
try:
link_el = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, selector))
)
href = link_el.get_attribute("href")
if href and "member" in href:
detail_url = href
print(f"[TuftsSCO PreAuth step2] Found detail URL: {href}")
break
except Exception:
continue
if not detail_url:
return "ERROR: step2 failed: could not find member link"
self.driver.get(detail_url)
print(f"[TuftsSCO PreAuth step2] Navigating to: {detail_url}")
try:
WebDriverWait(self.driver, 15).until(
lambda d: "member" in d.current_url
)
print(f"[TuftsSCO PreAuth step2] Member Information page loaded: {self.driver.current_url}")
except TimeoutException:
print(f"[TuftsSCO PreAuth step2] Warning — URL: {self.driver.current_url}")
# Wait for either Create claim or Create prior authorization button
try:
WebDriverWait(self.driver, 15).until(
EC.any_of(
EC.presence_of_element_located((By.XPATH, "//button[@aria-label='Create prior authorization']")),
EC.presence_of_element_located((By.XPATH, "//button[@aria-label='Create claim']")),
)
)
print("[TuftsSCO PreAuth step2] Action button found on member page")
except TimeoutException:
print("[TuftsSCO PreAuth step2] Warning: Action button not found within timeout")
time.sleep(2)
return "SUCCESS"
except Exception as e:
print(f"[TuftsSCO PreAuth step2] Exception: {e}")
return f"ERROR: step2 failed: {e}"
# ── Step 3: Click "Create prior authorization" button ─────────────────────
def step3_click_create_preauth(self):
"""Click the 'Create prior authorization' button on the Member Information page."""
try:
print(f"[TuftsSCO PreAuth step3] Current URL: {self.driver.current_url}")
handles_before = set(self.driver.window_handles)
self.driver.execute_script("window.scrollTo(0, 0);")
time.sleep(0.5)
all_btns = self.driver.find_elements(By.XPATH, "//button")
print(f"[TuftsSCO PreAuth step3] Buttons on page: {[b.get_attribute('aria-label') or b.text for b in all_btns]}")
btn = None
try:
btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[.//span[normalize-space(text())='pre-authorization']]"
))
)
print("[TuftsSCO PreAuth step3] Found button via <span>pre-authorization</span>")
except TimeoutException:
pass
if btn is None:
# Fallbacks
for xpath in [
"//button[@aria-label='Create prior authorization' and @data-react-aria-pressable='true']",
"//button[contains(normalize-space(.),'prior authorization') or contains(normalize-space(.),'pre-authorization')]",
]:
try:
btn = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, xpath))
)
print(f"[TuftsSCO PreAuth step3] Found button via fallback")
break
except TimeoutException:
continue
if btn is None:
return "ERROR: step3 failed: could not find pre-authorization button"
print(f"[TuftsSCO PreAuth step3] Found button: displayed={btn.is_displayed()}, enabled={btn.is_enabled()}")
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 step3] Dispatched pointer+click events")
time.sleep(2)
handles_after = set(self.driver.window_handles)
new_handles = handles_after - handles_before
if new_handles:
self.driver.switch_to.window(new_handles.pop())
print("[TuftsSCO PreAuth step3] Switched to new tab")
print(f"[TuftsSCO PreAuth step3] Post-click URL: {self.driver.current_url}")
try:
WebDriverWait(self.driver, 20).until(
EC.any_of(
EC.presence_of_element_located((By.XPATH, "//input[contains(@id,'procedureCode')]")),
EC.presence_of_element_located((By.XPATH, "//span[@data-type='month' and @contenteditable='true']")),
EC.presence_of_element_located((By.XPATH,
"//*[contains(text(),'date of service') or contains(text(),'Date of service') "
"or contains(text(),'Procedure code') or contains(text(),'prior auth')]"
)),
)
)
print("[TuftsSCO PreAuth step3] Pre-auth form loaded")
except TimeoutException:
page_text = self.driver.execute_script("return document.body.innerText;")[:400]
print(f"[TuftsSCO PreAuth step3] Pre-auth form not detected — page: {page_text}")
time.sleep(1)
return "SUCCESS"
except Exception as e:
print(f"[TuftsSCO PreAuth step3] Exception: {e}")
return f"ERROR: step3 failed: {e}"
# ── Step 4: Fill service date and procedure lines ──────────────────────────
def _parse_service_date(self):
s = str(self.serviceDate or "").strip()
if not s:
return None, None, None
if "-" in s:
parts = s.split("-")
if len(parts) == 3 and len(parts[0]) == 4:
return parts[1].zfill(2), parts[2].zfill(2), parts[0]
if len(parts) == 3 and len(parts[2]) == 4:
return parts[0].zfill(2), parts[1].zfill(2), parts[2]
if "/" in s:
parts = s.split("/")
if len(parts) == 3:
return parts[0].zfill(2), parts[1].zfill(2), parts[2]
return None, None, None
def _fill_spinbutton(self, label_fragment, value):
for sel in [
f"//span[@contenteditable='true' and contains(@aria-label,'{label_fragment}')]",
f"//span[@data-type='{label_fragment}' and @contenteditable='true']",
]:
try:
elem = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, sel))
)
elem.click()
elem.send_keys(Keys.CONTROL, "a")
elem.send_keys(Keys.BACKSPACE)
elem.send_keys(value)
time.sleep(0.1)
print(f"[TuftsSCO PreAuth step4] Filled spinbutton '{label_fragment}' = {value!r}")
return True
except Exception:
continue
print(f"[TuftsSCO PreAuth step4] Warning: spinbutton '{label_fragment}' not found")
return False
def _fill_combobox(self, inp, value, label="field"):
try:
inp.click()
inp.send_keys(Keys.CONTROL + "a")
inp.send_keys(Keys.DELETE)
inp.send_keys(str(value))
time.sleep(1.5) # give portal time to load autocomplete results
listbox_id = inp.get_attribute("aria-controls") or ""
try:
if listbox_id:
option = WebDriverWait(self.driver, 6).until(
EC.element_to_be_clickable((By.XPATH,
f"//*[@id='{listbox_id}']//*[@role='option'][1]"
))
)
else:
option = WebDriverWait(self.driver, 6).until(
EC.element_to_be_clickable((By.XPATH,
f"//*[@role='listbox']//*[@role='option' and contains(normalize-space(.),'{value}')]"
f" | //*[@role='listbox']//*[@role='option'][1]"
))
)
option.click()
print(f"[TuftsSCO PreAuth step4] {label}: selected '{value}'")
except TimeoutException:
inp.send_keys(Keys.TAB)
print(f"[TuftsSCO PreAuth step4] {label}: typed '{value}' (no dropdown — pressed TAB)")
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()
inp.send_keys(Keys.CONTROL + "a")
inp.send_keys(Keys.DELETE)
inp.send_keys(str(value))
time.sleep(0.1)
print(f"[TuftsSCO PreAuth step4] {label}: typed '{value}'")
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Warning: could not fill {label}: {e}")
def step4_fill_preauth_form(self):
"""Fill all procedure line fields. Preauth form has no date field — skip it."""
try:
active_lines = [ln for ln in self.serviceLines if str(ln.get("procedureCode") or "").strip()]
print(f"[TuftsSCO PreAuth step4] {len(active_lines)} service line(s)")
for idx, line in enumerate(active_lines):
code = str(line.get("procedureCode") or "").strip().upper()
tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
arch = str(line.get("arch") or "").strip()
quad = str(line.get("quad") or line.get("quadrant") or "").strip()
surface = str(line.get("toothSurface") or line.get("surface") or "").strip().upper()
billed = str(line.get("totalBilled") or line.get("billedAmount") or line.get("fee") or "").strip()
billed = billed.replace("$", "").strip()
print(f"[TuftsSCO PreAuth step4] Line {idx}: code={code} tooth={tooth!r} arch={arch!r} "
f"quad={quad!r} surface={surface!r} billed={billed!r}")
if idx > 0:
try:
add_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[.//span[contains(text(),'Add a procedure')]] | "
"//button[contains(normalize-space(text()),'Add a procedure')] | "
"//*[contains(text(),'Add a procedure') and @role='button']"
))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn)
add_btn.click()
print(f"[TuftsSCO PreAuth step4] Clicked 'Add a procedure' for line {idx}")
time.sleep(1)
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not click 'Add a procedure': {e}")
if code:
try:
proc_inputs = self.driver.find_elements(By.XPATH,
"//input[contains(@id,'procedureCode') and contains(@id,'-input')]"
)
proc_inp = proc_inputs[idx] if idx < len(proc_inputs) else proc_inputs[-1]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_inp)
self._fill_text_input(proc_inp, code, f"procedureCode[{idx}]")
time.sleep(1.5) # wait for warning banner layout shift to settle
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill procedure code: {e}")
if tooth:
try:
self._fill_tooth_field(idx, tooth)
time.sleep(0.1)
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill tooth: {e}")
if arch:
try:
arch_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Arch']")
if idx < len(arch_inputs):
self._fill_combobox(arch_inputs[idx], arch, f"arch[{idx}]")
time.sleep(0.3)
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill arch: {e}")
if quad:
try:
quad_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Quad']")
if idx < len(quad_inputs):
self._fill_combobox(quad_inputs[idx], quad, f"quad[{idx}]")
time.sleep(0.3)
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill quad: {e}")
if surface:
try:
surface_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Surface']")
if idx < len(surface_inputs):
surf_inp = surface_inputs[idx]
surf_inp.click()
surf_inp.send_keys(Keys.CONTROL + "a")
surf_inp.send_keys(Keys.DELETE)
surf_inp.send_keys(surface)
time.sleep(0.3)
surf_inp.send_keys(Keys.ESCAPE)
print(f"[TuftsSCO PreAuth step4] surface[{idx}]: typed '{surface}'")
time.sleep(0.2)
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill surface: {e}")
if billed:
try:
billed_inputs = self.driver.find_elements(By.XPATH,
"//input[@aria-label='Enter billed amount']"
)
if idx < len(billed_inputs):
self._fill_text_input(billed_inputs[idx], billed, f"billedAmount[{idx}]")
time.sleep(0.2)
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"
except Exception as e:
print(f"[TuftsSCO PreAuth step4] Exception: {e}")
return f"ERROR: step4 failed: {e}"
# ── Step 5: Attach files ────────────────────────────────────────────────────
def step5_attach_files(self):
"""For each claimFile with a filePath, click 'Add a file' and upload it."""
if not self.claimFiles:
print("[TuftsSCO PreAuth step5] No files to attach")
return "SUCCESS"
attached = 0
for cf in self.claimFiles:
relative_path = cf.get("filePath") or ""
if not relative_path:
print(f"[TuftsSCO PreAuth step5] Skipping file with no filePath: {cf}")
continue
abs_path = os.path.normpath(os.path.join(_BACKEND_CWD, relative_path.lstrip("/")))
if not os.path.isfile(abs_path):
print(f"[TuftsSCO PreAuth step5] File not found on disk: {abs_path}")
continue
print(f"[TuftsSCO PreAuth step5] Attaching: {abs_path}")
try:
# The react-aria-Input file input is always in the DOM (hidden).
# Do NOT click "Add a file" — that opens the OS dialog and blocks WebDriver.
file_input = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//input[@type='file' and contains(@class,'react-aria-Input')]"))
)
self.driver.execute_script(
"arguments[0].style.display='block'; arguments[0].style.visibility='visible';",
file_input
)
file_input.send_keys(abs_path)
# Wait until the filename appears on the page confirming upload completed
filename = os.path.basename(abs_path)
try:
WebDriverWait(self.driver, 15).until(
lambda d: filename.lower() in d.page_source.lower()
or filename.rsplit("_", 1)[-1].lower() in d.page_source.lower()
)
print(f"[TuftsSCO PreAuth step5] Upload confirmed: {filename}")
except Exception:
time.sleep(3)
print(f"[TuftsSCO PreAuth step5] Upload wait timed out — proceeding: {filename}")
print(f"[TuftsSCO PreAuth step5] Attached: {filename}")
attached += 1
except Exception as e:
print(f"[TuftsSCO PreAuth step5] Could not attach {abs_path}: {e}")
print(f"[TuftsSCO PreAuth step5] Attached {attached}/{len(self.claimFiles)} file(s)")
return "SUCCESS"
# ── Step 6: Click "Next step" ──────────────────────────────────────────────
def step6_click_next(self):
"""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}")
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}")
return f"ERROR: step6 failed: {e}"
# ── 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}")
# 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]}")
submit_btn = None
for xpath in [
"//button[.//span[normalize-space(text())='pre-authorization']]",
"//button[.//span[contains(text(),'Submit prior authorization')]] | //button[@aria-label='Submit prior authorization']",
"//button[.//span[contains(text(),'Submit pre-authorization')]] | //button[contains(normalize-space(text()),'Submit pre-authorization')]",
"//button[.//span[contains(text(),'Submit claim')]] | //button[@aria-label='Submit claim']",
"//button[.//span[contains(text(),'Submit')]] | //button[contains(normalize-space(text()),'Submit')]",
]:
try:
submit_btn = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, xpath))
)
print(f"[TuftsSCO PreAuth step7] Found submit button: {submit_btn.get_attribute('aria-label') or submit_btn.text[:40]!r}")
break
except Exception:
continue
if submit_btn is None:
return "ERROR: step7 failed: could not find submit button"
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", submit_btn)
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}));
""", submit_btn)
print("[TuftsSCO PreAuth step7] Clicked submit button")
time.sleep(2)
print(f"[TuftsSCO PreAuth step7] URL after submit: {self.driver.current_url}")
return "SUCCESS"
except Exception as e:
print(f"[TuftsSCO PreAuth step7] Exception: {e}")
return f"ERROR: step7 failed: {e}"
# ── Step 8: Extract pre-auth number + save confirmation PDF ───────────────
def step8_save_confirmation_pdf(self):
"""Wait for the confirmation page, extract the pre-auth number, save page as PDF."""
try:
WebDriverWait(self.driver, 60).until(
lambda d: "thank" in d.page_source.lower() or "submitted pre-authorization" in d.page_source.lower()
)
time.sleep(2)
print(f"[TuftsSCO PreAuth step8] Confirmation page URL: {self.driver.current_url}")
preauth_number = None
try:
body_text = self.driver.find_element(By.TAG_NAME, "body").text
match = re.search(r'submitted pre-authorization\s+(\d{10,})', body_text, re.IGNORECASE)
if match:
preauth_number = match.group(1)
print(f"[TuftsSCO PreAuth step8] Extracted pre-auth number: {preauth_number}")
else:
match = re.search(r'\b(\d{12,})\b', body_text)
if match:
preauth_number = match.group(1)
print(f"[TuftsSCO PreAuth step8] Extracted pre-auth number (fallback): {preauth_number}")
except Exception as e:
print(f"[TuftsSCO PreAuth step8] Could not extract pre-auth number: {e}")
shared_downloads = os.path.join(_SERVICE_DIR, "downloads")
os.makedirs(shared_downloads, exist_ok=True)
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
safe_preauth = ("_" + preauth_number[:20]) if preauth_number else ""
timestamp = time.strftime("%Y%m%d_%H%M%S")
pdf_filename = f"tuftssco_preauth_confirmation_{safe_member}{safe_preauth}_{timestamp}.pdf"
pdf_path = os.path.join(shared_downloads, pdf_filename)
try:
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {
"printBackground": True,
"paperWidth": 8.5,
"paperHeight": 11,
"marginTop": 0.4,
"marginBottom": 0.4,
"marginLeft": 0.4,
"marginRight": 0.4,
})
pdf_bytes = base64.b64decode(pdf_data["data"])
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print(f"[TuftsSCO PreAuth step8] PDF saved: {pdf_path}")
except Exception as e:
print(f"[TuftsSCO PreAuth step8] PDF capture failed: {e}")
return f"ERROR: step8 PDF failed: {e}"
return {
"status": "success",
"pdf_path": pdf_path,
"preAuthNumber": preauth_number,
}
except Exception as e:
print(f"[TuftsSCO PreAuth step8] Exception: {e}")
return f"ERROR: step8 failed: {e}"