- Add 'Select Procedures' right-click option on appointment page (separate from Claims/PreAuth) - Select Procedures form saves CDT codes + NPI provider to AppointmentProcedure storage - Remove Save button from insurance claim form; Claims/PreAuth opens for insurance submission only - Claims/PreAuth auto-prefills from saved procedures including NPI provider - Batch-column: procedures npiProviderId takes priority over stale claim npiProviderId - Batch-column: auto-save PDF to patient Documents after successful submission (no socket needed) - Add npiProviderId column to AppointmentProcedure table (prisma db push) - Fix 'invalid db creation invocation': guard staffId, npiProviderId, procedureDate as Date object, totalBilled NaN guard - Add full error logging to batch-column catch block Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
3.6 KiB
TypeScript
100 lines
3.6 KiB
TypeScript
/**
|
|
* Processors for "claim-submit" and "claim-pre-auth" jobs.
|
|
* Mirrors routes/claims.ts /selenium-claim and /selenium-claim-pre-auth
|
|
*/
|
|
import path from "path";
|
|
import axios from "axios";
|
|
import { storage } from "../../storage";
|
|
import { callPythonSync } from "./_shared";
|
|
|
|
export interface ClaimSubmitProcessorInput {
|
|
enrichedPayload: any;
|
|
files: { originalname: string; bufferBase64: string; mimetype: string }[];
|
|
claimId?: number;
|
|
/** "claimsubmit" (default) or "claim-pre-auth" */
|
|
variant?: "claimsubmit" | "claim-pre-auth";
|
|
/** When set, the frontend socket listener will handle the PDF download — skip auto-save */
|
|
socketId?: string;
|
|
}
|
|
|
|
export interface ClaimSubmitProcessorResult {
|
|
status: string;
|
|
claimNumber?: string;
|
|
pdf_url?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
/** Fetch the PDF from the selenium service and save it to cloud storage. */
|
|
async function savePdfFromSelenium(
|
|
pdf_url: string,
|
|
patientId: number,
|
|
variant: "claimsubmit" | "claim-pre-auth"
|
|
) {
|
|
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 });
|
|
|
|
const groupTitleKey = variant === "claim-pre-auth" ? "INSURANCE_CLAIM_PREAUTH" : "INSURANCE_CLAIM";
|
|
const groupTitle = variant === "claim-pre-auth" ? "Claims Preauth" : "Claims";
|
|
|
|
let group = await storage.findPdfGroupByPatientTitleKey(patientId, groupTitleKey);
|
|
if (!group) {
|
|
group = await storage.createPdfGroup(patientId, groupTitle, groupTitleKey);
|
|
}
|
|
|
|
await storage.createPdfFile(group.id!, filename, resp.data);
|
|
console.log(`[claimSubmitProcessor] PDF saved for patient ${patientId}: ${filename}`);
|
|
} catch (err: any) {
|
|
// Non-fatal — claim was submitted; just log the PDF failure
|
|
console.error("[claimSubmitProcessor] failed to save PDF:", err?.message ?? err);
|
|
}
|
|
}
|
|
|
|
export async function runClaimSubmitProcessor(
|
|
input: ClaimSubmitProcessorInput
|
|
): Promise<ClaimSubmitProcessorResult> {
|
|
const { enrichedPayload, files, claimId } = input;
|
|
const variant = input.variant ?? "claimsubmit";
|
|
|
|
// Build the same payload shape the Python /claimsubmit endpoint expects
|
|
const pdfs = files
|
|
.filter((f) => f.mimetype === "application/pdf")
|
|
.map(({ originalname, bufferBase64 }) => ({ originalname, bufferBase64 }));
|
|
|
|
const images = files
|
|
.filter((f) => f.mimetype.startsWith("image/"))
|
|
.map(({ originalname, bufferBase64 }) => ({ originalname, bufferBase64 }));
|
|
|
|
const payload = { claim: enrichedPayload, pdfs, images };
|
|
|
|
const endpoint = variant === "claim-pre-auth" ? "/claim-pre-auth" : "/claimsubmit";
|
|
|
|
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
|
const result = await callPythonSync(endpoint, payload, 10 * 60 * 1000);
|
|
|
|
// 2) Persist claimNumber and update status to REVIEW
|
|
if (claimId) {
|
|
try {
|
|
const updates: Record<string, any> = { status: "REVIEW" };
|
|
if (result?.claimNumber) updates.claimNumber = result.claimNumber;
|
|
await storage.updateClaim(claimId, updates);
|
|
} catch (e) {
|
|
console.error("[claimSubmitProcessor] failed to update claim after submission:", e);
|
|
}
|
|
}
|
|
|
|
// 3) Auto-save PDF for batch jobs (no socketId = no frontend listener to call fetchpdf)
|
|
if (result?.pdf_url && enrichedPayload?.patientId && !input.socketId) {
|
|
await savePdfFromSelenium(
|
|
result.pdf_url,
|
|
Number(enrichedPayload.patientId),
|
|
variant
|
|
);
|
|
}
|
|
|
|
return { ...result, claimId };
|
|
}
|