diff --git a/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py b/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py
index aeeb5941..8153ba28 100644
--- a/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py
+++ b/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py
@@ -46,6 +46,7 @@ class AutomationUnitedUcardClaimSubmit:
self.lastName = self.data.get("lastName", "")
self.serviceLines = self.data.get("serviceLines", [])
self.claimFiles = self.data.get("claimFiles", [])
+ self.remarks = self.data.get("remarks", "")
self.providerName = self.data.get("providerName", "")
self.uniteducard_username = self.data.get("uniteducardUsername", "")
self.uniteducard_password = self.data.get("uniteducardPassword", "")
@@ -792,28 +793,212 @@ class AutomationUnitedUcardClaimSubmit:
print(f"[UnitedUcard Claim step5] Attached {attached}/{len(self.claimFiles)} file(s)")
- try:
- review_label = WebDriverWait(self.driver, 15).until(
- EC.presence_of_element_located((By.XPATH,
- "//div[normalize-space(text())='Review and Sign']"))
- )
- self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", review_label)
- time.sleep(0.3)
- target = review_label
- try:
- target = review_label.find_element(By.XPATH,
- "ancestor::*[self::button or self::a or @role='button' or @data-state][1]")
- except Exception:
- pass
- try:
- target.click()
- except Exception:
- self.driver.execute_script("arguments[0].click();", target)
- print("[UnitedUcard Claim] Clicked 'Review and Sign'")
- time.sleep(2)
+ result = self._fill_provider_narrative()
+ if isinstance(result, str) and result.startswith("ERROR"):
+ return result
+
+ if self._click_review_and_sign_button():
return True
+ return "ERROR: Clicked 'Review and Sign' but the Review and Sign step never appeared (stuck on Upload Documents)"
+
+ @staticmethod
+ def _describe_procedure(code):
+ """Plain-language name for a CDT code (falls back to the code itself)."""
+ m = re.match(r"^D(\d{4})", code.upper())
+ n = int(m.group(1)) if m else -1
+ if 2140 <= n <= 2161:
+ return "amalgam filling"
+ if 2330 <= n <= 2394:
+ return "composite filling"
+ if 2510 <= n <= 2664:
+ return "inlay/onlay"
+ if 2710 <= n <= 2799:
+ return "crown"
+ if 3310 <= n <= 3348:
+ return "root canal treatment"
+ if n in (7140, 7210):
+ return "extraction"
+ if n == 1110:
+ return "adult prophylaxis"
+ if n == 1120:
+ return "child prophylaxis"
+ return code
+
+ @staticmethod
+ def _expand_narrative_shorthand(text):
+ """Expand office shorthand, e.g. "comp #8 MDB" -> "composite filling on #8 MDB"."""
+ text = re.sub(r"\bcomp(?:osite)?\b(?:\s+on\b)?", "composite filling on", text, flags=re.I)
+ text = re.sub(r"\bamal(?:gam)?\b(?:\s+on\b)?", "amalgam filling on", text, flags=re.I)
+ return text
+
+ def _build_provider_narrative(self):
+ """
+ Claim remarks (with shorthand expanded), or a description built from
+ the service lines, e.g. "Composite filling on #8 MDB."
+ """
+ text = self._expand_narrative_shorthand((self.remarks or "").strip())
+ if not text:
+ parts = []
+ for line in self.serviceLines:
+ code = str(line.get("procedureCode", "")).strip()
+ if not code:
+ continue
+ tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
+ surface = str(line.get("toothSurface") or line.get("surface") or "").strip().upper()
+ desc = self._describe_procedure(code)
+ if tooth:
+ desc += f" on #{tooth}"
+ if surface:
+ desc += f" {surface}"
+ parts.append(desc)
+ text = "; ".join(parts)
+ if text:
+ text += "."
+ text = " ".join(text.split())
+ if text:
+ text = text[0].upper() + text[1:]
+ return text[:250]
+
+ def _fill_provider_narrative(self):
+ """
+ The site requires a Provider Narrative before "Review and Sign" will
+ advance (the box turns red otherwise).
+
+ Confirmed against the live site (2026-10-22):
+
+ """
+ narrative = self._build_provider_narrative()
+ try:
+ box = WebDriverWait(self.driver, 10).until(
+ EC.visibility_of_element_located((By.ID, "narrative"))
+ )
except TimeoutException:
- return "ERROR: 'Review and Sign' button not found on Upload Documents step"
+ return "ERROR: Provider Narrative box (#narrative) not found on Upload Documents step"
+ self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", box)
+ box.click()
+ box.clear()
+ box.send_keys(narrative)
+ print(f"[UnitedUcard Claim] Entered Provider Narrative: {narrative}")
+ return True
+
+ def _click_review_and_sign_button(self):
+ """
+ Click "Review and Sign" on the Upload Documents step and confirm the
+ Review and Sign step actually rendered. Every step lives in the DOM
+ at once (hidden when inactive), so presence checks prove nothing —
+ wait for a *visible* button, and for #userSignatureText to become
+ visible afterwards.
+
+ Confirmed against the live site (2026-10-22):
+