From 865b754ae1ddb43a2e3fc321a84316fb4307b28a Mon Sep 17 00:00:00 2001 From: Gitead Date: Tue, 15 Sep 2026 22:44:59 -0400 Subject: [PATCH] fix: complete United Ucard claim flow and remember OTP device - Click the first visible 'Next' button on the Patient Information step instead of a stale hidden one, so the wizard advances after member select - Read procedure fee from totalBilled/billedAmount (matching every other claim worker) instead of a fee key the frontend never sends - Implement claim confirmation PDF capture + claim number extraction - Check "Remember this device" on the OTP page so future logins skip OTP Co-Authored-By: Claude Sonnet 5 --- .../helpers_uniteducard_claim.py | 16 ++++ .../helpers_uniteducard_eligibility.py | 16 ++++ .../selenium_UnitedUcard_claimSubmitWorker.py | 96 +++++++++++++++++-- 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/apps/SeleniumService/helpers_uniteducard_claim.py b/apps/SeleniumService/helpers_uniteducard_claim.py index 79dbd1a5..7bd4a87a 100644 --- a/apps/SeleniumService/helpers_uniteducard_claim.py +++ b/apps/SeleniumService/helpers_uniteducard_claim.py @@ -160,6 +160,22 @@ async def start_uniteducard_claim_run(sid: str, data: dict, url: str): ) otp_input.clear() otp_input.send_keys(otp_value) + + # Check "Remember this device" so future logins skip OTP. + # Confirmed against live site (2026-09-15): + # + try: + remember_checkbox = driver.find_element(By.CSS_SELECTOR, + "[data-cy='data-checkbox-rememberDevice']") + if remember_checkbox.get_attribute("aria-checked") != "true": + driver.execute_script( + "arguments[0].scrollIntoView({block:'center'});", remember_checkbox) + remember_checkbox.click() + print("[UnitedUcard Claim OTP] Checked 'Remember this device'") + except Exception as remember_err: + print(f"[UnitedUcard Claim OTP] Could not check 'Remember this device': {remember_err}") + try: try: verify_btn = driver.find_element(By.ID, "continuebtn") diff --git a/apps/SeleniumService/helpers_uniteducard_eligibility.py b/apps/SeleniumService/helpers_uniteducard_eligibility.py index 9f68ab5a..eada6232 100644 --- a/apps/SeleniumService/helpers_uniteducard_eligibility.py +++ b/apps/SeleniumService/helpers_uniteducard_eligibility.py @@ -179,6 +179,22 @@ async def start_uniteducard_run(sid: str, data: dict, url: str): ) otp_input.clear() otp_input.send_keys(otp_value) + + # Check "Remember this device" so future logins skip OTP. + # Confirmed against live site (2026-09-15): + # + try: + remember_checkbox = driver.find_element(By.CSS_SELECTOR, + "[data-cy='data-checkbox-rememberDevice']") + if remember_checkbox.get_attribute("aria-checked") != "true": + driver.execute_script( + "arguments[0].scrollIntoView({block:'center'});", remember_checkbox) + remember_checkbox.click() + print("[UnitedUcard OTP] Checked 'Remember this device'") + except Exception as remember_err: + print(f"[UnitedUcard OTP] Could not check 'Remember this device': {remember_err}") + try: # Try the confirmed id first — the broader text/type # fallback below can match the WRONG button if more diff --git a/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py b/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py index c787e8e0..3f39c939 100644 --- a/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py @@ -22,6 +22,8 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import os +import re +import base64 from uniteducard_browser_manager import get_browser_manager @@ -285,13 +287,26 @@ class AutomationUnitedUcardClaimSubmit: the visible label is a plain
("Next"); on the Claim Information step it's instead a chevron_right.jpg image with no text. Either way the click target is whichever clickable ancestor wraps it. + + On the Patient Information step specifically, after a member is + selected and its details card is shown, the old text-based "Next" + div is hidden (not removed) and a new chevron_right.jpg-based Next + renders below the member info section. Both XPath branches can + therefore match simultaneously, with the hidden old one appearing + first in DOM order — so this must pick the first *visible* match, + not just the first *present* one, or it silently clicks a dead + element and the wizard never advances. """ try: - next_label = WebDriverWait(self.driver, 15).until( - EC.presence_of_element_located((By.XPATH, - "//div[normalize-space(text())='Next'] | " - "//img[contains(@src,'chevron_right')]")) - ) + def _first_visible_next(driver): + for el in driver.find_elements(By.XPATH, + "//div[normalize-space(text())='Next'] | " + "//img[contains(@src,'chevron_right')]"): + if el.is_displayed(): + return el + return None + + next_label = WebDriverWait(self.driver, 15).until(_first_visible_next) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", next_label) time.sleep(0.3) # Prefer clicking a clickable ancestor (button/a/[role=button]) so @@ -650,7 +665,7 @@ class AutomationUnitedUcardClaimSubmit:
Save Procedure
""" procedure_code = str(line.get("procedureCode", "")).strip() - fee = line.get("fee") + fee = line.get("totalBilled") or line.get("billedAmount") or line.get("fee") procedure_date = line.get("procedureDate") or self.serviceDate if not procedure_code: @@ -811,6 +826,69 @@ class AutomationUnitedUcardClaimSubmit: return True def step7_save_confirmation_pdf(self): - """TODO: capture the claim confirmation number and save/print the confirmation as PDF - into self.download_dir. Return {"pdf_path": "...", "claimNumber": "..."}.""" - return {} + """ + Wait for the "Claim Submitted" confirmation page, extract the Claim + ID, and save the page as a PDF. + + Confirmed against the live site (2026-09-15): +

