/** * Shared utilities used by all job processors. * Avoids duplicating helpers that currently live inside route files. */ import axios from "axios"; import PDFDocument from "pdfkit"; import fsSync from "fs"; import { storage } from "../../storage"; import { InsertPatient, insertPatientSchema, } from "../../../../../packages/db/types/patient-types"; const SELENIUM_BASE_URL = process.env.SELENIUM_AGENT_BASE_URL || "http://localhost:5002"; // --------------------------------------------------------------------------- // Python service helpers // --------------------------------------------------------------------------- /** Start an async job on the Python service and return the session ID. */ export async function startPythonJob( endpoint: string, payload: any ): Promise { const resp = await axios.post( `${SELENIUM_BASE_URL}${endpoint}`, payload, { timeout: 10_000 } ); const sid: string = resp.data?.session_id; if (!sid) throw new Error(`Python service did not return a session_id from ${endpoint}`); return sid; } /** Poll /job//status until completed/failed or timeout. */ export async function pollPythonJob( sid: string, timeoutMs = 5 * 60 * 1000, intervalMs = 2_000 ): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const resp = await axios.get( `${SELENIUM_BASE_URL}/job/${sid}/status`, { timeout: 5_000 } ); const s = resp.data; if (s.status === "completed") { if (s.result?.status === "error") { const msg = typeof s.result.message === "string" ? s.result.message : s.result.message?.msg ?? "Selenium returned error status"; throw new Error(msg); } return s.result; } if (s.status === "failed") { throw new Error(s.error || "Python job failed"); } await sleep(intervalMs); } throw new Error("Selenium job timed out after polling"); } // --------------------------------------------------------------------------- // General utilities // --------------------------------------------------------------------------- export function sleep(ms: number) { return new Promise((r) => setTimeout(r, ms)); } export function splitName(fullName?: string | null) { if (!fullName) return { firstName: "", lastName: "" }; const parts = fullName.trim().split(/\s+/).filter(Boolean); const firstName = parts.shift() ?? ""; const lastName = parts.join(" ") ?? ""; return { firstName, lastName }; } export async function imageToPdfBuffer(imagePath: string): Promise { return new Promise((resolve, reject) => { try { const doc = new PDFDocument({ autoFirstPage: false }); const chunks: Uint8Array[] = []; doc.on("data", (c: any) => chunks.push(c)); doc.on("end", () => resolve(Buffer.concat(chunks))); doc.on("error", reject); const A4_W = 595.28; const A4_H = 841.89; doc.addPage({ size: [A4_W, A4_H] }); doc.image(imagePath, 0, 0, { fit: [A4_W, A4_H], align: "center", valign: "center" }); doc.end(); } catch (e) { reject(e); } }); } // --------------------------------------------------------------------------- // Patient DB helpers // --------------------------------------------------------------------------- export async function createOrUpdatePatientByInsuranceId(options: { insuranceId: string; firstName?: string | null; lastName?: string | null; dob?: string | Date | null; userId: number; }) { const { insuranceId, firstName, lastName, dob, userId } = options; if (!insuranceId) throw new Error("Missing insuranceId"); const incomingFirst = (firstName || "").trim(); const incomingLast = (lastName || "").trim(); let patient = await storage.getPatientByInsuranceId(insuranceId); if (patient && patient.id) { const updates: any = {}; if (incomingFirst && String(patient.firstName ?? "").trim() !== incomingFirst) updates.firstName = incomingFirst; if (incomingLast && String(patient.lastName ?? "").trim() !== incomingLast) updates.lastName = incomingLast; if (Object.keys(updates).length > 0) { await storage.updatePatient(patient.id, updates); patient = await storage.getPatientByInsuranceId(insuranceId); } return patient; } const createPayload: any = { firstName: incomingFirst, lastName: incomingLast, dateOfBirth: dob, gender: "", phone: "", userId, insuranceId, }; let patientData: InsertPatient; try { patientData = insertPatientSchema.parse(createPayload); } catch { const safePayload = { ...createPayload }; delete safePayload.dateOfBirth; patientData = insertPatientSchema.parse(safePayload); } await storage.createPatient(patientData); return storage.getPatientByInsuranceId(insuranceId); }