feat: route batch-column claims by insurance type, fix eligibility UX

- claims.ts: batch-column now routes each patient to the correct portal
  (MH/CCA/DDMA/TuftsSCO/UnitedSCO) based on patient.insuranceProvider
- appointments-page.tsx: eligibility badge falls back to patientStatus
  for all insurance types, not just MassHealth
- unitedDHClaimProcessor/ddmaClaimProcessor/tuftsSCOClaimProcessor:
  auto-save claim PDF when no socketId (batch-column path)
- unitedSCOEligibilityProcessor: unknown eligibility no longer stored as INACTIVE
- queues.ts: add tuftssco-claim-submit and uniteddh-claim-submit to SeleniumJobType
- selenium_UnitedSCO_eligibilityCheckWorker.py:
  - step1: add Select Insurance OK click with staleness wait; Provider &
    Location page just clicks Continue; skip first/last name input
  - step2: extract name from eligibility details tab (ALL CAPS → title case);
    skip "not available" guard; fix name regex to match only all-caps words
- .gitignore: ignore all chrome_profile_* directories

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-05-27 23:07:42 -04:00
parent 1b017177e9
commit 3e61bdec36
261 changed files with 331 additions and 119284 deletions

View File

@@ -9,6 +9,7 @@ import fs from "fs";
import axios from "axios";
import archiver from "archiver";
import { seleniumQueue } from "../queue/queues";
import { enqueueSeleniumJob } from "../queue/jobRunner";
import { Prisma } from "@repo/db/generated/prisma";
import { Decimal } from "decimal.js";
import {
@@ -415,6 +416,28 @@ router.post(
}
);
// Maps patient.insuranceProvider free-text → the siteKey stored in InsuranceCredential
function batchColumnDeriveSiteKey(provider: string): string {
const p = provider.toLowerCase().trim();
if (!p || p.includes("masshealth") || p === "mh" || p === "mass health") return "MH";
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
if (p.includes("ddma") || p.includes("delta dental ma")) return "DDMA";
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TUFTS_SCO";
if ((p.includes("united") && p.includes("sco")) || p.includes("dentalhub") || p === "united_sco") return "UNITED_SCO";
return "MH"; // default fallback
}
// Returns the canonical insuranceProvider name stored in the Claim record
function batchColumnCanonicalName(siteKey: string): string {
switch (siteKey) {
case "CCA": return "CCA";
case "DDMA": return "Delta Dental MA";
case "TUFTS_SCO": return "Tufts SCO";
case "UNITED_SCO": return "United/DentalHub";
default: return "MassHealth";
}
}
// POST /api/claims/batch-column
// Query params: date=YYYY-MM-DD (required), staffIds=1,2 (required)
// For each appointment in the selected staff columns:
@@ -555,10 +578,15 @@ router.post(
continue;
}
// MassHealth credentials
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(req.user.id, "MH");
// Always derive siteKey from the patient record — claim.insuranceProvider
// may be stale (e.g. previously set to "MassHealth" by the old hardcoded path)
const rawInsuranceProvider = (patient.insuranceProvider ?? "").trim();
const siteKey = batchColumnDeriveSiteKey(rawInsuranceProvider);
const canonicalInsuranceProvider = batchColumnCanonicalName(siteKey);
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(req.user.id, siteKey);
if (!credentials) {
resultItem.error = "No MassHealth credentials found — check Settings";
resultItem.error = `No ${canonicalInsuranceProvider} credentials found — check Settings`;
results.push(resultItem);
continue;
}
@@ -573,7 +601,7 @@ router.post(
// Priority: Select Procedures choice > existing claim > first provider
const claimNpiProviderId = procNpiProviderId ?? activeClaim?.npiProviderId ?? npiProvider?.id ?? null;
console.log(`[batch-column] apt=${apt.id} procNpiId=${procNpiProviderId} claimNpiId=${activeClaim?.npiProviderId} resolved=${claimNpiProviderId}`);
console.log(`[batch-column] apt=${apt.id} siteKey=${siteKey} procNpiId=${procNpiProviderId} claimNpiId=${activeClaim?.npiProviderId} resolved=${claimNpiProviderId}`);
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim();
@@ -597,7 +625,7 @@ router.post(
? Number(claimNpiProviderId)
: null;
console.log(`[batch-column] creating claim: patientId=${patient.id} aptId=${apt.id} staffId=${safeStaffId} npiProviderId=${safeNpiId} serviceDate=${serviceDate} dobStr=${dobStr} lines=${serviceLines.length}`);
console.log(`[batch-column] creating claim: patientId=${patient.id} aptId=${apt.id} staffId=${safeStaffId} npiProviderId=${safeNpiId} serviceDate=${serviceDate} dobStr=${dobStr} lines=${serviceLines.length} insurance=${canonicalInsuranceProvider}`);
const newClaim = await storage.createClaim({
patientId: Number(patient.id),
@@ -608,7 +636,7 @@ router.post(
memberId,
dateOfBirth: new Date(dobStr),
serviceDate: new Date(serviceDate),
insuranceProvider: "MassHealth",
insuranceProvider: canonicalInsuranceProvider,
remarks: "",
missingTeethStatus: "No_missing",
missingTeeth: {},
@@ -643,33 +671,6 @@ router.post(
if (saved) resolvedNpiProvider = saved;
}
// Build enriched payload for selenium
const enrichedPayload: any = {
patientId: Number(patient.id),
appointmentId: Number(apt.id),
userId: req.user.id,
staffId: Number(apt.staffId),
patientName,
memberId,
dateOfBirth: dobStr,
serviceDate,
insuranceProvider: "MassHealth",
insuranceSiteKey: "MH",
missingTeethStatus: activeClaim?.missingTeethStatus ?? "No_missing",
missingTeeth: activeClaim?.missingTeeth ?? {},
remarks: activeClaim?.remarks ?? "",
serviceLines,
claimId,
massdhpUsername: credentials.username,
massdhpPassword: credentials.password,
};
if (resolvedNpiProvider) {
enrichedPayload.npiProvider = {
npiNumber: resolvedNpiProvider.npiNumber,
providerName: resolvedNpiProvider.providerName,
};
}
// Collect attachments: appointment-level files + claim-level files
const apptFiles = await storage.getAppointmentFiles(Number(apt.id));
const claimFiles = (activeClaim as any)?.claimFiles ?? [];
@@ -689,17 +690,120 @@ router.post(
return [{ originalname: f.filename, bufferBase64, mimetype: f.mimeType ?? "application/octet-stream" }];
});
// Enqueue selenium claim-submit job
const job = await seleniumQueue.add("claim-submit", {
jobType: "claim-submit",
// Base claim data shared across all insurance pathways
const baseClaimPayload: any = {
patientId: Number(patient.id),
appointmentId: Number(apt.id),
userId: req.user.id,
enrichedPayload,
files: filesForQueue,
staffId: Number(apt.staffId),
patientName,
memberId,
dateOfBirth: dobStr,
serviceDate,
missingTeethStatus: activeClaim?.missingTeethStatus ?? "No_missing",
missingTeeth: activeClaim?.missingTeeth ?? {},
remarks: activeClaim?.remarks ?? "",
serviceLines,
claimId,
});
...(resolvedNpiProvider ? {
npiProvider: {
npiNumber: resolvedNpiProvider.npiNumber,
providerName: resolvedNpiProvider.providerName,
},
} : {}),
};
// Enqueue to the correct insurance pathway
let jobId: string;
if (siteKey === "MH") {
const enrichedPayload = {
...baseClaimPayload,
insuranceProvider: "MassHealth",
insuranceSiteKey: "MH",
massdhpUsername: credentials.username,
massdhpPassword: credentials.password,
};
const job = await seleniumQueue.add("claim-submit", {
jobType: "claim-submit",
userId: req.user.id,
enrichedPayload,
files: filesForQueue,
claimId,
});
jobId = String(job.id);
} else if (siteKey === "CCA") {
const enrichedPayload = {
claim: {
...baseClaimPayload,
insuranceProvider: "CCA",
insuranceSiteKey: "CCA",
cca_username: credentials.username,
cca_password: credentials.password,
},
files: filesForQueue,
};
jobId = enqueueSeleniumJob({
jobType: "cca-claim-submit",
userId: req.user.id,
enrichedPayload,
claimId,
});
} else if (siteKey === "DDMA") {
const enrichedPayload = {
claim: {
...baseClaimPayload,
insuranceProvider: "Delta Dental MA",
insuranceSiteKey: "DDMA",
massddmaUsername: credentials.username,
massddmaPassword: credentials.password,
},
};
jobId = enqueueSeleniumJob({
jobType: "ddma-claim-submit",
userId: req.user.id,
enrichedPayload,
claimId,
});
} else if (siteKey === "TUFTS_SCO") {
const enrichedPayload = {
claim: {
...baseClaimPayload,
insuranceProvider: "Tufts SCO",
insuranceSiteKey: "TuftsSCO",
dentaquestUsername: credentials.username,
dentaquestPassword: credentials.password,
},
};
jobId = enqueueSeleniumJob({
jobType: "tuftssco-claim-submit",
userId: req.user.id,
enrichedPayload,
claimId,
});
} else if (siteKey === "UNITED_SCO") {
const enrichedPayload = {
claim: {
...baseClaimPayload,
insuranceProvider: "United/DentalHub",
insuranceSiteKey: "UNITED_SCO",
uniteddhUsername: credentials.username,
uniteddhPassword: credentials.password,
},
};
jobId = enqueueSeleniumJob({
jobType: "uniteddh-claim-submit",
userId: req.user.id,
enrichedPayload,
claimId,
});
} else {
resultItem.error = `Unsupported insurance type: ${siteKey}`;
results.push(resultItem);
continue;
}
resultItem.processed = true;
resultItem.jobId = String(job.id);
resultItem.jobId = jobId;
} catch (aptErr: any) {
console.error("[batch-column] apt error:", aptErr);