feat: appointment type inference, procedure codes on cards, claim attachment fixes

- Add appointment type categories matching insurance claim form (recall, filling, pedo, dentures, implant, endo, crown, perio, extraction, ortho, consultation, emergency, other)
- Auto-infer appointment type from CDT codes with priority rules (endo > implant > crown > ...)
- typeLocked flag prevents auto-overwrite when user manually sets type
- Show appointment type label and procedure codes on schedule cards
- Background sync on /day route retroactively fixes stale appointment types
- Fix PUT /api/claims/:id to save claimFiles (previously silently dropped)
- Auto-link AppointmentFile records to ClaimFile when claim is created or updated
- Fix D5750 (denture reline) CDT range to map correctly to dentures category
- Fix typeLocked Zod rejection in appointment update route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-05-29 14:18:10 -04:00
parent b20dc8e976
commit 9d0cfe5dba
260 changed files with 2443 additions and 1968 deletions

View File

@@ -647,17 +647,34 @@ class AutomationUnitedDHClaimSubmit:
))
)
# Explicit wait: hold until Angular has auto-filled the location dropdown
# Select Treatment Location (first dropdown) — page auto-fills Billing Entity and rest
print("[UnitedDH Claim] step3: Selecting Treatment Location...")
location_selected = False
try:
WebDriverWait(self.driver, 10).until(
lambda d: d.find_elements(By.XPATH,
"//ng-select//span[contains(@class,'ng-value-label') and normalize-space(text())!=''] | "
"//ng-select//div[contains(@class,'ng-value') and normalize-space(.)!='']"
)
location_ng = self.driver.find_element(By.XPATH,
"//label[contains(text(),'Treatment Location') or contains(text(),'treatment location')]"
"/..//ng-select | "
"(//ng-select)[1]"
)
print("[UnitedDH Claim] step3: Location auto-filled")
except TimeoutException:
print("[UnitedDH Claim] step3: Location field did not populate in time — proceeding anyway")
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", location_ng)
time.sleep(0.3)
location_ng.click()
time.sleep(1)
first_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and not(contains(@class,'disabled'))]"
))
)
option_text = first_option.text.strip()
first_option.click()
print(f"[UnitedDH Claim] step3: Selected Treatment Location: {option_text}")
location_selected = True
time.sleep(1) # wait for page to auto-fill remaining fields
except Exception as e:
print(f"[UnitedDH Claim] step3: Treatment Location selection failed: {e}")
if not location_selected:
print("[UnitedDH Claim] step3: WARNING: Could not select Treatment Location — continuing anyway")
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", continue_btn)
continue_btn.click()
@@ -706,28 +723,29 @@ class AutomationUnitedDHClaimSubmit:
line.get("billedAmount") or
line.get("fee") or ""
).strip()
print(f"[UnitedDH Claim] step4: line {idx}: code={code}, billed={billed}")
tooth = str(line.get("toothNumber") or line.get("tooth_number") or "").strip()
surface = str(line.get("toothSurface") or line.get("tooth_surface") or "").strip().upper()
print(f"[UnitedDH Claim] step4: line {idx}: code={code}, billed={billed}, tooth={tooth}, surface={surface}")
# For lines after the first, click btnAddItem to open a new procedure row
if idx > 0:
try:
add_btn = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.ID, "btnAddItem"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn)
add_btn.click()
print(f"[UnitedDH Claim] step4: clicked btnAddItem to start row {idx}")
time.sleep(1)
except Exception as e:
print(f"[UnitedDH Claim] step4: could not click btnAddItem for row {idx}: {e}")
# For ALL rows, click btnAddItem to open/activate the procedure row
try:
add_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.ID, "btnAddItem"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn)
add_btn.click()
print(f"[UnitedDH Claim] step4: clicked btnAddItem to open row {idx}")
time.sleep(1)
except Exception as e:
print(f"[UnitedDH Claim] step4: could not click btnAddItem to open row {idx}: {e}")
# Type CDT code in procedureCode input
try:
proc_input = WebDriverWait(self.driver, 8).until(
proc_input = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.ID, "procedureCode"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_input)
proc_input.click()
self.driver.execute_script("arguments[0].click();", proc_input)
proc_input.send_keys(Keys.CONTROL + "a")
proc_input.send_keys(Keys.DELETE)
proc_input.send_keys(code)
@@ -750,6 +768,50 @@ class AutomationUnitedDHClaimSubmit:
print(f"[UnitedDH Claim] step4: could not click btnAddItem for billed amount row {idx}: {e}")
continue
# Fill tooth number
if tooth:
try:
tooth_input = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.ID, "tooth"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_input)
tooth_input.click()
tooth_input.send_keys(Keys.CONTROL + "a")
tooth_input.send_keys(Keys.DELETE)
tooth_input.send_keys(tooth)
print(f"[UnitedDH Claim] step4: entered tooth number: {tooth} for row {idx}")
time.sleep(0.3)
except Exception as e:
print(f"[UnitedDH Claim] step4: could not fill tooth number for row {idx}: {e}")
# Click surface boxes (B, D, F, L, M, O, etc.) — only present for filling codes
if surface:
try:
# Check surface box group is present before trying to click
surface_boxes = self.driver.find_elements(By.XPATH,
"//div[contains(@class,'claim-add-item-group__box')]"
)
if surface_boxes:
for letter in surface:
if not letter.strip():
continue
try:
box = self.driver.find_element(By.XPATH,
f"//div[contains(@class,'claim-add-item-group__box') "
f"and not(contains(@class,'--disabled')) "
f"and @id='{letter}']"
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", box)
box.click()
print(f"[UnitedDH Claim] step4: clicked surface '{letter}' for row {idx}")
time.sleep(0.2)
except Exception:
print(f"[UnitedDH Claim] step4: surface '{letter}' not found or disabled for row {idx}")
else:
print(f"[UnitedDH Claim] step4: no surface boxes on page for row {idx} — skipping")
except Exception as e:
print(f"[UnitedDH Claim] step4: surface click error for row {idx}: {e}")
# Fill billed amount
if billed:
try:

View File

@@ -702,12 +702,45 @@ class AutomationUnitedSCOEligibilityCheck:
except TimeoutException:
print("[UnitedSCO step1] Select Insurance popup not found — proceeding")
# Step 1.5: Provider & Location page — just click Continue
# Step 1.5: Provider & Location page — select Treatment Location (first dropdown),
# page auto-fills the rest, then click Continue
print("[UnitedSCO step1] Waiting for Provider & Location page...")
try:
# Wait for the Continue button to confirm the page loaded
continue_btn2 = WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Continue')]"))
)
# Select Treatment Location — click the dropdown and pick the first option;
# the page will auto-fill Billing Entity and other fields automatically
print("[UnitedSCO step1] Selecting Treatment Location...")
location_selected = False
try:
location_ng = self.driver.find_element(By.XPATH,
"//label[contains(text(),'Treatment Location') or contains(text(),'treatment location')]"
"/..//ng-select | "
"(//ng-select)[1]"
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", location_ng)
time.sleep(0.3)
location_ng.click()
time.sleep(1)
first_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and not(contains(@class,'disabled'))]"
))
)
option_text = first_option.text.strip()
first_option.click()
print(f"[UnitedSCO step1] Selected Treatment Location: {option_text}")
location_selected = True
time.sleep(1) # wait for page to auto-fill remaining fields
except Exception as e:
print(f"[UnitedSCO step1] Treatment Location selection failed: {e}")
if not location_selected:
print("[UnitedSCO step1] WARNING: Could not select Treatment Location — continuing anyway")
continue_btn2.click()
print("[UnitedSCO step1] Clicked Continue button (Provider & Location)")
time.sleep(5)