Files
DentalManagementMHAprilgg/apps/SeleniumService/selenium_claimSubmitWorker.py
2026-04-04 22:13:55 -04:00

1441 lines
64 KiB
Python
Executable File

from selenium import webdriver
from selenium.common import 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.support.ui import WebDriverWait, Select
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 shutil
import stat
import base64
import tempfile
from datetime import datetime
class AutomationMassHealthClaimsLogin:
def __init__(self, data):
self.headless = False
self.driver = None
self.extracted_data = {}
print(f"DEBUG: Received data = {data}")
self.upload_files = []
if isinstance(data, dict):
self.upload_files = (data.get("pdfs", []) or []) + (data.get("images", []) or [])
self.data = data.get("data") if isinstance(data, dict) else None
if not self.data and isinstance(data, dict) and isinstance(data.get("claim"), dict):
claim = data.get("claim")
first_line = None
if isinstance(claim.get("serviceLines"), list) and len(claim.get("serviceLines")) > 0:
first_line = claim.get("serviceLines")[0] if isinstance(claim.get("serviceLines")[0], dict) else None
patient_name = (claim.get("patientName") or "").strip()
first_name = ""
last_name = ""
if patient_name:
parts = patient_name.split()
first_name = parts[0] if len(parts) > 0 else ""
last_name = " ".join(parts[1:]) if len(parts) > 1 else ""
self.data = {
"massdhpUsername": claim.get("massdhpUsername", ""),
"massdhpPassword": claim.get("massdhpPassword", ""),
"memberId": claim.get("memberId", ""),
"dateOfBirth": claim.get("dateOfBirth", ""),
"submissionDate": claim.get("serviceDate", ""),
"firstName": claim.get("firstName", "") or first_name,
"lastName": claim.get("lastName", "") or last_name,
"procedureCode": (first_line or {}).get("procedureCode", "") if first_line else "",
"toothNumber": (first_line or {}).get("toothNumber", "") if first_line else "",
"toothSurface": (first_line or {}).get("toothSurface", "") if first_line else "",
}
if not self.data:
self.data = {}
print(f"DEBUG: Extracted data = {self.data}")
# Extract service lines data for multiple rows
self.serviceLines = []
if isinstance(data, dict) and isinstance(data.get("claim"), dict):
claim = data.get("claim")
service_lines = claim.get("serviceLines", [])
if isinstance(service_lines, list) and len(service_lines) > 0:
self.serviceLines = service_lines
print(f"DEBUG: Found {len(self.serviceLines)} service lines")
for i, line in enumerate(self.serviceLines):
print(f"DEBUG: Service line {i+1}: {line}")
# Flatten values for convenience
self.massdhp_username = self.data.get("massdhpUsername", "")
self.massdhp_password = self.data.get("massdhpPassword", "")
self.dateOfBirth = self.data.get("dateOfBirth", "")
self.originalDateOfBirth = self.data.get("dateOfBirth", "") # Keep original format
self.memberId = self.data.get("memberId", "")
self.submissionDate = self.data.get("submissionDate", "")
self.firstName = self.data.get("firstName", "")
self.lastName = self.data.get("lastName", "")
self.procedureCode = self.data.get("procedureCode", "")
self.toothNumber = self.data.get("toothNumber", "")
self.toothSurface = self.data.get("toothSurface", "")
print(f"DEBUG: submissionDate = '{self.submissionDate}'")
print(f"DEBUG: firstName = '{self.firstName}', lastName = '{self.lastName}'")
print(f"DEBUG: procedureCode = '{self.procedureCode}'")
print(f"DEBUG: toothNumber = '{self.toothNumber}'")
print(f"DEBUG: toothSurface = '{self.toothSurface}'")
# Convert dateOfBirth to MMDDYYYY format (if needed later)
if self.dateOfBirth:
dob_raw = str(self.dateOfBirth).strip()
if "T" in dob_raw:
dob_raw = dob_raw.split("T")[0]
parsed = None
for fmt in ("%Y-%m-%d", "%m-%d-%Y", "%m/%d/%Y"):
try:
parsed = datetime.strptime(dob_raw, fmt)
break
except Exception:
continue
if parsed:
self.dateOfBirth = parsed.strftime("%m%d%Y")
self.download_dir = os.path.abspath("downloads")
os.makedirs(self.download_dir, exist_ok=True)
def config_driver(self):
options = webdriver.ChromeOptions()
if self.headless:
options.add_argument("--headless")
# Add PDF download preferences (if needed later)
prefs = {
"download.default_directory": self.download_dir,
"plugins.always_open_pdf_externally": False,
"download.prompt_for_download": False,
"download.directory_upgrade": True
}
options.add_experimental_option("prefs", prefs)
s = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=s, options=options)
self.driver = driver
def login(self):
wait = WebDriverWait(self.driver, 30)
try:
# Step 1: Click the SIGN IN button on the initial page
signin_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "a.btn.btn-block.btn-primary[href='https://connectsso.masshealth-dental.org/mhprovider/index.html']")))
signin_button.click()
# Wait for the new page to load
time.sleep(2)
# Step 2: Enter email on the new login page
email_field = wait.until(EC.presence_of_element_located((By.ID, "User")))
email_field.clear()
email_field.send_keys(self.massdhp_username)
# Step 3: Enter password
password_field = wait.until(EC.presence_of_element_located((By.ID, "Password")))
password_field.clear()
password_field.send_keys(self.massdhp_password)
# Step 4: Click login button
login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "input[type='submit'][name='submit'][value='Login']")))
login_button.click()
return "Success"
except Exception as e:
print(f"Error while logging in: {e}")
return "ERROR:LOGIN FAILED"
def navigate_to_submit_claims(self):
wait = WebDriverWait(self.driver, 30)
try:
# Step 1: Click Claims/Prior Authorizations dropdown (use presence like eligibility worker)
claims_dropdown = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//strong[contains(@class,'navtitle') and contains(text(),'Claims/Prior Authorizations')]")
)
)
# Scroll + JS click = fixes most dropdown issues
self.driver.execute_script("arguments[0].scrollIntoView(true);", claims_dropdown)
self.driver.execute_script("arguments[0].click();", claims_dropdown)
time.sleep(1)
# Step 2: Click Submit Claims & Prior Authorizations (wait for dropdown to be visible)
submit_claims_link = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//a[contains(@class,'navitem subnav') and contains(text(),'Submit Claims & Prior Authorizations')]")
)
)
time.sleep(1) # ensure dropdown menu is fully rendered
self.driver.execute_script("arguments[0].scrollIntoView(true);", submit_claims_link)
self.driver.execute_script("arguments[0].click();", submit_claims_link)
time.sleep(1)
return "Success"
except Exception as e:
print(f"Error navigating to Submit Claims: {e}")
return "ERROR:NAVIGATION_FAILED"
def select_dental_claim(self):
wait = WebDriverWait(self.driver, 30)
try:
# Step 1: Wait for the submission type dropdown to be present
submission_dropdown = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//select[contains(@ng-model,'vm.newClaim.type')]")
)
)
time.sleep(1)
# Step 2: Use Select class to choose Dental Claim
select = Select(submission_dropdown)
select.select_by_visible_text("Dental Claim")
time.sleep(1)
return "Success"
except Exception as e:
print(f"Error selecting Dental Claim: {e}")
return "ERROR:SELECTION FAILED"
def fill_date_of_service(self):
wait = WebDriverWait(self.driver, 30)
try:
# For now, just use today's date to avoid parsing issues
formatted_date = datetime.now().strftime("%m/%d/%Y")
print(f"DEBUG: Using today's date = '{formatted_date}'")
# Step 1: Wait for the Date of Service input to be visible
date_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@name='dateOfService' and @ng-model='date' and @placeholder='mm/dd/yyyy']")
)
)
time.sleep(1)
# Step 2: Use JavaScript to set the value directly to avoid date picker interference
self.driver.execute_script(f"arguments[0].value = '{formatted_date}'; arguments[0].dispatchEvent(new Event('input', {{ bubbles: true }})); arguments[0].dispatchEvent(new Event('change', {{ bubbles: true }}));", date_input)
time.sleep(1)
# Press Tab to ensure the date is set
date_input.send_keys(Keys.TAB)
time.sleep(1)
return "Success"
except Exception as e:
print(f"Error filling Date of Service: {e}")
return "ERROR:DATE_FILL_FAILED"
def fill_member_eligibility(self):
wait = WebDriverWait(self.driver, 30)
try:
print(f"DEBUG: Filling member eligibility with DOB: '{self.originalDateOfBirth}'")
# Step 1: Fill Date of Birth (convert from original format to MM/DD/YYYY)
formatted_dob = ""
if self.originalDateOfBirth:
try:
dob_raw = str(self.originalDateOfBirth).strip()
if "T" in dob_raw:
dob_raw = dob_raw.split("T")[0]
date_obj = None
for fmt in ("%Y-%m-%d", "%m-%d-%Y", "%m/%d/%Y"):
try:
date_obj = datetime.strptime(dob_raw, fmt)
break
except Exception:
continue
if date_obj:
formatted_dob = date_obj.strftime("%m/%d/%Y")
print(f"DEBUG: Formatted DOB = '{formatted_dob}'")
except Exception as e:
print(f"DEBUG: DOB parsing error: {e}")
formatted_dob = ""
# Find and fill DOB input (using JavaScript to avoid date picker issues)
dob_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@name='dateInput' and @placeholder='mm/dd/yyyy' and @ng-model='date']")
)
)
time.sleep(1)
# Use JavaScript to set the value directly to avoid date picker interference
self.driver.execute_script(f"arguments[0].value = '{formatted_dob}'; arguments[0].dispatchEvent(new Event('input', {{ bubbles: true }})); arguments[0].dispatchEvent(new Event('change', {{ bubbles: true }}));", dob_input)
time.sleep(1)
# Press Tab to move to next field and ensure the date is set
dob_input.send_keys(Keys.TAB)
time.sleep(1)
# Step 2: Fill Member Number
if self.memberId:
print(f"DEBUG: Filling member ID: '{self.memberId}'")
member_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@placeholder='Member Number' and @ng-model='vm.number']")
)
)
time.sleep(1)
member_input.click()
time.sleep(1)
member_input.clear()
time.sleep(1)
member_input.send_keys(self.memberId)
time.sleep(1)
member_input.send_keys(Keys.TAB)
time.sleep(1)
# Step 3: Fill First Name and Last Name
# if self.firstName:
# print(f"DEBUG: Filling first name: '{self.firstName}'")
# first_name_input = wait.until(
# EC.visibility_of_element_located(
# (By.XPATH, "//input[@placeholder='First Name' and @ng-model='vm.firstName']")
# )
# )
# time.sleep(1)
# first_name_input.click()
# time.sleep(1)
# first_name_input.clear()
# time.sleep(1)
# first_name_input.send_keys(self.firstName)
# time.sleep(1)
# first_name_input.send_keys(Keys.TAB)
# time.sleep(1)
# if self.lastName:
# print(f"DEBUG: Filling last name: '{self.lastName}'")
# last_name_input = wait.until(
# EC.visibility_of_element_located(
# (By.XPATH, "//input[@placeholder='Last Name' and @ng-model='vm.lastName']")
# )
# )
# time.sleep(1)
# last_name_input.click()
# time.sleep(1)
# last_name_input.clear()
# time.sleep(1)
# last_name_input.send_keys(self.lastName)
# time.sleep(1)
# Step 4: Click SEARCH button
print("DEBUG: Clicking SEARCH button")
search_button = wait.until(
EC.element_to_be_clickable(
(By.XPATH, "//button[@ng-click='vm.searchPrep()' and contains(text(),'SEARCH')]")
)
)
search_button.click()
time.sleep(2) # Wait for search to complete
return "Success"
except Exception as e:
print(f"Error filling Member Eligibility: {e}")
return "ERROR:MEMBER_ELIGIBILITY_FAILED"
def fill_procedure_code(self):
wait = WebDriverWait(self.driver, 30)
try:
print(f"DEBUG: Filling procedure code: '{self.procedureCode}'")
if not self.procedureCode:
print("DEBUG: No procedure code provided, skipping")
return "Success"
# Step 1: Find the procedure code input field
procedure_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@ng-model='serviceLine.procedure' and @placeholder='Code']")
)
)
time.sleep(1)
# Step 2: Type the procedure code to trigger typeahead
procedure_input.clear()
procedure_input.send_keys(self.procedureCode)
time.sleep(3) # Wait for typeahead to load
# Step 3: Look for the typeahead dropdown and select the first option
# The dropdown appears as ul with class 'dropdown-menu'
try:
# Wait for the dropdown to be visible
dropdown = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//ul[contains(@class,'dropdown-menu') and not(contains(@class,'ng-hide'))]")
)
)
# Look for the first option in the dropdown
first_option = dropdown.find_element(By.XPATH, ".//li")
if first_option:
print("DEBUG: Found first option in procedure dropdown, clicking...")
first_option.click()
time.sleep(2)
return "Success"
except:
# If no dropdown appears, just press Tab to move to next field
print("DEBUG: No procedure dropdown found, pressing Tab")
procedure_input.send_keys(Keys.TAB)
time.sleep(1)
return "Success"
return "Success"
except Exception as e:
print(f"Error filling procedure code: {e}")
return "ERROR:PROCEDURE_CODE_FAILED"
def fill_tooth_number(self):
wait = WebDriverWait(self.driver, 30)
try:
# Use hardcoded value if toothNumber is not provided
tooth_value = self.toothNumber if self.toothNumber else "14" # Hardcoded default value
print(f"DEBUG: Filling tooth number: '{tooth_value}' (original: '{self.toothNumber}')")
# Step 1: Find the tooth input field
tooth_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@ng-model='serviceLine.toothCode' and @placeholder='Tooth']")
)
)
time.sleep(1)
# Step 2: Type the tooth number
tooth_input.clear()
tooth_input.send_keys(tooth_value.upper()) # Convert to uppercase as per onkeyup
time.sleep(2)
# Press Tab to move to next field
tooth_input.send_keys(Keys.TAB)
time.sleep(1)
return "Success"
except Exception as e:
print(f"Error filling tooth number: {e}")
return "ERROR:TOOTH_NUMBER_FAILED"
def fill_tooth_surface(self):
wait = WebDriverWait(self.driver, 30)
try:
# Use hardcoded value if toothSurface is not provided
surface_value = self.toothSurface if self.toothSurface else "B" # Hardcoded default value
print(f"DEBUG: Filling tooth surface: '{surface_value}' (original: '{self.toothSurface}')")
# Step 1: Find the tooth surface input field
surface_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@ng-model='serviceLine.surface' and @placeholder='Surface']")
)
)
time.sleep(1)
# Step 2: Type the tooth surface (convert to uppercase)
surface_input.clear()
surface_input.send_keys(surface_value.upper())
time.sleep(2)
# Press Tab to move to next field
surface_input.send_keys(Keys.TAB)
time.sleep(1)
return "Success"
except Exception as e:
print(f"Error filling tooth surface: {e}")
return "ERROR:TOOTH_SURFACE_FAILED"
def fill_quadrant(self):
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Selecting quadrant 'Upper Right'")
# Step 1: Find the quadrant dropdown
quadrant_dropdown = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//select[@ng-model='serviceLine.quadrant']")
)
)
time.sleep(1)
# Step 2: Use Select class to choose Upper Right
select = Select(quadrant_dropdown)
select.select_by_visible_text("Upper Right")
time.sleep(2)
return "Success"
except Exception as e:
print(f"Error selecting quadrant: {e}")
return "ERROR:QUADRANT_FAILED"
def fill_arch(self):
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Selecting arch 'Entire Oral Cavity'")
# Step 1: Find the arch dropdown
arch_dropdown = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//select[@ng-model='serviceLine.arch']")
)
)
time.sleep(1)
# Step 2: Use Select class to choose Entire Oral Cavity
select = Select(arch_dropdown)
select.select_by_visible_text("Entire Oral Cavity")
time.sleep(2)
return "Success"
except Exception as e:
print(f"Error selecting arch: {e}")
return "ERROR:ARCH_FAILED"
def fill_authorization_number(self):
wait = WebDriverWait(self.driver, 30)
try:
# Use hardcoded value for authorization number
auth_number = "AUTH123456" # Hardcoded default value
print(f"DEBUG: Filling authorization number: '{auth_number}'")
# Step 1: Find the authorization number input field
auth_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@ng-model='serviceLine.authorizationNumber' and @placeholder='No.']")
)
)
time.sleep(1)
# Step 2: Type the authorization number
auth_input.clear()
auth_input.send_keys(auth_number)
time.sleep(2)
# Press Tab to move to next field
auth_input.send_keys(Keys.TAB)
time.sleep(1)
return "Success"
except Exception as e:
print(f"Error filling authorization number: {e}")
return "ERROR:AUTH_NUMBER_FAILED"
def select_place_of_service_office(self):
wait = WebDriverWait(self.driver, 30)
try:
# Step 1: Wait for the place of service dropdown to be present
pos_dropdown = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//select[contains(@ng-model,'vm.newClaim.placeOfTreatmentCode')]")
)
)
time.sleep(1)
# Step 2: Use Select class to choose Office
select = Select(pos_dropdown)
select.select_by_visible_text("Office")
time.sleep(2)
return "Success"
except Exception as e:
print(f"Error selecting Office place of service: {e}")
return "ERROR:SELECTION FAILED"
def select_office_summit_framingham(self):
wait = WebDriverWait(self.driver, 30)
try:
# Step 1: Wait for the office dropdown to be visible and enabled
office_dropdown = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//select[@id='selectOffice']")
)
)
time.sleep(1)
# Step 2: Use Select class to choose Summit Dental Care - Framingham -
select = Select(office_dropdown)
# Fallback: try by visible text, then by value
try:
select.select_by_visible_text("Summit Dental Care - Framingham - ")
except:
select.select_by_value("string:0010a00001XPhhtAAD")
time.sleep(2)
return "Success"
except Exception as e:
print(f"Error selecting Summit Dental Care - Framingham office: {e}")
return "ERROR:SELECTION FAILED"
def select_dentist_gao_kai(self):
wait = WebDriverWait(self.driver, 30)
try:
# Step 1: Wait for the dentist input field to be visible and enabled
dentist_input = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//input[@id='inputProvider' and @placeholder='Enter a dentist']")
)
)
time.sleep(1)
# Step 2: Type "Gao" to trigger typeahead
dentist_input.clear()
dentist_input.send_keys("Gao")
time.sleep(3) # Wait for typeahead to load
# Step 3: Wait for the typeahead dropdown to appear and find the specific option
# Look for the <a> element with ng-bind-html containing the text
option_xpath = "//a[@ng-bind-html and contains(., 'Gao, Kai - 1457649006')]"
try:
# Wait for the option to be clickable
option = wait.until(
EC.element_to_be_clickable((By.XPATH, option_xpath))
)
print("DEBUG: Found dentist option, clicking...")
# Scroll into view and click
self.driver.execute_script("arguments[0].scrollIntoView(true);", option)
time.sleep(1)
option.click()
time.sleep(2)
return "Success"
except:
# Fallback: Try alternative selectors
fallback_selectors = [
"//ul[contains(@class,'dropdown-menu')]//a[contains(., 'Gao, Kai - 1457649006')]",
"//ul[contains(@class,'dropdown-menu')]//li[contains(., 'Gao, Kai - 1457649006')]",
"//div[contains(@class,'typeahead')]//a[contains(., 'Gao, Kai - 1457649006')]",
]
for selector in fallback_selectors:
try:
option = wait.until(
EC.element_to_be_clickable((By.XPATH, selector))
)
print(f"DEBUG: Found option with fallback selector: {selector}")
self.driver.execute_script("arguments[0].scrollIntoView(true);", option)
time.sleep(1)
option.click()
time.sleep(2)
return "Success"
except:
continue
# If all selectors fail, use Arrow Down + Enter
print("DEBUG: Using Arrow Down + Enter fallback")
dentist_input.send_keys(Keys.ARROW_DOWN)
time.sleep(1)
dentist_input.send_keys(Keys.ENTER)
time.sleep(2)
return "Success"
except Exception as e:
print(f"Error selecting dentist Gao, Kai: {e}")
return "ERROR:SELECTION_FAILED"
def click_plus_button(self):
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Clicking plus button to add new service line")
# Step 1: Wait a moment to ensure the previous service line is fully processed
time.sleep(3)
# Step 2: Try multiple approaches to find the plus button
plus_button = None
# First try to find any button with + text
try:
print("DEBUG: Looking for any button with + text")
all_plus_buttons = self.driver.find_elements(By.XPATH, "//button[contains(text(), '+')]")
print(f"DEBUG: Found {len(all_plus_buttons)} buttons with + text")
for i, btn in enumerate(all_plus_buttons):
try:
ng_click = btn.get_attribute("ng-click")
class_attr = btn.get_attribute("class")
print(f"DEBUG: Button {i+1} - ng-click: {ng_click}, class: {class_attr}")
if ng_click and "serviceLineAdd" in ng_click:
plus_button = btn
print(f"DEBUG: Found correct plus button at index {i+1}")
break
except:
continue
except Exception as e:
print(f"DEBUG: Error searching for + buttons: {e}")
# If still not found, try specific selectors
if not plus_button:
selectors = [
"//button[@ng-click='vm.serviceLineAdd()' and contains(@class,'btn') and text()='+']",
"//button[contains(@class,'btn') and @ng-click='vm.serviceLineAdd()']",
"//td/button[@ng-click='vm.serviceLineAdd()']",
"//button[text()='+']",
"//button[contains(@class,'btn-default') and text()='+']",
"//button[contains(@class,'btn-sm') and text()='+']",
"//*[contains(@ng-click,'serviceLineAdd') and contains(text(),'+')]"
]
for selector in selectors:
try:
print(f"DEBUG: Trying selector: {selector}")
plus_button = wait.until(
EC.element_to_be_clickable((By.XPATH, selector))
)
print(f"DEBUG: Found plus button with selector: {selector}")
break
except:
print(f"DEBUG: Selector failed: {selector}")
continue
if not plus_button:
# Last resort: try to find by JavaScript
try:
print("DEBUG: Trying JavaScript approach")
plus_button = self.driver.execute_script("""
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].textContent.trim() === '+' &&
buttons[i].getAttribute('ng-click') &&
buttons[i].getAttribute('ng-click').includes('serviceLineAdd')) {
return buttons[i];
}
}
return null;
""")
if plus_button:
print("DEBUG: Found plus button using JavaScript")
else:
raise Exception("JavaScript search also failed")
except Exception as e:
print(f"DEBUG: JavaScript approach failed: {e}")
raise Exception("Could not find plus button with any method")
time.sleep(1)
# Step 3: Scroll into view
self.driver.execute_script("arguments[0].scrollIntoView(true);", plus_button)
time.sleep(1)
# Step 4: Check if button is enabled
is_disabled = plus_button.get_attribute("disabled")
ng_disabled = plus_button.get_attribute("ng-disabled")
print(f"DEBUG: Button disabled attribute: {is_disabled}")
print(f"DEBUG: Button ng-disabled attribute: {ng_disabled}")
# Check if button is actually enabled and clickable
try:
if not plus_button.is_enabled():
print("DEBUG: Button is not enabled, waiting...")
time.sleep(2)
if not plus_button.is_enabled():
raise Exception("Plus button is still not enabled after waiting")
except Exception as e:
print(f"DEBUG: Error checking button enabled state: {e}")
# Step 5: Click the button using multiple methods
try:
print("DEBUG: Attempting direct click")
plus_button.click()
except:
try:
print("DEBUG: Direct click failed, trying JavaScript click")
self.driver.execute_script("arguments[0].click();", plus_button)
except:
print("DEBUG: JavaScript click failed, trying ActionChains")
from selenium.webdriver.common.action_chains import ActionChains
actions = ActionChains(self.driver)
actions.move_to_element(plus_button).click().perform()
time.sleep(2) # Wait for new row to be created
print("DEBUG: Successfully clicked plus button")
return "Success"
except Exception as e:
print(f"Error clicking plus button: {e}")
# Add debug information about current page state
try:
page_source_length = len(self.driver.page_source)
current_url = self.driver.current_url
print(f"DEBUG: Current URL: {current_url}")
print(f"DEBUG: Page source length: {page_source_length}")
# Try to find all buttons on page for debugging
all_buttons = self.driver.find_elements(By.TAG_NAME, "button")
print(f"DEBUG: Total buttons found: {len(all_buttons)}")
for i, btn in enumerate(all_buttons[:10]): # Show first 10
try:
text = btn.text.strip()
ng_click = btn.get_attribute("ng-click")
print(f"DEBUG: Button {i+1} - Text: '{text}', ng-click: {ng_click}")
except:
continue
except:
pass
return "ERROR:PLUS_BUTTON_FAILED"
def add_file_attachment(self):
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Adding file attachment")
if not self.upload_files:
print("DEBUG: No uploaded files available for attachment")
return "ERROR:NO_FILES"
add_file_button_locator = (
By.XPATH,
"//button[@ng-click='vm.addFile(vm.fileType)' and contains(., 'Add File')]"
)
with tempfile.TemporaryDirectory() as tmp_dir:
for file_obj in self.upload_files:
if not isinstance(file_obj, dict):
continue
base64_data = file_obj.get("bufferBase64", "")
file_name = file_obj.get("originalname", "file.pdf")
if not base64_data:
print("DEBUG: Skipping file because bufferBase64 is missing")
continue
safe_file_name = os.path.basename(file_name)
if not any(safe_file_name.lower().endswith(ext) for ext in [".pdf", ".jpg", ".jpeg", ".png", ".webp"]):
safe_file_name += ".bin"
tmp_path = os.path.join(tmp_dir, safe_file_name)
with open(tmp_path, "wb") as f:
f.write(base64.b64decode(base64_data))
print(f"DEBUG: Uploading file: {tmp_path}")
print(f"DEBUG: File base64 length: {len(base64_data)}")
# Instead of using send_keys, directly inject the file into Angular's displayedArray
print("DEBUG: Directly injecting file into Angular vm.displayedArray")
injection_result = self.driver.execute_script("""
try {
var fileInput = document.getElementById('fileAttachment');
if (!fileInput) return 'FILE_INPUT_NOT_FOUND';
var scope = angular.element(fileInput).scope();
if (!scope || !scope.vm) return 'SCOPE_NOT_FOUND';
// Create a file attachment object matching the Angular template structure
// Template expects: file.type, file.name, file.updatedDate
var fileAttachment = {
name: arguments[0], // file.name
type: scope.vm.fileType || 'RR', // file.type
data: arguments[1], // base64 data
updatedDate: new Date() // file.updatedDate (Date object for Angular date filter)
};
// Initialize displayedArray if it doesn't exist
if (!scope.vm.displayedArray) {
scope.vm.displayedArray = [];
}
// Add the file to the array
scope.vm.displayedArray.push(fileAttachment);
// Trigger Angular digest
scope.$apply();
return 'FILE_INJECTED:' + scope.vm.displayedArray.length;
} catch (err) {
return 'ERROR:' + err.message;
}
""", safe_file_name, base64_data)
print(f"DEBUG: Injection result = {injection_result}")
if not injection_result.startswith('FILE_INJECTED'):
print(f"DEBUG: Injection failed: {injection_result}")
return "ERROR:UPLOAD_FAILED"
print("DEBUG: Waiting for attachment row to appear in webpage table")
# Wait for the table to be visible with at least one row
wait.until(
EC.presence_of_element_located(
(By.XPATH, "//table[contains(@class, 'table-striped') and @ng-if='vm.displayedArray && vm.displayedArray.length']//tbody/tr")
)
)
# Verify what data is actually in the table row
table_data = self.driver.execute_script("""
try {
var table = document.querySelector("table[ng-if*='displayedArray']");
if (!table) return 'TABLE_NOT_FOUND';
var rows = table.querySelectorAll('tbody tr');
if (rows.length === 0) return 'NO_ROWS';
var firstRow = rows[0];
var cells = firstRow.querySelectorAll('td');
var cellData = [];
for (var i = 0; i < cells.length; i++) {
cellData.push(cells[i].textContent.trim());
}
return {rowCount: rows.length, firstRowData: cellData};
} catch (err) {
return 'ERROR:' + err.message;
}
""")
print(f"DEBUG: Table data after injection = {table_data}")
print("DEBUG: Attachment row appeared in webpage table")
time.sleep(1)
return "Success"
except Exception as e:
try:
print(f"DEBUG: Current URL during upload failure = {self.driver.current_url}")
file_inputs = self.driver.find_elements(By.XPATH, "//input[@type='file']")
print(f"DEBUG: Total file inputs found during failure = {len(file_inputs)}")
for index, item in enumerate(file_inputs[:5]):
try:
print(f"DEBUG: File input {index + 1} id={item.get_attribute('id')} style={item.get_attribute('style')} value={item.get_attribute('value')}")
except Exception:
continue
except Exception:
pass
print(f"Error adding file attachment: {e}")
return "ERROR:UPLOAD_FAILED"
def select_radiology_reports(self):
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Selecting Radiology Reports document type")
# Step 1: Find the document type dropdown
doc_type_dropdown = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//select[@ng-model='vm.fileType' and @ng-options=\"item.value as item.name for item in vm.documentTypes\"]")
)
)
time.sleep(1)
# Step 2: Use Select class to choose Radiology Reports
select = Select(doc_type_dropdown)
select.select_by_visible_text("Radiology Reports")
self.driver.execute_script(
"arguments[0].dispatchEvent(new Event('input', { bubbles: true }));"
"arguments[0].dispatchEvent(new Event('change', { bubbles: true }));",
doc_type_dropdown
)
wait.until(lambda d: (doc_type_dropdown.get_attribute("value") or "").strip() != "")
time.sleep(2)
print("DEBUG: Successfully selected Radiology Reports")
return "Success"
except Exception as e:
print(f"Error selecting Radiology Reports: {e}")
return "ERROR:RADIOLOGY_REPORTS_FAILED"
def click_submit_button(self):
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Clicking SUBMIT button")
# Find and click the SUBMIT button
submit_button = wait.until(
EC.element_to_be_clickable(
(By.XPATH, "//button[@ng-click=\"vm.submitForm('File Custom Pop Up Error')\" and contains(., 'SUBMIT')]")
)
)
# Check if button is disabled
if submit_button.get_attribute("disabled"):
print("DEBUG: SUBMIT button is disabled")
return "ERROR:SUBMIT_BUTTON_DISABLED"
print(f"DEBUG: SUBMIT button found. disabled={submit_button.get_attribute('disabled')}")
submit_button.click()
print("DEBUG: SUBMIT button clicked successfully")
# Wait for navigation to confirmation page
time.sleep(6)
print(f"DEBUG: Current URL after submit = {self.driver.current_url}")
return "Success"
except Exception as e:
print(f"Error clicking SUBMIT button: {e}")
return "ERROR:SUBMIT_FAILED"
def extract_claim_number(self):
"""Extract claim number from MassHealth confirmation page"""
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Extracting claim number from confirmation page")
# Wait for confirmation page to load
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
time.sleep(2)
# Try to get page source for debugging
page_text = self.driver.find_element(By.TAG_NAME, "body").text
print(f"DEBUG: Page text snippet: {page_text[:500]}")
# Try multiple selectors to find claim number
# Based on the screenshot: "Your claim/pre-authorization has been submitted and assigned the number 202609200006700"
claim_number_selectors = [
# Look for the specific text pattern
"//*[contains(text(), 'assigned the number')]/text()",
"//*[contains(text(), 'assigned the number')]",
"//p[contains(text(), 'assigned the number')]",
"//div[contains(text(), 'assigned the number')]",
"//span[contains(text(), 'assigned the number')]",
# Look for "Submission Success" section
"//h1[contains(text(), 'Submission Success')]/following-sibling::*",
"//h2[contains(text(), 'Submission Success')]/following-sibling::*",
# Common patterns for claim numbers on MassHealth
"//*[contains(text(), 'Claim Number') or contains(text(), 'Claim #')]/following-sibling::*",
"//td[contains(text(), 'Claim Number')]/following-sibling::td",
"//label[contains(text(), 'Claim Number')]/following-sibling::div",
"//div[contains(@class, 'confirmation')]//div[contains(text(), 'Number')]",
"//div[contains(@class, 'success')]//div[contains(text(), 'Number')]",
]
for selector in claim_number_selectors:
try:
elements = self.driver.find_elements(By.XPATH, selector)
for element in elements:
text = element.text.strip()
print(f"DEBUG: Checking element with selector {selector}: {text[:100]}")
# Look for 15-digit claim number pattern (MassHealth format: YYYYMMDD + 7 digits)
match = re.search(r'(\d{15})', text)
if match:
claim_number = match.group(1)
print(f"DEBUG: Found 15-digit claim number: {claim_number}")
return claim_number
# Also try 9-14 digit patterns
match = re.search(r'(\d{9,14})', text)
if match:
claim_number = match.group(1)
print(f"DEBUG: Found claim number (9-14 digits): {claim_number}")
return claim_number
except Exception as e:
print(f"DEBUG: Selector {selector} failed: {e}")
continue
# If specific selectors fail, try JavaScript to find claim number pattern
# MassHealth claim numbers are 15 digits: YYYYMMDD + 7 digit sequence
claim_number = self.driver.execute_script(r"""
// Look for text that matches claim number patterns
var allElements = document.querySelectorAll('body, p, div, span, td, label, h1, h2, h3');
for (var i = 0; i < allElements.length; i++) {
var text = allElements[i].textContent || '';
// MassHealth format: 15 digits (YYYYMMDD + 7 digits)
var match15 = text.match(/(\d{15})/);
if (match15) {
return match15[1];
}
// Also check for 9-14 digit patterns
var match9to14 = text.match(/(\d{9,14})/);
if (match9to14) {
return match9to14[1];
}
}
return null;
""")
if claim_number:
print(f"DEBUG: Found claim number via JavaScript: {claim_number}")
return claim_number
print("DEBUG: Could not extract claim number from page")
return None
except Exception as e:
print(f"Error extracting claim number: {e}")
import traceback
traceback.print_exc()
return None
def save_confirmation_pdf(self):
wait = WebDriverWait(self.driver, 30)
try:
print("DEBUG: Saving confirmation page as PDF")
# Wait for page to fully load
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
time.sleep(2)
print(f"DEBUG: Capturing PDF from: {self.driver.current_url}")
# Extract claim number from confirmation page
claim_number = self.extract_claim_number()
# Generate safe filename using member ID, claim number and timestamp
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
timestamp = time.strftime("%Y%m%d_%H%M%S")
if claim_number:
safe_claim = "".join(c for c in str(claim_number) if c.isalnum() or c in "-_.")[:20]
pdf_filename = f"claim_confirmation_{safe_member}_{safe_claim}_{timestamp}.pdf"
else:
pdf_filename = f"claim_confirmation_{safe_member}_{timestamp}.pdf"
# Use Chrome DevTools to generate PDF
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {
"printBackground": True
})
pdf_bytes = base64.b64decode(pdf_data['data'])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print(f"DEBUG: PDF saved at: {pdf_path}")
return {
"status": "success",
"pdf_path": pdf_path,
"file_type": "pdf",
"claimNumber": claim_number,
"message": "Confirmation PDF captured successfully"
}
except Exception as e:
print(f"PDF capture failed: {e}")
# Fallback to screenshot
try:
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
timestamp = time.strftime("%Y%m%d_%H%M%S")
screenshot_path = os.path.join(self.download_dir, f"claim_confirmation_{safe_member}_{timestamp}.png")
self.driver.save_screenshot(screenshot_path)
print(f"DEBUG: Screenshot saved at: {screenshot_path}")
return {
"status": "success",
"pdf_path": screenshot_path,
"file_type": "screenshot",
"claimNumber": None,
"message": "Screenshot captured (PDF failed)"
}
except Exception as ss_error:
return {
"status": "error",
"message": f"PDF + Screenshot failed: {ss_error}"
}
def fill_service_line(self, row_number=1, procedure_code="", tooth_number="", tooth_surface="", quadrant="Upper Right", arch="Entire Oral Cavity", auth_number="AUTH123456", billed_amount=""):
wait = WebDriverWait(self.driver, 30)
try:
print(f"DEBUG: Filling service line {row_number} - Procedure: {procedure_code}, Tooth: {tooth_number}, Surface: {tooth_surface}, Amount: {billed_amount}")
# Fill Procedure Code - find the specific row's procedure input
if procedure_code:
print(f"DEBUG: Filling procedure code: '{procedure_code}' for row {row_number}")
# Find all procedure inputs and select the one for the current row
procedure_inputs = wait.until(
EC.presence_of_all_elements_located(
(By.XPATH, "//input[@ng-model='serviceLine.procedure' and @placeholder='Code']")
)
)
if len(procedure_inputs) < row_number:
raise Exception(f"Only {len(procedure_inputs)} procedure inputs found, but need row {row_number}")
procedure_input = procedure_inputs[row_number - 1] # Convert to 0-based index
time.sleep(1)
procedure_input.clear()
procedure_input.send_keys(procedure_code)
time.sleep(3) # Wait for typeahead
# Try to select from dropdown if available
try:
dropdown = wait.until(
EC.visibility_of_element_located(
(By.XPATH, "//ul[contains(@class,'dropdown-menu') and not(contains(@class,'ng-hide'))]")
)
)
first_option = dropdown.find_element(By.XPATH, ".//li")
if first_option:
first_option.click()
time.sleep(2)
except:
procedure_input.send_keys(Keys.TAB)
time.sleep(1)
# Fill Tooth Number - only if provided in claim form
if tooth_number:
print(f"DEBUG: Filling tooth number: '{tooth_number}' for row {row_number}")
tooth_inputs = wait.until(
EC.presence_of_all_elements_located(
(By.XPATH, "//input[@ng-model='serviceLine.toothCode' and @placeholder='Tooth']")
)
)
if len(tooth_inputs) < row_number:
raise Exception(f"Only {len(tooth_inputs)} tooth inputs found, but need row {row_number}")
tooth_input = tooth_inputs[row_number - 1]
time.sleep(1)
tooth_input.clear()
tooth_input.send_keys(tooth_number.upper())
time.sleep(2)
tooth_input.send_keys(Keys.TAB)
time.sleep(1)
else:
print(f"DEBUG: No tooth number provided for row {row_number}, skipping")
# Fill Tooth Surface - only if provided in claim form
if tooth_surface:
print(f"DEBUG: Filling tooth surface: '{tooth_surface}' for row {row_number}")
surface_inputs = wait.until(
EC.presence_of_all_elements_located(
(By.XPATH, "//input[@ng-model='serviceLine.surface' and @placeholder='Surface']")
)
)
if len(surface_inputs) < row_number:
raise Exception(f"Only {len(surface_inputs)} surface inputs found, but need row {row_number}")
surface_input = surface_inputs[row_number - 1]
time.sleep(1)
surface_input.clear()
surface_input.send_keys(tooth_surface.upper())
time.sleep(2)
surface_input.send_keys(Keys.TAB)
time.sleep(1)
else:
print(f"DEBUG: No tooth surface provided for row {row_number}, skipping")
# COMMENTED OUT: Quadrant - not needed per user requirement
# Will be handled later for specific procedure codes that require it
# quadrant_dropdowns = wait.until(
# EC.presence_of_all_elements_located(
# (By.XPATH, "//select[@ng-model='serviceLine.quadrant']")
# )
# )
# if len(quadrant_dropdowns) < row_number:
# raise Exception(f"Only {len(quadrant_dropdowns)} quadrant dropdowns found, but need row {row_number}")
# quadrant_dropdown = quadrant_dropdowns[row_number - 1]
# time.sleep(1)
# select_quadrant = Select(quadrant_dropdown)
# select_quadrant.select_by_visible_text(quadrant)
# time.sleep(2)
# COMMENTED OUT: Arch - not needed per user requirement
# Will be handled later for specific procedure codes that require it
# arch_dropdowns = wait.until(
# EC.presence_of_all_elements_located(
# (By.XPATH, "//select[@ng-model='serviceLine.arch']")
# )
# )
# if len(arch_dropdowns) < row_number:
# raise Exception(f"Only {len(arch_dropdowns)} arch dropdowns found, but need row {row_number}")
# arch_dropdown = arch_dropdowns[row_number - 1]
# time.sleep(1)
# select_arch = Select(arch_dropdown)
# select_arch.select_by_visible_text(arch)
# time.sleep(2)
# COMMENTED OUT: Authorization Number - not needed per user requirement
# auth_inputs = wait.until(
# EC.presence_of_all_elements_located(
# (By.XPATH, "//input[@ng-model='serviceLine.authorizationNumber' and @placeholder='No.']")
# )
# )
# if len(auth_inputs) < row_number:
# raise Exception(f"Only {len(auth_inputs)} auth inputs found, but need row {row_number}")
# auth_input = auth_inputs[row_number - 1]
# time.sleep(1)
# auth_input.clear()
# auth_input.send_keys(auth_number)
# time.sleep(2)
# auth_input.send_keys(Keys.TAB)
# time.sleep(1)
# Fill Billing Amount - find the specific row's billed amount input
if billed_amount:
print(f"DEBUG: Filling billing amount: '{billed_amount}' for row {row_number}")
billed_amount_inputs = wait.until(
EC.presence_of_all_elements_located(
(By.XPATH, "//input[@ng-model='serviceLine.billedAmount' and @placeholder='00.00' and @type='number']")
)
)
if len(billed_amount_inputs) < row_number:
raise Exception(f"Only {len(billed_amount_inputs)} billed amount inputs found, but need row {row_number}")
billed_amount_input = billed_amount_inputs[row_number - 1]
time.sleep(1)
billed_amount_input.clear()
billed_amount_input.send_keys(billed_amount)
time.sleep(2)
# Trigger change event to ensure Angular recognizes the input
self.driver.execute_script("arguments[0].dispatchEvent(new Event('input', { bubbles: true })); arguments[0].dispatchEvent(new Event('change', { bubbles: true }));", billed_amount_input)
time.sleep(1)
billed_amount_input.send_keys(Keys.TAB)
time.sleep(2) # Extra wait to ensure the service line is fully processed
else:
print(f"DEBUG: No billing amount provided for row {row_number}, skipping")
time.sleep(2) # Wait even if no amount to ensure processing
print(f"DEBUG: Service line {row_number} filled successfully")
return "Success"
except Exception as e:
print(f"Error filling service line {row_number}: {e}")
return "ERROR:SERVICE_LINE_FILL_FAILED"
def run(self):
try:
self.config_driver()
self.driver.get("https://provider.masshealth-dental.org/mh_provider_login")
time.sleep(2)
login_result = self.login()
if login_result != "Success":
return {"status": "error", "message": login_result}
nav_result = self.navigate_to_submit_claims()
if nav_result != "Success":
return {"status": "error", "message": nav_result}
select_result = self.select_dental_claim()
if select_result != "Success":
return {"status": "error", "message": select_result}
date_result = self.fill_date_of_service()
if date_result != "Success":
return {"status": "error", "message": date_result}
pos_result = self.select_place_of_service_office()
if pos_result != "Success":
return {"status": "error", "message": pos_result}
office_result = self.select_office_summit_framingham()
if office_result != "Success":
return {"status": "error", "message": office_result}
dentist_result = self.select_dentist_gao_kai()
if dentist_result != "Success":
return {"status": "error", "message": dentist_result}
eligibility_result = self.fill_member_eligibility()
if eligibility_result != "Success":
return {"status": "error", "message": eligibility_result}
# Fill service lines - handle multiple lines
if not self.serviceLines:
print("DEBUG: No service lines found, using fallback values")
# Fallback: fill one service line with default values
first_service_result = self.fill_service_line(
row_number=1,
procedure_code=self.procedureCode,
tooth_number=self.toothNumber,
tooth_surface=self.toothSurface
)
if first_service_result != "Success":
return {"status": "error", "message": first_service_result}
else:
# Fill each service line
for i, service_line in enumerate(self.serviceLines):
row_num = i + 1 # Convert to 1-based indexing
print(f"DEBUG: Processing service line {row_num} of {len(self.serviceLines)}")
# For first line, fill directly
if i == 0:
service_result = self.fill_service_line(
row_number=row_num,
procedure_code=service_line.get("procedureCode", ""),
tooth_number=service_line.get("toothNumber", ""),
tooth_surface=service_line.get("toothSurface", ""),
billed_amount=service_line.get("totalBilled", "")
)
else:
# For subsequent lines, click plus button first
plus_result = self.click_plus_button()
if plus_result != "Success":
return {"status": "error", "message": plus_result}
service_result = self.fill_service_line(
row_number=row_num,
procedure_code=service_line.get("procedureCode", ""),
tooth_number=service_line.get("toothNumber", ""),
tooth_surface=service_line.get("toothSurface", ""),
billed_amount=service_line.get("totalBilled", "")
)
if service_result != "Success":
return {"status": "error", "message": f"Service line {row_num} failed: {service_result}"}
# Select Radiology Reports document type after filling all service lines
radiology_result = self.select_radiology_reports()
if radiology_result != "Success":
return {"status": "error", "message": radiology_result}
# Add file attachment after selecting document type (optional - only if files provided)
if self.upload_files:
file_result = self.add_file_attachment()
if file_result != "Success":
return {"status": "error", "message": file_result}
else:
print("DEBUG: No files to attach, skipping attachment step")
# Click SUBMIT button to submit the claim
submit_result = self.click_submit_button()
if submit_result != "Success":
return {"status": "error", "message": submit_result}
# Save confirmation page as PDF
pdf_result = self.save_confirmation_pdf()
if pdf_result.get("status") == "error":
return {"status": "error", "message": pdf_result.get("message")}
# Optional: wait a moment so you can see the confirmation page
time.sleep(3)
return {
"status": "success",
"message": "Claims automation completed successfully.",
"pdf_path": pdf_result.get("pdf_path"),
"file_type": pdf_result.get("file_type"),
"claimNumber": pdf_result.get("claimNumber")
}
except Exception as e:
print(f"Claims automation error: {e}")
return {"status": "error", "message": str(e)}
finally:
# Close the browser after all processes complete
if self.driver:
self.driver.quit()