/** * 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 // --------------------------------------------------------------------------- /** * Call a synchronous Python service endpoint and return its result. * The Python service runs the selenium job inline and returns the result directly. * Timeout is long (8 min) to accommodate slow selenium flows. */ export async function callPythonSync( endpoint: string, payload: any, timeoutMs = 8 * 60 * 1000 ): Promise { const resp = await axios.post( `${SELENIUM_BASE_URL}${endpoint}`, payload, { timeout: timeoutMs } ); const data = resp.data; if (data?.status === "error") { const msg = typeof data.message === "string" ? data.message : data.message?.msg ?? "Selenium returned error status"; throw new Error(msg); } return data; } // --------------------------------------------------------------------------- // 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 (dob && !patient.dateOfBirth) { const parsed = new Date(dob); if (!isNaN(parsed.getTime())) updates.dateOfBirth = parsed; } 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 { // Remove fields that may fail validation (invalid date or alphanumeric insuranceId) const safePayload = { ...createPayload }; delete safePayload.dateOfBirth; try { patientData = insertPatientSchema.parse(safePayload); } catch { // Last resort: skip schema validation and cast directly patientData = safePayload as InsertPatient; } } await storage.createPatient(patientData); return storage.getPatientByInsuranceId(insuranceId); }