feat: save insurance PDFs (eligibility/claims/preauth) to Cloud Storage instead of Postgres blobs

New saves go into the patient's Cloud Storage folder under category subfolders
(Eligibility/Claims/PreAuth/Claim Status/Attachments) via a new
storage.savePdfToCloudStorage helper, instead of the legacy PdfGroup/PdfFile
Postgres blob tables. Old records and their read paths are left untouched for
backward compatibility; PDF list/content/delete endpoints and viewers now
transparently handle both legacy numeric ids and new "cloud:<id>" ids.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 00:20:37 -04:00
co-authored by Claude Sonnet 5
parent 1190bc0494
commit 99cdfa702a
36 changed files with 496 additions and 296 deletions
+2
View File
@@ -73,6 +73,7 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string {
return runClaimStatusProcessor({
enrichedPayload: data.enrichedPayload,
insuranceId: data.insuranceId!,
userId: data.userId,
});
}
if (jobType === "claim-submit" || jobType === "claim-pre-auth") {
@@ -82,6 +83,7 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string {
claimId: data.claimId,
variant: jobType === "claim-pre-auth" ? "claim-pre-auth" : "claimsubmit",
socketId: data.socketId,
userId: data.userId,
});
}
if (jobType === "ddma-eligibility-check") {
@@ -37,7 +37,7 @@ export interface BcbsMaEligibilityProcessorInput {
export interface BcbsMaEligibilityProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
}
@@ -50,7 +50,7 @@ async function processBcbsMaResult(
seleniumResult: any
): Promise<BcbsMaEligibilityProcessorResult> {
const output: BcbsMaEligibilityProcessorResult = {};
let createdPdfFileId: number | null = null;
let createdPdfFileId: number | string | null = null;
try {
// Prefer names extracted from the BCBS MA results page (Demographic Information section)
@@ -133,16 +133,17 @@ async function processBcbsMaResult(
}
if (pdfBuffer && pdfFilename) {
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName,
"Eligibility",
pdfFilename,
pdfBuffer
);
createdPdfFileId = `cloud:${cloudFile.id}`;
output.pdfUploadStatus = "PDF saved to Eligibility folder";
output.pdfFilename = pdfFilename;
}
@@ -79,7 +79,7 @@ export interface CCAClaimProcessorInput {
export async function runCCAClaimProcessor(
input: CCAClaimProcessorInput,
jobId: string
): Promise<{ status: string; claimNumber?: string | null; pdfFileId?: number | null }> {
): Promise<{ status: string; claimNumber?: string | null; pdfFileId?: number | string | null }> {
const { enrichedPayload, userId, claimId, socketId } = input;
log("cca-claim-processor", "starting Python agent session", { claimId });
@@ -106,19 +106,22 @@ export async function runCCAClaimProcessor(
seleniumResult?.pdfFilename || `cca_claim_${claimId ?? "unknown"}_${Date.now()}.pdf`;
// Save PDF to patient's Claims document group
let pdfFileId: number | null = null;
let pdfFileId: number | string | null = null;
if (pdfBase64 && enrichedPayload?.claim?.patientId) {
try {
const patientId = Number(enrichedPayload.claim.patientId);
const pdfBuffer = Buffer.from(pdfBase64, "base64");
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
if (!group) {
group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
}
const created = await storage.createPdfFile(group.id!, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
pdfFileId = Number((created as any).id);
}
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patientId,
patientName || `Patient ${patientId}`,
"Claims",
pdfFilename,
pdfBuffer
);
pdfFileId = `cloud:${cloudFile.id}`;
log("cca-claim-processor", "PDF saved", { pdfFilename, pdfFileId, patientId });
} catch (e: any) {
log("cca-claim-processor", "failed to save PDF", { error: e?.message });
@@ -59,7 +59,7 @@ export interface CCAEligibilityProcessorInput {
export interface CCAEligibilityProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
}
@@ -74,7 +74,7 @@ async function processCCAResult(
seleniumResult: any
): Promise<CCAEligibilityProcessorResult> {
const output: CCAEligibilityProcessorResult = {};
let createdPdfFileId: number | null = null;
let createdPdfFileId: number | string | null = null;
try {
// 1) Resolve patient name
@@ -143,18 +143,17 @@ async function processCCAResult(
// 6) Save PDF to patient document group
if (pdfBuffer && pdfFilename) {
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName,
"Eligibility",
pdfFilename,
pdfBuffer
);
createdPdfFileId = `cloud:${cloudFile.id}`;
output.pdfUploadStatus = "PDF saved to Eligibility folder";
output.pdfFilename = pdfFilename;
}
@@ -73,7 +73,7 @@ export interface CCAPreAuthProcessorInput {
export async function runCCAPreAuthProcessor(
input: CCAPreAuthProcessorInput,
jobId: string
): Promise<{ status: string; authNumber?: string | null; pdfFileId?: number | null }> {
): Promise<{ status: string; authNumber?: string | null; pdfFileId?: number | string | null }> {
const { enrichedPayload, userId, claimId, socketId } = input;
log("cca-preauth-processor", "starting Python agent session", { claimId });
@@ -110,19 +110,22 @@ export async function runCCAPreAuthProcessor(
}
// Save PDF to patient's PreAuth document group
let pdfFileId: number | null = null;
let pdfFileId: number | string | null = null;
if (pdfBase64 && enrichedPayload?.claim?.patientId) {
try {
const patientId = Number(enrichedPayload.claim.patientId);
const pdfBuffer = Buffer.from(pdfBase64, "base64");
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
if (!group) {
group = await storage.createPdfGroup(patientId, "PreAuth", "INSURANCE_CLAIM_PREAUTH");
}
const created = await storage.createPdfFile(group.id!, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
pdfFileId = Number((created as any).id);
}
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patientId,
patientName || `Patient ${patientId}`,
"PreAuth",
pdfFilename,
pdfBuffer
);
pdfFileId = `cloud:${cloudFile.id}`;
log("cca-preauth-processor", "PDF saved", { pdfFilename, pdfFileId, patientId });
} catch (e: any) {
log("cca-preauth-processor", "failed to save PDF", { error: e?.message });
@@ -15,17 +15,18 @@ import {
export interface ClaimStatusProcessorInput {
enrichedPayload: any;
insuranceId: string; // memberId used to look up the patient
userId: number;
}
export interface ClaimStatusProcessorResult {
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
}
export async function runClaimStatusProcessor(
input: ClaimStatusProcessorInput
): Promise<ClaimStatusProcessorResult> {
const { enrichedPayload, insuranceId } = input;
const { enrichedPayload, insuranceId, userId } = input;
// 1) Call the Python service synchronously (BullMQ worker handles async)
const result = await callPythonSync("/claim-status-check", {
@@ -61,22 +62,19 @@ export async function runClaimStatusProcessor(
}
if (pdfBuffer && generatedPdfPath) {
const groupTitleKey = "CLAIM_STATUS";
const groupTitle = "Claim Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim();
const basename = path.basename(generatedPdfPath);
const created = await storage.createPdfFile(group.id, basename, pdfBuffer);
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName || `Patient ${patient.id}`,
"Claim Status",
basename,
pdfBuffer
);
const createdPdfFileId: number | string | null = `cloud:${cloudFile.id}`;
let createdPdfFileId: number | null = null;
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
outputResult.pdfUploadStatus = `PDF saved to group: ${group.title}`;
outputResult.pdfUploadStatus = `PDF saved to Claim Status`;
outputResult.pdfFileId = createdPdfFileId;
}
} else {
@@ -15,6 +15,7 @@ export interface ClaimSubmitProcessorInput {
variant?: "claimsubmit" | "claim-pre-auth";
/** When set, the frontend socket listener will handle the PDF download — skip auto-save */
socketId?: string;
userId: number;
}
export interface ClaimSubmitProcessorResult {
@@ -28,7 +29,8 @@ export interface ClaimSubmitProcessorResult {
async function savePdfFromSelenium(
pdf_url: string,
patientId: number,
variant: "claimsubmit" | "claim-pre-auth"
variant: "claimsubmit" | "claim-pre-auth",
userId: number
) {
try {
const filename = path.basename(new URL(pdf_url).pathname);
@@ -37,15 +39,19 @@ async function savePdfFromSelenium(
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
const groupTitleKey = variant === "claim-pre-auth" ? "INSURANCE_CLAIM_PREAUTH" : "INSURANCE_CLAIM";
const groupTitle = variant === "claim-pre-auth" ? "Preauth" : "Claims";
const category = variant === "claim-pre-auth" ? "PreAuth" : "Claims";
let group = await storage.findPdfGroupByPatientTitleKey(patientId, groupTitleKey);
if (!group) {
group = await storage.createPdfGroup(patientId, groupTitle, groupTitleKey);
}
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
await storage.createPdfFile(group.id!, filename, resp.data);
await storage.savePdfToCloudStorage(
userId,
patientId,
patientName || `Patient ${patientId}`,
category,
filename,
Buffer.from(resp.data)
);
console.log(`[claimSubmitProcessor] PDF saved for patient ${patientId}: ${filename}`);
} catch (err: any) {
// Non-fatal — claim was submitted; just log the PDF failure
@@ -56,7 +62,7 @@ async function savePdfFromSelenium(
export async function runClaimSubmitProcessor(
input: ClaimSubmitProcessorInput
): Promise<ClaimSubmitProcessorResult> {
const { enrichedPayload, files, claimId } = input;
const { enrichedPayload, files, claimId, userId } = input;
const variant = input.variant ?? "claimsubmit";
// Build the same payload shape the Python /claimsubmit endpoint expects
@@ -97,7 +103,8 @@ export async function runClaimSubmitProcessor(
await savePdfFromSelenium(
result.pdf_url,
Number(enrichedPayload.patientId),
variant
variant,
userId
);
}
@@ -26,13 +26,13 @@ export interface CmspEligibilityHistoryRemainingProcessorInput {
export interface CmspEligibilityHistoryRemainingProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
memberDetailsPdfFileId?: number | null;
memberDetailsPdfFileId?: number | string | null;
memberDetailsPdfFilename?: string | null;
historyPdfFileId?: number | null;
historyPdfFileId?: number | string | null;
historyPdfFilename?: string | null;
accumulatorPdfFileId?: number | null;
accumulatorPdfFileId?: number | string | null;
accumulatorPdfFilename?: string | null;
}
@@ -108,20 +108,20 @@ export async function runCmspEligibilityHistoryRemainingProcessor(
await storage.updatePatient(patient.id, updates);
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
// Helper: save a single PDF to the patient's ELIGIBILITY_STATUS group
const saveToGroup = async (pdfPath: string): Promise<number | null> => {
// Helper: save a single PDF to the patient's Cloud Storage Eligibility folder
const patientName = `${patient!.firstName ?? ""} ${patient!.lastName ?? ""}`.trim() || `Patient ${patient!.id}`;
const saveToGroup = async (pdfPath: string): Promise<number | string | null> => {
try {
const buffer = await fs.readFile(pdfPath);
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer);
if (created && typeof created === "object" && "id" in created) {
return Number(created.id);
}
return null;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient!.id,
patientName,
"Eligibility",
path.basename(pdfPath),
buffer
);
return `cloud:${cloudFile.id}`;
} catch (e: any) {
console.error("[cmspProcessor] saveToGroup failed:", e.message);
return null;
@@ -88,15 +88,22 @@ async function pollUntilDone(
throw new Error(`DDMA claim polling exhausted all attempts for session ${sessionId}`);
}
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
try {
const filename = path.basename(new URL(pdf_url).pathname);
const seleniumPort = process.env.SELENIUM_PORT || "5002";
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
if (!group) group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
await storage.createPdfFile(group.id!, filename, resp.data);
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : `Patient ${patientId}`;
await storage.savePdfToCloudStorage(
userId,
patientId,
patientName,
"Claims",
filename,
Buffer.from(resp.data)
);
log("ddma-claim-processor", "PDF saved", { patientId, filename });
} catch (err: any) {
log("ddma-claim-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
@@ -160,7 +167,7 @@ export async function runDDMAClaimProcessor(
if (pdf_url && !socketId) {
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
}
emitToSocket(socketId, "selenium:ddma_claim_completed", {
@@ -66,7 +66,7 @@ export interface DdmaEligibilityProcessorInput {
export interface DdmaEligibilityProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
}
@@ -81,7 +81,7 @@ async function processDdmaResult(
seleniumResult: any
): Promise<DdmaEligibilityProcessorResult> {
const output: DdmaEligibilityProcessorResult = {};
let createdPdfFileId: number | null = null;
let createdPdfFileId: number | string | null = null;
try {
// 1) Resolve patient name (prefer selenium extraction → form data)
@@ -185,18 +185,17 @@ async function processDdmaResult(
// 6) Save PDF to patient document group
if (pdfBuffer && pdfFilename) {
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName,
"Eligibility",
pdfFilename,
pdfBuffer
);
createdPdfFileId = `cloud:${cloudFile.id}`;
output.pdfUploadStatus = "PDF saved to Eligibility folder";
output.pdfFilename = pdfFilename;
}
@@ -56,7 +56,7 @@ export interface DeltaInsEligibilityProcessorInput {
export interface DeltaInsEligibilityProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
}
@@ -71,7 +71,7 @@ async function processDeltaInsResult(
seleniumResult: any
): Promise<DeltaInsEligibilityProcessorResult> {
const output: DeltaInsEligibilityProcessorResult = {};
let createdPdfFileId: number | null = null;
let createdPdfFileId: number | string | null = null;
try {
// 1) Resolve patient name
@@ -132,18 +132,17 @@ async function processDeltaInsResult(
// 6) Save PDF to patient document group
if (pdfBuffer && pdfFilename) {
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName,
"Eligibility",
pdfFilename,
pdfBuffer
);
createdPdfFileId = `cloud:${cloudFile.id}`;
output.pdfUploadStatus = "PDF saved to Eligibility folder";
output.pdfFilename = pdfFilename;
}
@@ -44,7 +44,7 @@ export interface DentaQuestEligibilityProcessorInput {
export interface DentaQuestEligibilityProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
}
@@ -57,7 +57,7 @@ async function processDentaQuestResult(
seleniumResult: any
): Promise<DentaQuestEligibilityProcessorResult> {
const output: DentaQuestEligibilityProcessorResult = {};
let createdPdfFileId: number | null = null;
let createdPdfFileId: number | string | null = null;
try {
const rawName =
@@ -130,18 +130,17 @@ async function processDentaQuestResult(
}
if (pdfBuffer && pdfFilename) {
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName,
"Eligibility",
pdfFilename,
pdfBuffer
);
createdPdfFileId = `cloud:${cloudFile.id}`;
output.pdfUploadStatus = "PDF saved to Eligibility folder";
output.pdfFilename = pdfFilename;
}
@@ -26,11 +26,11 @@ export interface EligibilityHistoryProcessorInput {
export interface EligibilityHistoryProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
memberDetailsPdfFileId?: number | null;
memberDetailsPdfFileId?: number | string | null;
memberDetailsPdfFilename?: string | null;
historyPdfFileId?: number | null;
historyPdfFileId?: number | string | null;
historyPdfFilename?: string | null;
}
@@ -106,20 +106,20 @@ export async function runEligibilityHistoryProcessor(
await storage.updatePatient(patient.id, updates);
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
// Helper: save a PDF buffer to the patient's ELIGIBILITY_STATUS group
const saveToGroup = async (pdfPath: string): Promise<number | null> => {
// Helper: save a PDF buffer to the patient's Cloud Storage Eligibility folder
const patientName = `${patient!.firstName ?? ""} ${patient!.lastName ?? ""}`.trim() || `Patient ${patient!.id}`;
const saveToGroup = async (pdfPath: string): Promise<number | string | null> => {
try {
const buffer = await fs.readFile(pdfPath);
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer);
if (created && typeof created === "object" && "id" in created) {
return Number(created.id);
}
return null;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient!.id,
patientName,
"Eligibility",
path.basename(pdfPath),
buffer
);
return `cloud:${cloudFile.id}`;
} catch (e: any) {
console.error("[eligibilityHistoryProcessor] saveToGroup failed:", e.message);
return null;
@@ -127,7 +127,7 @@ export async function runEligibilityHistoryProcessor(
};
// Save eligibility PDF
let eligibilityPdfFileId: number | null = null;
let eligibilityPdfFileId: number | string | null = null;
if (seleniumResult.pdf_path?.endsWith(".pdf")) {
eligibilityPdfFileId = await saveToGroup(seleniumResult.pdf_path);
outputResult.pdfUploadStatus = eligibilityPdfFileId
@@ -146,7 +146,7 @@ export async function runEligibilityHistoryProcessor(
}
// Save history PDF
let historyPdfFileId: number | null = null;
let historyPdfFileId: number | string | null = null;
if (seleniumResult.history_pdf_path?.endsWith(".pdf")) {
historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path);
outputResult.historyPdfFilename = path.basename(seleniumResult.history_pdf_path);
@@ -29,7 +29,7 @@ export interface EligibilityProcessorInput {
export interface EligibilityProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
}
export async function runEligibilityProcessor(
@@ -122,27 +122,23 @@ export async function runEligibilityProcessor(
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
// 6) Save PDF
let createdPdfFileId: number | null = null;
let createdPdfFileId: number | string | null = null;
if (seleniumResult.pdf_path?.endsWith(".pdf")) {
try {
const pdfBuffer = await fs.readFile(seleniumResult.pdf_path);
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(
group.id,
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName,
"Eligibility",
path.basename(seleniumResult.pdf_path),
pdfBuffer
);
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
outputResult.pdfUploadStatus = `PDF saved to group: ${group.title}`;
createdPdfFileId = `cloud:${cloudFile.id}`;
outputResult.pdfUploadStatus = "PDF saved to Eligibility folder";
} catch (e: any) {
outputResult.pdfUploadStatus = `PDF upload failed: ${e.message}`;
}
@@ -87,15 +87,22 @@ async function pollUntilDone(
throw new Error(`Tufts SCO claim polling exhausted all attempts for session ${sessionId}`);
}
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
try {
const filename = path.basename(new URL(pdf_url).pathname);
const seleniumPort = process.env.SELENIUM_PORT || "5002";
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
if (!group) group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
await storage.createPdfFile(group.id!, filename, resp.data);
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
await storage.savePdfToCloudStorage(
userId,
patientId,
patientName || `Patient ${patientId}`,
"Claims",
filename,
Buffer.from(resp.data)
);
log("tuftssco-claim-processor", "PDF saved", { patientId, filename });
} catch (err: any) {
log("tuftssco-claim-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
@@ -151,7 +158,7 @@ export async function runTuftsSCOClaimProcessor(
if (pdf_url && !socketId) {
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
}
emitToSocket(socketId, "selenium:tuftssco_claim_completed", {
@@ -88,16 +88,21 @@ async function pollUntilDone(
throw new Error(`Tufts SCO preauth polling exhausted all attempts for session ${sessionId}`);
}
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
try {
const filename = path.basename(new URL(pdf_url).pathname);
const resp = await axios.get(pdf_url, { responseType: "arraybuffer", timeout: 30000 });
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
if (!group) {
group = await storage.createPdfGroup(patientId, "PreAuth", "INSURANCE_CLAIM_PREAUTH");
}
await storage.createPdfFile(group.id!, filename, resp.data);
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
await storage.savePdfToCloudStorage(
userId,
patientId,
patientName || `Patient ${patientId}`,
"PreAuth",
filename,
Buffer.from(resp.data)
);
log("tuftssco-preauth-processor", "PDF saved", { patientId, filename });
} catch (err: any) {
log("tuftssco-preauth-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
@@ -158,7 +163,7 @@ export async function runTuftsSCOPreAuthProcessor(
if (pdf_url && !socketId) {
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
}
emitToSocket(socketId, "selenium:tuftssco_preauth_completed", {
@@ -99,18 +99,23 @@ async function pollUntilDone(
throw new Error(`UnitedDH claim polling exhausted all attempts for session ${sessionId}`);
}
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
try {
const filename = path.basename(new URL(pdf_url).pathname);
const seleniumPort = process.env.SELENIUM_PORT || "5002";
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
if (!group) {
group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
}
await storage.createPdfFile(group.id!, filename, resp.data);
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
await storage.savePdfToCloudStorage(
userId,
patientId,
patientName || `Patient ${patientId}`,
"Claims",
filename,
Buffer.from(resp.data)
);
log("uniteddh-claim-processor", "PDF saved", { patientId, filename });
} catch (err: any) {
log("uniteddh-claim-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
@@ -173,7 +178,7 @@ export async function runUnitedDHClaimProcessor(
if (pdf_url && !socketId) {
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
}
emitToSocket(socketId, "selenium:uniteddh_claim_completed", {
@@ -98,18 +98,23 @@ async function pollUntilDone(
throw new Error(`UnitedDH preauth polling exhausted all attempts for session ${sessionId}`);
}
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
try {
const filename = path.basename(new URL(pdf_url).pathname);
const seleniumPort = process.env.SELENIUM_PORT || "5002";
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
if (!group) {
group = await storage.createPdfGroup(patientId, "PreAuth", "INSURANCE_CLAIM_PREAUTH");
}
await storage.createPdfFile(group.id!, filename, resp.data);
const patient = await storage.getPatient(patientId);
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
await storage.savePdfToCloudStorage(
userId,
patientId,
patientName || `Patient ${patientId}`,
"PreAuth",
filename,
Buffer.from(resp.data)
);
log("uniteddh-preauth-processor", "PDF saved", { patientId, filename });
} catch (err: any) {
log("uniteddh-preauth-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
@@ -171,7 +176,7 @@ export async function runUnitedDHPreAuthProcessor(
if (pdf_url && !socketId) {
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
}
emitToSocket(socketId, "selenium:uniteddh_preauth_completed", {
@@ -58,7 +58,7 @@ export interface UnitedSCOEligibilityProcessorInput {
export interface UnitedSCOEligibilityProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
}
@@ -73,7 +73,7 @@ async function processUnitedSCOResult(
seleniumResult: any
): Promise<UnitedSCOEligibilityProcessorResult> {
const output: UnitedSCOEligibilityProcessorResult = {};
let createdPdfFileId: number | null = null;
let createdPdfFileId: number | string | null = null;
try {
// 1) Resolve patient name
@@ -155,18 +155,17 @@ async function processUnitedSCOResult(
// 6) Save PDF to patient document group
if (pdfBuffer && pdfFilename) {
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
if (created && typeof created === "object" && "id" in created) {
createdPdfFileId = Number(created.id);
}
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient.id,
patientName,
"Eligibility",
pdfFilename,
pdfBuffer
);
createdPdfFileId = `cloud:${cloudFile.id}`;
output.pdfUploadStatus = "PDF saved to Eligibility folder";
output.pdfFilename = pdfFilename;
}
@@ -50,6 +50,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
result = await runClaimStatusProcessor({
enrichedPayload,
insuranceId: job.data.insuranceId!,
userId,
});
} else if (jobType === "claim-submit") {
result = await runClaimSubmitProcessor({
@@ -58,6 +59,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
claimId: job.data.claimId,
variant: "claimsubmit",
socketId,
userId,
});
} else if (jobType === "claim-pre-auth") {
result = await runClaimSubmitProcessor({
@@ -66,6 +68,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
claimId: job.data.claimId,
variant: "claim-pre-auth",
socketId,
userId,
});
} else {
throw new Error(`Unknown selenium jobType: ${jobType}`);