diff --git a/apps/Backend/src/ai/cdt-lookup.ts b/apps/Backend/src/ai/cdt-lookup.ts index 79f84ff4..81c2963a 100644 --- a/apps/Backend/src/ai/cdt-lookup.ts +++ b/apps/Backend/src/ai/cdt-lookup.ts @@ -599,6 +599,37 @@ function matchOne(input: string): CdtMatch | null { return { code: bestEntry.code, description: bestEntry.description, input }; } +const TOOTH_SURFACE_RE = /#?\s*(\d{1,2})\s+([OMDBLFIV]{1,5})\b/gi; +const BARE_TOOTH_SURFACE_RE = /^#?\s*(\d{1,2})\s+([OMDBLFIV]{1,5})$/i; + +/** + * Composite lists often give the "#" only on the first tooth: + * "comp on #18 O, 20 O, 21 O" → ["comp #18 O", "comp #20 O", "comp #21 O"]. + * Also handles the LLM splitting that list into ["comp on #18 O", "20 O", ...]: + * a bare "20 O" right after a composite entry is another composite. + */ +function expandCompositeLists(procedureNames: string[]): string[] { + const out: string[] = []; + let prevComposite = false; + for (const name of procedureNames) { + const s = name.trim(); + if (prevComposite && BARE_TOOTH_SURFACE_RE.test(s)) { + const m = s.match(BARE_TOOTH_SURFACE_RE)!; + out.push(`comp #${m[1]} ${m[2]}`); + continue; + } + const isComposite = /^(comp|composite|resin|filling)\b/i.test(s) || /^#\s*\d{1,2}\s+[OMDBLFIV]{1,5}\b/i.test(s); + const teeth = isComposite ? [...s.matchAll(TOOTH_SURFACE_RE)] : []; + if (teeth.length > 1) { + for (const m of teeth) out.push(`comp #${m[1]} ${m[2]}`); + } else { + out.push(name); + } + prevComposite = teeth.length > 0; + } + return out; +} + /** * Map an array of free-text procedure names to CDT codes. * customAliases (from DB) are checked before the hardcoded ALIAS_MAP. @@ -617,7 +648,7 @@ export function lookupCdtCodes( // Pontics with no stated material — re-coded below to match the crowns const inferredPontics: CdtMatch[] = []; - const results = procedureNames.flatMap((name): (CdtMatch | { code: null; description: string; input: string })[] => { + const results = expandCompositeLists(procedureNames).flatMap((name): (CdtMatch | { code: null; description: string; input: string })[] => { const cleaned = name.trim().toLowerCase(); // If the LLM emitted a bare tooth-number token like "# 32" or "#32" with no procedure, diff --git a/apps/Backend/src/ai/internal-chat-graph.ts b/apps/Backend/src/ai/internal-chat-graph.ts index 743303d4..e0720dc9 100644 --- a/apps/Backend/src/ai/internal-chat-graph.ts +++ b/apps/Backend/src/ai/internal-chat-graph.ts @@ -166,8 +166,12 @@ Rules: "with the scan", "with the photo", "with the document", or any variation meaning an uploaded/attached file, do NOT include it in procedureNames. It refers to a file attachment, not a billable procedure. Only include actual clinical procedures in procedureNames. +- Replacement wording and prior placement dates are claim-form details, not procedures: + "replace old crown #3" → ["crown #3"]; "bridge #3-5 replacement, placed 5/2/18" → ["bridge #3-5"] - For composite fillings with a tooth number, preserve the EXACT notation including tooth# and surfaces: e.g. "composite #29 O", "#8 MO", "composite #11 MOD", "# 10 DL", "# 11 ML", "#8 ml", "comp #8 ml" — keep the #number and surface letters together as one entry + A tooth list after one composite keeps the composite for every tooth: "comp on #18 O, 20 O, 21 O" → + ["comp #18 O", "comp #20 O", "comp #21 O"] Note: "comp" is short for "composite". "# 10 DL", "composite on # 10 DL", and "comp # 10 DL" are all the same — preserve the space-after-# as-is IMPORTANT: Any letter(s) immediately after a tooth number are DENTAL SURFACE abbreviations indicating a composite filling: M = Mesial, D = Distal, L = Lingual, F = Facial, B = Buccal, O = Occlusal, V = Vestibular, I = Incisal diff --git a/apps/Backend/src/routes/claims.ts b/apps/Backend/src/routes/claims.ts index 218bd09e..1753480d 100755 --- a/apps/Backend/src/routes/claims.ts +++ b/apps/Backend/src/routes/claims.ts @@ -1230,56 +1230,104 @@ router.get("/all", async (req: Request, res: Response) => { } }); -// Get a single claim by ID -// GET /api/claims/:id/ada-form?payer=BCBS_MA -// Paper claim: returns the 2024 ADA Dental Claim Form filled from the saved claim. +// Paper claim: the 2024 ADA Dental Claim Form filled from a saved claim. +type AdaFormResult = + | { ok: false; status: number; error: string } + | { + ok: true; + pdf: Buffer; + filename: string; + claimId: number; + claimStatus: string; + patient: { id: number; firstName: string; lastName: string }; + }; + +async function buildAdaFormForRequest(req: Request): Promise { + const claimId = parseInt(String(req.params.id), 10); + if (isNaN(claimId)) return { ok: false, status: 400, error: "Invalid claim ID" }; + + const payerKey = String(req.query.payer || "BCBS_MA").toUpperCase(); + const payer = ADA_PAYERS[payerKey]; + if (!payer) return { ok: false, status: 400, error: `Unknown payer "${payerKey}"` }; + + const { prisma: db } = await import("@repo/db/client"); + const claim = await db.claim.findUnique({ + where: { id: claimId }, + include: { + serviceLines: { orderBy: { id: "asc" } }, + patient: true, + npiProvider: true, + appointment: { select: { notes: true } }, + }, + }); + if (!claim || claim.userId !== req.user!.id) { + return { ok: false, status: 404, error: "Claim not found" }; + } + + const office = await storage.getOfficeContact(req.user!.id); + // Fall back to the default (first) NPI provider when the claim has none + const treatingProvider = + claim.npiProvider ?? (await storage.getNpiProvidersByUser(req.user!.id))[0] ?? null; + + const pdf = await buildAdaClaimPdf({ + claim, + patient: claim.patient, + office, + treatingProvider, + payer, + appointmentNotes: claim.appointment?.notes ?? null, + }); + + const filename = `ADA_Claim_${claim.patient.lastName}_${claim.patient.firstName}_${claimId}.pdf` + .replace(/[^\w.-]+/g, "_"); + return { ok: true, pdf: Buffer.from(pdf), filename, claimId, claimStatus: claim.status, patient: claim.patient }; +} + +// GET /api/claims/:id/ada-form?payer=BCBS_MA — view/print only router.get("/:id/ada-form", async (req: Request, res: Response): Promise => { try { if (!req.user?.id) return res.status(401).json({ message: "Unauthorized" }); - const claimId = parseInt(String(req.params.id), 10); - if (isNaN(claimId)) return res.status(400).json({ error: "Invalid claim ID" }); - - const payerKey = String(req.query.payer || "BCBS_MA").toUpperCase(); - const payer = ADA_PAYERS[payerKey]; - if (!payer) return res.status(400).json({ error: `Unknown payer "${payerKey}"` }); - - const { prisma: db } = await import("@repo/db/client"); - const claim = await db.claim.findUnique({ - where: { id: claimId }, - include: { - serviceLines: { orderBy: { id: "asc" } }, - patient: true, - npiProvider: true, - }, - }); - if (!claim || claim.userId !== req.user.id) { - return res.status(404).json({ message: "Claim not found" }); - } - - const office = await storage.getOfficeContact(req.user.id); - // Fall back to the default (first) NPI provider when the claim has none - const treatingProvider = - claim.npiProvider ?? (await storage.getNpiProvidersByUser(req.user.id))[0] ?? null; - - const pdf = await buildAdaClaimPdf({ - claim, - patient: claim.patient, - office, - treatingProvider, - payer, - }); - - const filename = `ADA_Claim_${claim.patient.lastName}_${claim.patient.firstName}_${claimId}.pdf` - .replace(/[^\w.-]+/g, "_"); + const result = await buildAdaFormForRequest(req); + if (!result.ok) return res.status(result.status).json({ message: result.error }); res.setHeader("Content-Type", "application/pdf"); - res.setHeader("Content-Disposition", `inline; filename="${filename}"`); - return res.send(Buffer.from(pdf)); + res.setHeader("Content-Disposition", `inline; filename="${result.filename}"`); + return res.send(result.pdf); } catch (error) { console.error("[ada-form] failed:", error); return res.status(500).json({ message: "Failed to generate ADA claim form", details: String(error) }); } }); +// POST /api/claims/:id/ada-form?payer=BCBS_MA — also saves the PDF to the +// patient's Documents "Claims" folder, then returns it +router.post("/:id/ada-form", async (req: Request, res: Response): Promise => { + try { + if (!req.user?.id) return res.status(401).json({ message: "Unauthorized" }); + const result = await buildAdaFormForRequest(req); + if (!result.ok) return res.status(result.status).json({ message: result.error }); + const { patient } = result; + await storage.savePdfToCloudStorage( + req.user.id, + patient.id, + `${patient.firstName} ${patient.lastName}`, + "Claims", + result.filename, + result.pdf, + ); + // The form is the submission: mark it submitted so the appointment shows as claimed + if (result.claimStatus === "PENDING") { + await storage.updateClaimStatus(result.claimId, "REVIEW"); + } + res.setHeader("Content-Type", "application/pdf"); + res.setHeader("Content-Disposition", `inline; filename="${result.filename}"`); + return res.send(result.pdf); + } catch (error) { + console.error("[ada-form] failed:", error); + return res.status(500).json({ message: "Failed to generate ADA claim form", details: String(error) }); + } +}); + +// Get a single claim by ID router.get("/:id", async (req: Request, res: Response): Promise => { try { const idParam = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id; diff --git a/apps/Backend/src/routes/feeSchedule.ts b/apps/Backend/src/routes/feeSchedule.ts index ca69c252..21196b33 100644 --- a/apps/Backend/src/routes/feeSchedule.ts +++ b/apps/Backend/src/routes/feeSchedule.ts @@ -9,6 +9,8 @@ const SCHEDULE_FILES: Record = { MASSHEALTH: "procedureCodesMH.json", CCA: "procedureCodesCCA.json", DDMA: "procedureCodesDDMA.json", + BCBS: "procedureCodesBCBS.json", + BCBS_MA: "procedureCodesBCBS.json", TUFTSSCO: "procedureCodesTuftsSCO.json", TUFTS_SCO: "procedureCodesTuftsSCO.json", UNITEDDH: "procedureCodesUnitedDH.json", diff --git a/apps/Backend/src/routes/office-contact.ts b/apps/Backend/src/routes/office-contact.ts index 0ba8bf73..e28bdfe6 100644 --- a/apps/Backend/src/routes/office-contact.ts +++ b/apps/Backend/src/routes/office-contact.ts @@ -23,10 +23,11 @@ router.put("/", async (req: Request, res: Response): Promise => { if (!userId) return res.status(401).json({ message: "Unauthorized" }); const { - officeName, receptionistName, dentistName, phoneNumber, email, fax, streetAddress, city, state, zipCode, + legalName, officeName, receptionistName, dentistName, phoneNumber, email, fax, streetAddress, city, state, zipCode, billingName, billingNpi, billingLicense, billingTaxId, } = req.body; const record = await storage.upsertOfficeContact(userId, { + legalName: legalName ?? undefined, officeName: officeName ?? undefined, receptionistName: receptionistName ?? undefined, dentistName: dentistName ?? undefined, diff --git a/apps/Backend/src/services/adaClaimForm.ts b/apps/Backend/src/services/adaClaimForm.ts index f877a473..aad98175 100644 --- a/apps/Backend/src/services/adaClaimForm.ts +++ b/apps/Backend/src/services/adaClaimForm.ts @@ -5,7 +5,7 @@ import { PDFDocument, PDFFont, PDFForm, StandardFonts } from "pdf-lib"; /** * Fills the 2024 ADA Dental Claim Form (J43024) for payers that only accept * paper claims (e.g. BCBS MA). Field names come from the template's AcroForm; - * signatures and their dates are intentionally left blank for wet signing. + * signatures are left blank for wet signing; only item 53's date is filled (claim date). */ const TEMPLATE_PATH = path.join(__dirname, "../data/templates/ada-claim-form-2024.pdf"); @@ -33,6 +33,7 @@ export const ADA_PAYERS: Record = { export type AdaClaimInput = { claim: { id: number; + createdAt?: Date | string | null; memberId?: string | null; remarks?: string | null; status?: string | null; @@ -63,6 +64,7 @@ export type AdaClaimInput = { policyHolder?: string | null; }; office: { + legalName?: string | null; officeName?: string | null; phoneNumber?: string | null; streetAddress?: string | null; @@ -79,6 +81,8 @@ export type AdaClaimInput = { licenseNumber?: string | null; } | null; payer: AdaPayer; + /** Appointment notes — item 43/44 (replacement of prosthesis) */ + appointmentNotes?: string | null; }; let descriptionByCode: Map | null = null; @@ -107,6 +111,39 @@ function fmtDate(d: Date | string | null | undefined): string { return `${mm}/${dd}/${date.getUTCFullYear()}`; } +/** A timestamp as the office's local calendar date (fmtDate is for date-only values stored as UTC midnight). */ +function fmtLocalDate(d: Date | string | null | undefined): string { + if (!d) return ""; + const date = d instanceof Date ? d : new Date(d); + if (isNaN(date.getTime())) return ""; + const mm = String(date.getMonth() + 1).padStart(2, "0"); + const dd = String(date.getDate()).padStart(2, "0"); + return `${mm}/${dd}/${date.getFullYear()}`; +} + +const PROSTHESIS_WORDS = "crowns?|bridges?|dentures?|partials?|prosthes[ie]s|prosthetics?|pontics?|veneers?|onlays?|inlays?|fpd|rpd|implants?"; +const REPLACE_WORDS = "replac(?:e|ed|es|ing|ement)|remake|re-make|redo|re-do"; + +/** + * Item 43/44: does the note say an existing prosthesis is being replaced + * (e.g. "replace old crown #3", "bridge replacement", "replacement of prosthesis")? + * Returns the prior placement date when the note gives one as MM/DD/YYYY. + */ +export function prosthesisReplacement(notes?: string | null): { replacement: boolean; priorDate?: string } { + const s = (notes ?? "").toLowerCase(); + const replaceThenProsthesis = new RegExp(`\\b(?:${REPLACE_WORDS})\\b[^.;\\n]{0,30}?\\b(?:${PROSTHESIS_WORDS})\\b`); + const prosthesisThenReplace = new RegExp(`\\b(?:${PROSTHESIS_WORDS})\\b[^.;\\n]{0,15}?\\b(?:${REPLACE_WORDS})\\b`); + // "replace missing #19 with bridge" is a new prosthesis for a lost tooth, not a replacement + const hit = [s.match(replaceThenProsthesis), s.match(prosthesisThenReplace)].find( + (m) => m && !/\b(?:missing|extracted|lost)\b/.test(m[0]), + ); + if (!hit) return { replacement: false }; + const m = s.match(/\b(\d{1,2})[/-](\d{1,2})[/-](\d{4}|\d{2})\b/); + if (!m) return { replacement: true }; + const year = m[3]!.length === 2 ? `20${m[3]}` : m[3]!; + return { replacement: true, priorDate: `${m[1]!.padStart(2, "0")}/${m[2]!.padStart(2, "0")}/${year}` }; +} + function fmtMoney(v: unknown): string { const n = Number(v ?? 0); return isNaN(n) ? "" : n.toFixed(2); @@ -244,7 +281,6 @@ async function fillOnePage( setText(form, "20 City, State, Zip", patientCsz); setText(form, "21 Date of Birth MMDDCCYY", dob); check(form, `22 ${genderBox}`); - setText(form, "23 Patient IDAccount Assigned by Dentist", String(patient.id)); // 24-31. Service lines let total = 0; @@ -253,7 +289,6 @@ async function fillOnePage( const tooth = (line.toothNumber ?? "").trim(); setText(form, `24 Procedure Date MMDDCCYY${s}`, fmtDate(line.procedureDate)); setText(form, `25 Area of Oral Cavity${s}`, oralCavityArea(line.quad, line.arch)); - setText(form, `26 Tooth System${s}`, tooth ? "JP" : ""); setText(form, `27 Tooth Numbers or Letters${s}`, tooth); setText(form, `28 Tooth Surface${s}`, (line.toothSurface ?? "").trim()); setText(form, `29 Procedure Code${s}`, line.procedureCode.trim().toUpperCase()); @@ -277,15 +312,24 @@ async function fillOnePage( // 38-40. Ancillary setText(form, "38 Place of Treatment", "11"); check(form, "40 Check Box1"); + // 43-44. Replacement of prosthesis: Yes only when the appointment notes say so + const replacement = prosthesisReplacement(input.appointmentNotes); + if (replacement.replacement) { + check(form, "43 Check Box2"); + setText(form, "44 Date of Prior Placement MMDDCCYY", replacement.priorDate); + } else { + check(form, "43 Check Box1"); + } - // 48-52. Billing dentist (office NPI) + // 48-52. Billing dentist: legal name, office name (DBA) and address (Settings > Office Contact) const officeCsz = cityStateZip(office?.city, office?.state, office?.zipCode); - const officeLines = [office?.billingName, office?.officeName, office?.streetAddress] - .map((v) => v?.trim()) - .filter(Boolean) as string[]; - setText(form, "48 Name", officeLines[0]); - setText(form, "48 Address", officeLines[1]); - setText(form, "48 Address 2", officeLines[2]); + const legalName = (office?.legalName || office?.billingName || "").trim(); + const dbaName = office?.officeName?.trim() ?? ""; + const street = office?.streetAddress?.trim() ?? ""; + const billingLines = [legalName, dbaName !== legalName ? dbaName : "", street].filter(Boolean); + setText(form, "48 Name", billingLines[0]); + setText(form, "48 Address", billingLines[1]); + setText(form, "48 Address 2", billingLines[2]); setText(form, "48 City, State, Zip", officeCsz); setText(form, "49 NPI", office?.billingNpi); setText(form, "50 License Number", office?.billingLicense); @@ -300,11 +344,15 @@ async function fillOnePage( } } + // 53. Signature stays blank; date = claim date (when the claim was made) + setText(form, "53 Date", fmtLocalDate(claim.createdAt ?? new Date())); + // 54-56. Treating dentist and location setText(form, "54 NPI", treatingProvider?.npiNumber); setText(form, "55 License Number", treatingProvider?.licenseNumber || office?.billingLicense); - setText(form, "56 Address 1", office?.streetAddress); - setText(form, "56 Address 2", officeCsz); + // 56. Treatment location: office address only + setFittedText(form, helvetica, "56 Address 1", street); + setFittedText(form, helvetica, "56 Address 2", officeCsz); form.updateFieldAppearances(helvetica); form.flatten(); diff --git a/apps/Backend/src/storage/claims-storage.ts b/apps/Backend/src/storage/claims-storage.ts index a52560db..158a07d1 100755 --- a/apps/Backend/src/storage/claims-storage.ts +++ b/apps/Backend/src/storage/claims-storage.ts @@ -126,10 +126,15 @@ export const claimsStorage: IStorage = { async getAppointmentIdsWithClaimNumbers(ids: number[]): Promise> { if (!ids.length) return new Set(); const rows = await db.claim.findMany({ - where: { appointmentId: { in: ids }, claimNumber: { not: null } }, + // Same "already submitted" rule as batch-column: a claim number, or REVIEW/APPROVED + // (paper claims like BCBS have no claim number and are set to REVIEW once the form is saved) + where: { + appointmentId: { in: ids }, + OR: [{ claimNumber: { not: null } }, { status: { in: ["REVIEW", "APPROVED"] as ClaimStatus[] } }], + }, select: { appointmentId: true }, distinct: ["appointmentId"], }); - return new Set(rows.map((r) => r.appointmentId)); + return new Set(rows.map((r: { appointmentId: number | null }) => r.appointmentId)); }, }; diff --git a/apps/Backend/src/storage/office-contact-storage.ts b/apps/Backend/src/storage/office-contact-storage.ts index 6db95f1c..247c1c88 100644 --- a/apps/Backend/src/storage/office-contact-storage.ts +++ b/apps/Backend/src/storage/office-contact-storage.ts @@ -6,6 +6,7 @@ export const officeContactStorage = { }, async upsertOfficeContact(userId: number, data: { + legalName?: string; officeName?: string; receptionistName?: string; dentistName?: string; diff --git a/apps/Frontend/src/assets/data/procedureCodesBCBS.json b/apps/Frontend/src/assets/data/procedureCodesBCBS.json new file mode 100644 index 00000000..247c4b2b --- /dev/null +++ b/apps/Frontend/src/assets/data/procedureCodesBCBS.json @@ -0,0 +1,209 @@ +[ + { "Procedure Code": "D0120", "Description": "Periodic oral evaluation - established patient", "Price": 105 }, + { "Procedure Code": "D0140", "Description": "Limited oral evaluation - problem focused", "Price": 90 }, + { "Procedure Code": "D0145", "Description": "Oral evaluation for a patient under three years of age and counseling with primary caregiver", "Price": "NC" }, + { "Procedure Code": "D0150", "Description": "Comprehensive oral evaluation - new or established patient", "Price": 120 }, + { "Procedure Code": "D0180", "Description": "Comprehensive periodontal evaluation - new or established patient", "Price": 37 }, + { "Procedure Code": "D0190", "Description": "Screening of a patient (PHDH only)", "Price": 20 }, + { "Procedure Code": "D0191", "Description": "Assessment of a patient (PHDH only)", "Price": 20 }, + { "Procedure Code": "D0210", "Description": "Intraoral - complete series of radiographic images", "Price": 120 }, + { "Procedure Code": "D0220", "Description": "Intraoral - periapical, first radiographic image", "Price": 60 }, + { "Procedure Code": "D0230", "Description": "Intraoral - periapical, each additional radiographic image", "Price": 50 }, + { "Procedure Code": "D0240", "Description": "Intraoral - occlusal radiographic image", "Price": "NC" }, + { "Procedure Code": "D0270", "Description": "Bitewing - single radiographic image", "Price": 14 }, + { "Procedure Code": "D0272", "Description": "Bitewings - two radiographic images", "Price": 80 }, + { "Procedure Code": "D0273", "Description": "Bitewings - three radiographic images", "Price": 27 }, + { "Procedure Code": "D0274", "Description": "Bitewings - four radiographic images", "Price": 160 }, + { "Procedure Code": "D0330", "Description": "Panoramic radiographic image", "Price": 150 }, + { "Procedure Code": "D0340", "Description": "Cephalometric radiograph image (Oral surgeon only)", "Price": 74 }, + { "Procedure Code": "D0364", "Description": "CBCT - less than one jaw (interpret)", "Price": 350 }, + { "Procedure Code": "D0365", "Description": "CBCT - mandible (interpret)", "Price": 350 }, + { "Procedure Code": "D0366", "Description": "CBCT - maxilla (interpret)", "Price": 350 }, + { "Procedure Code": "D0367", "Description": "CBCT - both jaws (interpret)", "Price": 400 }, + { "Procedure Code": "D0368", "Description": "CBCT - include TMJ (interpret)", "Price": 375 }, + { "Procedure Code": "D0380", "Description": "CBCT - less than one jaw (capture only)", "Price": 300 }, + { "Procedure Code": "D0381", "Description": "CBCT - mandible (capture only)", "Price": 300 }, + { "Procedure Code": "D0382", "Description": "CBCT - maxilla (capture only)", "Price": 300 }, + { "Procedure Code": "D0383", "Description": "CBCT - both jaws / include TMJ (capture only)", "Price": 350 }, + { "Procedure Code": "D1110", "Description": "Prophylaxis - adult, 14 yo or older", "Price": 150 }, + { "Procedure Code": "D1120", "Description": "Prophylaxis - child, 0-13 yo", "Price": 120 }, + { "Procedure Code": "D1206", "Description": "Topical application of fluoride varnish", "Price": 26 }, + { "Procedure Code": "D1208", "Description": "Topical application of fluoride - excluding varnish", "Price": 90 }, + { "Procedure Code": "D1351", "Description": "Sealant - per tooth", "Price": 80 }, + { "Procedure Code": "D1354", "Description": "Application of caries arresting medicament - per tooth", "Price": 15 }, + { "Procedure Code": "D1510", "Description": "Space maintainer - fixed, unilateral - per quadrant", "Price": "NC" }, + { "Procedure Code": "D1516", "Description": "Space maintainer - fixed, bilateral, maxillary", "Price": "NC" }, + { "Procedure Code": "D1517", "Description": "Space maintainer - fixed, bilateral, mandibular", "Price": "NC" }, + { "Procedure Code": "D1520", "Description": "Space maintainer - removable, unilateral, per quadrant", "Price": "NC" }, + { "Procedure Code": "D1526", "Description": "Space maintainer - removable, bilateral, maxillary", "Price": "NC" }, + { "Procedure Code": "D1527", "Description": "Space maintainer - removable, bilateral, mandibular", "Price": "NC" }, + { "Procedure Code": "D1575", "Description": "Distal shoe space maintainer - fixed, unilateral, per quadrant", "Price": "NC" }, + { "Procedure Code": "D1701", "Description": "Pfizer-BioNTech Covid-19 vaccine - first dose", "Price": 45.87 }, + { "Procedure Code": "D1702", "Description": "Pfizer-BioNTech Covid-19 vaccine - second dose", "Price": 45.87 }, + { "Procedure Code": "D1707", "Description": "Janssen Covid-19 vaccine - single dose", "Price": 45.87 }, + { "Procedure Code": "D1708", "Description": "Pfizer-BioNTech Covid-19 vaccine - third dose", "Price": 45.87 }, + { "Procedure Code": "D1709", "Description": "Pfizer-BioNTech Covid-19 vaccine - booster dose", "Price": 45.87 }, + { "Procedure Code": "D1712", "Description": "Janssen Covid-19 vaccine - booster dose", "Price": 45.87 }, + { "Procedure Code": "D1713", "Description": "Pfizer-BioNTech Covid-19 vaccine pediatric - first dose", "Price": 45.87 }, + { "Procedure Code": "D1714", "Description": "Pfizer-BioNTech Covid-19 vaccine pediatric - second dose", "Price": 45.87 }, + { "Procedure Code": "D1999", "Description": "Unspecified preventive procedure, by report", "Price": 50 }, + { "Procedure Code": "D2140", "Description": "Amalgam - one surface, primary or permanent", "Price": 150 }, + { "Procedure Code": "D2150", "Description": "Amalgam - two surfaces, primary or permanent", "Price": 200 }, + { "Procedure Code": "D2160", "Description": "Amalgam - three surfaces, primary or permanent", "Price": 92 }, + { "Procedure Code": "D2161", "Description": "Amalgam - four or more surfaces, primary or permanent", "Price": 116 }, + { "Procedure Code": "D2330", "Description": "Resin-based composite - one surface, anterior", "Price": 180 }, + { "Procedure Code": "D2331", "Description": "Resin-based composite - two surfaces, anterior", "Price": 220 }, + { "Procedure Code": "D2332", "Description": "Resin-based composite - three surfaces, anterior", "Price": 280 }, + { "Procedure Code": "D2335", "Description": "Resin-based composite - four or more surfaces or involving incisal angle (anterior)", "Price": 350 }, + { "Procedure Code": "D2390", "Description": "Resin-based composite crown, anterior", "Price": "NC" }, + { "Procedure Code": "D2391", "Description": "Resin-based composite - one surface, posterior", "Price": 200 }, + { "Procedure Code": "D2392", "Description": "Resin-based composite - two surfaces, posterior", "Price": 250 }, + { "Procedure Code": "D2393", "Description": "Resin-based composite - three surfaces, posterior", "Price": 280 }, + { "Procedure Code": "D2394", "Description": "Resin-based composite - four or more surfaces, posterior", "Price": 320 }, + { "Procedure Code": "D2710", "Description": "Crown - resin-based composite (indirect)", "Price": "NC" }, + { "Procedure Code": "D2740", "Description": "Crown - porcelain/ceramic", "Price": 1300 }, + { "Procedure Code": "D2750", "Description": "Crown - porcelain fused to high noble metal", "Price": 1300 }, + { "Procedure Code": "D2751", "Description": "Crown - porcelain fused to predominantly base metal", "Price": 1200 }, + { "Procedure Code": "D2752", "Description": "Crown - porcelain fused to noble metal", "Price": 1200 }, + { "Procedure Code": "D2790", "Description": "Crown - full cast high noble metal", "Price": "NC" }, + { "Procedure Code": "D2910", "Description": "Re-cement or re-bond inlay, onlay or partial coverage restoration", "Price": 57 }, + { "Procedure Code": "D2920", "Description": "Re-cement or re-bond crown", "Price": 120 }, + { "Procedure Code": "D2929", "Description": "Prefabricated porcelain/ceramic crown - primary tooth", "Price": "NC" }, + { "Procedure Code": "D2930", "Description": "Prefabricated stainless steel crown - primary tooth", "Price": "NC" }, + { "Procedure Code": "D2931", "Description": "Prefabricated stainless steel crown - permanent tooth", "Price": 171 }, + { "Procedure Code": "D2932", "Description": "Prefabricated resin crown", "Price": "NC" }, + { "Procedure Code": "D2934", "Description": "Prefabricated esthetic coated stainless steel crown - primary tooth", "Price": "NC" }, + { "Procedure Code": "D2950", "Description": "Core buildup, including any pins when required", "Price": 350 }, + { "Procedure Code": "D2951", "Description": "Pin retention - per tooth, in addition to restoration", "Price": 27 }, + { "Procedure Code": "D2954", "Description": "Prefabricated post and core in addition to crown", "Price": 450 }, + { "Procedure Code": "D2955", "Description": "Post removal", "Price": 350 }, + { "Procedure Code": "D2980", "Description": "Crown repair necessitated by restorative material failure", "Price": 115 }, + { "Procedure Code": "D2999", "Description": "Unspecified restorative procedure, by report", "Price": "IC" }, + { "Procedure Code": "D3120", "Description": "Pulp cap - indirect (excluding final restoration)", "Price": 34 }, + { "Procedure Code": "D3220", "Description": "Therapeutic pulpotomy (excluding final restoration)", "Price": "NC" }, + { "Procedure Code": "D3230", "Description": "Pulpal therapy (resorbable filling) - anterior, primary tooth", "Price": 280 }, + { "Procedure Code": "D3240", "Description": "Pulpal therapy (resorbable filling) - posterior, primary tooth", "Price": 490 }, + { "Procedure Code": "D3310", "Description": "Endodontic therapy, anterior tooth (excluding final restoration)", "Price": 900 }, + { "Procedure Code": "D3320", "Description": "Endodontic therapy, premolar tooth (excluding final restoration)", "Price": 1050 }, + { "Procedure Code": "D3330", "Description": "Endodontic therapy, molar tooth (excluding final restoration)", "Price": 1250 }, + { "Procedure Code": "D3346", "Description": "Retreatment of previous root canal therapy - anterior", "Price": 950 }, + { "Procedure Code": "D3347", "Description": "Retreatment of previous root canal therapy - premolar", "Price": 1100 }, + { "Procedure Code": "D3348", "Description": "Retreatment of previous root canal therapy - molar", "Price": 1500 }, + { "Procedure Code": "D3410", "Description": "Apicoectomy - anterior", "Price": 407 }, + { "Procedure Code": "D3421", "Description": "Apicoectomy - premolar (first root)", "Price": 460 }, + { "Procedure Code": "D3425", "Description": "Apicoectomy - molar (first root)", "Price": 598 }, + { "Procedure Code": "D3426", "Description": "Apicoectomy (each additional root)", "Price": 230 }, + { "Procedure Code": "D4210", "Description": "Gingivectomy or gingivoplasty - four or more contiguous teeth per quadrant", "Price": 307 }, + { "Procedure Code": "D4211", "Description": "Gingivectomy or gingivoplasty - one to three contiguous teeth per quadrant", "Price": 111 }, + { "Procedure Code": "D4341", "Description": "Periodontal scaling and root planing - four or more teeth per quadrant", "Price": 250 }, + { "Procedure Code": "D4342", "Description": "Periodontal scaling and root planing - one to three teeth, per quadrant", "Price": 90 }, + { "Procedure Code": "D4346", "Description": "Scaling in presence of generalized moderate or severe gingival inflammation - full mouth", "Price": 60 }, + { "Procedure Code": "D4910", "Description": "Periodontal maintenance", "Price": 250 }, + { "Procedure Code": "D5110", "Description": "Complete denture - maxillary", "Price": 1700 }, + { "Procedure Code": "D5120", "Description": "Complete denture - mandibular", "Price": 1700 }, + { "Procedure Code": "D5130", "Description": "Immediate denture - maxillary", "Price": "NC" }, + { "Procedure Code": "D5140", "Description": "Immediate denture - mandibular", "Price": "NC" }, + { "Procedure Code": "D5211", "Description": "Maxillary partial denture - resin base", "Price": 1300 }, + { "Procedure Code": "D5212", "Description": "Mandibular partial denture - resin base", "Price": 1300 }, + { "Procedure Code": "D5213", "Description": "Maxillary partial denture - cast metal framework with resin denture bases", "Price": 1700 }, + { "Procedure Code": "D5214", "Description": "Mandibular partial denture - cast metal framework with resin denture bases", "Price": 1700 }, + { "Procedure Code": "D5225", "Description": "Maxillary partial denture - flexible base", "Price": "NC" }, + { "Procedure Code": "D5226", "Description": "Mandibular partial denture - flexible base", "Price": "NC" }, + { "Procedure Code": "D5510", "Description": "Repair broken complete denture base (QUAD)", "Price": 400 }, + { "Procedure Code": "D5511", "Description": "Repair broken complete denture base, mandibular", "Price": 85 }, + { "Procedure Code": "D5512", "Description": "Repair broken complete denture base, maxillary", "Price": 85 }, + { "Procedure Code": "D5520", "Description": "Replace missing or broken teeth - complete denture (each tooth)", "Price": 200 }, + { "Procedure Code": "D5611", "Description": "Repair broken resin partial denture base, mandibular", "Price": 77 }, + { "Procedure Code": "D5612", "Description": "Repair broken resin partial denture base, maxillary", "Price": 77 }, + { "Procedure Code": "D5621", "Description": "Repair broken cast partial denture base, mandibular", "Price": 104 }, + { "Procedure Code": "D5622", "Description": "Repair broken cast partial denture base, maxillary", "Price": 104 }, + { "Procedure Code": "D5630", "Description": "Repair or replace broken retentive/clasping materials - per tooth", "Price": 99 }, + { "Procedure Code": "D5640", "Description": "Replace broken teeth - per tooth", "Price": 77 }, + { "Procedure Code": "D5650", "Description": "Add tooth to existing partial denture", "Price": 92 }, + { "Procedure Code": "D5660", "Description": "Add clasp to existing partial denture per tooth", "Price": 98 }, + { "Procedure Code": "D5730", "Description": "Reline complete maxillary denture (chairside)", "Price": 500 }, + { "Procedure Code": "D5731", "Description": "Reline complete mandibular denture (chairside)", "Price": 500 }, + { "Procedure Code": "D5740", "Description": "Reline maxillary partial denture (chairside)", "Price": "NC" }, + { "Procedure Code": "D5741", "Description": "Reline mandibular partial denture (chairside)", "Price": "NC" }, + { "Procedure Code": "D5750", "Description": "Reline complete maxillary denture (laboratory)", "Price": 600 }, + { "Procedure Code": "D5751", "Description": "Reline complete mandibular denture (laboratory)", "Price": 600 }, + { "Procedure Code": "D5760", "Description": "Reline maxillary partial denture (laboratory)", "Price": "NC" }, + { "Procedure Code": "D5761", "Description": "Reline mandibular partial denture (laboratory)", "Price": "NC" }, + { "Procedure Code": "D6010", "Description": "Surgical placement of implant body", "Price": 1600 }, + { "Procedure Code": "D6056", "Description": "Prefabricated abutment", "Price": 750 }, + { "Procedure Code": "D6057", "Description": "Custom abutment", "Price": 800 }, + { "Procedure Code": "D6058", "Description": "Implant crown - porcelain/ceramic", "Price": 1400 }, + { "Procedure Code": "D6059", "Description": "Implant crown - PFM", "Price": 1400 }, + { "Procedure Code": "D6100", "Description": "Surgical removal of implant body", "Price": 320 }, + { "Procedure Code": "D6110", "Description": "Implant/abutment supported removable denture - maxillary", "Price": 1600 }, + { "Procedure Code": "D6111", "Description": "Implant/abutment supported removable denture - mandibular", "Price": 1600 }, + { "Procedure Code": "D6241", "Description": "Pontic - porcelain fused to metal", "Price": "NC" }, + { "Procedure Code": "D6242", "Description": "Pontic - noble metal", "Price": 1400 }, + { "Procedure Code": "D6245", "Description": "Pontic - porcelain/ceramic", "Price": 1400 }, + { "Procedure Code": "D6751", "Description": "Retainer crown - porcelain fused to metal", "Price": "NC" }, + { "Procedure Code": "D6930", "Description": "Re-cement or re-bond fixed partial denture", "Price": "NC" }, + { "Procedure Code": "D6980", "Description": "Fixed partial denture repair", "Price": "NC" }, + { "Procedure Code": "D6999", "Description": "Fixed prosthodontic procedure, by report", "Price": "IC" }, + { "Procedure Code": "D7111", "Description": "Extraction, coronal remnants - primary tooth", "Price": 120 }, + { "Procedure Code": "D7140", "Description": "Extraction, erupted tooth or exposed root (simple)", "Price": 150 }, + { "Procedure Code": "D7210", "Description": "Extraction, erupted tooth requiring removal of bone and/or sectioning (surgical)", "Price": 280 }, + { "Procedure Code": "D7220", "Description": "Removal of impacted tooth - soft tissue", "Price": 380 }, + { "Procedure Code": "D7230", "Description": "Removal of impacted tooth - partially bony", "Price": 450 }, + { "Procedure Code": "D7240", "Description": "Removal of impacted tooth - completely bony", "Price": 550 }, + { "Procedure Code": "D7250", "Description": "Surgical removal of residual tooth roots", "Price": 280 }, + { "Procedure Code": "D7251", "Description": "Coronectomy - intentional partial tooth removal, impacted teeth only", "Price": 134 }, + { "Procedure Code": "D7270", "Description": "Tooth reimplantation and/or stabilization of accidentally evulsed or displaced tooth", "Price": 106 }, + { "Procedure Code": "D7280", "Description": "Surgical access of an unerupted tooth", "Price": "NC" }, + { "Procedure Code": "D7283", "Description": "Placement of device to facilitate eruption of impacted tooth", "Price": "NC" }, + { "Procedure Code": "D7310", "Description": "Alveoloplasty in conjunction with extractions - four or more teeth per quadrant", "Price": 142 }, + { "Procedure Code": "D7311", "Description": "Alveoloplasty in conjunction with extractions - one to three teeth per quadrant", "Price": 128 }, + { "Procedure Code": "D7320", "Description": "Alveoloplasty not in conjunction with extractions - four or more teeth per quadrant", "Price": 187 }, + { "Procedure Code": "D7321", "Description": "Alveoloplasty not in conjunction with extractions - one to three teeth per quadrant", "Price": 149 }, + { "Procedure Code": "D7340", "Description": "Vestibuloplasty - ridge extension (second epithelialization)", "Price": 747 }, + { "Procedure Code": "D7350", "Description": "Vestibuloplasty - ridge extension (Oral surgeon only)", "Price": 943 }, + { "Procedure Code": "D7410", "Description": "Excision of benign lesion - diameter up to 1.25 cm", "Price": 115 }, + { "Procedure Code": "D7411", "Description": "Excision of benign lesion - diameter greater than 1.25 cm", "Price": 208 }, + { "Procedure Code": "D7450", "Description": "Removal of benign odontogenic cyst or tumor - up to 1.25 cm", "Price": 248 }, + { "Procedure Code": "D7451", "Description": "Removal of benign odontogenic cyst or tumor - greater than 1.25 cm", "Price": 288 }, + { "Procedure Code": "D7460", "Description": "Removal of benign nonodontogenic cyst or tumor - up to 1.25 cm", "Price": 121 }, + { "Procedure Code": "D7461", "Description": "Removal of benign nonodontogenic cyst or tumor - greater than 1.25 cm", "Price": 143 }, + { "Procedure Code": "D7471", "Description": "Removal of lateral exostosis (maxilla or mandible)", "Price": 143 }, + { "Procedure Code": "D7472", "Description": "Removal of torus palatinus", "Price": 143 }, + { "Procedure Code": "D7473", "Description": "Removal of torus mandibularis", "Price": 143 }, + { "Procedure Code": "D7910", "Description": "Suture of recent small wounds up to 5 cm", "Price": 400 }, + { "Procedure Code": "D7950", "Description": "Bone graft - maxilla/mandible", "Price": 800 }, + { "Procedure Code": "D7953", "Description": "Bone graft for socket preservation after extraction", "Price": 800 }, + { "Procedure Code": "D7961", "Description": "Buccal/labial frenectomy (frenulectomy)", "Price": 107 }, + { "Procedure Code": "D7962", "Description": "Lingual frenectomy (frenulectomy)", "Price": 107 }, + { "Procedure Code": "D7963", "Description": "Frenuloplasty", "Price": 416 }, + { "Procedure Code": "D7970", "Description": "Excision of hyperplastic tissue - per arch", "Price": 246 }, + { "Procedure Code": "D7999", "Description": "Unspecified oral surgery procedure, by report", "Price": "IC" }, + { "Procedure Code": "D8010", "Description": "Limited orthodontic treatment of the primary dentition (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8020", "Description": "Limited orthodontic treatment of the transitional dentition (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8030", "Description": "Limited orthodontic treatment of the adolescent dentition (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8040", "Description": "Limited orthodontic treatment of the adult dentition (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8070", "Description": "Comprehensive orthodontic treatment of the transitional dentition (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8080", "Description": "Comprehensive orthodontic treatment of the adolescent dentition (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8090", "Description": "Comprehensive orthodontic treatment of the adult dentition (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8660", "Description": "Pre-orthodontic treatment examination (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8670", "Description": "Periodic orthodontic treatment visit (Orthodontist only)", "Price": 215 }, + { "Procedure Code": "D8680", "Description": "Orthodontic retention (Orthodontist only)", "Price": 85 }, + { "Procedure Code": "D8703", "Description": "Replacement of lost or broken retainer - maxillary (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8704", "Description": "Replacement of lost or broken retainer - mandibular (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D8999", "Description": "Unspecified orthodontic procedure, by report (Orthodontist only)", "Price": "NC" }, + { "Procedure Code": "D9110", "Description": "Palliative treatment of dental pain - per visit", "Price": 36 }, + { "Procedure Code": "D9222", "Description": "Deep sedation/general anesthesia - first 15 minutes", "Price": 90 }, + { "Procedure Code": "D9223", "Description": "Deep sedation/general anesthesia - each additional 15-minute increment", "Price": 90 }, + { "Procedure Code": "D9230", "Description": "Analgesia, anxiolysis, inhalation of nitrous oxide", "Price": 15 }, + { "Procedure Code": "D9248", "Description": "Nonintravenous conscious sedation", "Price": 45 }, + { "Procedure Code": "D9310", "Description": "Consultation - diagnostic service (Specialist only)", "Price": 63 }, + { "Procedure Code": "D9410", "Description": "House/extended care facility call, once per facility per day", "Price": 39 }, + { "Procedure Code": "D9450", "Description": "Rural add-on encounter payment", "Price": 31 }, + { "Procedure Code": "D9920", "Description": "Behavior management, by report", "Price": 86 }, + { "Procedure Code": "D9930", "Description": "Treatment of complications (postsurgical) - unusual circumstances, by report", "Price": 30 }, + { "Procedure Code": "D9941", "Description": "Fabrication of athletic mouthguard", "Price": "NC" }, + { "Procedure Code": "D9944", "Description": "Occlusal guard - hard appliance, full arch", "Price": "NC" }, + { "Procedure Code": "D9945", "Description": "Occlusal guard - soft appliance, full arch", "Price": "NC" }, + { "Procedure Code": "D9946", "Description": "Occlusal guard - hard appliance, partial arch", "Price": "NC" }, + { "Procedure Code": "D9999", "Description": "Unspecified adjunctive procedure, by report", "Price": "IC" } +] diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index c88755c1..3210485d 100755 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -66,7 +66,7 @@ import { } from "@/components/procedure/procedure-combo-buttons"; import { inferTypeFromProcedureCodes, getAppointmentTypeLabel } from "@/utils/appointmentTypeUtils"; import { Switch } from "@/components/ui/switch"; -import { openPendingTab, showAdaClaimForm } from "@/utils/adaClaimForm"; +import { openPendingTab, saveAdaClaimForm, showAdaClaimForm } from "@/utils/adaClaimForm"; import { AlertDialog, AlertDialogAction, @@ -1564,7 +1564,7 @@ export function ClaimForm({ } else if (siteKey === "UNITEDUCARD" || siteKey === "UNITED_UCARD") { runWithPriceCheck(handleUnitedUcardClaim); } else if (siteKey === "BCBS" || siteKey === "BCBS_MA") { - runWithPriceCheck(handleBCBSClaim); + runWithPriceCheck(() => handleBCBSClaim()); } else { toast({ title: "No automated claim for this insurance", @@ -1893,10 +1893,15 @@ export function ClaimForm({ // No Selenium action — works like a real MassHealth claim for payment/report tracking. // BCBS MA has no online claim submission: save the claim, then open the filled // 2024 ADA paper form to print, sign and mail. - const handleBCBSClaim = async () => { + // `auto` (AI claim queue): no tab — just save the filled form to Documents > Claims. + const handleBCBSClaim = async ( + formToUse?: ClaimFormData & { uploadedFiles?: File[] }, + { auto = false }: { auto?: boolean } = {}, + ) => { + const f = formToUse ?? form; const missingFields: string[] = []; - if (!form.memberId?.trim()) missingFields.push("Member ID"); - if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); + if (!f.memberId?.trim()) missingFields.push("Member ID"); + if (!f.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ @@ -1907,7 +1912,7 @@ export function ClaimForm({ return; } - const filteredServiceLines = form.serviceLines.filter( + const filteredServiceLines = (f.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { @@ -1920,10 +1925,10 @@ export function ClaimForm({ } // Open the tab before any await so the browser doesn't block it - const tab = openPendingTab(); + const tab = auto ? null : openPendingTab(); const appointmentIdToUse = appointmentId ?? null; - const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = form; + const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = f; try { const claimFilesMeta: ClaimFileMeta[] = mergeWithExistingAttachments( @@ -1945,10 +1950,16 @@ export function ClaimForm({ ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), }); - await showAdaClaimForm(createdClaim.id!, tab); + if (auto) { + await saveAdaClaimForm(createdClaim.id!); + } else { + await showAdaClaimForm(createdClaim.id!, tab, "BCBS_MA", { save: true }); + } + // Saving the form marks the claim submitted — show the appointment as claimed + queryClient.invalidateQueries({ queryKey: ["appointments", "day"] }); toast({ title: "BCBS claim form ready", - description: "Claim saved. Print the ADA form, sign it, and mail it to BCBS MA.", + description: "Claim saved and ADA form added to the patient's Documents > Claims. Print, sign, and mail it to BCBS MA.", }); onClose(); } catch (err: any) { @@ -2106,6 +2117,8 @@ export function ClaimForm({ handleUnitedDHClaim(pricedForm); } else if (key === "united_ucard" || key === "uniteducard" || key === "ucard") { handleUnitedUcardClaim(pricedForm); + } else if (key === "bcbs" || key === "bcbs_ma") { + handleBCBSClaim(pricedForm, { auto: true }); } else { handleMHSubmit(pricedForm); } @@ -2773,7 +2786,7 @@ export function ClaimForm({