feat(eligibility-check) - enhance OTP handling and eligibility status retrieval for DDMA and DentaQuest; improved file processing logic for screenshots and PDFs, and updated frontend components for better user experience

This commit is contained in:
2026-01-29 21:25:18 -05:00
parent 279a6b8dbc
commit 5370a0e445
8 changed files with 737 additions and 309 deletions

View File

@@ -179,25 +179,35 @@ async function handleDdmaCompletedJob(
await storage.updatePatient(patient.id, { status: newStatus }); await storage.updatePatient(patient.id, { status: newStatus });
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`; outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
// convert screenshot -> pdf if available // Handle PDF or convert screenshot -> pdf if available
let pdfBuffer: Buffer | null = null; let pdfBuffer: Buffer | null = null;
let generatedPdfPath: string | null = null; let generatedPdfPath: string | null = null;
if ( if (
seleniumResult && seleniumResult &&
seleniumResult.ss_path && seleniumResult.ss_path &&
typeof seleniumResult.ss_path === "string" && typeof seleniumResult.ss_path === "string"
(seleniumResult.ss_path.endsWith(".png") ||
seleniumResult.ss_path.endsWith(".jpg") ||
seleniumResult.ss_path.endsWith(".jpeg"))
) { ) {
try { try {
if (!fsSync.existsSync(seleniumResult.ss_path)) { if (!fsSync.existsSync(seleniumResult.ss_path)) {
throw new Error( throw new Error(
`Screenshot file not found: ${seleniumResult.ss_path}` `File not found: ${seleniumResult.ss_path}`
); );
} }
// Check if the file is already a PDF (from Page.printToPDF)
if (seleniumResult.ss_path.endsWith(".pdf")) {
// Read PDF directly
pdfBuffer = await fs.readFile(seleniumResult.ss_path);
generatedPdfPath = seleniumResult.ss_path;
seleniumResult.pdf_path = generatedPdfPath;
console.log(`[ddma-eligibility] Using PDF directly from Selenium: ${generatedPdfPath}`);
} else if (
seleniumResult.ss_path.endsWith(".png") ||
seleniumResult.ss_path.endsWith(".jpg") ||
seleniumResult.ss_path.endsWith(".jpeg")
) {
// Convert image to PDF
pdfBuffer = await imageToPdfBuffer(seleniumResult.ss_path); pdfBuffer = await imageToPdfBuffer(seleniumResult.ss_path);
const pdfFileName = `ddma_eligibility_${insuranceEligibilityData.memberId}_${Date.now()}.pdf`; const pdfFileName = `ddma_eligibility_${insuranceEligibilityData.memberId}_${Date.now()}.pdf`;
@@ -206,16 +216,19 @@ async function handleDdmaCompletedJob(
pdfFileName pdfFileName
); );
await fs.writeFile(generatedPdfPath, pdfBuffer); await fs.writeFile(generatedPdfPath, pdfBuffer);
// ensure cleanup uses this
seleniumResult.pdf_path = generatedPdfPath; seleniumResult.pdf_path = generatedPdfPath;
console.log(`[ddma-eligibility] Converted screenshot to PDF: ${generatedPdfPath}`);
} else {
outputResult.pdfUploadStatus =
`Unsupported file format: ${seleniumResult.ss_path}`;
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to convert screenshot to PDF:", err); console.error("Failed to process PDF/screenshot:", err);
outputResult.pdfUploadStatus = `Failed to convert screenshot to PDF: ${String(err)}`; outputResult.pdfUploadStatus = `Failed to process file: ${String(err)}`;
} }
} else { } else {
outputResult.pdfUploadStatus = outputResult.pdfUploadStatus =
"No valid screenshot (ss_path) provided by Selenium; nothing to upload."; "No valid file path (ss_path) provided by Selenium; nothing to upload.";
} }
if (pdfBuffer && generatedPdfPath) { if (pdfBuffer && generatedPdfPath) {

View File

@@ -179,25 +179,35 @@ async function handleDentaQuestCompletedJob(
await storage.updatePatient(patient.id, { status: newStatus }); await storage.updatePatient(patient.id, { status: newStatus });
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`; outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
// convert screenshot -> pdf if available // Handle PDF or convert screenshot -> pdf if available
let pdfBuffer: Buffer | null = null; let pdfBuffer: Buffer | null = null;
let generatedPdfPath: string | null = null; let generatedPdfPath: string | null = null;
if ( if (
seleniumResult && seleniumResult &&
seleniumResult.ss_path && seleniumResult.ss_path &&
typeof seleniumResult.ss_path === "string" && typeof seleniumResult.ss_path === "string"
(seleniumResult.ss_path.endsWith(".png") ||
seleniumResult.ss_path.endsWith(".jpg") ||
seleniumResult.ss_path.endsWith(".jpeg"))
) { ) {
try { try {
if (!fsSync.existsSync(seleniumResult.ss_path)) { if (!fsSync.existsSync(seleniumResult.ss_path)) {
throw new Error( throw new Error(
`Screenshot file not found: ${seleniumResult.ss_path}` `File not found: ${seleniumResult.ss_path}`
); );
} }
// Check if the file is already a PDF (from Page.printToPDF)
if (seleniumResult.ss_path.endsWith(".pdf")) {
// Read PDF directly
pdfBuffer = await fs.readFile(seleniumResult.ss_path);
generatedPdfPath = seleniumResult.ss_path;
seleniumResult.pdf_path = generatedPdfPath;
console.log(`[dentaquest-eligibility] Using PDF directly from Selenium: ${generatedPdfPath}`);
} else if (
seleniumResult.ss_path.endsWith(".png") ||
seleniumResult.ss_path.endsWith(".jpg") ||
seleniumResult.ss_path.endsWith(".jpeg")
) {
// Convert image to PDF
pdfBuffer = await imageToPdfBuffer(seleniumResult.ss_path); pdfBuffer = await imageToPdfBuffer(seleniumResult.ss_path);
const pdfFileName = `dentaquest_eligibility_${insuranceEligibilityData.memberId}_${Date.now()}.pdf`; const pdfFileName = `dentaquest_eligibility_${insuranceEligibilityData.memberId}_${Date.now()}.pdf`;
@@ -206,16 +216,19 @@ async function handleDentaQuestCompletedJob(
pdfFileName pdfFileName
); );
await fs.writeFile(generatedPdfPath, pdfBuffer); await fs.writeFile(generatedPdfPath, pdfBuffer);
// ensure cleanup uses this
seleniumResult.pdf_path = generatedPdfPath; seleniumResult.pdf_path = generatedPdfPath;
console.log(`[dentaquest-eligibility] Converted screenshot to PDF: ${generatedPdfPath}`);
} else {
outputResult.pdfUploadStatus =
`Unsupported file format: ${seleniumResult.ss_path}`;
}
} catch (err: any) { } catch (err: any) {
console.error("Failed to convert screenshot to PDF:", err); console.error("Failed to process PDF/screenshot:", err);
outputResult.pdfUploadStatus = `Failed to convert screenshot to PDF: ${String(err)}`; outputResult.pdfUploadStatus = `Failed to process file: ${String(err)}`;
} }
} else { } else {
outputResult.pdfUploadStatus = outputResult.pdfUploadStatus =
"No valid screenshot (ss_path) provided by Selenium; nothing to upload."; "No valid file path (ss_path) provided by Selenium; nothing to upload.";
} }
if (pdfBuffer && generatedPdfPath) { if (pdfBuffer && generatedPdfPath) {

View File

@@ -14,6 +14,13 @@ type CredentialFormProps = {
}; };
}; };
// Available site keys - must match exactly what the automation buttons expect
const SITE_KEY_OPTIONS = [
{ value: "MH", label: "MassHealth" },
{ value: "DDMA", label: "Delta Dental MA" },
{ value: "DENTAQUEST", label: "Tufts SCO / DentaQuest" },
];
export function CredentialForm({ onClose, userId, defaultValues }: CredentialFormProps) { export function CredentialForm({ onClose, userId, defaultValues }: CredentialFormProps) {
const [siteKey, setSiteKey] = useState(defaultValues?.siteKey || ""); const [siteKey, setSiteKey] = useState(defaultValues?.siteKey || "");
const [username, setUsername] = useState(defaultValues?.username || ""); const [username, setUsername] = useState(defaultValues?.username || "");
@@ -91,14 +98,19 @@ export function CredentialForm({ onClose, userId, defaultValues }: CredentialFor
</h2> </h2>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div> <div>
<label className="block text-sm font-medium">Site Key</label> <label className="block text-sm font-medium">Insurance Provider</label>
<input <select
type="text"
value={siteKey} value={siteKey}
onChange={(e) => setSiteKey(e.target.value)} onChange={(e) => setSiteKey(e.target.value)}
className="mt-1 p-2 border rounded w-full" className="mt-1 p-2 border rounded w-full bg-white"
placeholder="e.g., MH, Delta MA, (keep the site key exact same)" >
/> <option value="">Select a provider...</option>
{SITE_KEY_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div> </div>
<div> <div>
<label className="block text-sm font-medium">Username</label> <label className="block text-sm font-medium">Username</label>

View File

@@ -13,6 +13,17 @@ type Credential = {
password: string; password: string;
}; };
// Map site keys to friendly labels
const SITE_KEY_LABELS: Record<string, string> = {
MH: "MassHealth",
DDMA: "Delta Dental MA",
DENTAQUEST: "Tufts SCO / DentaQuest",
};
function getSiteKeyLabel(siteKey: string): string {
return SITE_KEY_LABELS[siteKey] || siteKey;
}
export function CredentialTable() { export function CredentialTable() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -108,7 +119,7 @@ export function CredentialTable() {
<thead className="bg-gray-50"> <thead className="bg-gray-50">
<tr> <tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Site Key Provider
</th> </th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Username Username
@@ -141,7 +152,7 @@ export function CredentialTable() {
) : ( ) : (
currentCredentials.map((cred) => ( currentCredentials.map((cred) => (
<tr key={cred.id}> <tr key={cred.id}>
<td className="px-4 py-2">{cred.siteKey}</td> <td className="px-4 py-2">{getSiteKeyLabel(cred.siteKey)}</td>
<td className="px-4 py-2">{cred.username}</td> <td className="px-4 py-2">{cred.username}</td>
<td className="px-4 py-2"></td> <td className="px-4 py-2"></td>
<td className="px-4 py-2 text-right"> <td className="px-4 py-2 text-right">
@@ -227,7 +238,7 @@ export function CredentialTable() {
isOpen={isDeleteDialogOpen} isOpen={isDeleteDialogOpen}
onConfirm={handleConfirmDelete} onConfirm={handleConfirmDelete}
onCancel={handleCancelDelete} onCancel={handleCancelDelete}
entityName={credentialToDelete?.siteKey} entityName={credentialToDelete ? getSiteKeyLabel(credentialToDelete.siteKey) : undefined}
/> />
</div> </div>
); );

View File

@@ -5,7 +5,7 @@ from typing import Dict, Any
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import WebDriverException, TimeoutException
from selenium_DDMA_eligibilityCheckWorker import AutomationDeltaDentalMAEligibilityCheck from selenium_DDMA_eligibilityCheckWorker import AutomationDeltaDentalMAEligibilityCheck
@@ -127,81 +127,105 @@ async def start_ddma_run(sid: str, data: dict, url: str):
s["message"] = "Session persisted" s["message"] = "Session persisted"
# Continue to step1 below # Continue to step1 below
# OTP required path # OTP required path - POLL THE BROWSER to detect when user enters OTP
elif isinstance(login_result, str) and login_result == "OTP_REQUIRED": elif isinstance(login_result, str) and login_result == "OTP_REQUIRED":
s["status"] = "waiting_for_otp" s["status"] = "waiting_for_otp"
s["message"] = "OTP required for login" s["message"] = "OTP required for login - please enter OTP in browser"
s["last_activity"] = time.time() s["last_activity"] = time.time()
try:
await asyncio.wait_for(s["otp_event"].wait(), timeout=SESSION_OTP_TIMEOUT)
except asyncio.TimeoutError:
s["status"] = "error"
s["message"] = "OTP timeout"
await cleanup_session(sid)
return {"status": "error", "message": "OTP not provided in time"}
otp_value = s.get("otp_value")
if not otp_value:
s["status"] = "error"
s["message"] = "OTP missing after event"
await cleanup_session(sid)
return {"status": "error", "message": "OTP missing after event"}
# Submit OTP - check if it's in a popup window
try:
driver = s["driver"] driver = s["driver"]
wait = WebDriverWait(driver, 30)
# Check if there's a popup window and switch to it # Poll the browser to detect when OTP is completed (user enters it directly)
original_window = driver.current_window_handle # We check every 1 second for up to SESSION_OTP_TIMEOUT seconds (faster response)
all_windows = driver.window_handles max_polls = SESSION_OTP_TIMEOUT
if len(all_windows) > 1: login_success = False
for window in all_windows:
if window != original_window:
driver.switch_to.window(window)
print(f"[OTP] Switched to popup window for OTP entry")
break
otp_input = wait.until( print(f"[OTP] Waiting for user to enter OTP (polling browser for {SESSION_OTP_TIMEOUT}s)...")
EC.presence_of_element_located(
(By.XPATH, "//input[contains(@aria-lable,'Verification code') or contains(@placeholder,'Enter your verification code')]") for poll in range(max_polls):
) await asyncio.sleep(1)
) s["last_activity"] = time.time()
otp_input.clear()
otp_input.send_keys(otp_value)
try: try:
submit_btn = wait.until( # Check current URL - if we're on member search page, login succeeded
EC.element_to_be_clickable( current_url = driver.current_url.lower()
(By.XPATH, "//button[@type='button' and @aria-label='Verify']") print(f"[OTP Poll {poll+1}/{max_polls}] URL: {current_url[:60]}...")
)
)
submit_btn.click()
except Exception:
otp_input.send_keys("\n")
# Wait for verification and switch back to main window if needed # Check if we've navigated away from login/OTP pages
if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url:
# Verify by checking for member search input
try:
member_search = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[OTP] Member search input found - login successful!")
login_success = True
break
except TimeoutException:
print("[OTP] On member page but search input not found, continuing to poll...")
# Also check if OTP input is still visible
try:
otp_input = driver.find_element(By.XPATH,
"//input[contains(@aria-label,'Verification') or contains(@placeholder,'verification') or @type='tel']"
)
# OTP input still visible - user hasn't entered OTP yet
print(f"[OTP Poll {poll+1}] OTP input still visible - waiting...")
except:
# OTP input not found - might mean login is in progress or succeeded
# Try navigating to members page
if "onboarding" in current_url or "start" in current_url:
print("[OTP] OTP input gone, trying to navigate to members page...")
try:
driver.get("https://providers.deltadentalma.com/members")
await asyncio.sleep(2) await asyncio.sleep(2)
if len(driver.window_handles) > 0: except:
driver.switch_to.window(driver.window_handles[0]) pass
s["status"] = "otp_submitted" except Exception as poll_err:
s["last_activity"] = time.time() print(f"[OTP Poll {poll+1}] Error: {poll_err}")
await asyncio.sleep(0.5)
except Exception as e: if not login_success:
# Final attempt - navigate to members page and check
try:
print("[OTP] Final attempt - navigating to members page...")
driver.get("https://providers.deltadentalma.com/members")
await asyncio.sleep(3)
member_search = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[OTP] Member search input found - login successful!")
login_success = True
except TimeoutException:
s["status"] = "error" s["status"] = "error"
s["message"] = f"Failed to submit OTP into page: {e}" s["message"] = "OTP timeout - login not completed"
await cleanup_session(sid)
return {"status": "error", "message": "OTP not completed in time"}
except Exception as final_err:
s["status"] = "error"
s["message"] = f"OTP verification failed: {final_err}"
await cleanup_session(sid) await cleanup_session(sid)
return {"status": "error", "message": s["message"]} return {"status": "error", "message": s["message"]}
if login_success:
s["status"] = "running"
s["message"] = "Login successful after OTP"
print("[OTP] Proceeding to step1...")
elif isinstance(login_result, str) and login_result.startswith("ERROR"): elif isinstance(login_result, str) and login_result.startswith("ERROR"):
s["status"] = "error" s["status"] = "error"
s["message"] = login_result s["message"] = login_result
await cleanup_session(sid) await cleanup_session(sid)
return {"status": "error", "message": login_result} return {"status": "error", "message": login_result}
# Login succeeded without OTP (SUCCESS)
elif isinstance(login_result, str) and login_result == "SUCCESS":
print("[start_ddma_run] Login succeeded without OTP")
s["status"] = "running"
s["message"] = "Login succeeded"
# Continue to step1 below
# Step 1 # Step 1
step1_result = bot.step1() step1_result = bot.step1()
if isinstance(step1_result, str) and step1_result.startswith("ERROR"): if isinstance(step1_result, str) and step1_result.startswith("ERROR"):

View File

@@ -5,7 +5,7 @@ from typing import Dict, Any
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import WebDriverException, TimeoutException
from selenium_DentaQuest_eligibilityCheckWorker import AutomationDentaQuestEligibilityCheck from selenium_DentaQuest_eligibilityCheckWorker import AutomationDentaQuestEligibilityCheck
@@ -126,81 +126,105 @@ async def start_dentaquest_run(sid: str, data: dict, url: str):
s["message"] = "Session persisted" s["message"] = "Session persisted"
# Continue to step1 below # Continue to step1 below
# OTP required path # OTP required path - POLL THE BROWSER to detect when user enters OTP
elif isinstance(login_result, str) and login_result == "OTP_REQUIRED": elif isinstance(login_result, str) and login_result == "OTP_REQUIRED":
s["status"] = "waiting_for_otp" s["status"] = "waiting_for_otp"
s["message"] = "OTP required for login" s["message"] = "OTP required for login - please enter OTP in browser"
s["last_activity"] = time.time() s["last_activity"] = time.time()
try:
await asyncio.wait_for(s["otp_event"].wait(), timeout=SESSION_OTP_TIMEOUT)
except asyncio.TimeoutError:
s["status"] = "error"
s["message"] = "OTP timeout"
await cleanup_session(sid)
return {"status": "error", "message": "OTP not provided in time"}
otp_value = s.get("otp_value")
if not otp_value:
s["status"] = "error"
s["message"] = "OTP missing after event"
await cleanup_session(sid)
return {"status": "error", "message": "OTP missing after event"}
# Submit OTP
try:
driver = s["driver"] driver = s["driver"]
wait = WebDriverWait(driver, 30)
# Check if there's a popup window and switch to it # Poll the browser to detect when OTP is completed (user enters it directly)
original_window = driver.current_window_handle # We check every 1 second for up to SESSION_OTP_TIMEOUT seconds (faster response)
all_windows = driver.window_handles max_polls = SESSION_OTP_TIMEOUT
if len(all_windows) > 1: login_success = False
for window in all_windows:
if window != original_window:
driver.switch_to.window(window)
break
# DentaQuest OTP input field - adjust selectors as needed print(f"[DentaQuest OTP] Waiting for user to enter OTP (polling browser for {SESSION_OTP_TIMEOUT}s)...")
otp_input = wait.until(
EC.presence_of_element_located( for poll in range(max_polls):
(By.XPATH, "//input[contains(@name,'otp') or contains(@name,'code') or contains(@placeholder,'code') or contains(@id,'otp') or @type='tel']") await asyncio.sleep(1)
) s["last_activity"] = time.time()
)
otp_input.clear()
otp_input.send_keys(otp_value)
try: try:
submit_btn = wait.until( # Check current URL - if we're on dashboard/member page, login succeeded
EC.element_to_be_clickable( current_url = driver.current_url.lower()
(By.XPATH, "//button[contains(text(),'Verify') or contains(text(),'Submit') or contains(text(),'Continue') or @type='submit']") print(f"[DentaQuest OTP Poll {poll+1}/{max_polls}] URL: {current_url[:60]}...")
)
)
submit_btn.click()
except Exception:
otp_input.send_keys("\n")
# Wait for verification and switch back to main window if needed # Check if we've navigated away from login/OTP pages
if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url:
# Verify by checking for member search input
try:
member_search = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[DentaQuest OTP] Member search input found - login successful!")
login_success = True
break
except TimeoutException:
print("[DentaQuest OTP] On member page but search input not found, continuing to poll...")
# Also check if OTP input is still visible
try:
otp_input = driver.find_element(By.XPATH,
"//input[contains(@name,'otp') or contains(@name,'code') or @type='tel' or contains(@aria-label,'Verification') or contains(@placeholder,'code') or contains(@placeholder,'Code')]"
)
# OTP input still visible - user hasn't entered OTP yet
print(f"[DentaQuest OTP Poll {poll+1}] OTP input still visible - waiting...")
except:
# OTP input not found - might mean login is in progress or succeeded
# Try navigating to members page (like Delta MA)
if "onboarding" in current_url or "start" in current_url or "login" in current_url:
print("[DentaQuest OTP] OTP input gone, trying to navigate to members page...")
try:
driver.get("https://providers.dentaquest.com/members")
await asyncio.sleep(2) await asyncio.sleep(2)
if len(driver.window_handles) > 0: except:
driver.switch_to.window(driver.window_handles[0]) pass
s["status"] = "otp_submitted" except Exception as poll_err:
s["last_activity"] = time.time() print(f"[DentaQuest OTP Poll {poll+1}] Error: {poll_err}")
await asyncio.sleep(0.5)
except Exception as e: if not login_success:
# Final attempt - navigate to members page and check (like Delta MA)
try:
print("[DentaQuest OTP] Final attempt - navigating to members page...")
driver.get("https://providers.dentaquest.com/members")
await asyncio.sleep(3)
member_search = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[DentaQuest OTP] Member search input found - login successful!")
login_success = True
except TimeoutException:
s["status"] = "error" s["status"] = "error"
s["message"] = f"Failed to submit OTP into page: {e}" s["message"] = "OTP timeout - login not completed"
await cleanup_session(sid)
return {"status": "error", "message": "OTP not completed in time"}
except Exception as final_err:
s["status"] = "error"
s["message"] = f"OTP verification failed: {final_err}"
await cleanup_session(sid) await cleanup_session(sid)
return {"status": "error", "message": s["message"]} return {"status": "error", "message": s["message"]}
if login_success:
s["status"] = "running"
s["message"] = "Login successful after OTP"
print("[DentaQuest OTP] Proceeding to step1...")
elif isinstance(login_result, str) and login_result.startswith("ERROR"): elif isinstance(login_result, str) and login_result.startswith("ERROR"):
s["status"] = "error" s["status"] = "error"
s["message"] = login_result s["message"] = login_result
await cleanup_session(sid) await cleanup_session(sid)
return {"status": "error", "message": login_result} return {"status": "error", "message": login_result}
# Login succeeded without OTP (SUCCESS)
elif isinstance(login_result, str) and login_result == "SUCCESS":
print("[start_dentaquest_run] Login succeeded without OTP")
s["status"] = "running"
s["message"] = "Login succeeded"
# Continue to step1 below
# Step 1 # Step 1
step1_result = bot.step1() step1_result = bot.step1()
if isinstance(step1_result, str) and step1_result.startswith("ERROR"): if isinstance(step1_result, str) and step1_result.startswith("ERROR"):

View File

@@ -237,7 +237,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
if self.massddma_username: if self.massddma_username:
browser_manager.save_credentials_hash(self.massddma_username) browser_manager.save_credentials_hash(self.massddma_username)
# OTP detection # OTP detection - wait up to 30 seconds for OTP input to appear
try: try:
otp_candidate = WebDriverWait(self.driver, 30).until( otp_candidate = WebDriverWait(self.driver, 30).until(
EC.presence_of_element_located( EC.presence_of_element_located(
@@ -249,6 +249,36 @@ class AutomationDeltaDentalMAEligibilityCheck:
return "OTP_REQUIRED" return "OTP_REQUIRED"
except TimeoutException: except TimeoutException:
print("[login] No OTP input detected in allowed time.") print("[login] No OTP input detected in allowed time.")
# Check if we're now on the member search page (login succeeded without OTP)
try:
current_url = self.driver.current_url.lower()
if "member" in current_url or "dashboard" in current_url:
member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[login] Login successful - now on member search page")
return "SUCCESS"
except TimeoutException:
pass
# Check for error messages on page
try:
error_elem = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, "//*[contains(@class,'error') or contains(text(),'invalid') or contains(text(),'failed')]"))
)
print(f"[login] Login failed - error detected: {error_elem.text}")
return f"ERROR:LOGIN FAILED: {error_elem.text}"
except TimeoutException:
pass
# If still on login page, login failed
if "onboarding" in self.driver.current_url.lower() or "login" in self.driver.current_url.lower():
print("[login] Login failed - still on login page")
return "ERROR:LOGIN FAILED: Still on login page"
# Otherwise assume success (might be on an intermediate page)
print("[login] Assuming login succeeded (no errors detected)")
return "SUCCESS"
except Exception as e: except Exception as e:
print("[login] Exception during login:", e) print("[login] Exception during login:", e)
return f"ERROR:LOGIN FAILED: {e}" return f"ERROR:LOGIN FAILED: {e}"
@@ -329,73 +359,220 @@ class AutomationDeltaDentalMAEligibilityCheck:
wait = WebDriverWait(self.driver, 90) wait = WebDriverWait(self.driver, 90)
try: try:
# 1) find the eligibility <a> inside the correct cell # Wait for results table to load (use explicit wait instead of fixed sleep)
status_link = wait.until(EC.presence_of_element_located(( try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
)
except TimeoutException:
print("[DDMA step2] Warning: Results table not found within timeout")
# 1) Find and extract eligibility status from search results (use short timeout - not critical)
eligibilityText = "unknown"
try:
# Use short timeout (3s) since this is just for status extraction
short_wait = WebDriverWait(self.driver, 3)
status_link = short_wait.until(EC.presence_of_element_located((
By.XPATH, By.XPATH,
"(//tbody//tr)[1]//a[contains(@href, 'member-eligibility-search')]" "(//tbody//tr)[1]//a[contains(@href, 'member-eligibility-search')]"
))) )))
eligibilityText = status_link.text.strip().lower() eligibilityText = status_link.text.strip().lower()
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
except Exception as e:
print(f"[DDMA step2] Eligibility link not found, trying alternative...")
try:
alt_status = self.driver.find_element(By.XPATH, "//*[contains(text(),'Active') or contains(text(),'Inactive') or contains(text(),'Eligible')]")
eligibilityText = alt_status.text.strip().lower()
if "active" in eligibilityText or "eligible" in eligibilityText:
eligibilityText = "active"
elif "inactive" in eligibilityText:
eligibilityText = "inactive"
print(f"[DDMA step2] Found eligibility via alternative: {eligibilityText}")
except:
pass
# 2) finding patient name. # 2) Click on patient name to navigate to detailed patient page
patient_name_div = wait.until(EC.presence_of_element_located(( print("[DDMA step2] Clicking on patient name to open detailed page...")
By.XPATH, patient_name_clicked = False
'//div[@class="flex flex-row w-full items-center"]' patientName = ""
)))
patientName = patient_name_div.text.strip().lower() # First, let's print what we see on the page for debugging
current_url_before = self.driver.current_url
print(f"[DDMA step2] Current URL before click: {current_url_before}")
# Try to find all links in the first row and print them for debugging
try:
all_links = self.driver.find_elements(By.XPATH, "(//tbody//tr)[1]//a")
print(f"[DDMA step2] Found {len(all_links)} links in first row:")
for i, link in enumerate(all_links):
href = link.get_attribute("href") or "no-href"
text = link.text.strip() or "(empty text)"
print(f" Link {i}: href={href[:80]}..., text={text}")
except Exception as e:
print(f"[DDMA step2] Error listing links: {e}")
# Find the patient detail link and navigate DIRECTLY to it
detail_url = None
patient_link_selectors = [
"(//table//tbody//tr)[1]//td[1]//a", # First column link
"(//tbody//tr)[1]//a[contains(@href, 'member-details')]", # member-details link
"(//tbody//tr)[1]//a[contains(@href, 'member')]", # Any member link
]
for selector in patient_link_selectors:
try:
patient_link = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, selector))
)
patientName = patient_link.text.strip()
href = patient_link.get_attribute("href")
print(f"[DDMA step2] Found patient link: text='{patientName}', href={href}")
if href and "member-details" in href:
detail_url = href
patient_name_clicked = True
print(f"[DDMA step2] Will navigate directly to: {detail_url}")
break
except Exception as e:
print(f"[DDMA step2] Selector '{selector}' failed: {e}")
continue
if not detail_url:
# Fallback: Try to find ANY link to member-details
try:
all_links = self.driver.find_elements(By.XPATH, "//a[contains(@href, 'member-details')]")
if all_links:
detail_url = all_links[0].get_attribute("href")
patient_name_clicked = True
print(f"[DDMA step2] Found member-details link: {detail_url}")
except Exception as e:
print(f"[DDMA step2] Could not find member-details link: {e}")
# Navigate to detail page DIRECTLY instead of clicking (which may open new tab/fail)
if patient_name_clicked and detail_url:
print(f"[DDMA step2] Navigating directly to detail page: {detail_url}")
self.driver.get(detail_url)
time.sleep(3) # Wait for page to load
current_url_after = self.driver.current_url
print(f"[DDMA step2] Current URL after navigation: {current_url_after}")
if "member-details" in current_url_after:
print("[DDMA step2] Successfully navigated to member details page!")
else:
print(f"[DDMA step2] WARNING: Navigation might have redirected. Current URL: {current_url_after}")
# Wait for page to be ready
try: try:
WebDriverWait(self.driver, 30).until( WebDriverWait(self.driver, 30).until(
lambda d: d.execute_script("return document.readyState") == "complete" lambda d: d.execute_script("return document.readyState") == "complete"
) )
except Exception: except Exception:
print("Warning: document.readyState did not become 'complete' within timeout") print("[DDMA step2] Warning: document.readyState did not become 'complete'")
# Give some time for lazy content to finish rendering (adjust if needed) # Wait for member details content to load (wait for specific elements)
time.sleep(0.6) print("[DDMA step2] Waiting for member details content to fully load...")
content_loaded = False
# Get total page size and DPR content_selectors = [
total_width = int(self.driver.execute_script( "//div[contains(@class,'member') or contains(@class,'detail') or contains(@class,'patient')]",
"return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth, document.documentElement.clientWidth);" "//h1",
)) "//h2",
total_height = int(self.driver.execute_script( "//table",
"return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.documentElement.clientHeight);" "//*[contains(text(),'Member ID') or contains(text(),'Name') or contains(text(),'Date of Birth')]",
)) ]
dpr = float(self.driver.execute_script("return window.devicePixelRatio || 1;")) for selector in content_selectors:
# Set device metrics to the full page size so Page.captureScreenshot captures everything
# Note: Some pages are extremely tall; if you hit memory limits, you can capture in chunks.
self.driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', {
"mobile": False,
"width": total_width,
"height": total_height,
"deviceScaleFactor": dpr,
"screenOrientation": {"angle": 0, "type": "portraitPrimary"}
})
# Small pause for layout to settle after emulation change
time.sleep(0.15)
# Capture screenshot (base64 PNG)
result = self.driver.execute_cdp_cmd("Page.captureScreenshot", {"format": "png", "fromSurface": True})
image_data = base64.b64decode(result.get('data', ''))
screenshot_path = os.path.join(self.download_dir, f"ss_{self.memberId}.png")
with open(screenshot_path, "wb") as f:
f.write(image_data)
# Restore original metrics to avoid affecting further interactions
try: try:
self.driver.execute_cdp_cmd('Emulation.clearDeviceMetricsOverride', {}) WebDriverWait(self.driver, 10).until(
except Exception: EC.presence_of_element_located((By.XPATH, selector))
# non-fatal: continue )
content_loaded = True
print(f"[DDMA step2] Content element found: {selector}")
break
except:
continue
if not content_loaded:
print("[DDMA step2] Warning: Could not verify content loaded, waiting extra time...")
# Additional wait for dynamic content and animations
time.sleep(5) # Increased from 2 to 5 seconds
# Print page title for debugging
try:
page_title = self.driver.title
print(f"[DDMA step2] Page title: {page_title}")
except:
pass pass
print("Screenshot saved at:", screenshot_path) # Try to extract patient name from detailed page if not already found
if not patientName:
detail_name_selectors = [
"//h1",
"//h2",
"//*[contains(@class,'patient-name') or contains(@class,'member-name')]",
"//div[contains(@class,'header')]//span",
]
for selector in detail_name_selectors:
try:
name_elem = self.driver.find_element(By.XPATH, selector)
name_text = name_elem.text.strip()
if name_text and len(name_text) > 1:
if not any(x in name_text.lower() for x in ['active', 'inactive', 'eligible', 'search', 'date', 'print']):
patientName = name_text
print(f"[DDMA step2] Found patient name on detail page: {patientName}")
break
except:
continue
else:
print("[DDMA step2] Warning: Could not click on patient, capturing search results page")
# Still try to get patient name from search results
try:
name_elem = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]//td[1]")
patientName = name_elem.text.strip()
except:
pass
# Close the browser window after screenshot (session preserved in profile) if not patientName:
print("[DDMA step2] Could not extract patient name")
else:
print(f"[DDMA step2] Patient name: {patientName}")
# Wait for page to fully load before generating PDF
try:
WebDriverWait(self.driver, 30).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
except Exception:
pass
time.sleep(1)
# Generate PDF of the detailed patient page using Chrome DevTools Protocol
print("[DDMA step2] Generating PDF of patient detail page...")
pdf_options = {
"landscape": False,
"displayHeaderFooter": False,
"printBackground": True,
"preferCSSPageSize": True,
"paperWidth": 8.5, # Letter size in inches
"paperHeight": 11,
"marginTop": 0.4,
"marginBottom": 0.4,
"marginLeft": 0.4,
"marginRight": 0.4,
"scale": 0.9, # Slightly scale down to fit content
}
result = self.driver.execute_cdp_cmd("Page.printToPDF", pdf_options)
pdf_data = base64.b64decode(result.get('data', ''))
pdf_path = os.path.join(self.download_dir, f"eligibility_{self.memberId}.pdf")
with open(pdf_path, "wb") as f:
f.write(pdf_data)
print(f"[DDMA step2] PDF saved at: {pdf_path}")
# Close the browser window after PDF generation (session preserved in profile)
try: try:
from ddma_browser_manager import get_browser_manager from ddma_browser_manager import get_browser_manager
get_browser_manager().quit_driver() get_browser_manager().quit_driver()
@@ -406,7 +583,8 @@ class AutomationDeltaDentalMAEligibilityCheck:
output = { output = {
"status": "success", "status": "success",
"eligibility": eligibilityText, "eligibility": eligibilityText,
"ss_path": screenshot_path, "ss_path": pdf_path, # Keep key as ss_path for backward compatibility
"pdf_path": pdf_path, # Also add explicit pdf_path
"patientName": patientName "patientName": patientName
} }
return output return output

View File

@@ -198,25 +198,48 @@ class AutomationDentaQuestEligibilityCheck:
if self.dentaquest_username: if self.dentaquest_username:
browser_manager.save_credentials_hash(self.dentaquest_username) browser_manager.save_credentials_hash(self.dentaquest_username)
time.sleep(5) # OTP detection - wait up to 30 seconds for OTP input to appear (like Delta MA)
# Use comprehensive XPath to detect various OTP input patterns
# Check for OTP after login
try: try:
otp_input = WebDriverWait(self.driver, 10).until( otp_input = WebDriverWait(self.driver, 30).until(
EC.presence_of_element_located((By.XPATH, "//input[@type='tel' or contains(@placeholder,'code') or contains(@aria-label,'Verification')]")) EC.presence_of_element_located((By.XPATH,
"//input[@type='tel' or contains(@placeholder,'code') or contains(@placeholder,'Code') or "
"contains(@aria-label,'Verification') or contains(@aria-label,'verification') or "
"contains(@aria-label,'Code') or contains(@aria-label,'code') or "
"contains(@placeholder,'verification') or contains(@placeholder,'Verification') or "
"contains(@name,'otp') or contains(@name,'code') or contains(@id,'otp') or contains(@id,'code')]"
))
) )
print("[DentaQuest login] OTP input detected -> OTP_REQUIRED")
return "OTP_REQUIRED" return "OTP_REQUIRED"
except TimeoutException:
print("[DentaQuest login] No OTP input detected in 30 seconds")
# Check if login succeeded (redirected to dashboard or member search)
current_url_after_login = self.driver.current_url.lower()
print(f"[DentaQuest login] After login URL: {current_url_after_login}")
if "dashboard" in current_url_after_login or "member" in current_url_after_login:
# Verify by checking for member search input
try:
member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[DentaQuest login] Login successful - now on member search page")
return "SUCCESS"
except TimeoutException: except TimeoutException:
pass pass
# Check if login succeeded # Still on login page - login failed
if "dashboard" in self.driver.current_url.lower(): if "onboarding" in current_url_after_login or "login" in current_url_after_login:
return "SUCCESS" print("[DentaQuest login] Login failed - still on login page")
return "ERROR: Login failed - check credentials"
except TimeoutException: except TimeoutException:
print("[DentaQuest login] Login form elements not found") print("[DentaQuest login] Login form elements not found")
return "ERROR: Login form not found" return "ERROR: Login form not found"
# If we got here without going through login, we're already logged in
return "SUCCESS" return "SUCCESS"
except Exception as e: except Exception as e:
@@ -335,45 +358,184 @@ class AutomationDentaQuestEligibilityCheck:
def step2(self): def step2(self):
"""Get eligibility status and capture screenshot""" """Get eligibility status, navigate to detail page, and capture PDF"""
wait = WebDriverWait(self.driver, 90) wait = WebDriverWait(self.driver, 90)
try: try:
print("[DentaQuest step2] Starting eligibility capture") print("[DentaQuest step2] Starting eligibility capture")
# Wait for results to load # Wait for results table to load (use explicit wait instead of fixed sleep)
time.sleep(3)
# Try to find eligibility status from the results
eligibilityText = "unknown"
try: try:
# Look for a link or element with eligibility status WebDriverWait(self.driver, 10).until(
status_elem = wait.until(EC.presence_of_element_located(( EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
By.XPATH, )
"//a[contains(@href,'eligibility')] | //*[contains(@class,'status')] | //*[contains(text(),'Active') or contains(text(),'Inactive') or contains(text(),'Eligible')]"
)))
eligibilityText = status_elem.text.strip().lower()
print(f"[DentaQuest step2] Found status element: {eligibilityText}")
# Normalize status
if "active" in eligibilityText or "eligible" in eligibilityText:
eligibilityText = "active"
elif "inactive" in eligibilityText or "ineligible" in eligibilityText:
eligibilityText = "inactive"
except TimeoutException: except TimeoutException:
print("[DentaQuest step2] Could not find specific eligibility status") print("[DentaQuest step2] Warning: Results table not found within timeout")
# Try to find patient name # 1) Find and extract eligibility status from search results
patientName = "" eligibilityText = "unknown"
status_selectors = [
"(//tbody//tr)[1]//a[contains(@href, 'eligibility')]",
"//a[contains(@href,'eligibility')]",
"//*[contains(@class,'status')]",
"//*[contains(text(),'Active') or contains(text(),'Inactive') or contains(text(),'Eligible')]"
]
for selector in status_selectors:
try: try:
# Look for the patient name in the results status_elem = self.driver.find_element(By.XPATH, selector)
name_elem = self.driver.find_element(By.XPATH, "//h1 | //div[contains(@class,'name')] | //*[contains(@class,'member-name') or contains(@class,'patient-name')]") status_text = status_elem.text.strip().lower()
if status_text:
print(f"[DentaQuest step2] Found status with selector '{selector}': {status_text}")
if "active" in status_text or "eligible" in status_text:
eligibilityText = "active"
break
elif "inactive" in status_text or "ineligible" in status_text:
eligibilityText = "inactive"
break
except:
continue
print(f"[DentaQuest step2] Final eligibility status: {eligibilityText}")
# 2) Find the patient detail link and navigate DIRECTLY to it
print("[DentaQuest step2] Looking for patient detail link...")
patient_name_clicked = False
patientName = ""
detail_url = None
current_url_before = self.driver.current_url
print(f"[DentaQuest step2] Current URL before: {current_url_before}")
# Find all links in first row and log them
try:
all_links = self.driver.find_elements(By.XPATH, "(//tbody//tr)[1]//a")
print(f"[DentaQuest step2] Found {len(all_links)} links in first row:")
for i, link in enumerate(all_links):
href = link.get_attribute("href") or "no-href"
text = link.text.strip() or "(empty text)"
print(f" Link {i}: href={href[:80]}..., text={text}")
except Exception as e:
print(f"[DentaQuest step2] Error listing links: {e}")
# Find the patient detail link
patient_link_selectors = [
"(//table//tbody//tr)[1]//td[1]//a", # First column link
"(//tbody//tr)[1]//a[contains(@href, 'member-details')]", # member-details link
"(//tbody//tr)[1]//a[contains(@href, 'member')]", # Any member link
]
for selector in patient_link_selectors:
try:
patient_link = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, selector))
)
patientName = patient_link.text.strip()
href = patient_link.get_attribute("href")
print(f"[DentaQuest step2] Found patient link: text='{patientName}', href={href}")
if href and ("member-details" in href or "member" in href):
detail_url = href
patient_name_clicked = True
print(f"[DentaQuest step2] Will navigate directly to: {detail_url}")
break
except Exception as e:
print(f"[DentaQuest step2] Selector '{selector}' failed: {e}")
continue
if not detail_url:
# Fallback: Try to find ANY link to member-details
try:
all_links = self.driver.find_elements(By.XPATH, "//a[contains(@href, 'member')]")
if all_links:
detail_url = all_links[0].get_attribute("href")
patient_name_clicked = True
print(f"[DentaQuest step2] Found member link: {detail_url}")
except Exception as e:
print(f"[DentaQuest step2] Could not find member link: {e}")
# Navigate to detail page DIRECTLY
if patient_name_clicked and detail_url:
print(f"[DentaQuest step2] Navigating directly to detail page: {detail_url}")
self.driver.get(detail_url)
time.sleep(3) # Wait for page to load
current_url_after = self.driver.current_url
print(f"[DentaQuest step2] Current URL after navigation: {current_url_after}")
if "member-details" in current_url_after or "member" in current_url_after:
print("[DentaQuest step2] Successfully navigated to member details page!")
else:
print(f"[DentaQuest step2] WARNING: Navigation might have redirected. Current URL: {current_url_after}")
# Wait for page to be ready
try:
WebDriverWait(self.driver, 30).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
except Exception:
print("[DentaQuest step2] Warning: document.readyState did not become 'complete'")
# Wait for member details content to load
print("[DentaQuest step2] Waiting for member details content to fully load...")
content_loaded = False
content_selectors = [
"//div[contains(@class,'member') or contains(@class,'detail') or contains(@class,'patient')]",
"//h1",
"//h2",
"//table",
"//*[contains(text(),'Member ID') or contains(text(),'Name') or contains(text(),'Date of Birth')]",
]
for selector in content_selectors:
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, selector))
)
content_loaded = True
print(f"[DentaQuest step2] Content element found: {selector}")
break
except:
continue
if not content_loaded:
print("[DentaQuest step2] Warning: Could not verify content loaded, waiting extra time...")
# Additional wait for dynamic content
time.sleep(5)
# Try to extract patient name from detailed page if not already found
if not patientName:
detail_name_selectors = [
"//h1",
"//h2",
"//*[contains(@class,'patient-name') or contains(@class,'member-name')]",
"//div[contains(@class,'header')]//span",
]
for selector in detail_name_selectors:
try:
name_elem = self.driver.find_element(By.XPATH, selector)
name_text = name_elem.text.strip()
if name_text and len(name_text) > 1:
if not any(x in name_text.lower() for x in ['active', 'inactive', 'eligible', 'search', 'date', 'print', 'member id']):
patientName = name_text
print(f"[DentaQuest step2] Found patient name on detail page: {patientName}")
break
except:
continue
else:
print("[DentaQuest step2] Warning: Could not find detail URL, capturing search results page")
# Still try to get patient name from search results
try:
name_elem = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]//td[1]")
patientName = name_elem.text.strip() patientName = name_elem.text.strip()
print(f"[DentaQuest step2] Found patient name: {patientName}")
except: except:
pass pass
# Wait for page to fully load if not patientName:
print("[DentaQuest step2] Could not extract patient name")
else:
print(f"[DentaQuest step2] Patient name: {patientName}")
# Wait for page to fully load before generating PDF
try: try:
WebDriverWait(self.driver, 30).until( WebDriverWait(self.driver, 30).until(
lambda d: d.execute_script("return document.readyState") == "complete" lambda d: d.execute_script("return document.readyState") == "complete"
@@ -383,41 +545,31 @@ class AutomationDentaQuestEligibilityCheck:
time.sleep(1) time.sleep(1)
# Capture full page screenshot # Generate PDF of the detailed patient page using Chrome DevTools Protocol
print("[DentaQuest step2] Capturing screenshot") print("[DentaQuest step2] Generating PDF of patient detail page...")
total_width = int(self.driver.execute_script(
"return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth, document.documentElement.clientWidth);"
))
total_height = int(self.driver.execute_script(
"return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.documentElement.clientHeight);"
))
dpr = float(self.driver.execute_script("return window.devicePixelRatio || 1;"))
self.driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', { pdf_options = {
"mobile": False, "landscape": False,
"width": total_width, "displayHeaderFooter": False,
"height": total_height, "printBackground": True,
"deviceScaleFactor": dpr, "preferCSSPageSize": True,
"screenOrientation": {"angle": 0, "type": "portraitPrimary"} "paperWidth": 8.5, # Letter size in inches
}) "paperHeight": 11,
"marginTop": 0.4,
"marginBottom": 0.4,
"marginLeft": 0.4,
"marginRight": 0.4,
"scale": 0.9, # Slightly scale down to fit content
}
time.sleep(0.2) result = self.driver.execute_cdp_cmd("Page.printToPDF", pdf_options)
pdf_data = base64.b64decode(result.get('data', ''))
pdf_path = os.path.join(self.download_dir, f"dentaquest_eligibility_{self.memberId}_{int(time.time())}.pdf")
with open(pdf_path, "wb") as f:
f.write(pdf_data)
print(f"[DentaQuest step2] PDF saved: {pdf_path}")
# Capture screenshot # Close the browser window after PDF generation
result = self.driver.execute_cdp_cmd("Page.captureScreenshot", {"format": "png", "fromSurface": True})
image_data = base64.b64decode(result.get('data', ''))
screenshot_path = os.path.join(self.download_dir, f"dentaquest_ss_{self.memberId}_{int(time.time())}.png")
with open(screenshot_path, "wb") as f:
f.write(image_data)
print(f"[DentaQuest step2] Screenshot saved: {screenshot_path}")
# Restore original metrics
try:
self.driver.execute_cdp_cmd('Emulation.clearDeviceMetricsOverride', {})
except Exception:
pass
# Close the browser window after screenshot
try: try:
from dentaquest_browser_manager import get_browser_manager from dentaquest_browser_manager import get_browser_manager
get_browser_manager().quit_driver() get_browser_manager().quit_driver()
@@ -428,7 +580,8 @@ class AutomationDentaQuestEligibilityCheck:
output = { output = {
"status": "success", "status": "success",
"eligibility": eligibilityText, "eligibility": eligibilityText,
"ss_path": screenshot_path, "ss_path": pdf_path, # Keep key as ss_path for backward compatibility
"pdf_path": pdf_path, # Also add explicit pdf_path
"patientName": patientName "patientName": patientName
} }
print(f"[DentaQuest step2] Success: {output}") print(f"[DentaQuest step2] Success: {output}")