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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user