""" United/DentalHub Pre-Authorization Worker. Portal: app.dentalhub.com (United SCO) Flow (mirrors claim worker): 1. Navigate to eligibility page, fill member ID + DOB + payer, continue through Select Insurance popup and Provider & Location page → land on Selected Patient page. 2. Click Submit Pre-Authorization button on Selected Patient page. 3. Pre-auth page is pre-filled — select payer and click Continue. 4. Select Insurance popup — click Ok. 5. Practitioner & Location page — click Continue only (no dropdowns). 6. Date Entry / pre-auth form — fill CDT codes, tooth, billed amount, attach files, submit. 7. Capture pre-auth number and PDF from confirmation page. """ from selenium import webdriver from selenium.common.exceptions import WebDriverException, TimeoutException from selenium.webdriver.chrome.service import Service 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 from webdriver_manager.chrome import ChromeDriverManager import time import os import base64 import json import re from unitedsco_browser_manager import get_browser_manager _SERVICE_DIR = os.path.dirname(os.path.abspath(__file__)) _BACKEND_CWD = os.path.normpath(os.path.join(_SERVICE_DIR, "..", "Backend")) class AutomationUnitedDHPreAuth: 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.uniteddh_username = claim.get("uniteddhUsername", "") self.uniteddh_password = claim.get("uniteddhPassword", "") 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("[UnitedDH PreAuth login] Forcing logout due to credential change...") browser_manager = get_browser_manager() try: self.driver.get("https://app.dentalhub.com/app/dashboard") 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']", ]: try: btn = WebDriverWait(self.driver, 3).until(EC.element_to_be_clickable((By.XPATH, selector))) btn.click() print("[UnitedDH PreAuth login] Clicked logout button") time.sleep(2) break except TimeoutException: continue except Exception as e: print(f"[UnitedDH PreAuth login] Could not click logout button: {e}") try: self.driver.delete_all_cookies() print("[UnitedDH PreAuth login] Cleared all cookies") except Exception as e: print(f"[UnitedDH PreAuth login] Error clearing cookies: {e}") browser_manager.clear_credentials_hash() return True except Exception as e: print(f"[UnitedDH PreAuth login] Error during forced logout: {e}") return False def login(self, url): wait = WebDriverWait(self.driver, 30) browser_manager = get_browser_manager() try: if self.uniteddh_username and browser_manager.credentials_changed(self.uniteddh_username): self._force_logout() self.driver.get(url) time.sleep(2) try: current_url = self.driver.current_url print(f"[UnitedDH PreAuth login] Current URL: {current_url}") if "app.dentalhub.com" in current_url and "login" not in current_url.lower(): try: WebDriverWait(self.driver, 3).until( EC.presence_of_element_located((By.XPATH, '//input[contains(@placeholder,"Search")] | //*[contains(@class,"dashboard")] | //nav')) ) print("[UnitedDH PreAuth login] Already logged in") return "ALREADY_LOGGED_IN" except TimeoutException: pass except Exception as e: print(f"[UnitedDH PreAuth login] Error checking current state: {e}") self.driver.get(url) time.sleep(3) current_url = self.driver.current_url print(f"[UnitedDH PreAuth login] After navigation URL: {current_url}") if "app.dentalhub.com" in current_url and "login" not in current_url.lower(): print("[UnitedDH PreAuth login] Already on dashboard") return "ALREADY_LOGGED_IN" 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')]")) ) print("[UnitedDH PreAuth login] OTP input found") return "OTP_REQUIRED" except TimeoutException: pass if "app.dentalhub.com" in current_url: try: login_btn = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'LOGIN') or contains(text(),'Log In') or contains(text(),'Login')]")) ) login_btn.click() print("[UnitedDH PreAuth login] Clicked LOGIN button") time.sleep(5) except TimeoutException: print("[UnitedDH PreAuth login] No LOGIN button found, proceeding...") current_url = self.driver.current_url print(f"[UnitedDH PreAuth login] After LOGIN click URL: {current_url}") if "b2clogin.com" in current_url or "login" in current_url.lower(): print("[UnitedDH PreAuth login] On B2C login page - filling credentials") try: send_code_btn = self.driver.find_element(By.XPATH, "//button[@id='sendCode'] | //input[@id='sendCode'] | " "//button[contains(text(),'Text Me') or contains(text(),'Send Code')]" ) if send_code_btn.is_displayed(): print("[UnitedDH PreAuth login] Already on phone verification page - clicking 'Text Me'") self.driver.execute_script("arguments[0].click();", send_code_btn) time.sleep(3) return "OTP_REQUIRED" except Exception: pass try: email_field = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//input[@id='signInName' or @name='signInName' or @name='Email address' or @type='email']")) ) email_field.clear() email_field.send_keys(self.uniteddh_username) print(f"[UnitedDH PreAuth login] Entered username: {self.uniteddh_username}") password_field = WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.XPATH, "//input[@id='password' or @type='password']")) ) password_field.clear() password_field.send_keys(self.uniteddh_password) print("[UnitedDH PreAuth login] Entered password") signin_button = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//button[@id='next'] | //button[@type='submit' and contains(text(),'Sign')]")) ) signin_button.click() print("[UnitedDH PreAuth login] Clicked Sign in button") if self.uniteddh_username: browser_manager.save_credentials_hash(self.uniteddh_username) time.sleep(5) try: continue_btn = self.driver.find_element(By.XPATH, "//button[contains(text(),'Continue')]") phone_elements = self.driver.find_elements(By.XPATH, "//*[contains(text(),'Phone')]") if continue_btn and phone_elements: print("[UnitedDH PreAuth login] MFA method selection page detected") try: phone_radio = self.driver.find_element(By.XPATH, "//input[@type='radio' and (contains(@value,'phone') or contains(@value,'Phone'))] | " "//label[contains(text(),'Phone')]/preceding-sibling::input[@type='radio'] | " "//input[@type='radio']" ) if phone_radio and not phone_radio.is_selected(): phone_radio.click() print("[UnitedDH PreAuth login] Selected 'Phone' radio button") except Exception as radio_err: print(f"[UnitedDH PreAuth login] Could not click Phone radio: {radio_err}") time.sleep(1) continue_btn.click() print("[UnitedDH PreAuth login] Clicked 'Continue' on MFA selection page") time.sleep(3) except Exception: pass try: send_code_btn = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((By.XPATH, "//button[@id='sendCode'] | //input[@id='sendCode'] | " "//button[contains(text(),'Text Me') or contains(text(),'Send Code')]")) ) print("[UnitedDH PreAuth login] Found 'Text Me' / Send Code button") self.driver.execute_script("arguments[0].click();", send_code_btn) time.sleep(3) return "OTP_REQUIRED" except TimeoutException: pass try: WebDriverWait(self.driver, 5).until( EC.presence_of_element_located((By.XPATH, "//input[@type='tel' or contains(@placeholder,'code') or contains(@aria-label,'Verification')]")) ) print("[UnitedDH PreAuth login] OTP input appeared after sign-in") return "OTP_REQUIRED" except TimeoutException: pass current_url = self.driver.current_url if "app.dentalhub.com" in current_url and "login" not in current_url.lower(): print("[UnitedDH PreAuth login] Login succeeded without OTP") return "SUCCESS" print(f"[UnitedDH PreAuth login] Unexpected state - URL: {current_url}") return "SUCCESS" except Exception as e: return f"ERROR: Login failed - {e}" if "app.dentalhub.com" in current_url: return "ALREADY_LOGGED_IN" return "SUCCESS" except Exception as e: return f"ERROR: Login exception - {e}" # ── Helpers ──────────────────────────────────────────────────────────────── def _check_for_error_dialog(self): error_patterns = [ ("Patient Not Found", "Patient Not Found - please check the Subscriber ID, DOB, and Payer selection"), ("Insufficient Information", "Insufficient Information - need Subscriber ID + DOB, or First Name + Last Name + DOB"), ("No Eligibility", "No eligibility information found for this patient"), ("Error", None), ] for pattern, default_msg in error_patterns: try: dialog_elem = self.driver.find_element(By.XPATH, f"//modal-container//*[contains(text(),'{pattern}')] | " f"//div[contains(@class,'modal')]//*[contains(text(),'{pattern}')]" ) if dialog_elem.is_displayed(): try: modal = self.driver.find_element(By.XPATH, "//modal-container | //div[contains(@class,'modal-dialog')]") dialog_text = modal.text.strip()[:200] except Exception: dialog_text = dialog_elem.text.strip()[:200] print(f"[UnitedDH PreAuth] Error dialog detected: {dialog_text}") try: dismiss_btn = self.driver.find_element(By.XPATH, "//modal-container//button[contains(text(),'Ok') or contains(text(),'OK') or contains(text(),'Close')] | " "//div[contains(@class,'modal')]//button[contains(text(),'Ok') or contains(text(),'OK') or contains(text(),'Close')]" ) dismiss_btn.click() print("[UnitedDH PreAuth] Dismissed error dialog") time.sleep(1) except Exception: try: close_btn = self.driver.find_element(By.XPATH, "//modal-container//button[@class='close']") close_btn.click() except Exception: pass error_msg = default_msg if default_msg else f"ERROR: {dialog_text}" return f"ERROR: {error_msg}" except Exception: continue return None def _format_dob(self, dob_str): if dob_str and "-" in dob_str: dob_parts = dob_str.split("-") if len(dob_parts) == 3: return f"{dob_parts[1]}/{dob_parts[2]}/{dob_parts[0]}" return dob_str def _hide_browser(self): try: try: self.driver.get("about:blank") time.sleep(0.5) except Exception: pass try: self.driver.minimize_window() print("[UnitedDH PreAuth] Browser window minimized") return except Exception: pass try: self.driver.set_window_position(-10000, -10000) print("[UnitedDH PreAuth] Browser window moved off-screen") return except Exception: pass try: import subprocess subprocess.run(["xdotool", "getactivewindow", "windowminimize"], timeout=3, capture_output=True) print("[UnitedDH PreAuth] Browser minimized via xdotool") except Exception: pass except Exception as e: print(f"[UnitedDH PreAuth] Could not hide browser: {e}") # ── Pre-auth steps ───────────────────────────────────────────────────────── def step1_search_patient(self): """ Navigate to the eligibility page, fill member ID + DOB + payer, continue through the Select Insurance popup and Provider & Location dropdowns to land on the Selected Patient results page. (Identical to the claim worker's step1.) """ from selenium.webdriver.common.action_chains import ActionChains try: print(f"[UnitedDH PreAuth] step1: memberId={self.memberId}, dob={self.dateOfBirth}") self.driver.get("https://app.dentalhub.com/app/patient/eligibility") time.sleep(3) print(f"[UnitedDH PreAuth] step1 URL: {self.driver.current_url}") try: WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.ID, "firstName_Back")) ) print("[UnitedDH PreAuth] step1: Patient Information form loaded") except TimeoutException: print("[UnitedDH PreAuth] step1: Patient Information form not found") return "ERROR: step1 - Patient Information form not found" # Fill Subscriber ID if self.memberId: subscriber_id_selectors = [ "//input[@id='subscriberId_Front']", "//input[@id='subscriberId_Back' or @id='subscriberID_Back']", "//input[@id='memberId_Back' or @id='memberid_Back']", "//input[@id='medicaidId_Back']", "//label[contains(text(),'Subscriber ID')]/..//input[not(@id='firstName_Back') and not(@id='lastName_Back') and not(@id='dateOfBirth_Back')]", "//input[contains(@placeholder,'Subscriber') or contains(@placeholder,'subscriber')]", "//input[contains(@placeholder,'Medicaid') or contains(@placeholder,'medicaid')]", "//input[contains(@placeholder,'Member') or contains(@placeholder,'member')]", ] subscriber_filled = False for sel in subscriber_id_selectors: try: sid_input = self.driver.find_element(By.XPATH, sel) if sid_input.is_displayed(): sid_input.clear() sid_input.send_keys(self.memberId) field_id = sid_input.get_attribute("id") or "unknown" print(f"[UnitedDH PreAuth] step1: Subscriber ID entered: {self.memberId} (field='{field_id}')") subscriber_filled = True break except Exception: continue if not subscriber_filled: try: all_inputs = self.driver.find_elements(By.XPATH, "//form//input[@type='text' or not(@type)]") known_ids = {'firstName_Back', 'lastName_Back', 'dateOfBirth_Back', 'procedureDate_Back', 'insurerId'} for inp in all_inputs: inp_id = inp.get_attribute("id") or "" if inp_id not in known_ids and inp.is_displayed(): inp.clear() inp.send_keys(self.memberId) print(f"[UnitedDH PreAuth] step1: Subscriber ID in fallback field id='{inp_id}'") subscriber_filled = True break except Exception as e2: print(f"[UnitedDH PreAuth] step1: Fallback subscriber field error: {e2}") if not subscriber_filled: print(f"[UnitedDH PreAuth] step1: WARNING - Could not find Subscriber ID field") # Fill Date of Birth try: dob_input = self.driver.find_element(By.ID, "dateOfBirth_Back") dob_input.clear() dob_formatted = self._format_dob(self.dateOfBirth) dob_input.send_keys(dob_formatted) print(f"[UnitedDH PreAuth] step1: DOB entered: {dob_formatted}") except Exception as e: print(f"[UnitedDH PreAuth] step1: Error entering DOB: {e}") return "ERROR: step1 - Could not enter Date of Birth" time.sleep(1) # Dismiss any blocking overlays try: self.driver.execute_script(""" var dialogs = document.querySelectorAll('[role="dialog"], .cdk-overlay-container'); dialogs.forEach(function(d) { d.style.display = 'none'; }); """) except Exception: pass # Select Payer: UnitedHealthcare Massachusetts print("[UnitedDH PreAuth] step1: Selecting Payer...") payer_selected = False try: payer_selectors = [ "//label[contains(text(),'Payer')]/following-sibling::ng-select", "//label[contains(text(),'Payer')]/..//ng-select", "//ng-select[contains(@placeholder,'Payer') or contains(@placeholder,'payer')]", "//ng-select[.//input[contains(@placeholder,'Search by Payers')]]", ] payer_ng_select = None for sel in payer_selectors: try: elem = self.driver.find_element(By.XPATH, sel) if elem.is_displayed(): payer_ng_select = elem break except Exception: continue if payer_ng_select: self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", payer_ng_select) time.sleep(0.5) payer_ng_select.click() time.sleep(1) search_input = payer_ng_select.find_element(By.XPATH, ".//input[contains(@type,'text') or contains(@role,'combobox')]") search_input.clear() search_input.send_keys("UnitedHealthcare Massachusetts") print("[UnitedDH PreAuth] step1: Typed payer search text") time.sleep(2) search_input.send_keys(Keys.ENTER) print("[UnitedDH PreAuth] step1: Pressed Enter to select Payer") time.sleep(0.5) payer_selected = True else: print("[UnitedDH PreAuth] step1: Could not find Payer ng-select element") except Exception as e: print(f"[UnitedDH PreAuth] step1: Payer selection error: {e}") if not payer_selected: print("[UnitedDH PreAuth] step1: WARNING - Could not select Payer") time.sleep(1) # Click Continue (Patient Info) try: continue_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Continue')]")) ) continue_btn.click() print("[UnitedDH PreAuth] step1: Clicked Continue (Patient Info)") time.sleep(3) error_result = self._check_for_error_dialog() if error_result: return error_result except Exception as e: return f"ERROR: step1 - Could not click Continue: {e}" # Click Ok on Select Insurance popup print("[UnitedDH PreAuth] step1: Checking for Select Insurance popup...") try: ok_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(@class,'btn-primary') and " "(normalize-space(.)='Ok' or normalize-space(.)='OK' or normalize-space(.)='Okay')] | " "//modal-container//button[normalize-space(.)='Ok' or normalize-space(.)='OK' or normalize-space(.)='Okay'] | " "//div[contains(@class,'modal')]//button[normalize-space(.)='Ok' or normalize-space(.)='OK' or normalize-space(.)='Okay']" )) ) try: self.driver.execute_script("arguments[0].click();", ok_btn) print("[UnitedDH PreAuth] step1: Clicked OK on Select Insurance popup (JS)") except Exception: ok_btn.click() print("[UnitedDH PreAuth] step1: Clicked OK on Select Insurance popup (direct)") try: WebDriverWait(self.driver, 10).until(EC.staleness_of(ok_btn)) print("[UnitedDH PreAuth] step1: Select Insurance modal closed") except TimeoutException: print("[UnitedDH PreAuth] step1: Modal staleness timeout — continuing anyway") WebDriverWait(self.driver, 5).until( EC.presence_of_element_located((By.XPATH, "//label[@for='treatmentLocation'] | //label[@for='paymentGroupId']")) ) except TimeoutException: print("[UnitedDH PreAuth] step1: Select Insurance popup not found — proceeding") # Provider & Location page — select Treatment Location and Billing Entity, then Continue print("[UnitedDH PreAuth] step1: Waiting for Provider & Location page...") try: WebDriverWait(self.driver, 20).until( EC.visibility_of_element_located((By.XPATH, "//label[@for='paymentGroupId']")) ) print("[UnitedDH PreAuth] step1: Selecting Treatment Location...") location_selected = False try: location_ng = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//label[@for='treatmentLocation']/following-sibling::ng-select | " "//label[@for='treatmentLocation']/..//ng-select" )) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", location_ng) arrow = location_ng.find_element(By.CSS_SELECTOR, ".ng-arrow-wrapper") arrow.click() first_option = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((By.CSS_SELECTOR, ".ng-dropdown-panel .ng-option")) ) option_text = first_option.text.strip() first_option.click() print(f"[UnitedDH PreAuth] step1: Selected Treatment Location: {option_text}") location_selected = True except Exception as e: print(f"[UnitedDH PreAuth] step1: Treatment Location selection failed: {e}") if not location_selected: print("[UnitedDH PreAuth] step1: WARNING - Could not select Treatment Location") print("[UnitedDH PreAuth] step1: Selecting Billing Entity...") billing_selected = False try: billing_ng = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//label[@for='paymentGroupId']/following-sibling::ng-select | " "//label[@for='paymentGroupId']/..//ng-select" )) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", billing_ng) arrow = billing_ng.find_element(By.CSS_SELECTOR, ".ng-arrow-wrapper") arrow.click() first_option = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((By.CSS_SELECTOR, ".ng-dropdown-panel .ng-option")) ) option_text = first_option.text.strip() first_option.click() print(f"[UnitedDH PreAuth] step1: Selected Billing Entity: {option_text}") billing_selected = True except Exception as e: print(f"[UnitedDH PreAuth] step1: Billing Entity selection failed: {e}") if not billing_selected: print("[UnitedDH PreAuth] step1: WARNING - Could not select Billing Entity") continue_btn2 = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Continue')]")) ) continue_btn2.click() print("[UnitedDH PreAuth] step1: Clicked Continue (Provider & Location) → Selected Patient page") time.sleep(5) except TimeoutException: try: results_elem = self.driver.find_element(By.XPATH, "//*[contains(text(),'Selected Patient') or contains(@id,'patient-name') or contains(@id,'eligibility')]" ) if results_elem.is_displayed(): print("[UnitedDH PreAuth] step1: Already on Selected Patient page") return "OK" except Exception: pass print("[UnitedDH PreAuth] step1: Continue not found on Provider & Location page — proceeding") except Exception as e: print(f"[UnitedDH PreAuth] step1: Error clicking Continue on Provider & Location page: {e}") error_result = self._check_for_error_dialog() if error_result: return error_result error_result = self._check_for_error_dialog() if error_result: return error_result print("[UnitedDH PreAuth] step1: Patient search complete — on Selected Patient page") return "OK" except Exception as e: return f"ERROR: step1_search_patient - {e}" def step2_click_preauth_button(self): """ On the Selected Patient results page, click the Submit Pre-Authorization button. Tries pre-auth-specific IDs first, then falls back to text matching. """ try: print("[UnitedDH PreAuth] step2: Looking for Submit Pre-Authorization button...") time.sleep(2) # Try pre-auth specific button IDs first preauth_selectors = [ (By.ID, "btnSubmitAuthorization"), (By.ID, "btnSubmitPreAuth"), (By.ID, "btnPreAuth"), (By.ID, "btnPreAuthorization"), (By.XPATH, "//button[contains(normalize-space(.),'Submit Authorization') or " "contains(normalize-space(.),'Pre-Auth') or " "contains(normalize-space(.),'Pre Authorization') or " "contains(normalize-space(.),'PreAuth') or " "contains(normalize-space(.),'Prior Auth')]" ), ] for by, selector in preauth_selectors: try: btn = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((by, selector)) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn) time.sleep(0.5) btn.click() print(f"[UnitedDH PreAuth] step2: Clicked pre-auth button ({selector})") time.sleep(4) print(f"[UnitedDH PreAuth] step2 URL: {self.driver.current_url}") return "OK" except (TimeoutException, Exception): continue return "ERROR: step2 - Could not find Submit Pre-Authorization button on Selected Patient page" except Exception as e: return f"ERROR: step2_click_preauth_button - {e}" def step3_continue_prefilled(self): """ Pre-auth page: member ID and DOB are pre-filled. Select Payer by typing "UnitedHealthcare Massachusetts" + Enter, then click Continue. """ try: print("[UnitedDH PreAuth] step3: Pre-auth page — selecting Payer then clicking Continue...") WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.XPATH, "//label[contains(text(),'Payer')] | //ng-select" )) ) payer_selected = False try: payer_selectors = [ "//label[contains(text(),'Payer')]/following-sibling::ng-select", "//label[contains(text(),'Payer')]/..//ng-select", "//ng-select[contains(@placeholder,'Payer') or contains(@placeholder,'payer')]", "//ng-select[.//input[contains(@placeholder,'Search by Payers')]]", ] payer_ng_select = None for sel in payer_selectors: try: elem = self.driver.find_element(By.XPATH, sel) if elem.is_displayed(): payer_ng_select = elem break except Exception: continue if payer_ng_select: self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", payer_ng_select) time.sleep(0.5) payer_ng_select.click() time.sleep(1) search_input = payer_ng_select.find_element(By.XPATH, ".//input[contains(@type,'text') or contains(@role,'combobox')]") search_input.clear() search_input.send_keys("UnitedHealthcare Massachusetts") print("[UnitedDH PreAuth] step3: Typed payer search text") time.sleep(2) search_input.send_keys(Keys.ENTER) print("[UnitedDH PreAuth] step3: Pressed Enter to select Payer") time.sleep(0.5) payer_selected = True else: print("[UnitedDH PreAuth] step3: Could not find Payer ng-select element") except Exception as e: print(f"[UnitedDH PreAuth] step3: Payer selection error: {e}") if not payer_selected: print("[UnitedDH PreAuth] step3: WARNING - Could not select Payer") time.sleep(1) continue_btn = WebDriverWait(self.driver, 15).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Continue')]")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", continue_btn) continue_btn.click() print("[UnitedDH PreAuth] step3: Clicked Continue") time.sleep(4) error_result = self._check_for_error_dialog() if error_result: return error_result print(f"[UnitedDH PreAuth] step3 URL: {self.driver.current_url}") return "OK" except Exception as e: return f"ERROR: step3_continue_prefilled - {e}" def step4_select_insurance_ok(self): """Click Ok on the Select Insurance popup.""" try: print("[UnitedDH PreAuth] step4: Waiting for Select Insurance popup...") try: ok_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//button[@type='button' and contains(@class,'btn-primary') and " "(normalize-space(text())='Ok' or normalize-space(text())='OK')] | " "//modal-container//button[normalize-space(.)='Ok' or normalize-space(.)='OK'] | " "//div[contains(@class,'modal')]//button[normalize-space(.)='Ok' or normalize-space(.)='OK']" )) ) ActionChains(self.driver).move_to_element(ok_btn).pause(0.5).click().perform() print("[UnitedDH PreAuth] step4: Clicked Ok on Select Insurance popup") try: WebDriverWait(self.driver, 8).until(EC.staleness_of(ok_btn)) print("[UnitedDH PreAuth] step4: Select Insurance modal closed") except TimeoutException: print("[UnitedDH PreAuth] step4: Modal staleness timeout — continuing anyway") except TimeoutException: print("[UnitedDH PreAuth] step4: Select Insurance popup not found — proceeding") print(f"[UnitedDH PreAuth] step4 URL: {self.driver.current_url}") return "OK" except Exception as e: return f"ERROR: step4_select_insurance_ok - {e}" def step5_practitioner_continue(self): """Practitioner & Location page — click Continue only, no dropdown selections.""" try: print("[UnitedDH PreAuth] step5: Waiting for Practitioner & Location page...") try: WebDriverWait(self.driver, 20).until( EC.visibility_of_element_located((By.XPATH, "//label[@for='treatmentLocation'] | //label[@for='paymentGroupId'] | " "//label[@for='paymentGroup']" )) ) print("[UnitedDH PreAuth] step5: Practitioner & Location page loaded") except TimeoutException: print("[UnitedDH PreAuth] step5: Practitioner & Location labels not found — trying Continue anyway") continue_btn = WebDriverWait(self.driver, 15).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(@class,'btn-primary') and contains(normalize-space(text()),'Continue')] | " "//button[contains(normalize-space(text()),'Continue')]" )) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", continue_btn) continue_btn.click() print("[UnitedDH PreAuth] step5: Clicked Continue — waiting for Code Entry page") time.sleep(3) try: WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.ID, "procedureCode")) ) print("[UnitedDH PreAuth] step5: Code Entry page loaded (procedureCode found)") except TimeoutException: print("[UnitedDH PreAuth] step5: procedureCode input not found — proceeding anyway") print(f"[UnitedDH PreAuth] step5 URL: {self.driver.current_url}") return "OK" except Exception as e: return f"ERROR: step5_practitioner_continue - {e}" def step6_fill_preauth_form(self): """ Fill CDT codes, tooth, billed amount for each service line. Same structure as the claim form step. """ try: active_lines = [ ln for ln in self.serviceLines if str(ln.get("procedureCode") or "").strip() ] print(f"[UnitedDH PreAuth] step6: {len(active_lines)} service line(s)") if not active_lines: print("[UnitedDH PreAuth] step6: No service lines — skipping") return "OK" for idx, line in enumerate(active_lines): code = str(line.get("procedureCode") or "").strip().upper() billed = str( line.get("totalBilled") or line.get("billedAmount") or line.get("fee") or "" ).strip() tooth = str(line.get("toothNumber") or line.get("tooth_number") or "").strip() surface = str(line.get("toothSurface") or line.get("tooth_surface") or "").strip().upper() print(f"[UnitedDH PreAuth] step6: line {idx}: code={code}, billed={billed}, tooth={tooth}, surface={surface}") # Click btnAddItem to open/activate the procedure row try: add_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.ID, "btnAddItem")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn) add_btn.click() print(f"[UnitedDH PreAuth] step6: clicked btnAddItem to open row {idx}") time.sleep(1) except Exception as e: print(f"[UnitedDH PreAuth] step6: could not click btnAddItem to open row {idx}: {e}") # Type CDT code try: proc_input = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.ID, "procedureCode")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_input) self.driver.execute_script("arguments[0].click();", proc_input) proc_input.send_keys(Keys.CONTROL + "a") proc_input.send_keys(Keys.DELETE) proc_input.send_keys(code) print(f"[UnitedDH PreAuth] step6: typed procedure code: {code}") time.sleep(0.5) except Exception as e: print(f"[UnitedDH PreAuth] step6: could not type procedure code for row {idx}: {e}") continue # Click btnAddItem to confirm code and reveal billed amount input try: add_btn = WebDriverWait(self.driver, 8).until( EC.element_to_be_clickable((By.ID, "btnAddItem")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn) add_btn.click() print(f"[UnitedDH PreAuth] step6: clicked btnAddItem to reveal billedAmount for row {idx}") time.sleep(1.5) except Exception as e: print(f"[UnitedDH PreAuth] step6: could not click btnAddItem for billed amount row {idx}: {e}") continue # Fill tooth number if tooth: try: tooth_input = WebDriverWait(self.driver, 5).until( EC.element_to_be_clickable((By.ID, "tooth")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_input) tooth_input.click() tooth_input.send_keys(Keys.CONTROL + "a") tooth_input.send_keys(Keys.DELETE) tooth_input.send_keys(tooth) print(f"[UnitedDH PreAuth] step6: entered tooth number: {tooth}") time.sleep(0.3) except Exception as e: print(f"[UnitedDH PreAuth] step6: could not fill tooth number for row {idx}: {e}") # Click surface boxes if surface: try: surface_boxes = self.driver.find_elements(By.XPATH, "//div[contains(@class,'claim-add-item-group__box')]") if surface_boxes: for letter in surface: if not letter.strip(): continue try: box = self.driver.find_element(By.XPATH, f"//div[contains(@class,'claim-add-item-group__box') " f"and not(contains(@class,'--disabled')) " f"and @id='{letter}']" ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", box) box.click() print(f"[UnitedDH PreAuth] step6: clicked surface '{letter}'") time.sleep(0.2) except Exception: print(f"[UnitedDH PreAuth] step6: surface '{letter}' not found or disabled") else: print(f"[UnitedDH PreAuth] step6: no surface boxes on page — skipping") except Exception as e: print(f"[UnitedDH PreAuth] step6: surface click error for row {idx}: {e}") # Fill billed amount if billed: try: billed_input = WebDriverWait(self.driver, 8).until( EC.element_to_be_clickable((By.ID, "billedAmount")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", billed_input) billed_input.click() billed_input.send_keys(Keys.CONTROL + "a") billed_input.send_keys(Keys.DELETE) billed_input.send_keys(billed) print(f"[UnitedDH PreAuth] step6: entered billed amount: {billed}") time.sleep(0.5) except Exception as e: print(f"[UnitedDH PreAuth] step6: could not fill billed amount for row {idx}: {e}") # Click the span "Add" button to confirm the row try: span_add = WebDriverWait(self.driver, 8).until( EC.element_to_be_clickable((By.XPATH, "//span[contains(@class,'ng-star-inserted') and normalize-space(text())='Add'] | " "//button[normalize-space(text())='Add' and not(@id='btnAddItem')] | " "//span[normalize-space(text())='Add']" )) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", span_add) span_add.click() print(f"[UnitedDH PreAuth] step6: clicked span Add — row {idx} confirmed") time.sleep(1) except Exception as e: print(f"[UnitedDH PreAuth] step6: could not click span Add for row {idx}: {e}") # Other coverage: click "No" (second radio button) try: print("[UnitedDH PreAuth] step6: selecting 'No' for Other coverage") radio_buttons = WebDriverWait(self.driver, 8).until( lambda d: d.find_elements(By.XPATH, "//input[@type='radio']") ) if len(radio_buttons) >= 2: no_radio = radio_buttons[1] self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", no_radio) no_radio.click() print("[UnitedDH PreAuth] step6: Clicked 'No' (2nd radio) for Other coverage") else: print(f"[UnitedDH PreAuth] step6: Only {len(radio_buttons)} radio button(s) found — skipping") time.sleep(0.5) except Exception as e: print(f"[UnitedDH PreAuth] step6: Could not click 'No' for Other coverage (non-fatal): {e}") print("[UnitedDH PreAuth] step6: Done filling pre-auth form") return "OK" except Exception as e: return f"ERROR: step6_fill_preauth_form - {e}" def step7_attach_files(self): """ If there are claim files: 1. Click the fa-caret-up dropdown icon to reveal the Add Document button 2. Click id="upload-document" 3. Send the absolute file path to the file input """ try: if not self.claimFiles: print("[UnitedDH PreAuth] step7: No files to attach") return "OK" try: caret = WebDriverWait(self.driver, 8).until( EC.element_to_be_clickable((By.XPATH, "//em[contains(@class,'fa-caret-up')] | " "//i[contains(@class,'fa-caret-up')] | " "//*[contains(@class,'fa') and contains(@class,'fa-caret-up')]" )) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", caret) caret.click() print("[UnitedDH PreAuth] step7: Clicked caret-up to expand Attached Documents") time.sleep(1) except Exception as e: print(f"[UnitedDH PreAuth] step7: Could not click caret (section may already be open): {e}") attached = 0 for cf in self.claimFiles: relative_path = cf.get("filePath") or "" if not relative_path: print(f"[UnitedDH PreAuth] step7: 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"[UnitedDH PreAuth] step7: File not found on disk: {abs_path}") continue print(f"[UnitedDH PreAuth] step7: Attaching: {abs_path}") try: upload_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.ID, "upload-document")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", upload_btn) upload_btn.click() time.sleep(1) file_input = WebDriverWait(self.driver, 8).until( EC.presence_of_element_located((By.XPATH, "//input[@type='file']")) ) self.driver.execute_script("arguments[0].style.display='block';", file_input) file_input.send_keys(abs_path) time.sleep(1.5) print(f"[UnitedDH PreAuth] step7: Attached: {os.path.basename(abs_path)}") attached += 1 except Exception as e: print(f"[UnitedDH PreAuth] step7: Could not attach {abs_path}: {e}") print(f"[UnitedDH PreAuth] step7: Attached {attached}/{len(self.claimFiles)} file(s)") return "OK" except Exception as e: return f"ERROR: step7_attach_files - {e}" def step8_submit_preauth(self): """ Click Submit Pre-Authorization (or Submit Claim) on the form page, then click "View Status and History" on the post-submit popup. """ try: print(f"[UnitedDH PreAuth] step8: submitting pre-auth — URL: {self.driver.current_url}") submit_btn = WebDriverWait(self.driver, 15).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(normalize-space(.),'Submit Pre-Auth') or " "contains(normalize-space(.),'Submit Pre Authorization') or " "contains(normalize-space(.),'Submit Pre-Authorization') or " "contains(normalize-space(.),'Submit Claim')] | " "//button[contains(@class,'btn-primary') and (" "contains(normalize-space(text()),'Submit Pre') or " "contains(normalize-space(text()),'Submit Claim'))]" )) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", submit_btn) time.sleep(0.5) submit_btn.click() print("[UnitedDH PreAuth] step8: Clicked Submit — waiting for post-submit popup") time.sleep(3) try: view_btn = WebDriverWait(self.driver, 15).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(normalize-space(.),'View Status and History')] | " "//a[contains(normalize-space(.),'View Status and History')]" )) ) view_btn.click() print("[UnitedDH PreAuth] step8: Clicked 'View Status and History'") time.sleep(3) except TimeoutException: print("[UnitedDH PreAuth] step8: Post-submit popup not found — proceeding to step9") print(f"[UnitedDH PreAuth] step8: URL after popup: {self.driver.current_url}") return "OK" except Exception as e: return f"ERROR: step8_submit_preauth - {e}" def step9_save_confirmation_pdf(self): """ On the Status & History page, read the pre-auth/reference number from the first row, then save the page as PDF. """ try: print("[UnitedDH PreAuth] step9: waiting for Status & History page") WebDriverWait(self.driver, 40).until( lambda d: "status" in d.current_url.lower() or "history" in d.current_url.lower() or d.find_elements(By.XPATH, "//td | //th[contains(text(),'Reference')]") ) time.sleep(4) print(f"[UnitedDH PreAuth] step9: Status & History URL: {self.driver.current_url}") self.driver.refresh() print("[UnitedDH PreAuth] step9: Page refreshed — waiting for table to reload") WebDriverWait(self.driver, 30).until( EC.presence_of_element_located((By.XPATH, "//table//tr[td]")) ) time.sleep(4) preauth_number = None try: first_ref = WebDriverWait(self.driver, 20).until( EC.presence_of_element_located((By.XPATH, "(//table//tr[not(th)]/td[2] | " "//table//tr[td]/td[contains(normalize-space(.),'2026') or " " contains(normalize-space(.),'2025')])[1]" )) ) ref_text = first_ref.text.strip() match = re.search(r'\b(\d{14})\b', ref_text) if match: preauth_number = match.group(1) else: match = re.search(r'\b(\d{10,})\b', ref_text) if match: preauth_number = match.group(1) print(f"[UnitedDH PreAuth] step9: Pre-auth number: {preauth_number!r} (cell: {ref_text!r})") except Exception as e: print(f"[UnitedDH PreAuth] step9: Could not read first-row reference number: {e}") try: body_text = self.driver.find_element(By.TAG_NAME, "body").text match = re.search(r'\b(\d{14})\b', body_text) if match: preauth_number = match.group(1) print(f"[UnitedDH PreAuth] step9: Pre-auth number (body scan): {preauth_number}") except Exception: pass 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"uniteddh_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"[UnitedDH PreAuth] step9: PDF saved: {pdf_path}") except Exception as e: print(f"[UnitedDH PreAuth] step9: PDF capture failed: {e}") return f"ERROR: step9 PDF failed: {e}" self._hide_browser() return { "status": "success", "pdf_path": pdf_path, "preAuthNumber": preauth_number, } except Exception as e: return f"ERROR: step9_save_confirmation_pdf - {e}" # ── Main workflow ────────────────────────────────────────────────────────── def main_workflow(self, url): try: self.config_driver() login_result = self.login(url) print(f"[main_workflow] Login result: {login_result}") if login_result == "OTP_REQUIRED": return {"status": "otp_required", "message": "OTP required after login"} if isinstance(login_result, str) and login_result.startswith("ERROR"): return {"status": "error", "message": login_result} step1_result = self.step1_search_patient() print(f"[main_workflow] step1 result: {step1_result}") if isinstance(step1_result, str) and step1_result.startswith("ERROR"): return {"status": "error", "message": step1_result} step2_result = self.step2_click_preauth_button() print(f"[main_workflow] step2 result: {step2_result}") if isinstance(step2_result, str) and step2_result.startswith("ERROR"): return {"status": "error", "message": step2_result} step3_result = self.step3_continue_prefilled() print(f"[main_workflow] step3 result: {step3_result}") if isinstance(step3_result, str) and step3_result.startswith("ERROR"): return {"status": "error", "message": step3_result} step4_result = self.step4_select_insurance_ok() print(f"[main_workflow] step4 result: {step4_result}") if isinstance(step4_result, str) and step4_result.startswith("ERROR"): return {"status": "error", "message": step4_result} step5_result = self.step5_practitioner_continue() print(f"[main_workflow] step5 result: {step5_result}") if isinstance(step5_result, str) and step5_result.startswith("ERROR"): return {"status": "error", "message": step5_result} step6_result = self.step6_fill_preauth_form() print(f"[main_workflow] step6 result: {step6_result}") if isinstance(step6_result, str) and step6_result.startswith("ERROR"): return {"status": "error", "message": step6_result} step7_result = self.step7_attach_files() print(f"[main_workflow] step7 result: {step7_result}") if isinstance(step7_result, str) and step7_result.startswith("ERROR"): return {"status": "error", "message": step7_result} step8_result = self.step8_submit_preauth() print(f"[main_workflow] step8 result: {step8_result}") if isinstance(step8_result, str) and step8_result.startswith("ERROR"): return {"status": "error", "message": step8_result} step9_result = self.step9_save_confirmation_pdf() print(f"[main_workflow] step9 result: {step9_result}") return step9_result except Exception as e: return {"status": "error", "message": str(e)}