feat(eligibility-check) - enhance DentaQuest and United SCO workflows with flexible input handling; added Selenium session clearing on credential updates and deletions; improved patient name extraction and eligibility checks across services

This commit is contained in:
2026-02-06 08:57:29 -05:00
parent e43329e95f
commit e425a829b2
12 changed files with 418 additions and 224 deletions

View File

@@ -76,8 +76,33 @@ router.put("/:id", async (req: Request, res: Response): Promise<any> => {
const id = Number(req.params.id);
if (isNaN(id)) return res.status(400).send("Invalid credential ID");
// Get existing credential to know its siteKey
const existing = await storage.getInsuranceCredential(id);
if (!existing) {
return res.status(404).json({ message: "Credential not found" });
}
const updates = req.body as Partial<InsuranceCredential>;
const credential = await storage.updateInsuranceCredential(id, updates);
// Clear Selenium browser session when credentials are changed
const seleniumAgentUrl = process.env.SELENIUM_AGENT_URL || "http://localhost:5002";
try {
if (existing.siteKey === "DDMA") {
await fetch(`${seleniumAgentUrl}/clear-ddma-session`, { method: "POST" });
console.log("[insuranceCreds] Cleared DDMA browser session after credential update");
} else if (existing.siteKey === "DENTAQUEST") {
await fetch(`${seleniumAgentUrl}/clear-dentaquest-session`, { method: "POST" });
console.log("[insuranceCreds] Cleared DentaQuest browser session after credential update");
} else if (existing.siteKey === "UNITEDSCO") {
await fetch(`${seleniumAgentUrl}/clear-unitedsco-session`, { method: "POST" });
console.log("[insuranceCreds] Cleared United SCO browser session after credential update");
}
} catch (seleniumErr) {
// Don't fail the update if Selenium session clear fails
console.error("[insuranceCreds] Failed to clear Selenium session:", seleniumErr);
}
return res.status(200).json(credential);
} catch (err) {
return res
@@ -115,6 +140,25 @@ router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
.status(404)
.json({ message: "Credential not found or already deleted" });
}
// 4) Clear Selenium browser session for this provider
const seleniumAgentUrl = process.env.SELENIUM_AGENT_URL || "http://localhost:5002";
try {
if (existing.siteKey === "DDMA") {
await fetch(`${seleniumAgentUrl}/clear-ddma-session`, { method: "POST" });
console.log("[insuranceCreds] Cleared DDMA browser session after credential deletion");
} else if (existing.siteKey === "DENTAQUEST") {
await fetch(`${seleniumAgentUrl}/clear-dentaquest-session`, { method: "POST" });
console.log("[insuranceCreds] Cleared DentaQuest browser session after credential deletion");
} else if (existing.siteKey === "UNITEDSCO") {
await fetch(`${seleniumAgentUrl}/clear-unitedsco-session`, { method: "POST" });
console.log("[insuranceCreds] Cleared United SCO browser session after credential deletion");
}
} catch (seleniumErr) {
// Don't fail the delete if Selenium session clear fails
console.error("[insuranceCreds] Failed to clear Selenium session:", seleniumErr);
}
return res.status(204).send();
} catch (err) {
return res

View File

@@ -136,12 +136,17 @@ async function handleDentaQuestCompletedJob(
// 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();
// 1) Get Member ID - prefer the one extracted from the page by Selenium,
// since we now allow searching by name only
let insuranceId = String(seleniumResult?.memberId ?? "").trim();
if (!insuranceId) {
throw new Error("Missing memberId for DentaQuest job");
// Fallback to the one provided in the request
insuranceId = String(insuranceEligibilityData.memberId ?? "").trim();
}
console.log(`[dentaquest-eligibility] Insurance ID: ${insuranceId || "(none)"}`);
// 2) Create or update patient (with name from selenium result if available)
const patientNameFromResult =
@@ -149,23 +154,93 @@ async function handleDentaQuestCompletedJob(
? seleniumResult.patientName.trim()
: null;
const { firstName, lastName } = splitName(patientNameFromResult);
// Get name from request data as fallback
let firstName = insuranceEligibilityData.firstName || "";
let lastName = insuranceEligibilityData.lastName || "";
// Override with name from Selenium result if available
if (patientNameFromResult) {
const parsedName = splitName(patientNameFromResult);
firstName = parsedName.firstName || firstName;
lastName = parsedName.lastName || lastName;
}
await createOrUpdatePatientByInsuranceId({
insuranceId,
firstName,
lastName,
dob: insuranceEligibilityData.dateOfBirth,
userId: job.userId,
});
// Create or update patient if we have an insurance ID
if (insuranceId) {
await createOrUpdatePatientByInsuranceId({
insuranceId,
firstName,
lastName,
dob: insuranceEligibilityData.dateOfBirth,
userId: job.userId,
});
} else {
console.log("[dentaquest-eligibility] No Member ID available - will try to find patient by name/DOB");
}
// 3) Update patient status + PDF upload
const patient = await storage.getPatientByInsuranceId(
insuranceEligibilityData.memberId
);
// First try to find by insurance ID, then by name + DOB
let patient = insuranceId
? await storage.getPatientByInsuranceId(insuranceId)
: null;
// If not found by ID and we have name + DOB, try to find by those
if (!patient && firstName && lastName) {
console.log(`[dentaquest-eligibility] Looking up patient by name: ${firstName} ${lastName}`);
const patients = await storage.getPatientsByUserId(job.userId);
patient = patients.find(p =>
p.firstName?.toLowerCase() === firstName.toLowerCase() &&
p.lastName?.toLowerCase() === lastName.toLowerCase()
) || null;
// If found and we now have the insurance ID, update the patient record
if (patient && insuranceId) {
await storage.updatePatient(patient.id, { insuranceId });
console.log(`[dentaquest-eligibility] Updated patient ${patient.id} with insuranceId: ${insuranceId}`);
}
}
// 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}`);
const createPayload: any = {
firstName,
lastName,
dateOfBirth: insuranceEligibilityData.dateOfBirth || null,
gender: "",
phone: "",
userId: job.userId,
insuranceId: insuranceId || null,
};
try {
const patientData = insertPatientSchema.parse(createPayload);
const newPatient = await storage.createPatient(patientData);
if (newPatient) {
patient = newPatient;
console.log(`[dentaquest-eligibility] Created new patient with ID: ${patient.id}`);
}
} catch (err: any) {
// Try without dateOfBirth if it fails
try {
const safePayload = { ...createPayload };
delete safePayload.dateOfBirth;
const patientData = insertPatientSchema.parse(safePayload);
const newPatient = await storage.createPatient(patientData);
if (newPatient) {
patient = newPatient;
console.log(`[dentaquest-eligibility] Created new patient (no DOB) with ID: ${patient.id}`);
}
} catch (err2: any) {
console.error(`[dentaquest-eligibility] Failed to create patient: ${err2?.message}`);
}
}
}
if (!patient?.id) {
outputResult.patientUpdateStatus =
"Patient not found; no update performed";
"Patient not found and could not be created";
return {
patientUpdateStatus: outputResult.patientUpdateStatus,
pdfUploadStatus: "none",

View File

@@ -135,6 +135,7 @@ async function handleUnitedSCOCompletedJob(
seleniumResult: any
) {
let createdPdfFileId: number | null = null;
let generatedPdfPath: string | null = null;
const outputResult: any = {};
// We'll wrap the processing in try/catch/finally so cleanup always runs
@@ -204,7 +205,6 @@ async function handleUnitedSCOCompletedJob(
// Handle PDF or convert screenshot -> pdf if available
let pdfBuffer: Buffer | null = null;
let generatedPdfPath: string | null = null;
if (
seleniumResult &&
@@ -233,7 +233,8 @@ async function handleUnitedSCOCompletedJob(
// Convert image to PDF
pdfBuffer = await imageToPdfBuffer(seleniumResult.ss_path);
const pdfFileName = `unitedsco_eligibility_${insuranceEligibilityData.memberId}_${Date.now()}.pdf`;
// Use insuranceId (which may come from Selenium result) for filename
const pdfFileName = `unitedsco_eligibility_${insuranceId || "unknown"}_${Date.now()}.pdf`;
generatedPdfPath = path.join(
path.dirname(seleniumResult.ss_path),
pdfFileName
@@ -287,18 +288,25 @@ async function handleUnitedSCOCompletedJob(
"No valid PDF path provided by Selenium, Couldn't upload pdf to server.";
}
// Get filename for frontend preview
const pdfFilename = generatedPdfPath ? path.basename(generatedPdfPath) : null;
return {
patientUpdateStatus: outputResult.patientUpdateStatus,
pdfUploadStatus: outputResult.pdfUploadStatus,
pdfFileId: createdPdfFileId,
pdfFilename,
};
} catch (err: any) {
// Get filename for frontend preview if available
const pdfFilename = generatedPdfPath ? path.basename(generatedPdfPath) : null;
return {
patientUpdateStatus: outputResult.patientUpdateStatus,
pdfUploadStatus:
outputResult.pdfUploadStatus ??
`Failed to process United SCO job: ${err?.message ?? String(err)}`,
pdfFileId: createdPdfFileId,
pdfFilename,
error: err?.message ?? String(err),
};
} finally {