diff --git a/apps/Backend/src/routes/feeSchedule.ts b/apps/Backend/src/routes/feeSchedule.ts index 83e70bdd..872d5dc2 100644 --- a/apps/Backend/src/routes/feeSchedule.ts +++ b/apps/Backend/src/routes/feeSchedule.ts @@ -14,6 +14,8 @@ const SCHEDULE_FILES: Record = { UNITEDDH: "procedureCodesUnitedDH.json", UNITED_SCO: "procedureCodesUnitedDH.json", UNITEDSCO: "procedureCodesUnitedDH.json", + UNITED_UCARD: "procedureCodesUnitedUcard.json", + UNITEDUCARD: "procedureCodesUnitedUcard.json", }; function getSchedulePath(siteKey: string): string | null { diff --git a/apps/Frontend/src/assets/data/procedureCodesUnitedUcard.json b/apps/Frontend/src/assets/data/procedureCodesUnitedUcard.json new file mode 100644 index 00000000..247c4b2b --- /dev/null +++ b/apps/Frontend/src/assets/data/procedureCodesUnitedUcard.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/appointment-procedures/appointment-procedures-dialog.tsx b/apps/Frontend/src/components/appointment-procedures/appointment-procedures-dialog.tsx index 7a37605c..46cef0fc 100755 --- a/apps/Frontend/src/components/appointment-procedures/appointment-procedures-dialog.tsx +++ b/apps/Frontend/src/components/appointment-procedures/appointment-procedures-dialog.tsx @@ -92,6 +92,7 @@ export function AppointmentProceduresDialog({ if (p.includes("ddma") || p.includes("delta dental ma")) return "DDMA"; 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("ucard") || p.includes("onehealthcareid") || p.includes("one healthcare id")) return "UnitedUcard"; return ""; }; diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index 0993358f..e97711b5 100755 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -1487,6 +1487,8 @@ export function ClaimForm({ runWithPriceCheck(handleTuftsSCOClaim); } else if (siteKey === "UNITEDSCO" || siteKey === "UNITEDDH" || siteKey === "UNITED_SCO") { runWithPriceCheck(handleUnitedDHClaim); + } else if (siteKey === "UNITEDUCARD" || siteKey === "UNITED_UCARD") { + runWithPriceCheck(handleUnitedUcardClaim); } else { toast({ title: "No automated claim for this insurance", @@ -1954,6 +1956,8 @@ export function ClaimForm({ handleDDMAClaim(pricedForm); } else if (key === "united_sco" || key === "unitedco" || key === "dentalhub") { handleUnitedDHClaim(pricedForm); + } else if (key === "united_ucard" || key === "uniteducard" || key === "ucard") { + handleUnitedUcardClaim(pricedForm); } else { handleMHSubmit(pricedForm); } diff --git a/apps/Frontend/src/utils/procedureCombosMapping.ts b/apps/Frontend/src/utils/procedureCombosMapping.ts index f4d7dbdc..ae7ce26d 100755 --- a/apps/Frontend/src/utils/procedureCombosMapping.ts +++ b/apps/Frontend/src/utils/procedureCombosMapping.ts @@ -4,6 +4,7 @@ import rawCodeTable from "@/assets/data/procedureCodesMH.json"; import rawCCACodeTable from "@/assets/data/procedureCodesCCA.json"; import rawDDMACodeTable from "@/assets/data/procedureCodesDDMA.json"; import rawUnitedDHCodeTable from "@/assets/data/procedureCodesUnitedDH.json"; +import rawUnitedUcardCodeTable from "@/assets/data/procedureCodesUnitedUcard.json"; import rawTuftsSCOCodeTable from "@/assets/data/procedureCodesTuftsSCO.json"; import { PROCEDURE_COMBOS } from "./procedureCombos"; @@ -20,6 +21,7 @@ const CODE_TABLE = rawCodeTable as CodeRow[]; const CCA_CODE_TABLE = rawCCACodeTable as CodeRow[]; const DDMA_CODE_TABLE = rawDDMACodeTable as CodeRow[]; const UNITEDDH_CODE_TABLE = rawUnitedDHCodeTable as CodeRow[]; +const UNITEDUCARD_CODE_TABLE = rawUnitedUcardCodeTable as CodeRow[]; const TUFTSSCO_CODE_TABLE = rawTuftsSCOCodeTable as CodeRow[]; export type ClaimFormLike = { @@ -90,12 +92,22 @@ const TUFTSSCO_CODE_MAP: Map = (() => { return m; })(); +const UNITEDUCARD_CODE_MAP: Map = (() => { + const m = new Map(); + for (const r of UNITEDUCARD_CODE_TABLE) { + const k = normalizeCode(String(r["Procedure Code"] || "")); + if (k && !m.has(k)) m.set(k, r); + } + return m; +})(); + /** Return the correct fee-schedule map for the given insurance type. */ function getCodeMap(insuranceSiteKey?: string): Map { const k = (insuranceSiteKey ?? "").replace(/_/g, "").toLowerCase(); if (k === "cca") return CCA_CODE_MAP; if (k === "ddma") return DDMA_CODE_MAP; if (k === "unitedsco" || k === "uniteddh" || k === "dentalhub") return UNITEDDH_CODE_MAP; + if (k === "uniteducard" || k === "ucard") return UNITEDUCARD_CODE_MAP; if (k === "tuftssco" || k === "tufts") return TUFTSSCO_CODE_MAP; return CODE_MAP; // default: MassHealth } @@ -357,7 +369,7 @@ export function applyComboToForm( } -export { CODE_MAP, CCA_CODE_MAP, DDMA_CODE_MAP, UNITEDDH_CODE_MAP, TUFTSSCO_CODE_MAP, getCodeMap, getPriceForCodeWithAgeFromMap }; +export { CODE_MAP, CCA_CODE_MAP, DDMA_CODE_MAP, UNITEDDH_CODE_MAP, UNITEDUCARD_CODE_MAP, TUFTSSCO_CODE_MAP, getCodeMap, getPriceForCodeWithAgeFromMap }; export type PriceMismatch = { procedureCode: string; @@ -374,7 +386,7 @@ export function findPriceMismatches( patientDOB: string, serviceDate: string, ): PriceMismatch[] { - const supported = ["MH", "MASSHEALTH", "CCA", "DDMA", "UNITEDDH", "UNITEDSCO", "TUFTSSCO"]; + const supported = ["MH", "MASSHEALTH", "CCA", "DDMA", "UNITEDDH", "UNITEDSCO", "UNITED_UCARD", "UNITEDUCARD", "TUFTSSCO"]; if (!insuranceSiteKey || !supported.includes(insuranceSiteKey.toUpperCase())) return []; const map = getCodeMap(insuranceSiteKey); diff --git a/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py b/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py index 11bde7b9..c787e8e0 100644 --- a/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_UnitedUcard_claimSubmitWorker.py @@ -412,14 +412,17 @@ class AutomationUnitedUcardClaimSubmit: except TimeoutException: return "ERROR: Search button not found on Patient Information step" - return self._select_last_search_result() + return self._select_search_result() - def _select_last_search_result(self): + def _select_search_result(self): """ When the Subscriber ID + DOB search matches multiple coverage records ("Duplicate records have been found"), select the LAST row in the - results table (per business rule — the last entry is the current/ - most-recent coverage record) and click SELECT. + results table (the default/most-recent coverage record) and click + SELECT. If the "Member Details" name link doesn't show up afterward + (the last row turned out to be an inactive coverage record — seen + for some providers' patients, e.g. provider "JOHN"), fall back to + selecting the FIRST row instead and click SELECT again. Confirmed against the live site (2026-09-13): a results table with columns Name / Group / Coverage End Date / Address, a radio button @@ -445,14 +448,55 @@ class AutomationUnitedUcardClaimSubmit: if not result_rows: return "ERROR: No matching patient records found" - last_row = result_rows[-1] + total_rows = len(result_rows) + result = self._select_row_and_wait_for_member_link(-1, "last", total_rows, member_link_timeout=8) + if result is True: + return self._click_next("Patient Information step") + + if result != "NO_MEMBER_LINK": + return result # propagate a hard ERROR (radio/select button not found) + + if total_rows < 2: + return "ERROR: Member Details link not found after selecting last row" + + print("[UnitedUcard Claim] Last row showed no active member — retrying with first row") try: - radio = last_row.find_element(By.CSS_SELECTOR, "input[type='radio']") + WebDriverWait(self.driver, 10).until( + lambda d: d.find_elements(By.CSS_SELECTOR, "table tbody tr") or None + ) + except TimeoutException: + return "ERROR: Results table no longer available to retry with first row" + + result = self._select_row_and_wait_for_member_link(0, "first", total_rows, member_link_timeout=15) + if result is True: + return self._click_next("Patient Information step") + if result == "NO_MEMBER_LINK": + return "ERROR: Member Details link not found after selecting either the last or first row" + return result + + def _select_row_and_wait_for_member_link(self, row_index, row_label, total_rows, member_link_timeout): + """ + Clicks the radio button in the row at `row_index` (re-located fresh, + since a prior failed SELECT may have altered the DOM), clicks SELECT, + then waits up to `member_link_timeout` seconds for the "Member + Details" name link to appear. + + Returns True (link found & clicked), "NO_MEMBER_LINK" (link never + appeared — likely an inactive record), or an "ERROR: ..." string for + a hard failure (radio/select button not found). + """ + try: + row = self.driver.find_elements(By.CSS_SELECTOR, "table tbody tr")[row_index] + except IndexError: + return f"ERROR: Could not re-locate {row_label} search result row" + + try: + radio = row.find_element(By.CSS_SELECTOR, "input[type='radio']") except Exception: try: - radio = last_row.find_element(By.XPATH, ".//*[@role='radio' or contains(@class,'radio')]") + radio = row.find_element(By.XPATH, ".//*[@role='radio' or contains(@class,'radio')]") except Exception as e: - return f"ERROR: Could not find radio button in last search result row: {e}" + return f"ERROR: Could not find radio button in {row_label} search result row: {e}" self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio) time.sleep(0.3) @@ -460,7 +504,7 @@ class AutomationUnitedUcardClaimSubmit: radio.click() except Exception: self.driver.execute_script("arguments[0].click();", radio) - print(f"[UnitedUcard Claim] Selected last matching patient record ({len(result_rows)} found)") + print(f"[UnitedUcard Claim] Selected {row_label} matching patient record ({total_rows} found)") # Confirmed against the live site (2026-09-13): #