feat: add CCA claim submission with Selenium automation

- Add CCA claim submit Selenium worker (login, fill form, attach docs, submit, capture dashboard PDF)
- Add CCA fee schedule (procedureCodesMH.json renamed, procedureCodesCCA.json added with D6010)
- Add backend route /api/claims/cca-claim, processor, and Selenium client
- Wire CCA claim handler in claims-page with job tracking and PDF preview popup
- Add insurance type dropdown in claim form (same options as eligibility page)
- Auto-populate insurance type from patient.insuranceProvider in claim form and patient edit form
- Map fee schedule by insurance type in Map Price button and combo buttons
- Fix CCA login speed (remove fixed sleeps, use readyState check)
- Fix CCA claim DOB format bug (was sending MM-DD-YYYY, now sends YYYY-MM-DD)
- Fix npiProviderId not saved for CCA claims
- Change Add Service → CCA Claim button (blue), MH → MH Claim

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-22 13:34:03 -04:00
co-authored by Claude Sonnet 4.6
parent 58b2e4af93
commit 0e664e4813
21 changed files with 4768 additions and 77 deletions
@@ -0,0 +1,328 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useAuth } from "@/hooks/use-auth";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select";
import { useEffect, useMemo } from "react";
import { forwardRef, useImperativeHandle } from "react";
import { formatLocalDate } from "@/utils/dateUtils";
import { insertPatientSchema, patientStatusOptions, updatePatientSchema, } from "@repo/db/types";
import { z } from "zod";
import { DateInputField } from "@/components/ui/dateInputField";
export const PatientForm = forwardRef(({ patient, extractedInfo, onSubmit }, ref) => {
const { user } = useAuth();
const isEditing = !!patient;
const schema = useMemo(() => isEditing
? updatePatientSchema
: insertPatientSchema.extend({ userId: z.number().optional() }), [isEditing]);
const normalizeInsuranceProvider = (val) => {
const p = (val || "").toLowerCase().trim();
if (p.includes("masshealth") || p === "mh" || p === "mass health") 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";
if (p.includes("altus")) return "Altus";
if (p.includes("metlife")) return "MetlifeDental";
if (p.includes("cigna")) return "Cigna";
if (p.includes("delta wa") || p === "deltawa") return "DeltaWA";
if (p.includes("delta il") || p === "deltail") return "DeltaIL";
if (p.includes("other")) return "Others";
return val || "";
};
const computedDefaultValues = useMemo(() => {
if (isEditing && patient) {
const { id, userId, createdAt, ...sanitizedPatient } = patient;
return {
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: null,
insuranceProvider: normalizeInsuranceProvider(patient.insuranceProvider),
};
}
return {
firstName: extractedInfo?.firstName || "",
lastName: extractedInfo?.lastName || "",
dateOfBirth: extractedInfo?.dateOfBirth || null,
gender: "",
phone: "",
email: "",
address: "",
city: "",
zipCode: "",
insuranceProvider: "",
insuranceId: extractedInfo?.insuranceId || "",
groupNumber: "",
policyHolder: "",
allergies: "",
medicalConditions: "",
preferredLanguage: "English",
status: "UNKNOWN",
userId: user?.id,
};
}, [isEditing, patient, extractedInfo, user?.id]);
const form = useForm({
resolver: zodResolver(schema),
defaultValues: computedDefaultValues,
});
useImperativeHandle(ref, () => ({
submit() {
document.getElementById("patient-form")?.requestSubmit();
},
}));
// Debug form errors
useEffect(() => {
const errors = form.formState.errors;
if (Object.keys(errors).length > 0) {
console.log("❌ Form validation errors:", errors);
}
}, [form.formState.errors]);
useEffect(() => {
if (patient) {
const { id, userId, createdAt, ...sanitizedPatient } = patient;
const resetValues = {
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: null,
insuranceProvider: normalizeInsuranceProvider(patient.insuranceProvider),
};
const normalized = normalizeInsuranceProvider(patient.insuranceProvider);
form.reset(resetValues);
form.setValue("insuranceProvider", normalized);
}
else {
form.reset(computedDefaultValues);
}
}, [patient, computedDefaultValues, form]);
const handleSubmit2 = (data) => {
onSubmit(data);
};
return (<Form {...form}>
<form id="patient-form" key={patient?.id || "new"} noValidate onSubmit={form.handleSubmit((data) => {
handleSubmit2(data);
})} className="space-y-6">
{/* Personal Information */}
<div>
<h4 className="text-md font-medium text-gray-700 mb-3">
Personal Information
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField control={form.control} name="firstName" render={({ field }) => (<FormItem>
<FormLabel>First Name *</FormLabel>
<FormControl>
<Input {...field}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="lastName" render={({ field }) => (<FormItem>
<FormLabel>Last Name *</FormLabel>
<FormControl>
<Input {...field}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
<DateInputField control={form.control} name="dateOfBirth" label="Date of Birth *" disableFuture/>
<FormField control={form.control} name="gender" render={({ field }) => (<FormItem>
<FormLabel>Gender</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select gender"/>
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="male">Male</SelectItem>
<SelectItem value="female">Female</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="preferredLanguage" render={({ field }) => (<FormItem>
<FormLabel>Preferred Language</FormLabel>
<Select onValueChange={field.onChange} value={field.value || "English"}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select language"/>
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="English">English</SelectItem>
<SelectItem value="Spanish">Spanish</SelectItem>
<SelectItem value="Portuguese">Portuguese</SelectItem>
<SelectItem value="Mandarin">Mandarin</SelectItem>
<SelectItem value="Cantonese">Cantonese</SelectItem>
<SelectItem value="Arabic">Arabic</SelectItem>
<SelectItem value="Haitian Creole">Haitian Creole</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>)}/>
</div>
</div>
{/* Contact Information */}
<div>
<h4 className="text-md font-medium text-gray-700 mb-3">
Contact Information
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField control={form.control} name="phone" render={({ field }) => {
const display = field.value?.startsWith("1")
? field.value.slice(1)
: field.value || "";
return (<FormItem>
<FormLabel>Phone Number *</FormLabel>
<FormControl>
<div className="flex">
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-input bg-muted text-muted-foreground text-sm">
+1
</span>
<Input className="rounded-l-none" placeholder="6175551234" value={display} onChange={(e) => {
const digits = e.target.value.replace(/\D/g, "").slice(0, 10);
field.onChange(digits ? "1" + digits : "");
}} onBlur={field.onBlur} name={field.name} ref={field.ref}/>
</div>
</FormControl>
<FormMessage />
</FormItem>);
}}/>
<FormField control={form.control} name="email" render={({ field }) => (<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} value={field.value || ""}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="address" render={({ field }) => (<FormItem className="md:col-span-2">
<FormLabel>Address</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="city" render={({ field }) => (<FormItem>
<FormLabel>City</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="zipCode" render={({ field }) => (<FormItem>
<FormLabel>ZIP Code</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
</div>
</div>
{/* Insurance Information */}
<div>
<h4 className="text-md font-medium text-gray-700 mb-3">
Insurance Information
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField control={form.control} name="status" render={({ field }) => {
const options = Object.values(patientStatusOptions); // ['ACTIVE','INACTIVE','UNKNOWN']
const toLabel = (v) => {
if (v === "PLAN_NOT_ACCEPTED")
return "Plan Not Accepted";
return v[0] + v.slice(1).toLowerCase();
};
return (<FormItem>
<FormLabel>Status</FormLabel>
<Select value={field.value ?? "UNKNOWN"} onValueChange={(v) => field.onChange(v)}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select status"/>
</SelectTrigger>
</FormControl>
<SelectContent>
{options.map((v) => (<SelectItem key={v} value={v}>
{toLabel(v)}
</SelectItem>))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>);
}}/>
<FormField control={form.control} name="insuranceProvider" render={({ field }) => (<FormItem>
<FormLabel>Insurance Type</FormLabel>
<Select onValueChange={field.onChange} value={field.value || ""}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select insurance type"/>
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="MassHealth">MassHealth</SelectItem>
<SelectItem value="CCA">CCA</SelectItem>
<SelectItem value="DDMA">DDMA</SelectItem>
<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>
<SelectItem value="Altus">Altus</SelectItem>
<SelectItem value="MetlifeDental">Metlife Dental</SelectItem>
<SelectItem value="Cigna">Cigna</SelectItem>
<SelectItem value="DeltaWA">Delta WA</SelectItem>
<SelectItem value="DeltaIL">Delta IL</SelectItem>
<SelectItem value="Others">Others</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="insuranceId" render={({ field }) => (<FormItem>
<FormLabel>Insurance ID</FormLabel>
<FormControl>
<Input {...field} value={String(field.value) || ""}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="groupNumber" render={({ field }) => (<FormItem>
<FormLabel>Group Number</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
<FormField control={form.control} name="policyHolder" render={({ field }) => (<FormItem>
<FormLabel>Policy Holder (if not self)</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""}/>
</FormControl>
<FormMessage />
</FormItem>)}/>
</div>
</div>
{/* Hidden submit button for form validation */}
<button type="submit" className="hidden" aria-hidden="true"></button>
</form>
</Form>);
});
@@ -60,6 +60,27 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
[isEditing],
);
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("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";
if (p.includes("altus")) return "Altus";
if (p.includes("metlife")) return "MetlifeDental";
if (p.includes("cigna")) return "Cigna";
if (p.includes("delta wa") || p === "deltawa") return "DeltaWA";
if (p.includes("delta il") || p === "deltail") return "DeltaIL";
if (p.includes("other")) return "Others";
return val || "";
};
const computedDefaultValues = useMemo(() => {
if (isEditing && patient) {
const { id, userId, createdAt, ...sanitizedPatient } = patient;
@@ -68,6 +89,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: null,
insuranceProvider: normalizeInsuranceProvider(patient.insuranceProvider),
};
}
return {
@@ -116,13 +138,17 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
useEffect(() => {
if (patient) {
const { id, userId, createdAt, ...sanitizedPatient } = patient;
const normalized = normalizeInsuranceProvider(patient.insuranceProvider);
const resetValues: Partial<Patient> = {
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: null,
insuranceProvider: normalized,
};
form.reset(resetValues);
// Explicit setValue ensures the controlled Select re-renders with the new value
form.setValue("insuranceProvider" as any, normalized);
} else {
form.reset(computedDefaultValues);
}
@@ -396,27 +422,33 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
name="insuranceProvider"
render={({ field }) => (
<FormItem>
<FormLabel>Insurance Provider</FormLabel>
<FormLabel>Insurance Type</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={(field.value as string) || ""}
value={(field.value as string) || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select provider" />
<SelectValue placeholder="Select insurance type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="placeholder">
Select provider
</SelectItem>
<SelectItem value="Mass Health">Mass Health</SelectItem>
<SelectItem value="Delta MA">Delta MA</SelectItem>
<SelectItem value="Metlife">MetLife</SelectItem>
<SelectItem value="Cigna">Cigna</SelectItem>
<SelectItem value="MassHealth">MassHealth</SelectItem>
<SelectItem value="CCA">CCA</SelectItem>
<SelectItem value="DDMA">DDMA</SelectItem>
<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>
<SelectItem value="Other">Other</SelectItem>
<SelectItem value="none">None</SelectItem>
<SelectItem value="Altus">Altus</SelectItem>
<SelectItem value="MetlifeDental">Metlife Dental</SelectItem>
<SelectItem value="Cigna">Cigna</SelectItem>
<SelectItem value="DeltaWA">Delta WA</SelectItem>
<SelectItem value="DeltaIL">Delta IL</SelectItem>
<SelectItem value="Others">Others</SelectItem>
</SelectContent>
</Select>
<FormMessage />