diff --git a/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py b/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py index 0b94a978..291cdc7d 100644 --- a/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py @@ -446,14 +446,13 @@ class AutomationCCAClaimSubmit: time.sleep(1) print(f"[CCA Claim step4] Capturing dashboard — URL: {self.driver.current_url}") - # Extract Encounter ID from the LAST row (newest claim is at the bottom) + # Extract Encounter ID from the FIRST row (newest claim is at the top) claim_number = None # Try table cells first (Encounter ID is the first column) for sel in [ - (By.XPATH, "//table//tbody//tr[last()]/td[1]"), - (By.XPATH, "//*[contains(@class,'wbx-table-results')]//tr[last()]/td[1]"), - (By.XPATH, "//table//tbody//tr[last()-0]/td[1]"), + (By.XPATH, "//table//tbody//tr[1]/td[1]"), + (By.XPATH, "//*[contains(@class,'wbx-table-results')]//tr[1]/td[1]"), ]: try: cell = WebDriverWait(self.driver, 8).until( @@ -461,7 +460,7 @@ class AutomationCCAClaimSubmit: text = cell.text.strip() if re.match(r'\d{10,}', text): claim_number = text - print(f"[CCA Claim step4] Encounter ID from last row: {claim_number}") + print(f"[CCA Claim step4] Encounter ID from top row: {claim_number}") break except Exception: continue @@ -591,14 +590,12 @@ class AutomationCCAClaimSubmit: # ------------------------------------------------------------------ # def _build_col_map(self): """ - Map column names to 1-based td positions using ej-mappingname divs. - Falls back to hardcoded positions based on the known PDF column order. + Map column names to 1-based td positions using ej-mappingname divs, + in the order they actually appear in the live grid header (1-based + index = td position). Falls back to hardcoded positions (stale — + do not trust for BILLED_AMOUNT) only if the live scan fails. """ - # Hardcoded based on PDF column order: - # rownum(1), Code(2), Desc(3), Tooth(4), - # Surf1-5(5-9), OralCavity1-4(10-13), DiagPtr1-4(14-17), - # EPSDT(18), Qty(19), Auth(20), ServiceDate(21), BilledAmt(22) - col_map = { + fallback_col_map = { "CODE": 2, "TOOTH": 4, "SURF1": 5, "S1": 5, @@ -608,10 +605,9 @@ class AutomationCCAClaimSubmit: "SURF5": 9, "S5": 9, "QUANTITY": 19, "QTY": 19, "DOS": 21, "SERVICE DATE": 21, "SERVICEDATE": 21, - "BILLED_AMOUNT": 22, "BILLED AMT": 22, "BILLED A": 22, + "BILLED_AMOUNT": 19, "BILLED AMT": 19, "BILLED A": 19, } - # Try to confirm/override positions using ej-mappingname divs in header try: divs = self.driver.find_elements( By.XPATH, @@ -619,14 +615,29 @@ class AutomationCCAClaimSubmit: "//div[@id='ServicesGrid']//div[@ej-mappingname]" ) print(f"[CCA Claim grid] Found {len(divs)} ej-mappingname header divs") - for div in divs: - mapping = div.get_attribute("ej-mappingname") or "" + col_map = {} + for i, div in enumerate(divs): + mapping = (div.get_attribute("ej-mappingname") or "").strip().upper() text = div.text.strip() - print(f"[CCA Claim grid] Header: mapping={mapping!r} text={text!r}") + print(f"[CCA Claim grid] Header[{i+1}]: mapping={mapping!r} text={text!r}") + if mapping: + col_map[mapping] = i + 1 + + # Aliases for the surface columns, which have empty mapping + # names in some grid states — fill in from SURFACE1..5 if present. + for n in range(1, 6): + key = f"SURFACE{n}" + if key in col_map: + col_map[f"SURF{n}"] = col_map[key] + col_map[f"S{n}"] = col_map[key] + + if col_map: + return col_map except Exception as e: print(f"[CCA Claim grid] Header scan error: {e}") - return col_map + print("[CCA Claim grid] Falling back to hardcoded column map") + return fallback_col_map def _dbl_click_col(self, row_num, col_idx): """Double-click the cell at (row_num, col_idx) — both 1-based. @@ -733,38 +744,51 @@ class AutomationCCAClaimSubmit: cell_clicked = False # --- Single-click cell to activate (yellow), then fill input --- - def click_cell_and_fill(col_expr, input_id, value): + def click_cell_and_fill(col_expr, input_id, value, row_timeout=6): """ Click the cell at (row_num, col_expr) to activate it (turns yellow), then fill the pre-rendered input by ID. col_expr can be a number like 2 or an XPath expression like 'last()' or 'last()-1'. + Polls for up to row_timeout seconds since a new row is rendered + asynchronously by the grid after the previous row commits. """ - # Find the cell in the grid CONTENT rows (not headers) - cell = None - for xpath in [ + xpaths = [ f"(//div[contains(@class,'e-gridcontent')]//tr)[{row_num}]/td[{col_expr}]", f"(//div[contains(@class,'e-content')]//tr)[{row_num}]/td[{col_expr}]", f"(//div[@id='Services']//div[contains(@class,'e-content')]//tr)[{row_num}]/td[{col_expr}]", f"(//tr[@aria-rowindex='{row_num-1}'])/td[{col_expr}]", - ]: - try: - cell = self.driver.find_element(By.XPATH, xpath) - break - except Exception: - continue + ] - if cell: - try: - self.driver.execute_script( - "arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", - cell) - cell.click() + deadline = time.monotonic() + row_timeout + clicked = False + while time.monotonic() < deadline and not clicked: + cell = None + for xpath in xpaths: + try: + cell = self.driver.find_element(By.XPATH, xpath) + break + except Exception: + continue + + if cell: + try: + self.driver.execute_script( + "arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", + cell) + cell.click() + clicked = True + time.sleep(0.3) + print(f"[CCA Claim grid] Clicked row={row_num} col={col_expr}") + except Exception as e: + print(f"[CCA Claim grid] Cell click failed, retrying: {e}") + time.sleep(0.3) + else: + print(f"[CCA Claim grid] Cell not found yet row={row_num} col={col_expr}, retrying") time.sleep(0.3) - print(f"[CCA Claim grid] Clicked row={row_num} col={col_idx}") - except Exception as e: - print(f"[CCA Claim grid] Cell click failed: {e}") - else: - print(f"[CCA Claim grid] Cell not found row={row_num} col={col_idx}") + + if not clicked: + print(f"[CCA Claim grid] Gave up clicking row={row_num} col={col_expr} after {row_timeout}s") + return False # Fill the pre-rendered input try: @@ -785,23 +809,31 @@ class AutomationCCAClaimSubmit: print(f"[CCA Claim grid] Fill {input_id} failed: {e}") return False - # CODE (col 2) - click_cell_and_fill(2, "ServicesCODE", code) + # CODE + code_ok = click_cell_and_fill(col_map["CODE"], "ServicesCODE", code) - # TOOTH (col 4) — skip if empty + # TOOTH — skip if empty + tooth_ok = True if tooth: - click_cell_and_fill(4, "ServicesTOOTH", tooth) + tooth_ok = click_cell_and_fill(col_map["TOOTH"], "ServicesTOOTH", tooth) - # Surfaces (col 5-9) — skip if empty + # Surfaces — skip if empty if surface_chars: + surf_ids = ["ServicesSURF1","ServicesSURF2","ServicesSURF3", + "ServicesSURF4","ServicesSURF5"] for si, char in enumerate(surface_chars[:5]): - surf_ids = ["ServicesSURF1","ServicesSURF2","ServicesSURF3", - "ServicesSURF4","ServicesSURF5"] - click_cell_and_fill(5 + si, surf_ids[si], char) + click_cell_and_fill(col_map[f"SURF{si+1}"], surf_ids[si], char) - # Billed Amount — last column + # Billed Amount # QTY and Service Date auto-fill after CODE is entered and this cell is clicked - click_cell_and_fill("last()", "ServicesBILLED_AMOUNT", billed_str) + billed_ok = click_cell_and_fill(col_map["BILLED_AMOUNT"], "ServicesBILLED_AMOUNT", billed_str) + + if not (code_ok and tooth_ok and billed_ok): + missing = [name for name, ok in + [("code", code_ok), ("tooth", tooth_ok), ("billed amount", billed_ok)] + if not ok] + return (f"ERROR: failed to fill {', '.join(missing)} for service line " + f"{idx+1} (code={code}) — claim NOT submitted") time.sleep(0.3)