From 765368216f742afce3f9b6505fd36c371b72a3e3 Mon Sep 17 00:00:00 2001 From: Gitead Date: Tue, 15 Sep 2026 13:21:53 -0400 Subject: [PATCH] fix: route United Ucard claims correctly and add fee schedule - AI-column auto-submit and manual "Claim All" dispatchers in claim-form.tsx had no United Ucard branch, so United Ucard claims silently fell through to the MassHealth (MH) submit handler instead of opening the United Ucard site. - Added a United Ucard fee schedule (copied from United SCO as a starting point) and wired it into the frontend price-mapping/mismatch-check code and the backend fee-schedule update route, which previously had no schedule for United Ucard and fell back to MassHealth's prices. - Selenium claim worker: when the member search returns multiple coverage records, it now tries the last row first (existing default) and falls back to the first row only if the "Member Details" link never appears (i.e. the last row was an inactive record), instead of only ever trying one or the other. Co-Authored-By: Claude Sonnet 5 --- apps/Backend/src/routes/feeSchedule.ts | 2 + .../data/procedureCodesUnitedUcard.json | 209 ++++++++++++++++++ .../appointment-procedures-dialog.tsx | 1 + .../src/components/claims/claim-form.tsx | 4 + .../src/utils/procedureCombosMapping.ts | 16 +- .../selenium_UnitedUcard_claimSubmitWorker.py | 73 ++++-- 6 files changed, 289 insertions(+), 16 deletions(-) create mode 100644 apps/Frontend/src/assets/data/procedureCodesUnitedUcard.json 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): #