feat: rename DDMA to ClaimMD across selenium service, remove MH/CMSP eligibility feature
Rename ddma_browser_manager/helpers/worker files to claimmd_* naming in SeleniumService. Remove the MassHealth (MH) and CMSP eligibility-check feature end to end: buttons and dead placeholder buttons on the insurance eligibility page, their handlers and auto-trigger wiring, the chatbot's MassHealth-only manual paste flow, backend routes/queue job types/processors, and the corresponding Python Selenium worker endpoints. Multi-provider eligibility checking (DDMA, Delta, Tufts SCO, United SCO, CCA, BCBS MA) and the Twilio SMS eligibility flow are untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
00fd213291
commit
a3d2960c98
@@ -2,7 +2,6 @@ import { useState, useRef, useEffect } from "react";
|
||||
import {
|
||||
Bot,
|
||||
X,
|
||||
ChevronRight,
|
||||
Stethoscope,
|
||||
Calendar,
|
||||
FileText,
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
Paperclip,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useLocation } from "wouter";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
@@ -21,8 +19,6 @@ import { setChatbotPendingFiles } from "@/lib/chatbotFileStore";
|
||||
|
||||
type Step =
|
||||
| "menu"
|
||||
| "eligibility-input"
|
||||
| "eligibility-confirm"
|
||||
| "ai-loading"
|
||||
| "patient-found"
|
||||
| "eligibility-id-ready"
|
||||
@@ -53,12 +49,6 @@ interface PatientResult {
|
||||
dateOfBirth: string | null;
|
||||
}
|
||||
|
||||
interface EligibilityData {
|
||||
memberId: string;
|
||||
dob: string;
|
||||
dobISO: string;
|
||||
}
|
||||
|
||||
interface CheckAndClaimData {
|
||||
patient: PatientResult | null;
|
||||
memberId: string;
|
||||
@@ -75,40 +65,6 @@ function makeMsg(role: "bot" | "user", text: string, isLoading = false): Message
|
||||
return { id: ++msgCounter, role, text, isLoading };
|
||||
}
|
||||
|
||||
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 parseEligibilityInput(
|
||||
raw: string
|
||||
): { memberId: string; display: string; iso: string } | null {
|
||||
const dateMatch = raw.match(/\b(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})\b/);
|
||||
if (!dateMatch) return null;
|
||||
const m = dateMatch[1];
|
||||
const d = dateMatch[2];
|
||||
const y = dateMatch[3];
|
||||
if (!m || !d || !y) return null;
|
||||
const month = parseInt(m, 10);
|
||||
const day = parseInt(d, 10);
|
||||
const year = parseInt(y, 10);
|
||||
if (month < 1 || month > 12 || day < 1 || day > 31 || year < 1900) return null;
|
||||
|
||||
const withoutDate = raw.replace(dateMatch[0], "");
|
||||
const memberId = (withoutDate.match(/[a-zA-Z0-9]/g) ?? []).join("");
|
||||
if (!memberId) return null;
|
||||
|
||||
return {
|
||||
memberId,
|
||||
display: `${m.padStart(2, "0")}/${d.padStart(2, "0")}/${y}`,
|
||||
iso: `${y}-${m.padStart(2, "0")}-${d.padStart(2, "0")}`,
|
||||
};
|
||||
}
|
||||
|
||||
const CHAT_STORAGE_KEY = "chatbot_messages";
|
||||
const CHATBOT_JOB_TS_KEY = "chatbot_job_started_at";
|
||||
|
||||
@@ -158,9 +114,6 @@ export function ChatbotButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [step, setStep] = useState<Step>("menu");
|
||||
const [messages, setMessages] = useState<Message[]>(loadSavedMessages);
|
||||
const [pasteInput, setPasteInput] = useState("");
|
||||
const [parseError, setParseError] = useState("");
|
||||
const [eligibilityData, setEligibilityData] = useState<EligibilityData | null>(null);
|
||||
const [freeTextInput, setFreeTextInput] = useState("");
|
||||
const [patientResult, setPatientResult] = useState<PatientResult | null>(null);
|
||||
const [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null; appointmentDate?: string | null } | null>(null);
|
||||
@@ -206,7 +159,6 @@ export function ChatbotButton() {
|
||||
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
|
||||
const [, setLocation] = useLocation();
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const pasteRef = useRef<HTMLTextAreaElement>(null);
|
||||
const freeTextRef = useRef<HTMLTextAreaElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -244,9 +196,6 @@ export function ChatbotButton() {
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (step === "eligibility-input") {
|
||||
setTimeout(() => pasteRef.current?.focus(), 50);
|
||||
}
|
||||
if (step === "menu") {
|
||||
setTimeout(() => freeTextRef.current?.focus(), 50);
|
||||
}
|
||||
@@ -266,9 +215,6 @@ export function ChatbotButton() {
|
||||
// Resets step/data only — keeps message history
|
||||
const resetStep = () => {
|
||||
setStep("menu");
|
||||
setPasteInput("");
|
||||
setParseError("");
|
||||
setEligibilityData(null);
|
||||
setFreeTextInput("");
|
||||
setPatientResult(null);
|
||||
setEligibilityIdData(null);
|
||||
@@ -300,7 +246,7 @@ export function ChatbotButton() {
|
||||
resetStep();
|
||||
};
|
||||
|
||||
const handleOptionSelect = (option: "eligibility" | "schedule" | "claims") => {
|
||||
const handleOptionSelect = (option: "schedule" | "claims") => {
|
||||
if (option === "schedule") {
|
||||
addMsg("user", "Schedule an appointment");
|
||||
addMsg("bot", "Opening the appointments page...");
|
||||
@@ -309,67 +255,9 @@ export function ChatbotButton() {
|
||||
addMsg("user", "View claims");
|
||||
addMsg("bot", "Opening the claims page...");
|
||||
setTimeout(() => { setLocation("/claims"); setOpen(false); resetStep(); }, 600);
|
||||
} else if (option === "eligibility") {
|
||||
addMsg("user", "Check Eligibility");
|
||||
addMsg("bot", "Please enter the patient's Member ID and Date of Birth:");
|
||||
setStep("eligibility-input");
|
||||
}
|
||||
};
|
||||
|
||||
const handleEligibilitySubmit = () => {
|
||||
const parsed = parseEligibilityInput(pasteInput);
|
||||
if (!parsed) {
|
||||
setParseError("Couldn't find both a Member ID and a date. Try: 123456789 01/15/1985");
|
||||
return;
|
||||
}
|
||||
setParseError("");
|
||||
const data: EligibilityData = { memberId: parsed.memberId, dob: parsed.display, dobISO: parsed.iso };
|
||||
setEligibilityData(data);
|
||||
addMsg("user", pasteInput.trim());
|
||||
addMsg("bot", `Ready to check MassHealth eligibility for:\n• Member ID: ${parsed.memberId}\n• Date of Birth: ${parsed.display}\n\nShall I proceed?`);
|
||||
setStep("eligibility-confirm");
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!eligibilityData) return;
|
||||
addMsg("user", "Yes, check now");
|
||||
addMsg("bot", "Opening the eligibility check page with this patient...");
|
||||
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({
|
||||
memberId: eligibilityData.memberId,
|
||||
dob: eligibilityData.dobISO,
|
||||
autoCheck: getAutoCheck(eligibilityData.dobISO),
|
||||
}));
|
||||
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
|
||||
setTimeout(() => { setLocation("/insurance-status"); setOpen(false); resetStep(); }, 600);
|
||||
};
|
||||
|
||||
const handleEligibilityFromPatient = () => {
|
||||
if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return;
|
||||
addMsg("user", "Check eligibility now");
|
||||
addMsg("bot", "Opening the eligibility check page...");
|
||||
prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth));
|
||||
};
|
||||
|
||||
const handleEligibilityAndAppointmentFromPatient = async () => {
|
||||
if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return;
|
||||
addMsg("user", "Check eligibility & add to schedule (today)");
|
||||
addMsg("bot", "Creating appointment for today...", true);
|
||||
try {
|
||||
const res = await apiRequest("POST", "/api/ai/create-appointment-today", {
|
||||
patientId: patientResult.id,
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
replaceLastMsg(data.message ?? "Could not create appointment.");
|
||||
return;
|
||||
}
|
||||
replaceLastMsg(`Appointment added at ${data.startTime} (${data.column ?? "Column A"}) — opening eligibility check page...`);
|
||||
} catch {
|
||||
replaceLastMsg("Could not create appointment — opening eligibility check page...");
|
||||
}
|
||||
prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth));
|
||||
};
|
||||
|
||||
const prefillAndNavigate = (memberId: string, dobISO: string, autoCheck: string) => {
|
||||
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({ memberId, dob: dobISO, autoCheck }));
|
||||
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
|
||||
@@ -728,14 +616,7 @@ export function ChatbotButton() {
|
||||
|
||||
{/* Quick-action buttons (menu step) */}
|
||||
{step === "menu" && (
|
||||
<div className="grid grid-cols-2 gap-2 pt-1">
|
||||
<button
|
||||
onClick={() => handleOptionSelect("eligibility")}
|
||||
className="flex items-center gap-2 p-3 rounded-xl border border-primary/30 hover:bg-primary/5 text-sm text-left transition-colors"
|
||||
>
|
||||
<Stethoscope className="h-4 w-4 text-primary shrink-0" />
|
||||
<span>Check Eligibility</span>
|
||||
</button>
|
||||
<div className="grid grid-cols-1 gap-2 pt-1">
|
||||
<button
|
||||
onClick={() => handleOptionSelect("schedule")}
|
||||
className="flex items-center gap-2 p-3 rounded-xl border border-blue-300 hover:bg-blue-50 text-sm text-left transition-colors"
|
||||
@@ -745,7 +626,7 @@ export function ChatbotButton() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleOptionSelect("claims")}
|
||||
className="flex items-center gap-2 p-3 rounded-xl border border-orange-300 hover:bg-orange-50 text-sm text-left transition-colors col-span-2"
|
||||
className="flex items-center gap-2 p-3 rounded-xl border border-orange-300 hover:bg-orange-50 text-sm text-left transition-colors"
|
||||
>
|
||||
<FileText className="h-4 w-4 text-orange-500 shrink-0" />
|
||||
<span>Claims</span>
|
||||
@@ -753,58 +634,6 @@ export function ChatbotButton() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Eligibility manual input */}
|
||||
{step === "eligibility-input" && (
|
||||
<div className="space-y-2 bg-gray-50 rounded-xl p-3 border">
|
||||
<Label className="text-xs text-gray-600">Paste Member ID and Date of Birth</Label>
|
||||
<textarea
|
||||
ref={pasteRef}
|
||||
rows={3}
|
||||
placeholder={"e.g. 123456789 01/15/1985\nor 01/15/1985 123456789"}
|
||||
value={pasteInput}
|
||||
onChange={(e) => { setPasteInput(e.target.value); setParseError(""); }}
|
||||
className={cn(
|
||||
"w-full rounded-md border bg-white px-3 py-2 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-ring",
|
||||
parseError && "border-red-400"
|
||||
)}
|
||||
/>
|
||||
{parseError && <p className="text-xs text-red-500">{parseError}</p>}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex-1 h-8 text-xs"
|
||||
onClick={handleEligibilitySubmit}
|
||||
disabled={!pasteInput.trim()}
|
||||
>
|
||||
Continue <ChevronRight className="h-3 w-3 ml-1" />
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" className="h-8 text-xs" onClick={resetStep}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Eligibility confirm */}
|
||||
{step === "eligibility-confirm" && (
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" className="flex-1 h-8 text-xs" onClick={handleConfirm}>
|
||||
Yes, Check Now
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-8 text-xs"
|
||||
onClick={() => {
|
||||
setStep("eligibility-input");
|
||||
setMessages((prev) => prev.slice(0, -2));
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* AI-found patient card */}
|
||||
{step === "patient-found" && patientResult && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-xl p-3 space-y-2">
|
||||
@@ -821,26 +650,6 @@ export function ChatbotButton() {
|
||||
<p className="text-xs text-gray-500">DOB: {patientResult.dateOfBirth}</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-2 pt-1">
|
||||
{patientResult.insuranceId && patientResult.dateOfBirth && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-full h-8 text-xs bg-primary hover:bg-primary/90"
|
||||
onClick={handleEligibilityFromPatient}
|
||||
>
|
||||
<Stethoscope className="h-3 w-3 mr-1" />
|
||||
Check Eligibility
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-full h-8 text-xs bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
onClick={handleEligibilityAndAppointmentFromPatient}
|
||||
>
|
||||
<Calendar className="h-3 w-3 mr-1" />
|
||||
Eligibility & Appointment Today
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button size="sm" variant="ghost" className="w-full h-8 text-xs" onClick={resetStep}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user