Files
DentalManagement09/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts
T
GiteadandClaude Sonnet 5 d35efe7366 fix: reliability fixes for UHC SCO and CMSP eligibility checks
- unitedSCOEligibilityProcessor: overwrite patient's stored insuranceId when
  the eligibility page reports a different (updated) Member ID than the one
  searched with.
- CMSP worker: add AngularJS late-binding retry to results-table extraction
  (ported from the MH worker's a0417653 fix), since every patient now routes
  through this worker for the accumulator check.
- cmspEligibilityHistoryRemainingProcessor: stop forcing patient status to
  UNKNOWN when extraction fails; a failed run landing after a successful one
  was clobbering a correct ACTIVE/INACTIVE status.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 13:11:58 -04:00

194 lines
7.4 KiB
TypeScript

/**
* Processor for "cmsp-eligibility-history-remaining-check" jobs.
* Saves eligibility PDF, service history PDF, and accumulator PDF
* to the patient's ELIGIBILITY_STATUS document group.
*/
import fs from "fs/promises";
import path from "path";
import { storage } from "../../storage";
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService";
import {
callPythonSync,
splitName,
createOrUpdatePatientByInsuranceId,
} from "./_shared";
export interface CmspEligibilityHistoryRemainingProcessorInput {
enrichedPayload: any;
userId: number;
insuranceId: string;
formFirstName?: string;
formLastName?: string;
formDob?: string;
}
export interface CmspEligibilityHistoryRemainingProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | string | null;
pdfFilename?: string | null;
memberDetailsPdfFileId?: number | string | null;
memberDetailsPdfFilename?: string | null;
historyPdfFileId?: number | string | null;
historyPdfFilename?: string | null;
accumulatorPdfFileId?: number | string | null;
accumulatorPdfFilename?: string | null;
remainingBenefit?: string | null;
thirdPartyInsurance?: string | null;
}
export async function runCmspEligibilityHistoryRemainingProcessor(
input: CmspEligibilityHistoryRemainingProcessorInput
): Promise<CmspEligibilityHistoryRemainingProcessorResult> {
const { enrichedPayload, userId, insuranceId, formFirstName, formLastName, formDob } = input;
const seleniumResult = await callPythonSync("/cmsp-eligibility-history-remaining-check", {
data: enrichedPayload,
});
const outputResult: CmspEligibilityHistoryRemainingProcessorResult = {};
// Extract patient name
const seleniumFirst = seleniumResult.firstName?.trim() || null;
const seleniumLast = seleniumResult.lastName?.trim() || null;
const seleniumName = seleniumResult.name?.trim() || null;
const seleniumInsurance = seleniumResult.insurance?.trim() || null;
const extracted: { firstName?: string | null; lastName?: string | null } = {};
if (seleniumFirst || seleniumLast) {
extracted.firstName = seleniumFirst;
extracted.lastName = seleniumLast;
} else if (seleniumName) {
const parts = splitName(seleniumName);
extracted.firstName = parts.firstName || null;
extracted.lastName = parts.lastName || null;
}
if (!extracted.firstName && !extracted.lastName && seleniumResult?.pdf_path?.endsWith(".pdf")) {
try {
const pdfBuffer = await fs.readFile(seleniumResult.pdf_path);
const extraction = await forwardToPatientDataExtractorService({
buffer: pdfBuffer,
originalname: path.basename(seleniumResult.pdf_path),
mimetype: "application/pdf",
} as any);
if (extraction.name) {
const parts = splitName(extraction.name);
extracted.firstName = parts.firstName || null;
extracted.lastName = parts.lastName || null;
}
} catch (e) {
console.error("[cmspProcessor] PDF name extraction failed:", e);
}
}
const preferFirst = extracted.firstName || formFirstName || null;
const preferLast = extracted.lastName || formLastName || null;
let patient;
try {
patient = await createOrUpdatePatientByInsuranceId({
insuranceId,
firstName: preferFirst,
lastName: preferLast,
dob: formDob,
userId,
});
} catch (e: any) {
throw new Error(`Failed to create/update patient: ${e.message}`);
}
if (patient && patient.id !== undefined) {
// eligibility is null when extraction found no matching row (e.g. the
// AngularJS grid didn't finish rendering in time). Leave status untouched
// in that case rather than forcing UNKNOWN, so a failed run can't clobber
// a correct ACTIVE/INACTIVE status set by another (e.g. concurrent) run.
let newStatus: string | null = null;
if (seleniumResult.eligibility === "Y") newStatus = "ACTIVE";
else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE";
const updates: any = {};
if (newStatus) updates.status = newStatus;
if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance;
const remainingBenefit = seleniumResult.remaining_amount?.trim() || null;
if (remainingBenefit) updates.remainingBenefit = remainingBenefit;
// third_party_insurance is present (even as null) whenever the Third Party
// Liability table was found — null means the member has no 2nd insurance,
// so we still write it to clear any stale value from a prior check.
if (Object.prototype.hasOwnProperty.call(seleniumResult, "third_party_insurance")) {
updates.thirdPartyInsurance = seleniumResult.third_party_insurance?.trim() || null;
}
if (Object.keys(updates).length > 0) {
await storage.updatePatient(patient.id, updates);
}
outputResult.patientUpdateStatus = newStatus
? `Patient status updated to ${newStatus}`
: "Eligibility unknown — status not changed";
outputResult.remainingBenefit = remainingBenefit;
outputResult.thirdPartyInsurance = updates.thirdPartyInsurance ?? null;
// Helper: save a single PDF to the patient's Cloud Storage Eligibility folder
const patientName = `${patient!.firstName ?? ""} ${patient!.lastName ?? ""}`.trim() || `Patient ${patient!.id}`;
const saveToGroup = async (pdfPath: string): Promise<number | string | null> => {
try {
const buffer = await fs.readFile(pdfPath);
const cloudFile = await storage.savePdfToCloudStorage(
userId,
patient!.id,
patientName,
"Eligibility",
path.basename(pdfPath),
buffer
);
return `cloud:${cloudFile.id}`;
} catch (e: any) {
console.error("[cmspProcessor] saveToGroup failed:", e.message);
return null;
}
};
// Save eligibility PDF
if (seleniumResult.pdf_path?.endsWith(".pdf")) {
outputResult.pdfFileId = await saveToGroup(seleniumResult.pdf_path);
outputResult.pdfFilename = path.basename(seleniumResult.pdf_path);
outputResult.pdfUploadStatus = outputResult.pdfFileId ? "Eligibility PDF saved" : "Eligibility PDF upload failed";
} else {
outputResult.pdfUploadStatus = "No eligibility PDF returned by Selenium";
}
// Save member details PDF
if (seleniumResult.member_details_pdf_path?.endsWith(".pdf")) {
outputResult.memberDetailsPdfFileId = await saveToGroup(seleniumResult.member_details_pdf_path);
outputResult.memberDetailsPdfFilename = path.basename(seleniumResult.member_details_pdf_path);
}
// Save history PDF
if (seleniumResult.history_pdf_path?.endsWith(".pdf")) {
outputResult.historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path);
outputResult.historyPdfFilename = path.basename(seleniumResult.history_pdf_path);
}
// Save accumulator PDF
if (seleniumResult.accumulator_pdf_path?.endsWith(".pdf")) {
outputResult.accumulatorPdfFileId = await saveToGroup(seleniumResult.accumulator_pdf_path);
outputResult.accumulatorPdfFilename = path.basename(seleniumResult.accumulator_pdf_path);
}
} else {
outputResult.patientUpdateStatus = "Patient not found or missing ID; no update performed";
}
// Cleanup temp files
try {
if (seleniumResult.pdf_path) await emptyFolderContainingFile(seleniumResult.pdf_path);
} catch (e) {
console.error("[cmspProcessor] cleanup failed:", e);
}
return outputResult;
}