feat: add Remaining/Third Party columns, toggle PDF viewer, retire CMSP as separate insurance type
- Add remainingBenefit and thirdPartyInsurance columns to Patient; extract both from the MassHealth accumulator/member-detail pages during the eligibility check and surface them in the eligibility table - Add an updated-date display and a toggle to suppress the post-check PDF viewer popup (default off) on the insurance eligibility page - Rename CMSP-prefixed PDF filenames and UI copy to MH, since CMSP and MH now share the same worker - Fold CMSP into MassHealth/MH across patient/claim insurance-type dropdowns and the chatbot's eligibility routing, since they're the same insurance - Remove auto-download of saved eligibility PDFs; downloading is now manual Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -791,13 +791,12 @@ export function ClaimForm({
|
||||
const deriveInsuranceSiteKey = (provider: string | null | undefined): string => {
|
||||
const p = (provider || "").toLowerCase().trim();
|
||||
if (!p) return "";
|
||||
if (p.includes("masshealth") || p === "mh" || p === "mass health") return "MH";
|
||||
if (p.includes("masshealth") || p === "mh" || p === "mass health" || p.includes("cmsp")) 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("delta ins") || p === "deltains") return "DeltaIns";
|
||||
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
||||
if ((p.includes("united") && p.includes("sco")) || p === "unitedsco") return "UnitedSCO";
|
||||
if (p.includes("cmsp")) return "CMSP";
|
||||
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
||||
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
||||
if (p.includes("aetna")) return "Aetna";
|
||||
@@ -2008,7 +2007,6 @@ export function ClaimForm({
|
||||
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
||||
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
||||
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
||||
<SelectItem value="CMSP">CMSP</SelectItem>
|
||||
<SelectItem value="BCBS">BCBS</SelectItem>
|
||||
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
||||
<SelectItem value="Aetna">Aetna</SelectItem>
|
||||
@@ -2596,7 +2594,6 @@ export function ClaimForm({
|
||||
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
||||
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
||||
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
||||
<SelectItem value="CMSP">CMSP</SelectItem>
|
||||
<SelectItem value="BCBS">BCBS</SelectItem>
|
||||
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
||||
<SelectItem value="Aetna">Aetna</SelectItem>
|
||||
|
||||
@@ -135,7 +135,6 @@ function mdyToISO(mdy: string): string {
|
||||
|
||||
const INSURANCE_TYPE_OPTIONS: { value: string; label: string }[] = [
|
||||
{ value: "mh", label: "MassHealth" },
|
||||
{ value: "cmsp", label: "CMSP" },
|
||||
{ value: "ddma", label: "DDMA" },
|
||||
{ value: "delta-ins", label: "Delta Dental" },
|
||||
{ value: "united-sco", label: "United SCO" },
|
||||
@@ -143,13 +142,10 @@ const INSURANCE_TYPE_OPTIONS: { value: string; label: string }[] = [
|
||||
{ value: "cca", label: "CCA" },
|
||||
];
|
||||
|
||||
function getAutoCheck(dobISO: string): "mh" | "cmsp" {
|
||||
const [y, m, d] = dobISO.split("-").map(Number);
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - (y ?? 0);
|
||||
const monthDiff = today.getMonth() + 1 - (m ?? 0);
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < (d ?? 0))) age--;
|
||||
return age >= 21 ? "mh" : "cmsp";
|
||||
function getAutoCheck(_dobISO: string): "mh" {
|
||||
// CMSP and MassHealth eligibility checks now run through the same MH worker,
|
||||
// so there's no longer an age-based split — everyone is "mh".
|
||||
return "mh";
|
||||
}
|
||||
|
||||
function parseEligibilityInput(
|
||||
|
||||
@@ -64,13 +64,12 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
||||
|
||||
const normalizeInsuranceProvider = (val: string | null | undefined): string => {
|
||||
const p = (val || "").toLowerCase().trim();
|
||||
if (p.includes("masshealth") || p === "mh" || p === "mass health") return "MassHealth";
|
||||
if (p.includes("masshealth") || p === "mh" || p === "mass health" || p.includes("cmsp")) return "MassHealth";
|
||||
if (p.includes("commonwealth care alliance") || p === "cca") return "CCA";
|
||||
if (p.includes("ddma")) return "DDMA";
|
||||
if (p.includes("delta dental") || p.includes("delta ins") || p === "deltains") return "DeltaIns";
|
||||
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
||||
if (p.includes("united sco") || p === "unitedsco") return "UnitedSCO";
|
||||
if (p.includes("cmsp")) return "CMSP";
|
||||
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
||||
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
||||
if (p.includes("aetna")) return "Aetna";
|
||||
@@ -451,7 +450,6 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
||||
<SelectItem value="DeltaIns">Delta Ins</SelectItem>
|
||||
<SelectItem value="TuftsSCO">Tufts SCO</SelectItem>
|
||||
<SelectItem value="UnitedSCO">United SCO</SelectItem>
|
||||
<SelectItem value="CMSP">CMSP</SelectItem>
|
||||
<SelectItem value="BCBS">BCBS</SelectItem>
|
||||
<SelectItem value="UnitedAAPR">United AAPR</SelectItem>
|
||||
<SelectItem value="Aetna">Aetna</SelectItem>
|
||||
|
||||
@@ -63,6 +63,7 @@ interface PatientTableProps {
|
||||
allowCheckbox?: boolean;
|
||||
allowNewClaim?: boolean;
|
||||
allowFinancial?: boolean;
|
||||
showEligibilityColumns?: boolean;
|
||||
onNewClaim?: (patientId: number) => void;
|
||||
onSelectPatient?: (patient: Patient | null) => void;
|
||||
onPageChange?: (page: number) => void;
|
||||
@@ -82,6 +83,7 @@ export function PatientTable({
|
||||
allowCheckbox,
|
||||
allowNewClaim,
|
||||
allowFinancial,
|
||||
showEligibilityColumns,
|
||||
onNewClaim,
|
||||
onSelectPatient,
|
||||
onPageChange,
|
||||
@@ -976,6 +978,12 @@ export function PatientTable({
|
||||
{/*<TableHead>Contact</TableHead>*/}
|
||||
<TableHead>Insurance</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
{showEligibilityColumns && (
|
||||
<>
|
||||
<TableHead>Remaining</TableHead>
|
||||
<TableHead>Third Party/2nd Insurance</TableHead>
|
||||
</>
|
||||
)}
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -983,7 +991,7 @@ export function PatientTable({
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
colSpan={showEligibilityColumns ? 8 : 6}
|
||||
className="text-center py-8 text-muted-foreground"
|
||||
>
|
||||
<LoadingScreen />
|
||||
@@ -992,7 +1000,7 @@ export function PatientTable({
|
||||
) : isError ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
colSpan={showEligibilityColumns ? 8 : 6}
|
||||
className="text-center py-8 text-red-500"
|
||||
>
|
||||
Error loading patients.
|
||||
@@ -1001,7 +1009,7 @@ export function PatientTable({
|
||||
) : patientsData?.patients.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
colSpan={showEligibilityColumns ? 8 : 6}
|
||||
className="text-center py-8 text-muted-foreground"
|
||||
>
|
||||
No patients found.
|
||||
@@ -1088,8 +1096,28 @@ export function PatientTable({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{patient.updatedAt && (
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
Updated: {formatDateToHumanReadable(patient.updatedAt)}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
{showEligibilityColumns && (
|
||||
<>
|
||||
<TableCell>
|
||||
<div className="text-sm text-gray-900">
|
||||
{patient.remainingBenefit || "—"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-sm text-gray-900">
|
||||
{patient.thirdPartyInsurance || "—"}
|
||||
</div>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end">
|
||||
{allowDelete && isAdmin && (
|
||||
|
||||
@@ -114,13 +114,12 @@ interface ScheduledAppointment {
|
||||
function deriveInsuranceSiteKey(provider: string | null | undefined): string {
|
||||
const p = (provider || "").toLowerCase().trim();
|
||||
if (!p) return "";
|
||||
if (p.includes("masshealth") || p === "mh" || p === "mass health") return "MH";
|
||||
if (p.includes("masshealth") || p === "mh" || p === "mass health" || p.includes("cmsp")) 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("delta ins") || p === "deltains") return "DeltaIns";
|
||||
if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO";
|
||||
if ((p.includes("united") && p.includes("sco")) || p === "unitedsco") return "UnitedSCO";
|
||||
if (p.includes("cmsp")) return "CMSP";
|
||||
if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS";
|
||||
if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR";
|
||||
if (p.includes("aetna")) return "Aetna";
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -122,6 +123,7 @@ export default function InsuranceStatusPage() {
|
||||
const [isCheckingEligibilityHistory, setIsCheckingEligibilityHistory] =
|
||||
useState(false);
|
||||
const [isCheckingCMSP, setIsCheckingCMSP] = useState(false);
|
||||
const [showPdfViewerOnComplete, setShowPdfViewerOnComplete] = useState(false);
|
||||
|
||||
// AI Call Insurance section
|
||||
const [aiCallOpen, setAiCallOpen] = useState(false);
|
||||
@@ -710,7 +712,7 @@ export default function InsuranceStatusPage() {
|
||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg }))
|
||||
);
|
||||
|
||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "CMSP PDFs saved to Documents." }));
|
||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "MH PDFs saved to Documents." }));
|
||||
toast({
|
||||
title: "Done",
|
||||
description: "Eligibility, history, and accumulator PDFs saved.",
|
||||
@@ -727,14 +729,14 @@ export default function InsuranceStatusPage() {
|
||||
// Open 4-panel modal
|
||||
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) {
|
||||
setCmspEligibilityPdfId(jobResult.pdfFileId ?? null);
|
||||
setCmspEligibilityFilename(jobResult.pdfFilename ?? `cmsp_eligibility_${memberId}.pdf`);
|
||||
setCmspEligibilityFilename(jobResult.pdfFilename ?? `mh_eligibility_${memberId}.pdf`);
|
||||
setCmspMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ?? null);
|
||||
setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `cmsp_member_details_${memberId}.pdf`);
|
||||
setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `mh_member_details_${memberId}.pdf`);
|
||||
setCmspHistoryPdfId(jobResult.historyPdfFileId ?? null);
|
||||
setCmspHistoryFilename(jobResult.historyPdfFilename ?? `cmsp_history_${memberId}.pdf`);
|
||||
setCmspHistoryFilename(jobResult.historyPdfFilename ?? `mh_history_${memberId}.pdf`);
|
||||
setCmspAccumulatorPdfId(jobResult.accumulatorPdfFileId ?? null);
|
||||
setCmspAccumulatorFilename(jobResult.accumulatorPdfFilename ?? `cmsp_accumulator_${memberId}.pdf`);
|
||||
setCmspPreviewOpen(true);
|
||||
setCmspAccumulatorFilename(jobResult.accumulatorPdfFilename ?? `mh_accumulator_${memberId}.pdf`);
|
||||
if (showPdfViewerOnComplete) setCmspPreviewOpen(true);
|
||||
}
|
||||
} catch (error: any) {
|
||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
||||
@@ -750,11 +752,9 @@ export default function InsuranceStatusPage() {
|
||||
const check = pendingAutoCheck.current;
|
||||
pendingAutoCheck.current = null;
|
||||
if (check === "mh" || check === "mh-history") {
|
||||
if (isUnder14(dateOfBirth)) {
|
||||
handleCMSPButton();
|
||||
} else {
|
||||
handleMHEligibilityHistoryButton();
|
||||
}
|
||||
// Temporarily disabled: selenium_MH_eligibilityHistoryCheckWorker.py is no
|
||||
// longer used — all MH eligibility checks route through the CMSP/MH worker.
|
||||
handleCMSPButton();
|
||||
} else if (check === "cmsp") {
|
||||
handleCMSPButton();
|
||||
} else {
|
||||
@@ -1021,82 +1021,7 @@ export default function InsuranceStatusPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col-2 gap-4">
|
||||
<Button
|
||||
onClick={() => handleMHEligibilityButton()}
|
||||
className="w-full"
|
||||
disabled={isCheckingEligibilityStatus}
|
||||
>
|
||||
{isCheckingEligibilityStatus ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH Eligibility
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => handleMHEligibilityAppointmentButton()}
|
||||
className="w-full"
|
||||
disabled={isCheckingEligibilityAppointment}
|
||||
>
|
||||
{isCheckingEligibilityAppointment ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH Eligibility&Appointment
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => handleMHEligibilityClaimsPreAuthButton()}
|
||||
className="w-full"
|
||||
disabled={isCheckingEligibilityClaimsPreAuth}
|
||||
>
|
||||
{isCheckingEligibilityClaimsPreAuth ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH Eligibility&Claims/PreAuth
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col-2 gap-4 mt-4">
|
||||
<Button
|
||||
id="eligibility-mh-history"
|
||||
className="w-full"
|
||||
disabled={isCheckingEligibilityHistory}
|
||||
onClick={() => handleMHEligibilityHistoryButton()}
|
||||
>
|
||||
{isCheckingEligibilityHistory ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH Eligibility & History
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
id="eligibility-cmsp"
|
||||
className="w-full"
|
||||
@@ -1111,12 +1036,23 @@ export default function InsuranceStatusPage() {
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
CMSP Eligibility & History & Remaining
|
||||
MH Eligibility & Remaining & History
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<Switch
|
||||
id="mh-show-pdf-viewer"
|
||||
checked={showPdfViewerOnComplete}
|
||||
onCheckedChange={setShowPdfViewerOnComplete}
|
||||
/>
|
||||
<Label htmlFor="mh-show-pdf-viewer" className="text-sm text-muted-foreground font-normal">
|
||||
Show PDF viewer after check completes
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{/* TEMP PROVIDER BUTTONS */}
|
||||
<div id="eligibility-other-providers" className="space-y-4 mt-6">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">
|
||||
@@ -1464,6 +1400,7 @@ export default function InsuranceStatusPage() {
|
||||
allowDelete={true}
|
||||
allowCheckbox={true}
|
||||
allowEdit={true}
|
||||
showEligibilityColumns={true}
|
||||
onSelectPatient={setSelectedPatient}
|
||||
/>
|
||||
</CardContent>
|
||||
@@ -1480,7 +1417,6 @@ export default function InsuranceStatusPage() {
|
||||
}}
|
||||
pdfId={previewPdfId ?? undefined}
|
||||
fallbackFilename={previewFallbackFilename ?? undefined}
|
||||
autoDownload
|
||||
/>
|
||||
|
||||
{/* 4-panel modal for CMSP — eligibility, member details, history, accumulator */}
|
||||
@@ -1497,13 +1433,12 @@ export default function InsuranceStatusPage() {
|
||||
setCmspAccumulatorPdfId(null);
|
||||
setCmspAccumulatorFilename(null);
|
||||
}}
|
||||
title="CMSP Eligibility, History & Remaining"
|
||||
title="MH Eligibility, Remaining & History"
|
||||
panels={[
|
||||
{
|
||||
pdfId: cmspEligibilityPdfId,
|
||||
fallbackFilename: cmspEligibilityFilename,
|
||||
label: "Eligibility",
|
||||
autoDownload: true,
|
||||
},
|
||||
{
|
||||
pdfId: cmspMemberDetailsPdfId,
|
||||
@@ -1541,7 +1476,6 @@ export default function InsuranceStatusPage() {
|
||||
pdfId: dualEligibilityPdfId,
|
||||
fallbackFilename: dualEligibilityFilename,
|
||||
label: "Eligibility",
|
||||
autoDownload: true,
|
||||
},
|
||||
{
|
||||
pdfId: dualMemberDetailsPdfId,
|
||||
|
||||
Reference in New Issue
Block a user