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:
Summit Dental Care
2026-09-23 16:26:17 -04:00
co-authored by Claude Sonnet 5
parent 00fd213291
commit a3d2960c98
203 changed files with 2221 additions and 4409 deletions
+3 -194
View File
@@ -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 &amp; Appointment Today
</Button>
</>
)}
<Button size="sm" variant="ghost" className="w-full h-8 text-xs" onClick={resetStep}>
Cancel
</Button>