Claim Submitted

+ Claim ID: 260483578000 + Submitted On: Tue Sep 15 2026 22:29:39 GMT-0400 (Eastern Daylight Time) + """ + try: + WebDriverWait(self.driver, 30).until( + lambda d: "claim submitted" in d.page_source.lower() + ) + time.sleep(2) + print(f"[UnitedUcard Claim step7] Confirmation page URL: {self.driver.current_url}") + + claim_number = None + try: + body_text = self.driver.find_element(By.TAG_NAME, "body").text + match = re.search(r'Claim ID:?\s*(\d{6,})', body_text, re.IGNORECASE) + if match: + claim_number = match.group(1) + print(f"[UnitedUcard Claim step7] Extracted claim number: {claim_number}") + else: + match = re.search(r'\b(\d{10,})\b', body_text) + if match: + claim_number = match.group(1) + print(f"[UnitedUcard Claim step7] Extracted claim number (fallback): {claim_number}") + except Exception as e: + print(f"[UnitedUcard Claim step7] Could not extract claim number: {e}") + + shared_downloads = os.path.join(_SERVICE_DIR, "downloads") + os.makedirs(shared_downloads, exist_ok=True) + safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.") + safe_claim = ("_" + claim_number[:20]) if claim_number else "" + timestamp = time.strftime("%Y%m%d_%H%M%S") + pdf_filename = f"uniteducard_claim_confirmation_{safe_member}{safe_claim}_{timestamp}.pdf" + pdf_path = os.path.join(shared_downloads, pdf_filename) + + try: + pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", { + "printBackground": True, + "paperWidth": 8.5, + "paperHeight": 11, + "marginTop": 0.4, + "marginBottom": 0.4, + "marginLeft": 0.4, + "marginRight": 0.4, + }) + pdf_bytes = base64.b64decode(pdf_data["data"]) + with open(pdf_path, "wb") as f: + f.write(pdf_bytes) + print(f"[UnitedUcard Claim step7] PDF saved: {pdf_path}") + except Exception as e: + print(f"[UnitedUcard Claim step7] PDF capture failed: {e}") + return f"ERROR: step7 PDF failed: {e}" + + return { + "status": "success", + "pdf_path": pdf_path, + "claimNumber": claim_number, + } + + except Exception as e: + print(f"[UnitedUcard Claim step7] Exception: {e}") + return f"ERROR: step7 failed: {e}"