feat: add BullMQ queue infrastructure and frontend job status hook

- apps/Backend/src/queue/: connection, queues, workers, processors
- apps/Frontend/src/hooks/use-job-status.ts: WebSocket job progress hook
- apps/Frontend/src/lib/socket.ts: shared Socket.IO singleton

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-04-13 22:30:40 -04:00
parent e10126f772
commit 90302a76b7
13 changed files with 1079 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
/**
* Processor for "claim-status-check" jobs.
* Mirrors routes/insuranceStatus.ts /claim-status-check
*/
import fs from "fs/promises";
import fsSync from "fs";
import path from "path";
import { storage } from "../../storage";
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
import {
startPythonJob,
pollPythonJob,
imageToPdfBuffer,
} from "./_shared";
export interface ClaimStatusProcessorInput {
enrichedPayload: any;
insuranceId: string; // memberId used to look up the patient
}
export interface ClaimStatusProcessorResult {
pdfUploadStatus?: string;
pdfFileId?: number | null;
}
export async function runClaimStatusProcessor(
input: ClaimStatusProcessorInput
): Promise<ClaimStatusProcessorResult> {
const { enrichedPayload, insuranceId } = input;
// 1) Start async Python job
const sid = await startPythonJob("/claim-status-check/async", {
data: enrichedPayload,
});
// 2) Poll for completion
const result = await pollPythonJob(sid);
const outputResult: ClaimStatusProcessorResult = {};
// 3) Look up patient
const patient = await storage.getPatientByInsuranceId(insuranceId);
if (patient && patient.id !== undefined) {
let pdfBuffer: Buffer | null = null;
let generatedPdfPath: string | null = null;
if (
result.ss_path &&
/\.(png|jpg|jpeg)$/i.test(result.ss_path) &&
fsSync.existsSync(result.ss_path)
) {
try {
pdfBuffer = await imageToPdfBuffer(result.ss_path);
const pdfFileName = `claimStatus_${insuranceId}_${Date.now()}.pdf`;
generatedPdfPath = path.join(path.dirname(result.ss_path), pdfFileName);
await fs.writeFile(generatedPdfPath, pdfBuffer);
} catch (e) {
console.error("[claimStatusProcessor] img→PDF conversion failed:", e);
outputResult.pdfUploadStatus = `Failed to convert screenshot to PDF: ${e}`;
}
} else {
outputResult.pdfUploadStatus =
"No valid screenshot provided by Selenium; nothing to upload.";
}
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 basename = path.basename(generatedPdfPath);
const created = await storage.createPdfFile(group.id, basename, pdfBuffer);
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.pdfFileId = createdPdfFileId;
}
} else {
outputResult.pdfUploadStatus =
"Patient not found; no PDF saved.";
}
// 4) Cleanup
try {
if (result.ss_path) await emptyFolderContainingFile(result.ss_path);
} catch (e) {
console.error("[claimStatusProcessor] cleanup failed:", e);
}
return outputResult;
}