feat(eligibility-check) - enhance DDMA and DentaQuest workflows with flexible input handling; added detailed logging for patient data processing and eligibility status updates; improved browser cache management in Selenium service
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
NODE_ENV="development"
|
||||
HOST=0.0.0.0
|
||||
PORT=5000
|
||||
FRONTEND_URLS=http://localhost:3000,http://192.168.1.8:3000
|
||||
# FRONTEND_URLS=http://localhost:3000,http://192.168.1.8:3000
|
||||
FRONTEND_URLS=http://localhost:3000
|
||||
SELENIUM_AGENT_BASE_URL=http://localhost:5002
|
||||
JWT_SECRET = 'dentalsecret'
|
||||
DB_HOST=localhost
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=mypassword
|
||||
DB_NAME=dentalapp
|
||||
DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/dentalapp
|
||||
DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/dentalapp
|
||||
|
||||
@@ -41,7 +41,8 @@ function isOriginAllowed(origin?: string | null) {
|
||||
// Dev mode: allow localhost origins automatically
|
||||
if (
|
||||
origin.startsWith("http://localhost") ||
|
||||
origin.startsWith("http://127.0.0.1")
|
||||
origin.startsWith("http://127.0.0.1") ||
|
||||
origin.startsWith("http://192.168.0.238")
|
||||
)
|
||||
return true;
|
||||
// allow explicit FRONTEND_URLS if provided
|
||||
|
||||
@@ -136,36 +136,135 @@ async function handleDdmaCompletedJob(
|
||||
|
||||
// We'll wrap the processing in try/catch/finally so cleanup always runs
|
||||
try {
|
||||
// 1) ensuring memberid.
|
||||
const insuranceEligibilityData = job.insuranceEligibilityData;
|
||||
const insuranceId = String(insuranceEligibilityData.memberId ?? "").trim();
|
||||
|
||||
// DEBUG: Log the raw selenium result
|
||||
console.log(`[ddma-eligibility] === DEBUG: Raw seleniumResult ===`);
|
||||
console.log(`[ddma-eligibility] seleniumResult.patientName: '${seleniumResult?.patientName}'`);
|
||||
console.log(`[ddma-eligibility] seleniumResult.memberId: '${seleniumResult?.memberId}'`);
|
||||
console.log(`[ddma-eligibility] seleniumResult.status: '${seleniumResult?.status}'`);
|
||||
|
||||
// 1) Get insuranceId - prefer from Selenium result (flexible search support)
|
||||
let insuranceId = String(seleniumResult?.memberId || "").trim();
|
||||
if (!insuranceId) {
|
||||
throw new Error("Missing memberId for ddma job");
|
||||
insuranceId = String(insuranceEligibilityData.memberId ?? "").trim();
|
||||
}
|
||||
console.log(`[ddma-eligibility] Resolved insuranceId: ${insuranceId || "(none)"}`);
|
||||
|
||||
// 2) Create or update patient (with name from selenium result if available)
|
||||
// 2) Get patient name - prefer from Selenium result
|
||||
const patientNameFromResult =
|
||||
typeof seleniumResult?.patientName === "string"
|
||||
? seleniumResult.patientName.trim()
|
||||
: null;
|
||||
|
||||
console.log(`[ddma-eligibility] patientNameFromResult: '${patientNameFromResult}'`);
|
||||
|
||||
const { firstName, lastName } = splitName(patientNameFromResult);
|
||||
// Get name from input data as fallback
|
||||
let firstName = String(insuranceEligibilityData.firstName || "").trim();
|
||||
let lastName = String(insuranceEligibilityData.lastName || "").trim();
|
||||
|
||||
// Override with name from Selenium result if available
|
||||
if (patientNameFromResult) {
|
||||
const parsedName = splitName(patientNameFromResult);
|
||||
console.log(`[ddma-eligibility] splitName result: firstName='${parsedName.firstName}', lastName='${parsedName.lastName}'`);
|
||||
if (parsedName.firstName) firstName = parsedName.firstName;
|
||||
if (parsedName.lastName) lastName = parsedName.lastName;
|
||||
}
|
||||
console.log(`[ddma-eligibility] Resolved name: firstName='${firstName}', lastName='${lastName}'`);
|
||||
|
||||
await createOrUpdatePatientByInsuranceId({
|
||||
insuranceId,
|
||||
firstName,
|
||||
lastName,
|
||||
dob: insuranceEligibilityData.dateOfBirth,
|
||||
userId: job.userId,
|
||||
});
|
||||
|
||||
// 3) Update patient status + PDF upload
|
||||
const patient = await storage.getPatientByInsuranceId(
|
||||
insuranceEligibilityData.memberId
|
||||
);
|
||||
// 3) Find or create patient
|
||||
let patient: any = null;
|
||||
|
||||
// First, try to find by insuranceId if available
|
||||
if (insuranceId) {
|
||||
patient = await storage.getPatientByInsuranceId(insuranceId);
|
||||
if (patient) {
|
||||
console.log(`[ddma-eligibility] Found patient by insuranceId: ${patient.id}`);
|
||||
|
||||
// Update name if we have better data
|
||||
const updates: any = {};
|
||||
if (firstName && String(patient.firstName ?? "").trim() !== firstName) {
|
||||
updates.firstName = firstName;
|
||||
}
|
||||
if (lastName && String(patient.lastName ?? "").trim() !== lastName) {
|
||||
updates.lastName = lastName;
|
||||
}
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await storage.updatePatient(patient.id, updates);
|
||||
console.log(`[ddma-eligibility] Updated patient name to: ${firstName} ${lastName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not found by ID, try to find by name
|
||||
if (!patient && firstName && lastName) {
|
||||
try {
|
||||
console.log(`[ddma-eligibility] Looking up patient by name: ${firstName} ${lastName}`);
|
||||
const patients = await storage.getPatientsByUserId(job.userId);
|
||||
patient = patients.find(
|
||||
(p: any) =>
|
||||
String(p.firstName ?? "").toLowerCase() === firstName.toLowerCase() &&
|
||||
String(p.lastName ?? "").toLowerCase() === lastName.toLowerCase()
|
||||
) || null;
|
||||
if (patient) {
|
||||
console.log(`[ddma-eligibility] Found patient by name: ${patient.id}`);
|
||||
// Update insuranceId if we have it
|
||||
if (insuranceId && String(patient.insuranceId ?? "").trim() !== insuranceId) {
|
||||
await storage.updatePatient(patient.id, { insuranceId });
|
||||
console.log(`[ddma-eligibility] Updated patient insuranceId to: ${insuranceId}`);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.log(`[ddma-eligibility] Error finding patient by name: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine eligibility status from Selenium result
|
||||
const eligibilityStatus = seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE";
|
||||
console.log(`[ddma-eligibility] Eligibility status from Delta MA: ${eligibilityStatus}`);
|
||||
|
||||
// If still not found, create new patient
|
||||
console.log(`[ddma-eligibility] Patient creation check: patient=${patient?.id || 'null'}, firstName='${firstName}', lastName='${lastName}'`);
|
||||
if (!patient && firstName && lastName) {
|
||||
console.log(`[ddma-eligibility] Creating new patient: ${firstName} ${lastName} with status: ${eligibilityStatus}`);
|
||||
try {
|
||||
let parsedDob: Date | undefined = undefined;
|
||||
if (insuranceEligibilityData.dateOfBirth) {
|
||||
try {
|
||||
parsedDob = new Date(insuranceEligibilityData.dateOfBirth);
|
||||
if (isNaN(parsedDob.getTime())) parsedDob = undefined;
|
||||
} catch {
|
||||
parsedDob = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const newPatientData: InsertPatient = {
|
||||
firstName,
|
||||
lastName,
|
||||
dateOfBirth: parsedDob || new Date(), // Required field
|
||||
insuranceId: insuranceId || undefined,
|
||||
insuranceProvider: "Delta MA", // Set insurance provider
|
||||
gender: "Unknown", // Required field - default value
|
||||
phone: "", // Required field - default empty
|
||||
userId: job.userId, // Required field
|
||||
status: eligibilityStatus, // Set status from eligibility check
|
||||
};
|
||||
|
||||
const validation = insertPatientSchema.safeParse(newPatientData);
|
||||
if (validation.success) {
|
||||
patient = await storage.createPatient(validation.data);
|
||||
console.log(`[ddma-eligibility] Created new patient: ${patient.id} with status: ${eligibilityStatus}`);
|
||||
} else {
|
||||
console.log(`[ddma-eligibility] Patient validation failed: ${validation.error.message}`);
|
||||
}
|
||||
} catch (createErr: any) {
|
||||
console.log(`[ddma-eligibility] Failed to create patient: ${createErr.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!patient?.id) {
|
||||
outputResult.patientUpdateStatus =
|
||||
"Patient not found; no update performed";
|
||||
"Patient not found and could not be created; no update performed";
|
||||
return {
|
||||
patientUpdateStatus: outputResult.patientUpdateStatus,
|
||||
pdfUploadStatus: "none",
|
||||
@@ -173,11 +272,10 @@ async function handleDdmaCompletedJob(
|
||||
};
|
||||
}
|
||||
|
||||
// update patient status.
|
||||
const newStatus =
|
||||
seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE";
|
||||
await storage.updatePatient(patient.id, { status: newStatus });
|
||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
// Update patient status from Delta MA eligibility result
|
||||
await storage.updatePatient(patient.id, { status: eligibilityStatus });
|
||||
outputResult.patientUpdateStatus = `Patient ${patient.id} status set to ${eligibilityStatus} (Delta MA eligibility: ${seleniumResult.eligibility})`;
|
||||
console.log(`[ddma-eligibility] ${outputResult.patientUpdateStatus}`);
|
||||
|
||||
// Handle PDF or convert screenshot -> pdf if available
|
||||
let pdfBuffer: Buffer | null = null;
|
||||
|
||||
@@ -200,9 +200,13 @@ async function handleDentaQuestCompletedJob(
|
||||
}
|
||||
}
|
||||
|
||||
// Determine eligibility status from Selenium result
|
||||
const eligibilityStatus = seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE";
|
||||
console.log(`[dentaquest-eligibility] Eligibility status from DentaQuest: ${eligibilityStatus}`);
|
||||
|
||||
// If still no patient found, CREATE a new one with the data we have
|
||||
if (!patient?.id && firstName && lastName) {
|
||||
console.log(`[dentaquest-eligibility] Creating new patient: ${firstName} ${lastName}`);
|
||||
console.log(`[dentaquest-eligibility] Creating new patient: ${firstName} ${lastName} with status: ${eligibilityStatus}`);
|
||||
|
||||
const createPayload: any = {
|
||||
firstName,
|
||||
@@ -212,6 +216,8 @@ async function handleDentaQuestCompletedJob(
|
||||
phone: "",
|
||||
userId: job.userId,
|
||||
insuranceId: insuranceId || null,
|
||||
insuranceProvider: "DentaQuest", // Set insurance provider
|
||||
status: eligibilityStatus, // Set status from eligibility check
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -219,7 +225,7 @@ async function handleDentaQuestCompletedJob(
|
||||
const newPatient = await storage.createPatient(patientData);
|
||||
if (newPatient) {
|
||||
patient = newPatient;
|
||||
console.log(`[dentaquest-eligibility] Created new patient with ID: ${patient.id}`);
|
||||
console.log(`[dentaquest-eligibility] Created new patient with ID: ${patient.id}, status: ${eligibilityStatus}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Try without dateOfBirth if it fails
|
||||
@@ -230,7 +236,7 @@ async function handleDentaQuestCompletedJob(
|
||||
const newPatient = await storage.createPatient(patientData);
|
||||
if (newPatient) {
|
||||
patient = newPatient;
|
||||
console.log(`[dentaquest-eligibility] Created new patient (no DOB) with ID: ${patient.id}`);
|
||||
console.log(`[dentaquest-eligibility] Created new patient (no DOB) with ID: ${patient.id}, status: ${eligibilityStatus}`);
|
||||
}
|
||||
} catch (err2: any) {
|
||||
console.error(`[dentaquest-eligibility] Failed to create patient: ${err2?.message}`);
|
||||
@@ -248,11 +254,10 @@ async function handleDentaQuestCompletedJob(
|
||||
};
|
||||
}
|
||||
|
||||
// update patient status.
|
||||
const newStatus =
|
||||
seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE";
|
||||
await storage.updatePatient(patient.id, { status: newStatus });
|
||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
// Update patient status from DentaQuest eligibility result
|
||||
await storage.updatePatient(patient.id, { status: eligibilityStatus });
|
||||
outputResult.patientUpdateStatus = `Patient ${patient.id} status set to ${eligibilityStatus} (DentaQuest eligibility: ${seleniumResult.eligibility})`;
|
||||
console.log(`[dentaquest-eligibility] ${outputResult.patientUpdateStatus}`);
|
||||
|
||||
// Handle PDF or convert screenshot -> pdf if available
|
||||
let pdfBuffer: Buffer | null = null;
|
||||
|
||||
@@ -119,6 +119,10 @@ export function DdmaEligibilityButton({
|
||||
const { toast } = useToast();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Flexible validation: require DOB + at least one identifier (memberId OR firstName OR lastName)
|
||||
const isDdmaFormIncomplete =
|
||||
!dateOfBirth || (!memberId && !firstName && !lastName);
|
||||
|
||||
const socketRef = useRef<Socket | null>(null);
|
||||
const connectingRef = useRef<Promise<void> | null>(null);
|
||||
|
||||
@@ -371,10 +375,11 @@ export function DdmaEligibilityButton({
|
||||
};
|
||||
|
||||
const startDdmaEligibility = async () => {
|
||||
if (!memberId || !dateOfBirth) {
|
||||
// Flexible validation: require DOB + at least one identifier
|
||||
if (!dateOfBirth || (!memberId && !firstName && !lastName)) {
|
||||
toast({
|
||||
title: "Missing fields",
|
||||
description: "Member ID and Date of Birth are required.",
|
||||
description: "Date of Birth and at least one identifier (Member ID, First Name, or Last Name) are required.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
@@ -539,7 +544,7 @@ export function DdmaEligibilityButton({
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="default"
|
||||
disabled={isFormIncomplete || isStarting}
|
||||
disabled={isDdmaFormIncomplete || isStarting}
|
||||
onClick={startDdmaEligibility}
|
||||
>
|
||||
{isStarting ? (
|
||||
|
||||
@@ -111,6 +111,26 @@ class DDMABrowserManager:
|
||||
except Exception as e:
|
||||
print(f"[DDMA BrowserManager] Could not clear IndexedDB: {e}")
|
||||
|
||||
# Clear browser cache (prevents corrupted cached responses)
|
||||
cache_dirs = [
|
||||
os.path.join(self.profile_dir, "Default", "Cache"),
|
||||
os.path.join(self.profile_dir, "Default", "Code Cache"),
|
||||
os.path.join(self.profile_dir, "Default", "GPUCache"),
|
||||
os.path.join(self.profile_dir, "Default", "Service Worker"),
|
||||
os.path.join(self.profile_dir, "Cache"),
|
||||
os.path.join(self.profile_dir, "Code Cache"),
|
||||
os.path.join(self.profile_dir, "GPUCache"),
|
||||
os.path.join(self.profile_dir, "Service Worker"),
|
||||
os.path.join(self.profile_dir, "ShaderCache"),
|
||||
]
|
||||
for cache_dir in cache_dirs:
|
||||
if os.path.exists(cache_dir):
|
||||
try:
|
||||
shutil.rmtree(cache_dir)
|
||||
print(f"[DDMA BrowserManager] Cleared {os.path.basename(cache_dir)}")
|
||||
except Exception as e:
|
||||
print(f"[DDMA BrowserManager] Could not clear {os.path.basename(cache_dir)}: {e}")
|
||||
|
||||
# Set flag to clear session via JavaScript after browser opens
|
||||
self._needs_session_clear = True
|
||||
|
||||
@@ -235,6 +255,12 @@ class DDMABrowserManager:
|
||||
options.add_argument("--no-sandbox")
|
||||
options.add_argument("--disable-dev-shm-usage")
|
||||
|
||||
# Anti-detection options (prevent bot detection)
|
||||
options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
options.add_experimental_option("useAutomationExtension", False)
|
||||
options.add_argument("--disable-infobars")
|
||||
|
||||
prefs = {
|
||||
"download.default_directory": self.download_dir,
|
||||
"plugins.always_open_pdf_externally": True,
|
||||
@@ -247,6 +273,12 @@ class DDMABrowserManager:
|
||||
self._driver = webdriver.Chrome(service=service, options=options)
|
||||
self._driver.maximize_window()
|
||||
|
||||
# Remove webdriver property to avoid detection
|
||||
try:
|
||||
self._driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Reset the session clear flag (file-based clearing is done on startup)
|
||||
self._needs_session_clear = False
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
# Flatten values for convenience
|
||||
self.memberId = self.data.get("memberId", "")
|
||||
self.dateOfBirth = self.data.get("dateOfBirth", "")
|
||||
self.firstName = self.data.get("firstName", "")
|
||||
self.lastName = self.data.get("lastName", "")
|
||||
self.massddma_username = self.data.get("massddmaUsername", "")
|
||||
self.massddma_password = self.data.get("massddmaPassword", "")
|
||||
|
||||
@@ -284,58 +286,105 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
return f"ERROR:LOGIN FAILED: {e}"
|
||||
|
||||
def step1(self):
|
||||
"""Fill search form with all available fields (flexible search)"""
|
||||
wait = WebDriverWait(self.driver, 30)
|
||||
|
||||
try:
|
||||
# Fill Member ID
|
||||
member_id_input = wait.until(EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')))
|
||||
member_id_input.clear()
|
||||
member_id_input.send_keys(self.memberId)
|
||||
# Log what fields are available
|
||||
fields = []
|
||||
if self.memberId:
|
||||
fields.append(f"ID: {self.memberId}")
|
||||
if self.firstName:
|
||||
fields.append(f"FirstName: {self.firstName}")
|
||||
if self.lastName:
|
||||
fields.append(f"LastName: {self.lastName}")
|
||||
if self.dateOfBirth:
|
||||
fields.append(f"DOB: {self.dateOfBirth}")
|
||||
print(f"[DDMA step1] Starting search with: {', '.join(fields)}")
|
||||
|
||||
# Fill DOB parts
|
||||
try:
|
||||
dob_parts = self.dateOfBirth.split("-")
|
||||
year = dob_parts[0] # "1964"
|
||||
month = dob_parts[1].zfill(2) # "04"
|
||||
day = dob_parts[2].zfill(2) # "17"
|
||||
except Exception as e:
|
||||
print(f"Error parsing DOB: {e}")
|
||||
return "ERROR: PARSING DOB"
|
||||
|
||||
# 1) locate the specific member DOB container
|
||||
dob_container = wait.until(
|
||||
EC.presence_of_element_located(
|
||||
(By.XPATH, "//div[@data-testid='member-search_date-of-birth']")
|
||||
)
|
||||
)
|
||||
|
||||
# 2) find the editable spans *inside that container* using relative XPaths
|
||||
month_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
|
||||
day_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
|
||||
year_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
|
||||
|
||||
# Helper to click, select-all and type (pure send_keys approach)
|
||||
# Helper to click, select-all and type
|
||||
def replace_with_sendkeys(el, value):
|
||||
# focus (same as click)
|
||||
el.click()
|
||||
# select all (Ctrl+A) and delete (some apps pick up BACKSPACE better — we use BACKSPACE after select)
|
||||
el.send_keys(Keys.CONTROL, "a")
|
||||
el.send_keys(Keys.BACKSPACE)
|
||||
# type the value
|
||||
el.send_keys(value)
|
||||
# optionally blur or tab out if app expects it
|
||||
# el.send_keys(Keys.TAB)
|
||||
|
||||
replace_with_sendkeys(month_elem, month)
|
||||
time.sleep(0.05)
|
||||
replace_with_sendkeys(day_elem, day)
|
||||
time.sleep(0.05)
|
||||
replace_with_sendkeys(year_elem, year)
|
||||
# 1. Fill Member ID if provided
|
||||
if self.memberId:
|
||||
try:
|
||||
member_id_input = wait.until(EC.presence_of_element_located(
|
||||
(By.XPATH, '//input[@placeholder="Search by member ID"]')
|
||||
))
|
||||
member_id_input.clear()
|
||||
member_id_input.send_keys(self.memberId)
|
||||
print(f"[DDMA step1] Entered Member ID: {self.memberId}")
|
||||
time.sleep(0.2)
|
||||
except Exception as e:
|
||||
print(f"[DDMA step1] Warning: Could not fill Member ID: {e}")
|
||||
|
||||
# 2. Fill DOB if provided
|
||||
if self.dateOfBirth:
|
||||
try:
|
||||
dob_parts = self.dateOfBirth.split("-")
|
||||
year = dob_parts[0]
|
||||
month = dob_parts[1].zfill(2)
|
||||
day = dob_parts[2].zfill(2)
|
||||
|
||||
# Click Continue button
|
||||
continue_btn = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[@data-testid="member-search_search-button"]')))
|
||||
dob_container = wait.until(
|
||||
EC.presence_of_element_located(
|
||||
(By.XPATH, "//div[@data-testid='member-search_date-of-birth']")
|
||||
)
|
||||
)
|
||||
|
||||
month_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
|
||||
day_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
|
||||
year_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
|
||||
|
||||
replace_with_sendkeys(month_elem, month)
|
||||
time.sleep(0.05)
|
||||
replace_with_sendkeys(day_elem, day)
|
||||
time.sleep(0.05)
|
||||
replace_with_sendkeys(year_elem, year)
|
||||
print(f"[DDMA step1] Filled DOB: {month}/{day}/{year}")
|
||||
except Exception as e:
|
||||
print(f"[DDMA step1] Warning: Could not fill DOB: {e}")
|
||||
|
||||
# 3. Fill First Name if provided
|
||||
if self.firstName:
|
||||
try:
|
||||
first_name_input = wait.until(EC.presence_of_element_located(
|
||||
(By.XPATH, '//input[@placeholder="First name - 1 char minimum" or contains(@placeholder,"first name") or contains(@name,"firstName")]')
|
||||
))
|
||||
first_name_input.clear()
|
||||
first_name_input.send_keys(self.firstName)
|
||||
print(f"[DDMA step1] Entered First Name: {self.firstName}")
|
||||
time.sleep(0.2)
|
||||
except Exception as e:
|
||||
print(f"[DDMA step1] Warning: Could not fill First Name: {e}")
|
||||
|
||||
# 4. Fill Last Name if provided
|
||||
if self.lastName:
|
||||
try:
|
||||
last_name_input = wait.until(EC.presence_of_element_located(
|
||||
(By.XPATH, '//input[@placeholder="Last name - 2 char minimum" or contains(@placeholder,"last name") or contains(@name,"lastName")]')
|
||||
))
|
||||
last_name_input.clear()
|
||||
last_name_input.send_keys(self.lastName)
|
||||
print(f"[DDMA step1] Entered Last Name: {self.lastName}")
|
||||
time.sleep(0.2)
|
||||
except Exception as e:
|
||||
print(f"[DDMA step1] Warning: Could not fill Last Name: {e}")
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
# Click Search button
|
||||
continue_btn = wait.until(EC.element_to_be_clickable(
|
||||
(By.XPATH, '//button[@data-testid="member-search_search-button"]')
|
||||
))
|
||||
continue_btn.click()
|
||||
print("[DDMA step1] Clicked Search button")
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
# Check for error message
|
||||
try:
|
||||
@@ -343,23 +392,24 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
(By.XPATH, '//div[@data-testid="member-search-result-no-results"]')
|
||||
))
|
||||
if error_msg:
|
||||
print("Error: Invalid Member ID or Date of Birth.")
|
||||
return "ERROR: INVALID MEMBERID OR DOB"
|
||||
print("[DDMA step1] Error: No results found")
|
||||
return "ERROR: INVALID SEARCH CRITERIA"
|
||||
except TimeoutException:
|
||||
pass
|
||||
|
||||
print("[DDMA step1] Search completed successfully")
|
||||
return "Success"
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error while step1 i.e Cheking the MemberId and DOB in: {e}")
|
||||
return "ERROR:STEP1"
|
||||
print(f"[DDMA step1] Exception: {e}")
|
||||
return f"ERROR:STEP1 - {e}"
|
||||
|
||||
|
||||
def step2(self):
|
||||
wait = WebDriverWait(self.driver, 90)
|
||||
|
||||
try:
|
||||
# Wait for results table to load (use explicit wait instead of fixed sleep)
|
||||
# Wait for results table to load
|
||||
try:
|
||||
WebDriverWait(self.driver, 10).until(
|
||||
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
|
||||
@@ -367,10 +417,50 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
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)
|
||||
# 1) Extract eligibility status and Member ID from search results
|
||||
eligibilityText = "unknown"
|
||||
foundMemberId = ""
|
||||
patientName = ""
|
||||
|
||||
# Extract data from first row
|
||||
import re
|
||||
try:
|
||||
first_row = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]")
|
||||
row_text = first_row.text.strip()
|
||||
print(f"[DDMA step2] First row text: {row_text[:150]}...")
|
||||
|
||||
if row_text:
|
||||
lines = row_text.split('\n')
|
||||
|
||||
# Extract patient name (first line, before "DOB:")
|
||||
if lines:
|
||||
potential_name = lines[0].strip()
|
||||
# Remove DOB if included in the name
|
||||
potential_name = re.sub(r'\s*DOB[:\s]*\d{1,2}/\d{1,2}/\d{2,4}\s*', '', potential_name, flags=re.IGNORECASE).strip()
|
||||
if potential_name and not potential_name.startswith('DOB') and not potential_name.isdigit():
|
||||
patientName = potential_name
|
||||
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'")
|
||||
|
||||
# Extract Member ID (usually a numeric/alphanumeric ID on its own line)
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line and re.match(r'^[A-Z0-9]{5,}$', line) and not line.startswith('DOB'):
|
||||
foundMemberId = line
|
||||
print(f"[DDMA step2] Extracted Member ID from row: {foundMemberId}")
|
||||
break
|
||||
|
||||
# Fallback: use input memberId if not found
|
||||
if not foundMemberId and self.memberId:
|
||||
foundMemberId = self.memberId
|
||||
print(f"[DDMA step2] Using input Member ID: {foundMemberId}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[DDMA step2] Error extracting data from row: {e}")
|
||||
if self.memberId:
|
||||
foundMemberId = self.memberId
|
||||
|
||||
# Extract eligibility status
|
||||
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,
|
||||
@@ -394,7 +484,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
# 2) Click on patient name to navigate to detailed patient page
|
||||
print("[DDMA step2] Clicking on patient name to open detailed page...")
|
||||
patient_name_clicked = False
|
||||
patientName = ""
|
||||
# Note: Don't reset patientName here - preserve the name extracted from row above
|
||||
|
||||
# First, let's print what we see on the page for debugging
|
||||
current_url_before = self.driver.current_url
|
||||
@@ -424,9 +514,13 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
patient_link = WebDriverWait(self.driver, 5).until(
|
||||
EC.presence_of_element_located((By.XPATH, selector))
|
||||
)
|
||||
patientName = patient_link.text.strip()
|
||||
link_text = patient_link.text.strip()
|
||||
href = patient_link.get_attribute("href")
|
||||
print(f"[DDMA step2] Found patient link: text='{patientName}', href={href}")
|
||||
print(f"[DDMA step2] Found patient link: text='{link_text}', href={href}")
|
||||
|
||||
# Only update patientName if link has text (preserve previously extracted name)
|
||||
if link_text and not patientName:
|
||||
patientName = link_text
|
||||
|
||||
if href and "member-details" in href:
|
||||
detail_url = href
|
||||
@@ -525,12 +619,13 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
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
|
||||
# Still try to get patient name from search results if not already found
|
||||
if not patientName:
|
||||
try:
|
||||
name_elem = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]//td[1]")
|
||||
patientName = name_elem.text.strip()
|
||||
except:
|
||||
pass
|
||||
|
||||
if not patientName:
|
||||
print("[DDMA step2] Could not extract patient name")
|
||||
@@ -566,7 +661,9 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
|
||||
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")
|
||||
# Use foundMemberId for filename if available, otherwise fall back to input memberId
|
||||
pdf_id = foundMemberId or self.memberId or "unknown"
|
||||
pdf_path = os.path.join(self.download_dir, f"eligibility_{pdf_id}.pdf")
|
||||
with open(pdf_path, "wb") as f:
|
||||
f.write(pdf_data)
|
||||
|
||||
@@ -580,12 +677,23 @@ class AutomationDeltaDentalMAEligibilityCheck:
|
||||
except Exception as e:
|
||||
print(f"[step2] Error closing browser: {e}")
|
||||
|
||||
# Clean patient name - remove DOB if it was included (already cleaned above but double check)
|
||||
if patientName:
|
||||
# Remove "DOB: MM/DD/YYYY" or similar patterns from the name
|
||||
cleaned_name = re.sub(r'\s*DOB[:\s]*\d{1,2}/\d{1,2}/\d{2,4}\s*', '', patientName, flags=re.IGNORECASE).strip()
|
||||
if cleaned_name:
|
||||
patientName = cleaned_name
|
||||
print(f"[DDMA step2] Cleaned patient name: {patientName}")
|
||||
|
||||
print(f"[DDMA step2] Final data - PatientName: '{patientName}', MemberID: '{foundMemberId}'")
|
||||
|
||||
output = {
|
||||
"status": "success",
|
||||
"eligibility": eligibilityText,
|
||||
"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,
|
||||
"memberId": foundMemberId # Include extracted Member ID
|
||||
}
|
||||
return output
|
||||
except Exception as e:
|
||||
|
||||
@@ -471,48 +471,35 @@ class AutomationDentaQuestEligibilityCheck:
|
||||
eligibilityText = "unknown"
|
||||
foundMemberId = ""
|
||||
|
||||
# Try to extract Member ID from the search results
|
||||
# Try to extract Member ID from the first row of search results
|
||||
# Row format: "NAME\nDOB: MM/DD/YYYY\nMEMBER_ID\n..."
|
||||
import re
|
||||
try:
|
||||
# Look for Member ID in various places
|
||||
member_id_selectors = [
|
||||
"(//tbody//tr)[1]//td[contains(text(),'ID:') or contains(@data-testid,'member-id')]",
|
||||
"//span[contains(text(),'Member ID')]/..//span",
|
||||
"//div[contains(text(),'Member ID')]",
|
||||
"(//tbody//tr)[1]//td[2]", # Often Member ID is in second column
|
||||
]
|
||||
first_row = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]")
|
||||
row_text = first_row.text.strip()
|
||||
|
||||
page_text = self.driver.find_element(By.TAG_NAME, "body").text
|
||||
if row_text:
|
||||
lines = row_text.split('\n')
|
||||
# Member ID is typically the 3rd line (index 2) - a pure number
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
# Member ID is usually a number, could be alphanumeric
|
||||
# It should be after DOB line and be mostly digits
|
||||
if line and re.match(r'^[A-Z0-9]{5,}$', line) and not line.startswith('DOB'):
|
||||
foundMemberId = line
|
||||
print(f"[DentaQuest step2] Extracted Member ID from row: {foundMemberId}")
|
||||
break
|
||||
|
||||
# Try regex patterns to find Member ID
|
||||
patterns = [
|
||||
r'Member ID[:\s]+([A-Z0-9]+)',
|
||||
r'ID[:\s]+([A-Z0-9]{5,})',
|
||||
r'MemberID[:\s]+([A-Z0-9]+)',
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, page_text, re.IGNORECASE)
|
||||
if match:
|
||||
foundMemberId = match.group(1).strip()
|
||||
print(f"[DentaQuest step2] Extracted Member ID: {foundMemberId}")
|
||||
break
|
||||
|
||||
if not foundMemberId:
|
||||
for selector in member_id_selectors:
|
||||
try:
|
||||
elem = self.driver.find_element(By.XPATH, selector)
|
||||
text = elem.text.strip()
|
||||
# Extract just the ID part
|
||||
id_match = re.search(r'([A-Z0-9]{5,})', text)
|
||||
if id_match:
|
||||
foundMemberId = id_match.group(1)
|
||||
print(f"[DentaQuest step2] Found Member ID via selector: {foundMemberId}")
|
||||
break
|
||||
except:
|
||||
continue
|
||||
# Fallback: if we have self.memberId from input, use that
|
||||
if not foundMemberId and self.memberId:
|
||||
foundMemberId = self.memberId
|
||||
print(f"[DentaQuest step2] Using input Member ID: {foundMemberId}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[DentaQuest step2] Error extracting Member ID: {e}")
|
||||
# Fallback to input memberId
|
||||
if self.memberId:
|
||||
foundMemberId = self.memberId
|
||||
|
||||
# Extract eligibility status
|
||||
status_selectors = [
|
||||
@@ -558,21 +545,45 @@ class AutomationDentaQuestEligibilityCheck:
|
||||
except Exception as e:
|
||||
print(f"[DentaQuest step2] Error listing links: {e}")
|
||||
|
||||
# Find the patient detail link
|
||||
# Find the patient detail link and extract patient name from row
|
||||
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
|
||||
]
|
||||
|
||||
# First, try to extract patient name from the row text (not the link)
|
||||
try:
|
||||
first_row = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]")
|
||||
row_text = first_row.text.strip()
|
||||
print(f"[DentaQuest step2] First row text: {row_text[:100]}...")
|
||||
|
||||
# The name is typically the first line, before "DOB:"
|
||||
if row_text:
|
||||
lines = row_text.split('\n')
|
||||
if lines:
|
||||
# First line is usually the patient name
|
||||
potential_name = lines[0].strip()
|
||||
# Make sure it's not a date or ID
|
||||
if potential_name and not potential_name.startswith('DOB') and not potential_name.isdigit():
|
||||
patientName = potential_name
|
||||
print(f"[DentaQuest step2] Extracted patient name from row: '{patientName}'")
|
||||
except Exception as e:
|
||||
print(f"[DentaQuest step2] Error extracting name from row: {e}")
|
||||
|
||||
# Now find the detail 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()
|
||||
link_text = patient_link.text.strip()
|
||||
href = patient_link.get_attribute("href")
|
||||
print(f"[DentaQuest step2] Found patient link: text='{patientName}', href={href}")
|
||||
print(f"[DentaQuest step2] Found patient link: text='{link_text}', href={href}")
|
||||
|
||||
# If link has text and we don't have patientName yet, use it
|
||||
if link_text and not patientName:
|
||||
patientName = link_text
|
||||
|
||||
if href and ("member-details" in href or "member" in href):
|
||||
detail_url = href
|
||||
|
||||
Reference in New Issue
Block a user