fix: update DDMA claim submit worker for new deltadentalma.com claims-submission flow
Site now uses a dedicated claims-submission page with a Subscriber ID + Birthdate search form instead of the member-ID search box, and login detection now checks for the check-patient-benefits-search link rather than the old member-search input placeholder. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -152,7 +152,7 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str):
|
|||||||
if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url:
|
if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url:
|
||||||
try:
|
try:
|
||||||
WebDriverWait(driver, 5).until(
|
WebDriverWait(driver, 5).until(
|
||||||
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
|
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
|
||||||
)
|
)
|
||||||
print("[DDMA Claim OTP] Member search found — login successful!")
|
print("[DDMA Claim OTP] Member search found — login successful!")
|
||||||
login_success = True
|
login_success = True
|
||||||
@@ -184,7 +184,7 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str):
|
|||||||
driver.get("https://www.deltadentalma.com/private/provider/dashboard")
|
driver.get("https://www.deltadentalma.com/private/provider/dashboard")
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
WebDriverWait(driver, 10).until(
|
WebDriverWait(driver, 10).until(
|
||||||
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
|
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
|
||||||
)
|
)
|
||||||
print("[DDMA Claim OTP] Member search found — login successful!")
|
print("[DDMA Claim OTP] Member search found — login successful!")
|
||||||
login_success = True
|
login_success = True
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
import calendar
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from ddma_browser_manager import get_browser_manager
|
from ddma_browser_manager import get_browser_manager
|
||||||
@@ -108,7 +109,7 @@ class AutomationDDMAClaimSubmit:
|
|||||||
if "member" not in current_url.lower():
|
if "member" not in current_url.lower():
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 5).until(
|
WebDriverWait(self.driver, 5).until(
|
||||||
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
|
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
|
||||||
)
|
)
|
||||||
print("[DDMA Claim login] Already logged in")
|
print("[DDMA Claim login] Already logged in")
|
||||||
return "ALREADY_LOGGED_IN"
|
return "ALREADY_LOGGED_IN"
|
||||||
@@ -117,7 +118,7 @@ class AutomationDDMAClaimSubmit:
|
|||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 5).until(
|
WebDriverWait(self.driver, 5).until(
|
||||||
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
|
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
|
||||||
)
|
)
|
||||||
print("[DDMA Claim login] Already logged in — member search found")
|
print("[DDMA Claim login] Already logged in — member search found")
|
||||||
return "ALREADY_LOGGED_IN"
|
return "ALREADY_LOGGED_IN"
|
||||||
@@ -134,7 +135,7 @@ class AutomationDDMAClaimSubmit:
|
|||||||
current_url = self.driver.current_url
|
current_url = self.driver.current_url
|
||||||
if "sign-in" not in current_url.lower():
|
if "sign-in" not in current_url.lower():
|
||||||
member_search = WebDriverWait(self.driver, 3).until(
|
member_search = WebDriverWait(self.driver, 3).until(
|
||||||
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
|
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
|
||||||
)
|
)
|
||||||
if member_search:
|
if member_search:
|
||||||
print("[DDMA Claim login] Session valid — skipping login")
|
print("[DDMA Claim login] Session valid — skipping login")
|
||||||
@@ -180,7 +181,7 @@ class AutomationDDMAClaimSubmit:
|
|||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 5).until(
|
WebDriverWait(self.driver, 5).until(
|
||||||
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
|
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
|
||||||
)
|
)
|
||||||
print("[DDMA Claim login] Already authenticated after modal dismiss")
|
print("[DDMA Claim login] Already authenticated after modal dismiss")
|
||||||
return "ALREADY_LOGGED_IN"
|
return "ALREADY_LOGGED_IN"
|
||||||
@@ -239,94 +240,24 @@ class AutomationDDMAClaimSubmit:
|
|||||||
print(f"[DDMA Claim login] Exception: {e}")
|
print(f"[DDMA Claim login] Exception: {e}")
|
||||||
return f"ERROR:LOGIN FAILED: {e}"
|
return f"ERROR:LOGIN FAILED: {e}"
|
||||||
|
|
||||||
def _select_provider_dropdown(self):
|
|
||||||
"""
|
|
||||||
Click the provider dropdown on the member search page and select the option
|
|
||||||
matching self.providerName (case-insensitive). Falls back to the first option.
|
|
||||||
The button data-testid is 'member-search_provider_select-btn'.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
short_wait = WebDriverWait(self.driver, 5)
|
|
||||||
try:
|
|
||||||
provider_btn = short_wait.until(
|
|
||||||
EC.element_to_be_clickable(
|
|
||||||
(By.XPATH, '//button[@data-testid="member-search_provider_select-btn"]')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except TimeoutException:
|
|
||||||
print("[DDMA Claim step1] No provider dropdown found — skipping")
|
|
||||||
return
|
|
||||||
|
|
||||||
provider_btn.click()
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
try:
|
|
||||||
WebDriverWait(self.driver, 5).until(
|
|
||||||
EC.presence_of_element_located((By.XPATH, "//*[@role='option']"))
|
|
||||||
)
|
|
||||||
except TimeoutException:
|
|
||||||
print("[DDMA Claim step1] Provider listbox did not open")
|
|
||||||
return
|
|
||||||
|
|
||||||
options = self.driver.find_elements(By.XPATH, "//*[@role='option']")
|
|
||||||
print(f"[DDMA Claim step1] Provider options: {[o.text.strip() for o in options]}")
|
|
||||||
|
|
||||||
target = (self.providerName or "").strip().lower()
|
|
||||||
selected = False
|
|
||||||
|
|
||||||
if target:
|
|
||||||
for opt in options:
|
|
||||||
if target in opt.text.lower():
|
|
||||||
opt.click()
|
|
||||||
print(f"[DDMA Claim step1] Selected provider: '{opt.text.strip()}'")
|
|
||||||
selected = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not selected and options:
|
|
||||||
options[0].click()
|
|
||||||
print(f"[DDMA Claim step1] No match for '{self.providerName}', selected first: '{options[0].text.strip()}'")
|
|
||||||
|
|
||||||
time.sleep(0.3)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[DDMA Claim step1] Provider selection error (non-fatal): {e}")
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Step 1 — Navigate directly to /members then search patient #
|
# Step 1 — Navigate to claims-submission page, search by Subscriber #
|
||||||
# (same as eligibility — bypasses onboarding date/location screen) #
|
# ID + Birthdate (same search form as the eligibility flow) #
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
def step1_search_patient(self):
|
def step1_search_patient(self):
|
||||||
"""Search for the patient — identical to DDMA eligibility step1.
|
"""Open the claims-submission page and search for the patient."""
|
||||||
Does NOT navigate: login already left the browser on the search page."""
|
|
||||||
wait = WebDriverWait(self.driver, 30)
|
wait = WebDriverWait(self.driver, 30)
|
||||||
|
|
||||||
def replace_with_sendkeys(el, value):
|
|
||||||
el.click()
|
|
||||||
el.send_keys(Keys.CONTROL, "a")
|
|
||||||
el.send_keys(Keys.BACKSPACE)
|
|
||||||
el.send_keys(value)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print(f"[DDMA Claim step1] Current URL: {self.driver.current_url}")
|
claims_url = "https://www.deltadentalma.com/private/provider/claims-submission"
|
||||||
print(f"[DDMA Claim step1] Waiting for member search input...")
|
print(f"[DDMA Claim step1] Navigating to claims-submission page: {claims_url}")
|
||||||
|
self.driver.get(claims_url)
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
# Select provider from dropdown based on NPI settings
|
if "claims-submission" not in self.driver.current_url.lower():
|
||||||
self._select_provider_dropdown()
|
print(f"[DDMA Claim step1] Warning: Ended up at {self.driver.current_url} instead of claims-submission page")
|
||||||
|
|
||||||
# Fill Member ID
|
# Fill Birthdate (masked mm/dd/yyyy input — send digits only, mask inserts slashes)
|
||||||
if self.memberId:
|
|
||||||
try:
|
|
||||||
member_id_input = wait.until(EC.presence_of_element_located(
|
|
||||||
(By.XPATH, '//input[@placeholder="Search by member ID"]')
|
|
||||||
))
|
|
||||||
member_id_input.clear()
|
|
||||||
member_id_input.send_keys(self.memberId)
|
|
||||||
print(f"[DDMA Claim step1] Entered Member ID: {self.memberId}")
|
|
||||||
time.sleep(0.2)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[DDMA Claim step1] Warning: Could not fill Member ID: {e}")
|
|
||||||
|
|
||||||
# Fill DOB
|
|
||||||
if self.dateOfBirth:
|
if self.dateOfBirth:
|
||||||
try:
|
try:
|
||||||
dob_parts = self.dateOfBirth.split("-")
|
dob_parts = self.dateOfBirth.split("-")
|
||||||
@@ -334,46 +265,47 @@ class AutomationDDMAClaimSubmit:
|
|||||||
month = dob_parts[1].zfill(2)
|
month = dob_parts[1].zfill(2)
|
||||||
day = dob_parts[2].zfill(2)
|
day = dob_parts[2].zfill(2)
|
||||||
|
|
||||||
dob_container = wait.until(EC.presence_of_element_located(
|
birthdate_input = wait.until(EC.element_to_be_clickable(
|
||||||
(By.XPATH, "//div[@data-testid='member-search_date-of-birth']")
|
(By.XPATH, "//input[@data-test-id='InStateSearchBirthdate']")
|
||||||
))
|
))
|
||||||
month_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
|
birthdate_input.click()
|
||||||
day_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
|
birthdate_input.send_keys(Keys.CONTROL, "a")
|
||||||
year_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
|
birthdate_input.send_keys(Keys.BACKSPACE)
|
||||||
|
birthdate_input.send_keys(f"{month}{day}{year}")
|
||||||
replace_with_sendkeys(month_elem, month)
|
print(f"[DDMA Claim step1] Filled Birthdate: {month}/{day}/{year}")
|
||||||
time.sleep(0.05)
|
|
||||||
replace_with_sendkeys(day_elem, day)
|
|
||||||
time.sleep(0.05)
|
|
||||||
replace_with_sendkeys(year_elem, year)
|
|
||||||
print(f"[DDMA Claim step1] Filled DOB: {month}/{day}/{year}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[DDMA Claim step1] Warning: Could not fill DOB: {e}")
|
print(f"[DDMA Claim step1] Warning: Could not fill Birthdate: {e}")
|
||||||
|
|
||||||
|
# Fill Subscriber ID (== member ID)
|
||||||
|
if self.memberId:
|
||||||
|
try:
|
||||||
|
subscriber_input = wait.until(EC.element_to_be_clickable(
|
||||||
|
(By.XPATH, "//input[@data-test-id='InStateSearchSubscriberId']")
|
||||||
|
))
|
||||||
|
subscriber_input.clear()
|
||||||
|
subscriber_input.send_keys(self.memberId)
|
||||||
|
print(f"[DDMA Claim step1] Filled Subscriber ID: {self.memberId}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DDMA Claim step1] Warning: Could not fill Subscriber ID: {e}")
|
||||||
|
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
|
||||||
# Click Search
|
# Click Search
|
||||||
search_btn = wait.until(EC.element_to_be_clickable(
|
search_btn = wait.until(EC.element_to_be_clickable(
|
||||||
(By.XPATH, '//button[@data-testid="member-search_search-button"]')
|
(By.XPATH, "//button[@data-test-id='InStateSearchButton']")
|
||||||
))
|
))
|
||||||
search_btn.click()
|
search_btn.click()
|
||||||
print("[DDMA Claim step1] Clicked Search button")
|
print("[DDMA Claim step1] Clicked Search button")
|
||||||
|
|
||||||
# Wait for the member search result page to load
|
# Wait for the Members results table to render
|
||||||
WebDriverWait(self.driver, 15).until(
|
|
||||||
EC.any_of(
|
|
||||||
EC.presence_of_element_located((By.XPATH, "//tbody//tr")),
|
|
||||||
EC.presence_of_element_located((By.XPATH, '//div[@data-testid="member-search-result-no-results"]')),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
time.sleep(4)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
no_results = self.driver.find_element(By.XPATH, '//div[@data-testid="member-search-result-no-results"]')
|
WebDriverWait(self.driver, 15).until(
|
||||||
if no_results:
|
EC.presence_of_element_located((By.XPATH, "//table//tbody//tr"))
|
||||||
return "ERROR: No patient found with given search criteria"
|
)
|
||||||
except Exception:
|
time.sleep(2)
|
||||||
pass
|
except TimeoutException:
|
||||||
|
print("[DDMA Claim step1] Warning: No results table appeared within timeout")
|
||||||
|
return "ERROR: No patient found with given search criteria"
|
||||||
|
|
||||||
print("[DDMA Claim step1] Search completed")
|
print("[DDMA Claim step1] Search completed")
|
||||||
return "SUCCESS"
|
return "SUCCESS"
|
||||||
@@ -383,54 +315,61 @@ class AutomationDDMAClaimSubmit:
|
|||||||
return f"ERROR: step1 failed: {e}"
|
return f"ERROR: step1 failed: {e}"
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Step 2 — Click patient name → Member Information page #
|
# Step 2 — Select the matching family member row → Member detail page #
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
def step2_open_member_page(self):
|
def step2_open_member_page(self):
|
||||||
"""Navigate to member detail page — same approach as DDMA eligibility step2."""
|
"""Pick the family member row matching this patient and open their detail page."""
|
||||||
try:
|
try:
|
||||||
# Wait for the results table to appear, then let it stabilize
|
# Wait for the results table to appear, then let it stabilize
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 20).until(
|
WebDriverWait(self.driver, 20).until(
|
||||||
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
|
EC.presence_of_element_located((By.XPATH, "//table//tbody//tr"))
|
||||||
)
|
)
|
||||||
time.sleep(3) # wait for the list to fully stabilize
|
time.sleep(2)
|
||||||
except TimeoutException:
|
except TimeoutException:
|
||||||
print("[DDMA Claim step2] Warning: Results table not found within timeout")
|
print("[DDMA Claim step2] Warning: Results table not found within timeout")
|
||||||
|
|
||||||
# Wait until the patient name link in the first row is clickable
|
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
|
||||||
detail_url = None
|
print(f"[DDMA Claim step2] Found {len(rows)} family member row(s)")
|
||||||
PATIENT_LINK_XPATHS = [
|
|
||||||
"(//table//tbody//tr)[1]//td[1]//a",
|
target_row = None
|
||||||
"(//tbody//tr)[1]//a[contains(@href,'member-details')]",
|
|
||||||
"(//tbody//tr)[1]//a[contains(@href,'member')]",
|
if self.dateOfBirth and rows:
|
||||||
"//a[contains(@href,'member-details')]",
|
|
||||||
]
|
|
||||||
for selector in PATIENT_LINK_XPATHS:
|
|
||||||
try:
|
try:
|
||||||
link_el = WebDriverWait(self.driver, 20).until(
|
dob_parts = self.dateOfBirth.split("-")
|
||||||
EC.element_to_be_clickable((By.XPATH, selector))
|
dob_year, dob_month, dob_day = dob_parts[0], int(dob_parts[1]), int(dob_parts[2])
|
||||||
)
|
expected_dob = f"{calendar.month_abbr[dob_month]} {dob_day}, {dob_year}".lower()
|
||||||
href = link_el.get_attribute("href")
|
for row in rows:
|
||||||
if href and "member-details" in href:
|
cells = row.find_elements(By.XPATH, "./td")
|
||||||
detail_url = href
|
if len(cells) >= 3 and cells[2].text.strip().lower() == expected_dob:
|
||||||
print(f"[DDMA Claim step2] Patient link is clickable: {href}")
|
target_row = row
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DDMA Claim step2] DOB matching error (non-fatal): {e}")
|
||||||
|
|
||||||
|
if target_row is None and self.lastName and rows:
|
||||||
|
for row in rows:
|
||||||
|
if self.lastName.strip().lower() in row.text.lower():
|
||||||
|
target_row = row
|
||||||
break
|
break
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not detail_url:
|
if target_row is None and rows:
|
||||||
return "ERROR: step2 failed: could not find member-details link"
|
target_row = rows[0]
|
||||||
|
print("[DDMA Claim step2] No DOB/name match — defaulting to first family member row")
|
||||||
|
|
||||||
self.driver.get(detail_url)
|
if target_row is None:
|
||||||
print(f"[DDMA Claim step2] Navigating to: {detail_url}")
|
return "ERROR: step2 failed: no family member rows found"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, 15).until(
|
name_link = target_row.find_element(By.XPATH, ".//a")
|
||||||
lambda d: "member-details" in d.current_url
|
name_link.click()
|
||||||
)
|
print("[DDMA Claim step2] Clicked into patient detail page")
|
||||||
print(f"[DDMA Claim step2] Member Information page loaded: {self.driver.current_url}")
|
except Exception as e:
|
||||||
except TimeoutException:
|
print(f"[DDMA Claim step2] Warning: Could not click patient name link: {e}")
|
||||||
print(f"[DDMA Claim step2] Warning — URL: {self.driver.current_url}")
|
return f"ERROR: step2 failed: could not click patient link: {e}"
|
||||||
|
|
||||||
|
time.sleep(2) # let the detail page navigate/render
|
||||||
|
print(f"[DDMA Claim step2] Member detail page loaded: {self.driver.current_url}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Wait until Create claim button is visible AND enabled (not just in the DOM)
|
# Wait until Create claim button is visible AND enabled (not just in the DOM)
|
||||||
|
|||||||
Reference in New Issue
Block a user