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:
2026-07-08 00:22:35 -04:00
co-authored by Claude Sonnet 5
parent 99cdfa702a
commit 3582da8510
2 changed files with 85 additions and 146 deletions
+2 -2
View File
@@ -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)