1441 lines
64 KiB
Python
Executable File
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()
|
|
|