From 6cfca0d015fa8025c3820e34fe7a46029240b73c Mon Sep 17 00:00:00 2001 From: Gitead Date: Thu, 11 Jun 2026 15:32:01 -0400 Subject: [PATCH] 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 --- .../Backend/src/routes/insuranceStatusDDMA.ts | 5 ++ .../src/routes/insuranceStatusDDMAClaim.ts | 5 ++ .../selenium_DDMA_claimSubmitWorker.py | 56 +++++++++++++++++++ .../selenium_DDMA_eligibilityCheckWorker.py | 56 +++++++++++++++++++ 4 files changed, 122 insertions(+) diff --git a/apps/Backend/src/routes/insuranceStatusDDMA.ts b/apps/Backend/src/routes/insuranceStatusDDMA.ts index 1b8afe35..4c0e2214 100755 --- a/apps/Backend/src/routes/insuranceStatusDDMA.ts +++ b/apps/Backend/src/routes/insuranceStatusDDMA.ts @@ -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 = { ...rawData, massddmaUsername: credentials.username, massddmaPassword: credentials.password, + providerName: primaryProvider?.providerName ?? "", }; const socketId: string | undefined = req.body.socketId; diff --git a/apps/Backend/src/routes/insuranceStatusDDMAClaim.ts b/apps/Backend/src/routes/insuranceStatusDDMAClaim.ts index 8c0dd216..a62b94ff 100644 --- a/apps/Backend/src/routes/insuranceStatusDDMAClaim.ts +++ b/apps/Backend/src/routes/insuranceStatusDDMAClaim.ts @@ -40,11 +40,16 @@ router.post("/ddma-claim", async (req: Request, res: Response): Promise => }); } + // 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 = { claim: { ...claimData, massddmaUsername: credentials.username, massddmaPassword: credentials.password, + providerName: primaryProvider?.providerName ?? "", }, }; diff --git a/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py b/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py index 931bbce4..08fc2c45 100644 --- a/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py @@ -41,6 +41,7 @@ class AutomationDDMAClaimSubmit: self.lastName = claim.get("lastName", "") or last_name self.serviceLines = claim.get("serviceLines", []) or [] self.claimFiles = claim.get("claimFiles", []) or [] + self.providerName = claim.get("providerName", "") or raw.get("providerName", "") self.download_dir = get_browser_manager().download_dir @@ -247,6 +248,58 @@ class AutomationDDMAClaimSubmit: print(f"[DDMA Claim login] Exception: {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 # # (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] Waiting for member search input...") + # Select provider from dropdown based on NPI settings + self._select_provider_dropdown() + # Fill Member ID if self.memberId: try: diff --git a/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py b/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py index 76590d18..c885ad53 100755 --- a/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py +++ b/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py @@ -26,6 +26,7 @@ class AutomationDeltaDentalMAEligibilityCheck: self.lastName = self.data.get("lastName", "") self.massddma_username = self.data.get("massddmaUsername", "") self.massddma_password = self.data.get("massddmaPassword", "") + self.providerName = self.data.get("providerName", "") # Use browser manager's download dir self.download_dir = get_browser_manager().download_dir @@ -273,6 +274,58 @@ class AutomationDeltaDentalMAEligibilityCheck: print("[login] Exception during login:", 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): """Fill search form with all available fields (flexible search).""" wait = WebDriverWait(self.driver, 30) @@ -289,6 +342,9 @@ class AutomationDeltaDentalMAEligibilityCheck: fields.append(f"DOB: {self.dateOfBirth}") 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): el.click() el.send_keys(Keys.CONTROL, "a")