feat: select DDMA provider by NPI settings instead of always first

Pass the user's primary NPI provider name through the eligibility and
claim routes so the Selenium workers click the matching option in the
DDMA member-search provider dropdown (data-testid=member-search_provider_select-btn)
rather than always falling back to the first entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-06-11 15:32:01 -04:00
parent 75c49ab1df
commit 6cfca0d015
4 changed files with 122 additions and 0 deletions

View File

@@ -67,10 +67,15 @@ router.post(
}); });
} }
// Fetch NPI providers to pick the target provider on the DDMA portal
const npiProviders = await storage.getNpiProvidersByUser(req.user.id);
const primaryProvider = npiProviders[0]; // sorted by sortOrder asc, then id asc
const enrichedData = { const enrichedData = {
...rawData, ...rawData,
massddmaUsername: credentials.username, massddmaUsername: credentials.username,
massddmaPassword: credentials.password, massddmaPassword: credentials.password,
providerName: primaryProvider?.providerName ?? "",
}; };
const socketId: string | undefined = req.body.socketId; const socketId: string | undefined = req.body.socketId;

View File

@@ -40,11 +40,16 @@ router.post("/ddma-claim", async (req: Request, res: Response): Promise<any> =>
}); });
} }
// Fetch NPI providers to pick the target provider on the DDMA portal
const npiProviders = await storage.getNpiProvidersByUser(req.user.id);
const primaryProvider = npiProviders[0]; // sorted by sortOrder asc, then id asc
const enrichedPayload = { const enrichedPayload = {
claim: { claim: {
...claimData, ...claimData,
massddmaUsername: credentials.username, massddmaUsername: credentials.username,
massddmaPassword: credentials.password, massddmaPassword: credentials.password,
providerName: primaryProvider?.providerName ?? "",
}, },
}; };

View File

@@ -41,6 +41,7 @@ class AutomationDDMAClaimSubmit:
self.lastName = claim.get("lastName", "") or last_name self.lastName = claim.get("lastName", "") or last_name
self.serviceLines = claim.get("serviceLines", []) or [] self.serviceLines = claim.get("serviceLines", []) or []
self.claimFiles = claim.get("claimFiles", []) or [] self.claimFiles = claim.get("claimFiles", []) or []
self.providerName = claim.get("providerName", "") or raw.get("providerName", "")
self.download_dir = get_browser_manager().download_dir self.download_dir = get_browser_manager().download_dir
@@ -247,6 +248,58 @@ 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 directly to /members then search patient #
# (same as eligibility — bypasses onboarding date/location screen) # # (same as eligibility — bypasses onboarding date/location screen) #
@@ -266,6 +319,9 @@ class AutomationDDMAClaimSubmit:
print(f"[DDMA Claim step1] Current URL: {self.driver.current_url}") print(f"[DDMA Claim step1] Current URL: {self.driver.current_url}")
print(f"[DDMA Claim step1] Waiting for member search input...") print(f"[DDMA Claim step1] Waiting for member search input...")
# Select provider from dropdown based on NPI settings
self._select_provider_dropdown()
# Fill Member ID # Fill Member ID
if self.memberId: if self.memberId:
try: try:

View File

@@ -26,6 +26,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
self.lastName = self.data.get("lastName", "") self.lastName = self.data.get("lastName", "")
self.massddma_username = self.data.get("massddmaUsername", "") self.massddma_username = self.data.get("massddmaUsername", "")
self.massddma_password = self.data.get("massddmaPassword", "") self.massddma_password = self.data.get("massddmaPassword", "")
self.providerName = self.data.get("providerName", "")
# Use browser manager's download dir # Use browser manager's download dir
self.download_dir = get_browser_manager().download_dir self.download_dir = get_browser_manager().download_dir
@@ -273,6 +274,58 @@ class AutomationDeltaDentalMAEligibilityCheck:
print("[login] Exception during login:", e) print("[login] Exception during login:", 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 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 step1] Provider listbox did not open")
return
options = self.driver.find_elements(By.XPATH, "//*[@role='option']")
print(f"[DDMA 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 step1] Selected provider: '{opt.text.strip()}'")
selected = True
break
if not selected and options:
options[0].click()
print(f"[DDMA step1] No match for '{self.providerName}', selected first: '{options[0].text.strip()}'")
time.sleep(0.3)
except Exception as e:
print(f"[DDMA step1] Provider selection error (non-fatal): {e}")
def step1(self): def step1(self):
"""Fill search form with all available fields (flexible search).""" """Fill search form with all available fields (flexible search)."""
wait = WebDriverWait(self.driver, 30) wait = WebDriverWait(self.driver, 30)
@@ -289,6 +342,9 @@ class AutomationDeltaDentalMAEligibilityCheck:
fields.append(f"DOB: {self.dateOfBirth}") fields.append(f"DOB: {self.dateOfBirth}")
print(f"[DDMA step1] Starting search with: {', '.join(fields)}") print(f"[DDMA step1] Starting search with: {', '.join(fields)}")
# Select provider from dropdown based on NPI settings
self._select_provider_dropdown()
def replace_with_sendkeys(el, value): def replace_with_sendkeys(el, value):
el.click() el.click()
el.send_keys(Keys.CONTROL, "a") el.send_keys(Keys.CONTROL, "a")