Files
DentalManagement09/apps/Backend/src/queue/jobRunner.ts
T
GiteadandClaude Opus 5.5 db7d74a13b feat: Altus Dental eligibility check and ADA paper claim/preauth
Eligibility (no OTP, fresh browser per run like MH): logs in, opens Benefits &
Claims Information, searches by Subscriber ID (or Subscriber Last Name via
"Click Here"), First Name and DOB, picks the matching row from the patient list,
sets ACTIVE/INACTIVE from the Coverage range, reads Group Number into the
patient's Group Number, opens View Details for the last name and plan name,
and saves the details page PDF to the patient's Eligibility folder. A patient
switching plans is matched by name + DOB so Altus overwrites the old insurance
instead of creating a duplicate.

Claims: Altus uses the same 2024 ADA form as BCBS with the Altus payer address
(P.O. Box 1557, Providence, RI 02901-1557). Adds Altus Claim/PreAuth (ADA Form)
buttons, Claim All and AI queue routing, Print ADA Form in the claim viewer, and
an Altus fee schedule (copied from BCBS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:47:01 -04:00

420 lines
14 KiB
TypeScript

/**
* jobRunner — the single source of truth for enqueueing async jobs.
*
* Uses InProcessQueue (no Redis) instead of BullMQ.
* When a job finishes the worker emits a `job:update` Socket.IO event
* to the originating client so the frontend can react in real time.
*/
import { InProcessQueue } from "./inProcessQueue";
import { io } from "../socket";
import { runEligibilityProcessor } from "./processors/eligibilityProcessor";
import { runClaimStatusProcessor } from "./processors/claimStatusProcessor";
import { runClaimSubmitProcessor } from "./processors/claimSubmitProcessor";
import { runOcrProcessor } from "./processors/ocrProcessor";
import { runDdmaEligibilityProcessor } from "./processors/ddmaEligibilityProcessor";
import { runDeltaIndivEligibilityProcessor } from "./processors/deltaIndivEligibilityProcessor";
import { runDeltaInsEligibilityProcessor } from "./processors/deltaInsEligibilityProcessor";
import { runUnitedSCOEligibilityProcessor } from "./processors/unitedSCOEligibilityProcessor";
import { runUnitedUcardEligibilityProcessor } from "./processors/unitedUcardEligibilityProcessor";
import { runDentaQuestEligibilityProcessor } from "./processors/dentaQuestEligibilityProcessor";
import { runCCAEligibilityProcessor } from "./processors/ccaEligibilityProcessor";
import { runCCAClaimProcessor } from "./processors/ccaClaimProcessor";
import { runCCAPreAuthProcessor } from "./processors/ccaPreAuthProcessor";
import { runDDMAClaimProcessor } from "./processors/ddmaClaimProcessor";
import { runDeltainsClaimProcessor } from "./processors/deltainsClaimProcessor";
import { runUnitedDHClaimProcessor } from "./processors/unitedDHClaimProcessor";
import { runUnitedDHPreAuthProcessor } from "./processors/unitedDHPreAuthProcessor";
import { runTuftsSCOPreAuthProcessor } from "./processors/tuftsSCOPreAuthProcessor";
import { runTuftsSCOClaimProcessor } from "./processors/tuftsSCOClaimProcessor";
import { runUnitedUcardClaimProcessor } from "./processors/unitedUcardClaimProcessor";
import { runEligibilityHistoryProcessor } from "./processors/eligibilityHistoryProcessor";
import { runCmspEligibilityHistoryRemainingProcessor } from "./processors/cmspEligibilityHistoryRemainingProcessor";
import { runBcbsMaEligibilityProcessor } from "./processors/bcbsMaEligibilityProcessor";
import { runMetLifeEligibilityProcessor } from "./processors/metlifeEligibilityProcessor";
import { runAltusEligibilityProcessor } from "./processors/altusEligibilityProcessor";
import type { SeleniumJobData, OcrJobData } from "./queues";
// ── Queue instances ──────────────────────────────────────────────────────────
// Selenium: 1 browser at a time (mirrors Python semaphore)
const seleniumQ = new InProcessQueue<SeleniumJobData>(1);
// OCR: allow 2 concurrent (mirrors Python MAX_CONCURRENCY=2)
const ocrQ = new InProcessQueue<OcrJobData>(2);
// ── WebSocket helper ─────────────────────────────────────────────────────────
function emitJobUpdate(
socketId: string | undefined,
jobId: string,
jobType: string,
status: "active" | "completed" | "failed",
extra: Record<string, any> = {}
) {
const payload = { jobId, jobType, status, ...extra };
if (socketId && io) {
io.to(socketId).emit("job:update", payload);
} else if (io) {
io.emit("job:update", payload);
}
}
// ── Selenium enqueue ─────────────────────────────────────────────────────────
export function enqueueSeleniumJob(data: SeleniumJobData): string {
const { jobType, socketId } = data;
const jobId = seleniumQ.add(data, async (job) => {
emitJobUpdate(socketId, job.id, jobType, "active", {
message: "Selenium browser starting…",
});
if (jobType === "eligibility-check") {
return runEligibilityProcessor({
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
});
}
if (jobType === "claim-status-check") {
return runClaimStatusProcessor({
enrichedPayload: data.enrichedPayload,
insuranceId: data.insuranceId!,
userId: data.userId,
});
}
if (jobType === "claim-submit" || jobType === "claim-pre-auth") {
return runClaimSubmitProcessor({
enrichedPayload: data.enrichedPayload,
files: data.files ?? [],
claimId: data.claimId,
variant: jobType === "claim-pre-auth" ? "claim-pre-auth" : "claimsubmit",
socketId: data.socketId,
userId: data.userId,
});
}
if (jobType === "ddma-eligibility-check") {
return runDdmaEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "metlife-eligibility-check") {
return runMetLifeEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "deltains-eligibility-check") {
return runDeltaInsEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "deltaindiv-eligibility-check") {
return runDeltaIndivEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "unitedsco-eligibility-check") {
return runUnitedSCOEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "uniteducard-eligibility-check") {
return runUnitedUcardEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "tuftssco-eligibility-check") {
return runDentaQuestEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "cca-claim-submit") {
return runCCAClaimProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "cca-preauth-submit") {
return runCCAPreAuthProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "ddma-claim-submit") {
return runDDMAClaimProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "deltains-claim-submit") {
return runDeltainsClaimProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "uniteddh-claim-submit") {
return runUnitedDHClaimProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "uniteddh-preauth-submit") {
return runUnitedDHPreAuthProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "tuftssco-preauth-submit") {
return runTuftsSCOPreAuthProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "tuftssco-claim-submit") {
return runTuftsSCOClaimProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "uniteducard-claim-submit") {
return runUnitedUcardClaimProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
claimId: data.claimId,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "cca-eligibility-check") {
return runCCAEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "cmsp-eligibility-history-remaining-check") {
return runCmspEligibilityHistoryRemainingProcessor({
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
});
}
if (jobType === "mh-eligibility-history-check") {
return runEligibilityHistoryProcessor({
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
});
}
if (jobType === "bcbs-ma-eligibility-check") {
return runBcbsMaEligibilityProcessor(
{
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
socketId: data.socketId,
},
job.id
);
}
if (jobType === "altus-eligibility-check") {
return runAltusEligibilityProcessor({
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
});
}
throw new Error(`Unknown selenium jobType: ${jobType}`);
});
// Attach completion/failure callbacks after the job is in the queue.
// We poll the job object once per tick until it settles.
(async () => {
while (true) {
await new Promise((r) => setTimeout(r, 500));
const job = seleniumQ.getJob(jobId);
if (!job) break;
if (job.status === "completed") {
emitJobUpdate(socketId, jobId, jobType, "completed", {
result: job.result,
});
console.log(`[seleniumQ] job ${jobId} (${jobType}) completed`);
break;
}
if (job.status === "failed") {
emitJobUpdate(socketId, jobId, jobType, "failed", {
error: job.error,
});
console.error(`[seleniumQ] job ${jobId} (${jobType}) failed:`, job.error);
break;
}
}
})();
return jobId;
}
// ── Job status query (HTTP polling fallback) ─────────────────────────────────
export function getSeleniumJobStatus(jobId: string) {
const job = seleniumQ.getJob(jobId);
if (!job) return null;
return { status: job.status, result: job.result ?? null, error: job.error ?? null };
}
// ── OCR enqueue ──────────────────────────────────────────────────────────────
export function enqueueOcrJob(data: OcrJobData): string {
const { socketId } = data;
const jobId = ocrQ.add(data, async () => {
emitJobUpdate(socketId, jobId, "ocr", "active", {
message: "OCR processing started…",
});
return runOcrProcessor({ files: data.files });
});
(async () => {
while (true) {
await new Promise((r) => setTimeout(r, 500));
const job = ocrQ.getJob(jobId);
if (!job) break;
if (job.status === "completed") {
emitJobUpdate(socketId, jobId, "ocr", "completed", {
result: { rows: job.result },
});
console.log(`[ocrQ] job ${jobId} completed`);
break;
}
if (job.status === "failed") {
emitJobUpdate(socketId, jobId, "ocr", "failed", { error: job.error });
console.error(`[ocrQ] job ${jobId} failed:`, job.error);
break;
}
}
})();
return jobId;
}