fix: Tufts SCO + UnitedDH pre-auth file upload, tooth fill, PDF and pre-auth number

- Frontend: upload attachments to disk before sending pre-auth payload (same pattern as claims)
- cloud-storage: finalizeFileUpload returns diskPath so Python workers get real file paths
- upload-to-cloud route: return diskPath instead of API URL
- TuftsSCO preAuth worker: skip 'Add a file' button click; send_keys directly to hidden react-aria-Input
- TuftsSCO preAuth worker: JS focus() on tooth field to bypass warning-banner overlay
- TuftsSCO preAuth worker: 1.5s wait after procedure code for layout shift to settle
- TuftsSCO preAuth worker: step8 waits for 'thank' in page_source then extracts via 'submitted pre-authorization' regex
- helpers_tuftssco_preauth: convert pdf_path → pdf_url (http://localhost:5002/downloads/...)
- tuftsSCOPreAuthProcessor: use pdf_url (not pdf_path), save preAuthNumber to preAuthNumber field
- unitedDHPreAuthProcessor: save preAuthNumber to preAuthNumber field (not claimNumber)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-15 23:54:05 -04:00
parent beb6a6a8e8
commit dc039741ca
9 changed files with 107 additions and 105 deletions

View File

@@ -88,12 +88,10 @@ async function pollUntilDone(
throw new Error(`Tufts SCO preauth polling exhausted all attempts for session ${sessionId}`); throw new Error(`Tufts SCO preauth polling exhausted all attempts for session ${sessionId}`);
} }
async function savePdfFromSelenium(pdf_path: string, patientId: number) { async function savePdfFromSelenium(pdf_url: string, patientId: number) {
try { try {
const filename = path.basename(pdf_path); const filename = path.basename(new URL(pdf_url).pathname);
const seleniumPort = process.env.SELENIUM_PORT || "5002"; const resp = await axios.get(pdf_url, { responseType: "arraybuffer", timeout: 30000 });
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH"); let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
if (!group) { if (!group) {
@@ -116,7 +114,7 @@ export interface TuftsSCOPreAuthProcessorInput {
export async function runTuftsSCOPreAuthProcessor( export async function runTuftsSCOPreAuthProcessor(
input: TuftsSCOPreAuthProcessorInput, input: TuftsSCOPreAuthProcessorInput,
jobId: string jobId: string
): Promise<{ status: string; pdf_path?: string; preAuthNumber?: string }> { ): Promise<{ status: string; pdf_url?: string; preAuthNumber?: string }> {
const { enrichedPayload, userId, claimId, socketId } = input; const { enrichedPayload, userId, claimId, socketId } = input;
log("tuftssco-preauth-processor", "starting Python agent session", { claimId }); log("tuftssco-preauth-processor", "starting Python agent session", { claimId });
@@ -138,12 +136,12 @@ export async function runTuftsSCOPreAuthProcessor(
} }
const preAuthNumber: string | undefined = seleniumResult.preAuthNumber ?? undefined; const preAuthNumber: string | undefined = seleniumResult.preAuthNumber ?? undefined;
const pdf_path: string | undefined = seleniumResult.pdf_path ?? undefined; const pdf_url: string | undefined = seleniumResult.pdf_url ?? undefined;
if (claimId) { if (claimId) {
try { try {
const updates: Record<string, any> = { status: "PREAUTH" }; const updates: Record<string, any> = { status: "PREAUTH" };
if (preAuthNumber) updates.claimNumber = preAuthNumber; if (preAuthNumber) updates.preAuthNumber = preAuthNumber;
await storage.updateClaim(claimId, updates); await storage.updateClaim(claimId, updates);
log("tuftssco-preauth-processor", "claim record updated", { claimId, preAuthNumber }); log("tuftssco-preauth-processor", "claim record updated", { claimId, preAuthNumber });
@@ -157,22 +155,22 @@ export async function runTuftsSCOPreAuthProcessor(
} }
} }
if (pdf_path && !socketId) { if (pdf_url && !socketId) {
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null; const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId; const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
if (patientId) await savePdfFromSelenium(pdf_path, Number(patientId)); if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
} }
emitToSocket(socketId, "selenium:tuftssco_preauth_completed", { emitToSocket(socketId, "selenium:tuftssco_preauth_completed", {
jobId, jobId,
claimId, claimId,
preAuthNumber, preAuthNumber,
pdf_path, pdf_url,
message: preAuthNumber message: preAuthNumber
? `Tufts SCO pre-authorization submitted — PreAuth #: ${preAuthNumber}` ? `Tufts SCO pre-authorization submitted — PreAuth #: ${preAuthNumber}`
: (seleniumResult?.message ?? "Tufts SCO pre-authorization submitted successfully"), : (seleniumResult?.message ?? "Tufts SCO pre-authorization submitted successfully"),
}); });
log("tuftssco-preauth-processor", "done", { claimId, preAuthNumber }); log("tuftssco-preauth-processor", "done", { claimId, preAuthNumber });
return { status: "success", pdf_path, preAuthNumber }; return { status: "success", pdf_url, preAuthNumber };
} }

View File

@@ -153,7 +153,7 @@ export async function runUnitedDHPreAuthProcessor(
if (claimId) { if (claimId) {
try { try {
const updates: Record<string, any> = { status: "PREAUTH" }; const updates: Record<string, any> = { status: "PREAUTH" };
if (preAuthNumber) updates.claimNumber = preAuthNumber; if (preAuthNumber) updates.preAuthNumber = preAuthNumber;
await storage.updateClaim(claimId, updates); await storage.updateClaim(claimId, updates);
log("uniteddh-preauth-processor", "claim record updated", { claimId, preAuthNumber }); log("uniteddh-preauth-processor", "claim record updated", { claimId, preAuthNumber });

View File

@@ -136,12 +136,12 @@ router.post(
(folder as any).id (folder as any).id
); );
await storage.appendFileChunk((cloudFile as any).id, 0, file.buffer); await storage.appendFileChunk((cloudFile as any).id, 0, file.buffer);
await storage.finalizeFileUpload((cloudFile as any).id); const finalized = await storage.finalizeFileUpload((cloudFile as any).id);
result.push({ result.push({
filename: file.originalname, filename: file.originalname,
mimeType: file.mimetype, mimeType: file.mimetype,
filePath: `/api/cloud-storage/files/${(cloudFile as any).id}/content`, filePath: finalized.diskPath,
}); });
} }

View File

@@ -10,13 +10,12 @@ const router = Router();
* POST /tuftssco-preauth * POST /tuftssco-preauth
* *
* Enqueues a Tufts SCO (DentaQuest) pre-authorization submission job. * Enqueues a Tufts SCO (DentaQuest) pre-authorization submission job.
* Uses persistent session + OTP handling, same pattern as UnitedDH preauth.
* *
* Body fields (JSON): * Body fields (JSON):
* data — preauth payload (memberId, dateOfBirth, serviceDate, serviceLines, patientName, etc.) * data — preauth payload (memberId, dateOfBirth, serviceDate, serviceLines, patientName, etc.)
* socketId — socket.io client id * socketId — socket.io client id
* claimId — existing claim DB id (optional) * claimId — existing claim DB id (optional)
*
* Response: { status: "queued", jobId: "…" }
*/ */
router.post("/tuftssco-preauth", async (req: Request, res: Response): Promise<any> => { router.post("/tuftssco-preauth", async (req: Request, res: Response): Promise<any> => {
if (!req.user?.id) { if (!req.user?.id) {

View File

@@ -1,6 +1,6 @@
import axios from "axios"; import axios from "axios";
const SELENIUM_BASE = process.env.SELENIUM_SERVICE_URL || "http://localhost:8000"; const SELENIUM_BASE = process.env.SELENIUM_SERVICE_URL ?? "http://localhost:5002";
export async function forwardToSeleniumTuftsSCOPreAuthAgent(data: any) { export async function forwardToSeleniumTuftsSCOPreAuthAgent(data: any) {
const response = await axios.post(`${SELENIUM_BASE}/tuftssco-preauth`, data, { const response = await axios.post(`${SELENIUM_BASE}/tuftssco-preauth`, data, {

View File

@@ -106,7 +106,7 @@ export interface IStorage {
folderId?: number | null folderId?: number | null
): Promise<CloudFile>; ): Promise<CloudFile>;
appendFileChunk(fileId: number, seq: number, data: Buffer): Promise<void>; appendFileChunk(fileId: number, seq: number, data: Buffer): Promise<void>;
finalizeFileUpload(fileId: number): Promise<{ ok: true; size: string }>; finalizeFileUpload(fileId: number): Promise<{ ok: true; size: string; diskPath: string }>;
deleteFile(fileId: number): Promise<boolean>; deleteFile(fileId: number): Promise<boolean>;
updateFile( updateFile(
id: number, id: number,
@@ -354,7 +354,7 @@ export const cloudStorageStorage: IStorage = {
}); });
await updateFolderTimestampsRecursively(file.folderId); await updateFolderTimestampsRecursively(file.folderId);
return { ok: true, size: BigInt(total).toString() }; return { ok: true, size: BigInt(total).toString(), diskPath };
}, },
async deleteFile(fileId: number) { async deleteFile(fileId: number) {

View File

@@ -1463,13 +1463,19 @@ export function ClaimForm({
return; return;
} }
const { uploadedFiles: udPreAuthFiles, ...udPreAuthRestForm } = form;
const udPreAuthFilesMeta: ClaimFileMeta[] = udPreAuthFiles?.length
? await uploadAttachmentsToLocalFolder(udPreAuthFiles)
: [];
onHandleForUnitedDHSeleniumPreAuth({ onHandleForUnitedDHSeleniumPreAuth({
...form, ...udPreAuthRestForm,
serviceLines: filteredServiceLines, serviceLines: filteredServiceLines,
staffId: appointmentStaffId ?? Number(staff?.id), staffId: appointmentStaffId ?? Number(staff?.id),
patientId, patientId,
insuranceProvider: "United/DentalHub", insuranceProvider: "United/DentalHub",
insuranceSiteKey: "UNITED_SCO", insuranceSiteKey: "UNITED_SCO",
claimFiles: udPreAuthFilesMeta,
}); });
onClose(); onClose();
@@ -1501,13 +1507,19 @@ export function ClaimForm({
return; return;
} }
const { uploadedFiles: preAuthUploadedFiles, ...preAuthRestForm } = form;
const preAuthFilesMeta: ClaimFileMeta[] = preAuthUploadedFiles?.length
? await uploadAttachmentsToLocalFolder(preAuthUploadedFiles)
: [];
onHandleForTuftsSCOSeleniumPreAuth({ onHandleForTuftsSCOSeleniumPreAuth({
...form, ...preAuthRestForm,
serviceLines: filteredServiceLines, serviceLines: filteredServiceLines,
staffId: appointmentStaffId ?? Number(staff?.id), staffId: appointmentStaffId ?? Number(staff?.id),
patientId, patientId,
insuranceProvider: "Tufts SCO", insuranceProvider: "Tufts SCO",
insuranceSiteKey: "TUFTS_SCO", insuranceSiteKey: "TUFTS_SCO",
claimFiles: preAuthFilesMeta,
}); });
onClose(); onClose();

View File

@@ -159,14 +159,18 @@ async def start_tuftssco_preauth_run(sid: str, data: dict, url: str):
otp_input.send_keys(otp_value) otp_input.send_keys(otp_value)
try: try:
verify_btn = driver.find_element(By.XPATH, verify_btn = driver.find_element(By.XPATH,
"//button[@type='submit'] | " "//button[@type='button' and @aria-label='Verify']")
"//button[contains(text(),'Verify') or contains(text(),'Submit') or contains(text(),'Confirm')]"
)
verify_btn.click() verify_btn.click()
print("[TuftsSCO PreAuth OTP] Clicked verify button") print("[TuftsSCO PreAuth OTP] Clicked Verify button (aria-label)")
except: except Exception:
otp_input.send_keys("\n") try:
print("[TuftsSCO PreAuth OTP] Pressed Enter as fallback") verify_btn = driver.find_element(By.XPATH,
"//button[contains(text(),'Verify') or contains(text(),'Submit') or @type='submit']")
verify_btn.click()
print("[TuftsSCO PreAuth OTP] Clicked verify button (text fallback)")
except Exception:
otp_input.send_keys("\n")
print("[TuftsSCO PreAuth OTP] Pressed Enter as fallback")
print("[TuftsSCO PreAuth OTP] OTP typed and submitted via app") print("[TuftsSCO PreAuth OTP] OTP typed and submitted via app")
s["otp_value"] = None s["otp_value"] = None
await asyncio.sleep(3) await asyncio.sleep(3)
@@ -288,11 +292,20 @@ async def start_tuftssco_preauth_run(sid: str, data: dict, url: str):
pdf_path = step8_result.get("pdf_path") if isinstance(step8_result, dict) else None pdf_path = step8_result.get("pdf_path") if isinstance(step8_result, dict) else None
preauth_number = step8_result.get("preAuthNumber") if isinstance(step8_result, dict) else None preauth_number = step8_result.get("preAuthNumber") if isinstance(step8_result, dict) else None
pdf_url = None
if pdf_path:
import os as _os
filename = _os.path.basename(pdf_path)
port = _os.getenv("PORT", "5002")
url_host = _os.getenv("HOST", "localhost")
pdf_url = f"http://{url_host}:{port}/downloads/{filename}"
print(f"[TuftsSCO PreAuth] pdf_url: {pdf_url}")
result = { result = {
"status": "success", "status": "success",
"message": "Tufts SCO pre-authorization submitted successfully", "message": "Tufts SCO pre-authorization submitted successfully",
"preAuthNumber": preauth_number, "preAuthNumber": preauth_number,
"pdf_path": pdf_path, "pdf_url": pdf_url,
} }
s["status"] = "completed" s["status"] = "completed"
s["result"] = result s["result"] = result

View File

@@ -572,17 +572,17 @@ class AutomationTuftsSCOPreAuth:
inp.send_keys(Keys.CONTROL + "a") inp.send_keys(Keys.CONTROL + "a")
inp.send_keys(Keys.DELETE) inp.send_keys(Keys.DELETE)
inp.send_keys(str(value)) inp.send_keys(str(value))
time.sleep(0.5) time.sleep(1.5) # give portal time to load autocomplete results
listbox_id = inp.get_attribute("aria-controls") or "" listbox_id = inp.get_attribute("aria-controls") or ""
try: try:
if listbox_id: if listbox_id:
option = WebDriverWait(self.driver, 4).until( option = WebDriverWait(self.driver, 6).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
f"//*[@id='{listbox_id}']//*[@role='option'][1]" f"//*[@id='{listbox_id}']//*[@role='option'][1]"
)) ))
) )
else: else:
option = WebDriverWait(self.driver, 4).until( option = WebDriverWait(self.driver, 6).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
f"//*[@role='listbox']//*[@role='option' and contains(normalize-space(.),'{value}')]" f"//*[@role='listbox']//*[@role='option' and contains(normalize-space(.),'{value}')]"
f" | //*[@role='listbox']//*[@role='option'][1]" f" | //*[@role='listbox']//*[@role='option'][1]"
@@ -592,7 +592,7 @@ class AutomationTuftsSCOPreAuth:
print(f"[TuftsSCO PreAuth step4] {label}: selected '{value}'") print(f"[TuftsSCO PreAuth step4] {label}: selected '{value}'")
except TimeoutException: except TimeoutException:
inp.send_keys(Keys.TAB) inp.send_keys(Keys.TAB)
print(f"[TuftsSCO PreAuth step4] {label}: typed '{value}' (no dropdown)") print(f"[TuftsSCO PreAuth step4] {label}: typed '{value}' (no dropdown — pressed TAB)")
except Exception as e: except Exception as e:
print(f"[TuftsSCO PreAuth step4] Warning: could not fill {label}: {e}") print(f"[TuftsSCO PreAuth step4] Warning: could not fill {label}: {e}")
@@ -608,38 +608,8 @@ class AutomationTuftsSCOPreAuth:
print(f"[TuftsSCO PreAuth step4] Warning: could not fill {label}: {e}") print(f"[TuftsSCO PreAuth step4] Warning: could not fill {label}: {e}")
def step4_fill_preauth_form(self): def step4_fill_preauth_form(self):
"""Fill service date then all procedure line fields.""" """Fill all procedure line fields. Preauth form has no date field — skip it."""
try: try:
month, day, year = self._parse_service_date()
if month and day and year:
print(f"[TuftsSCO PreAuth step4] Filling service date: {month}/{day}/{year}")
try:
dos_container = WebDriverWait(self.driver, 8).until(
EC.presence_of_element_located((By.XPATH,
"//*[@data-testid and contains(@data-testid,'date-of-service')] | "
"//*[contains(@aria-label,'Select date of service')]/ancestor::div[1]"
))
)
month_el = dos_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
day_el = dos_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
year_el = dos_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
for elem, val in [(month_el, month), (day_el, day), (year_el, year)]:
elem.click()
elem.send_keys(Keys.CONTROL, "a")
elem.send_keys(Keys.BACKSPACE)
elem.send_keys(val)
time.sleep(0.05)
print("[TuftsSCO PreAuth step4] Service date filled")
except Exception:
self._fill_spinbutton("month", month)
self._fill_spinbutton("day", day)
self._fill_spinbutton("year", year)
else:
print(f"[TuftsSCO PreAuth step4] No valid service date: {self.serviceDate!r}")
time.sleep(0.3)
active_lines = [ln for ln in self.serviceLines if str(ln.get("procedureCode") or "").strip()] active_lines = [ln for ln in self.serviceLines if str(ln.get("procedureCode") or "").strip()]
print(f"[TuftsSCO PreAuth step4] {len(active_lines)} service line(s)") print(f"[TuftsSCO PreAuth step4] {len(active_lines)} service line(s)")
@@ -678,8 +648,8 @@ class AutomationTuftsSCOPreAuth:
) )
proc_inp = proc_inputs[idx] if idx < len(proc_inputs) else proc_inputs[-1] proc_inp = proc_inputs[idx] if idx < len(proc_inputs) else proc_inputs[-1]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_inp) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_inp)
self._fill_combobox(proc_inp, code, f"procedureCode[{idx}]") self._fill_text_input(proc_inp, code, f"procedureCode[{idx}]")
time.sleep(0.5) time.sleep(1.5) # wait for warning banner layout shift to settle
except Exception as e: except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill procedure code: {e}") print(f"[TuftsSCO PreAuth step4] Could not fill procedure code: {e}")
@@ -687,7 +657,14 @@ class AutomationTuftsSCOPreAuth:
try: try:
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']") tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
if idx < len(tooth_inputs): if idx < len(tooth_inputs):
self._fill_combobox(tooth_inputs[idx], tooth, f"tooth[{idx}]") tooth_inp = tooth_inputs[idx]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_inp)
self.driver.execute_script("arguments[0].focus();", tooth_inp)
time.sleep(0.2)
tooth_inp.send_keys(Keys.CONTROL + "a")
tooth_inp.send_keys(Keys.DELETE)
tooth_inp.send_keys(str(tooth))
print(f"[TuftsSCO PreAuth step4] tooth[{idx}]: typed '{tooth}'")
time.sleep(0.3) time.sleep(0.3)
except Exception as e: except Exception as e:
print(f"[TuftsSCO PreAuth step4] Could not fill tooth: {e}") print(f"[TuftsSCO PreAuth step4] Could not fill tooth: {e}")
@@ -766,24 +743,30 @@ class AutomationTuftsSCOPreAuth:
print(f"[TuftsSCO PreAuth step5] Attaching: {abs_path}") print(f"[TuftsSCO PreAuth step5] Attaching: {abs_path}")
try: try:
add_file_btn = WebDriverWait(self.driver, 10).until( # The react-aria-Input file input is always in the DOM (hidden).
EC.element_to_be_clickable((By.XPATH, # Do NOT click "Add a file" — that opens the OS dialog and blocks WebDriver.
"//button[.//span[contains(text(),'Add a file')]] | " file_input = WebDriverWait(self.driver, 10).until(
"//button[contains(normalize-space(text()),'Add a file')] | " EC.presence_of_element_located((By.XPATH, "//input[@type='file' and contains(@class,'react-aria-Input')]"))
"//*[contains(text(),'Add a file') and (@role='button' or self::label)]"
))
) )
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_file_btn) self.driver.execute_script(
ActionChains(self.driver).move_to_element(add_file_btn).click().perform() "arguments[0].style.display='block'; arguments[0].style.visibility='visible';",
time.sleep(1) file_input
file_input = WebDriverWait(self.driver, 8).until(
EC.presence_of_element_located((By.XPATH, "//input[@type='file']"))
) )
self.driver.execute_script("arguments[0].style.display='block';", file_input)
file_input.send_keys(abs_path) file_input.send_keys(abs_path)
time.sleep(1.5)
print(f"[TuftsSCO PreAuth step5] Attached: {os.path.basename(abs_path)}") # Wait until the filename appears on the page confirming upload completed
filename = os.path.basename(abs_path)
try:
WebDriverWait(self.driver, 15).until(
lambda d: filename.lower() in d.page_source.lower()
or filename.rsplit("_", 1)[-1].lower() in d.page_source.lower()
)
print(f"[TuftsSCO PreAuth step5] Upload confirmed: {filename}")
except Exception:
time.sleep(3)
print(f"[TuftsSCO PreAuth step5] Upload wait timed out — proceeding: {filename}")
print(f"[TuftsSCO PreAuth step5] Attached: {filename}")
attached += 1 attached += 1
except Exception as e: except Exception as e:
print(f"[TuftsSCO PreAuth step5] Could not attach {abs_path}: {e}") print(f"[TuftsSCO PreAuth step5] Could not attach {abs_path}: {e}")
@@ -847,20 +830,24 @@ class AutomationTuftsSCOPreAuth:
print("[TuftsSCO PreAuth step7] Checked acknowledgement checkbox") print("[TuftsSCO PreAuth step7] Checked acknowledgement checkbox")
time.sleep(0.5) time.sleep(0.5)
# Try pre-auth submit button first, fall back to generic submit all_btns = self.driver.find_elements(By.XPATH, "//button")
print(f"[TuftsSCO PreAuth step7] Buttons: {[b.get_attribute('aria-label') or b.text[:40] for b in all_btns]}")
submit_btn = None submit_btn = None
for xpath in [ for xpath in [
"//button[.//span[contains(text(),'Submit prior authorization')]] | //button[contains(normalize-space(text()),'Submit prior authorization')] | //button[@aria-label='Submit prior authorization']", "//button[.//span[normalize-space(text())='pre-authorization']]",
"//button[.//span[contains(text(),'Submit prior authorization')]] | //button[@aria-label='Submit prior authorization']",
"//button[.//span[contains(text(),'Submit pre-authorization')]] | //button[contains(normalize-space(text()),'Submit pre-authorization')]", "//button[.//span[contains(text(),'Submit pre-authorization')]] | //button[contains(normalize-space(text()),'Submit pre-authorization')]",
"//button[.//span[contains(text(),'Submit claim')]] | //button[contains(normalize-space(text()),'Submit claim')] | //button[@aria-label='Submit claim']", "//button[.//span[contains(text(),'Submit claim')]] | //button[@aria-label='Submit claim']",
"//button[.//span[contains(text(),'Submit')]] | //button[contains(normalize-space(text()),'Submit')]",
]: ]:
try: try:
submit_btn = WebDriverWait(self.driver, 5).until( submit_btn = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, xpath)) EC.presence_of_element_located((By.XPATH, xpath))
) )
print(f"[TuftsSCO PreAuth step7] Found submit button") print(f"[TuftsSCO PreAuth step7] Found submit button: {submit_btn.get_attribute('aria-label') or submit_btn.text[:40]!r}")
break break
except TimeoutException: except Exception:
continue continue
if submit_btn is None: if submit_btn is None:
@@ -889,13 +876,8 @@ class AutomationTuftsSCOPreAuth:
def step8_save_confirmation_pdf(self): def step8_save_confirmation_pdf(self):
"""Wait for the confirmation page, extract the pre-auth number, save page as PDF.""" """Wait for the confirmation page, extract the pre-auth number, save page as PDF."""
try: try:
WebDriverWait(self.driver, 30).until( WebDriverWait(self.driver, 60).until(
lambda d: ( lambda d: "thank" in d.page_source.lower() or "submitted pre-authorization" in d.page_source.lower()
"thank" in d.page_source.lower()
or "submitted" in d.page_source.lower()
or "prior auth" in d.page_source.lower()
or "pre-auth" in d.page_source.lower()
)
) )
time.sleep(2) time.sleep(2)
print(f"[TuftsSCO PreAuth step8] Confirmation page URL: {self.driver.current_url}") print(f"[TuftsSCO PreAuth step8] Confirmation page URL: {self.driver.current_url}")
@@ -903,17 +885,15 @@ class AutomationTuftsSCOPreAuth:
preauth_number = None preauth_number = None
try: try:
body_text = self.driver.find_element(By.TAG_NAME, "body").text body_text = self.driver.find_element(By.TAG_NAME, "body").text
for pattern in [ match = re.search(r'submitted pre-authorization\s+(\d{10,})', body_text, re.IGNORECASE)
r'prior auth(?:orization)?\s+(?:number\s+)?(\d{8,})', if match:
r'pre-auth(?:orization)?\s+(?:number\s+)?(\d{8,})', preauth_number = match.group(1)
r'submitted\s+(?:prior auth|pre-auth|authorization)\s+(\d{8,})', print(f"[TuftsSCO PreAuth step8] Extracted pre-auth number: {preauth_number}")
r'\b(\d{12,})\b', else:
]: match = re.search(r'\b(\d{12,})\b', body_text)
match = re.search(pattern, body_text, re.IGNORECASE)
if match: if match:
preauth_number = match.group(1) preauth_number = match.group(1)
print(f"[TuftsSCO PreAuth step8] Extracted pre-auth number: {preauth_number}") print(f"[TuftsSCO PreAuth step8] Extracted pre-auth number (fallback): {preauth_number}")
break
except Exception as e: except Exception as e:
print(f"[TuftsSCO PreAuth step8] Could not extract pre-auth number: {e}") print(f"[TuftsSCO PreAuth step8] Could not extract pre-auth number: {e}")