feat: Altus Dental eligibility check and ADA paper claim/preauth
Eligibility (no OTP, fresh browser per run like MH): logs in, opens Benefits & Claims Information, searches by Subscriber ID (or Subscriber Last Name via "Click Here"), First Name and DOB, picks the matching row from the patient list, sets ACTIVE/INACTIVE from the Coverage range, reads Group Number into the patient's Group Number, opens View Details for the last name and plan name, and saves the details page PDF to the patient's Eligibility folder. A patient switching plans is matched by name + DOB so Altus overwrites the old insurance instead of creating a duplicate. Claims: Altus uses the same 2024 ADA form as BCBS with the Altus payer address (P.O. Box 1557, Providence, RI 02901-1557). Adds Altus Claim/PreAuth (ADA Form) buttons, Claim All and AI queue routing, Print ADA Form in the claim viewer, and an Altus fee schedule (copied from BCBS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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" }
|
||||
]
|
||||
@@ -1587,6 +1587,8 @@ export function ClaimForm({
|
||||
runWithPriceCheck(handleUnitedUcardClaim);
|
||||
} else if (siteKey === "BCBS" || siteKey === "BCBS_MA") {
|
||||
runWithPriceCheck(() => handleBCBSClaim());
|
||||
} else if (siteKey === "ALTUS") {
|
||||
runWithPriceCheck(() => handleAltusClaim());
|
||||
} else {
|
||||
toast({
|
||||
title: "No automated claim for this insurance",
|
||||
@@ -1916,15 +1918,22 @@ export function ClaimForm({
|
||||
|
||||
// Saves claim to DB with all info (member ID, DOB, service date, provider, CDT codes, NPI).
|
||||
// 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.
|
||||
// BCBS MA and Altus have no online claim submission: save the claim, then open the
|
||||
// filled 2024 ADA paper form (payer address per insurance) to print, sign and mail.
|
||||
// `auto` (AI claim queue): no tab — just save the filled form to Documents > Claims.
|
||||
// `preauth`: same ADA form with box 1 "Request for Predetermination/Preauthorization"
|
||||
// ticked (claim saved as PREAUTH), saved to Documents > PreAuth.
|
||||
const handleBCBSClaim = async (
|
||||
const ADA_PAPER_PAYERS = {
|
||||
BCBS_MA: { label: "BCBS", insuranceProvider: "BCBS MA", mailTo: "BCBS MA" },
|
||||
ALTUS: { label: "Altus", insuranceProvider: "Altus Dental", mailTo: "Altus Dental" },
|
||||
} as const;
|
||||
|
||||
const handleAdaPaperClaim = async (
|
||||
payerKey: keyof typeof ADA_PAPER_PAYERS,
|
||||
formToUse?: ClaimFormData & { uploadedFiles?: File[] },
|
||||
{ auto = false, preauth = false }: { auto?: boolean; preauth?: boolean } = {},
|
||||
) => {
|
||||
const payer = ADA_PAPER_PAYERS[payerKey];
|
||||
const kind = preauth ? "preauth" : "claim";
|
||||
const f = formToUse ?? form;
|
||||
const missingFields: string[] = [];
|
||||
@@ -1972,7 +1981,7 @@ export function ClaimForm({
|
||||
serviceLines: filteredServiceLines,
|
||||
staffId: appointmentStaffId ?? Number(staff?.id),
|
||||
patientId: patientId,
|
||||
insuranceProvider: "BCBS MA",
|
||||
insuranceProvider: payer.insuranceProvider,
|
||||
appointmentId: appointmentIdToUse,
|
||||
claimFiles: claimFilesMeta,
|
||||
...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}),
|
||||
@@ -1980,29 +1989,39 @@ export function ClaimForm({
|
||||
} as any);
|
||||
|
||||
if (auto) {
|
||||
await saveAdaClaimForm(createdClaim.id!);
|
||||
await saveAdaClaimForm(createdClaim.id!, payerKey);
|
||||
} else {
|
||||
await showAdaClaimForm(createdClaim.id!, tab, "BCBS_MA", { save: true });
|
||||
await showAdaClaimForm(createdClaim.id!, tab, payerKey, { save: true });
|
||||
}
|
||||
// Saving the form marks the claim submitted — show the appointment as claimed
|
||||
queryClient.invalidateQueries({ queryKey: ["appointments", "day"] });
|
||||
toast({
|
||||
title: preauth ? "BCBS preauth form ready" : "BCBS claim form ready",
|
||||
title: `${payer.label} ${kind} form ready`,
|
||||
description: preauth
|
||||
? "PreAuth saved and ADA form added to the patient's Documents > PreAuth. Print, sign, and mail it to BCBS MA."
|
||||
: "Claim saved and ADA form added to the patient's Documents > Claims. Print, sign, and mail it to BCBS MA.",
|
||||
? `PreAuth saved and ADA form added to the patient's Documents > PreAuth. Print, sign, and mail it to ${payer.mailTo}.`
|
||||
: `Claim saved and ADA form added to the patient's Documents > Claims. Print, sign, and mail it to ${payer.mailTo}.`,
|
||||
});
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
tab?.close();
|
||||
toast({
|
||||
title: preauth ? "BCBS preauth failed" : "BCBS claim failed",
|
||||
title: `${payer.label} ${kind} failed`,
|
||||
description: err?.message ?? `Failed to create the ADA ${kind} form.`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleBCBSClaim = (
|
||||
formToUse?: ClaimFormData & { uploadedFiles?: File[] },
|
||||
opts?: { auto?: boolean; preauth?: boolean },
|
||||
) => handleAdaPaperClaim("BCBS_MA", formToUse, opts);
|
||||
|
||||
const handleAltusClaim = (
|
||||
formToUse?: ClaimFormData & { uploadedFiles?: File[] },
|
||||
opts?: { auto?: boolean; preauth?: boolean },
|
||||
) => handleAdaPaperClaim("ALTUS", formToUse, opts);
|
||||
|
||||
const handleClaimSaved = async () => {
|
||||
const missingFields: string[] = [];
|
||||
if (!form.memberId?.trim()) missingFields.push("Member ID");
|
||||
@@ -2150,6 +2169,8 @@ export function ClaimForm({
|
||||
handleUnitedUcardClaim(pricedForm);
|
||||
} else if (key === "bcbs" || key === "bcbs_ma") {
|
||||
handleBCBSClaim(pricedForm, { auto: true });
|
||||
} else if (key === "altus") {
|
||||
handleAltusClaim(pricedForm, { auto: true });
|
||||
} else {
|
||||
handleMHSubmit(pricedForm);
|
||||
}
|
||||
@@ -2177,6 +2198,13 @@ export function ClaimForm({
|
||||
insuranceSiteKey: "bcbs",
|
||||
});
|
||||
handleBCBSClaim(pricedForm, { auto: true, preauth: true });
|
||||
} else if (key === "altus") {
|
||||
const pricedForm = mapPricesForForm({
|
||||
form: { ...form, insuranceSiteKey: "altus" },
|
||||
patientDOB: patient?.dateOfBirth ?? "",
|
||||
insuranceSiteKey: "altus",
|
||||
});
|
||||
handleAltusClaim(pricedForm, { auto: true, preauth: true });
|
||||
} else {
|
||||
// Re-apply fee-schedule prices with the now-loaded patient DOB before submitting —
|
||||
// the earlier chatbot-prefill pass may have run before `patient` finished loading,
|
||||
@@ -2829,6 +2857,13 @@ export function ClaimForm({
|
||||
>
|
||||
BCBS Claim (ADA Form)
|
||||
</Button>
|
||||
<Button
|
||||
className="w-44 bg-indigo-700 hover:bg-indigo-800 text-white"
|
||||
onClick={() => runWithPriceCheck(() => handleAltusClaim())}
|
||||
title="Save the claim and open a filled ADA paper form to print and mail to Altus Dental"
|
||||
>
|
||||
Altus Claim (ADA Form)
|
||||
</Button>
|
||||
<Button
|
||||
className="w-36 bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
onClick={() => runWithPriceCheck(handleClaimSaved)}
|
||||
@@ -3371,6 +3406,13 @@ export function ClaimForm({
|
||||
>
|
||||
BCBS PreAuth (ADA Form)
|
||||
</Button>
|
||||
<Button
|
||||
className="w-44 bg-indigo-700 hover:bg-indigo-800 text-white"
|
||||
onClick={() => handleAltusClaim(undefined, { preauth: true })}
|
||||
title="Save the preauth and open a filled ADA form (Request for Predetermination/Preauthorization) to print and mail to Altus Dental"
|
||||
>
|
||||
Altus PreAuth (ADA Form)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -354,12 +354,16 @@ export default function ClaimViewModal({
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
</Button>
|
||||
{/bcbs|blue cross/i.test(claim.insuranceProvider ?? "") && (
|
||||
{/bcbs|blue cross|altus/i.test(claim.insuranceProvider ?? "") && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const tab = openPendingTab();
|
||||
showAdaClaimForm(claim.id!, tab).catch((err) =>
|
||||
showAdaClaimForm(
|
||||
claim.id!,
|
||||
tab,
|
||||
/altus/i.test(claim.insuranceProvider ?? "") ? "ALTUS" : "BCBS_MA",
|
||||
).catch((err) =>
|
||||
toast({
|
||||
title: "ADA form failed",
|
||||
description: err?.message ?? "Failed to generate ADA claim form.",
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CheckCircle, LoaderCircleIcon } from "lucide-react";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { useAppDispatch } from "@/redux/hooks";
|
||||
import { setTaskStatus } from "@/redux/slices/seleniumTaskSlice";
|
||||
import { formatLocalDate } from "@/utils/dateUtils";
|
||||
import { socket } from "@/lib/socket";
|
||||
import { QK_PATIENTS_BASE } from "@/components/patients/patient-table";
|
||||
|
||||
// ─── Main component ───────────────────────────────────────────────────────────
|
||||
|
||||
interface AltusEligibilityButtonProps {
|
||||
memberId: string;
|
||||
dateOfBirth: Date | null;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
isFormIncomplete: boolean;
|
||||
autoTrigger?: boolean;
|
||||
onAutoTriggered?: () => void;
|
||||
onPdfReady: (pdfId: number | string, fallbackFilename: string | null) => void;
|
||||
}
|
||||
|
||||
export function AltusEligibilityButton({
|
||||
memberId,
|
||||
dateOfBirth,
|
||||
firstName,
|
||||
lastName,
|
||||
isFormIncomplete,
|
||||
autoTrigger,
|
||||
onAutoTriggered,
|
||||
onPdfReady,
|
||||
}: AltusEligibilityButtonProps) {
|
||||
const { toast } = useToast();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const autoTriggeredRef = useRef(false);
|
||||
|
||||
const [isStarting, setIsStarting] = useState(false);
|
||||
|
||||
const handleStart = async () => {
|
||||
if (!dateOfBirth || !firstName?.trim() || (!memberId && !lastName?.trim())) {
|
||||
toast({
|
||||
title: "Missing fields",
|
||||
description: "Altus requires First Name, Date of Birth, and Member ID or Last Name.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const formattedDob = formatLocalDate(dateOfBirth);
|
||||
const payload = {
|
||||
memberId,
|
||||
dateOfBirth: formattedDob,
|
||||
firstName: firstName?.trim(),
|
||||
lastName: lastName?.trim(),
|
||||
insuranceSiteKey: "ALTUS",
|
||||
};
|
||||
|
||||
setIsStarting(true);
|
||||
|
||||
try {
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
key: "eligibilityCheck",
|
||||
status: "pending",
|
||||
message: "Starting Altus eligibility check…",
|
||||
})
|
||||
);
|
||||
|
||||
const response = await apiRequest(
|
||||
"POST",
|
||||
"/api/insurance-status-altus/altus-eligibility",
|
||||
{ data: JSON.stringify(payload), socketId: socket.id }
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || result.error) {
|
||||
throw new Error(result.error || `Server error (${response.status})`);
|
||||
}
|
||||
|
||||
const jobId: string = result.jobId;
|
||||
if (!jobId) throw new Error("No jobId returned from server");
|
||||
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
key: "eligibilityCheck",
|
||||
status: "pending",
|
||||
message: "Altus job queued. Logging in…",
|
||||
})
|
||||
);
|
||||
|
||||
function cleanup() {
|
||||
clearTimeout(safetyTimer);
|
||||
socket.off("job:update", onJobUpdate);
|
||||
}
|
||||
|
||||
const onJobUpdate = (data: any) => {
|
||||
if (String(data?.jobId) !== String(jobId)) return;
|
||||
|
||||
if (data.status === "active") {
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
key: "eligibilityCheck",
|
||||
status: "pending",
|
||||
message: data.message ?? "Selenium browser starting…",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
if (data.status === "completed") {
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
key: "eligibilityCheck",
|
||||
status: "success",
|
||||
message: "Altus eligibility updated and PDF attached to patient documents.",
|
||||
})
|
||||
);
|
||||
toast({
|
||||
title: "Altus eligibility complete",
|
||||
description: "Patient status was updated and the eligibility PDF was saved.",
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const pdfId = data.result?.pdfFileId;
|
||||
if (pdfId) {
|
||||
onPdfReady(pdfId, data.result?.pdfFilename ?? `eligibility_altus_${memberId || `${lastName}_${firstName}`}.pdf`);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "Altus eligibility job failed.";
|
||||
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: msg }));
|
||||
toast({ title: "Altus selenium error", description: msg, variant: "destructive" });
|
||||
}
|
||||
|
||||
setIsStarting(false);
|
||||
};
|
||||
|
||||
socket.on("job:update", onJobUpdate);
|
||||
|
||||
const safetyTimer = setTimeout(() => {
|
||||
cleanup();
|
||||
setIsStarting(false);
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
key: "eligibilityCheck",
|
||||
status: "error",
|
||||
message: "Altus job timed out waiting for completion.",
|
||||
})
|
||||
);
|
||||
}, 6 * 60 * 1000);
|
||||
|
||||
} catch (err: any) {
|
||||
console.error("AltusEligibilityButton error:", err);
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
key: "eligibilityCheck",
|
||||
status: "error",
|
||||
message: err?.message || "Failed to start Altus eligibility",
|
||||
})
|
||||
);
|
||||
toast({
|
||||
title: "Altus selenium error",
|
||||
description: err?.message || "Failed to start Altus eligibility",
|
||||
variant: "destructive",
|
||||
});
|
||||
setIsStarting(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoTrigger) { autoTriggeredRef.current = false; return; }
|
||||
if (autoTriggeredRef.current || isFormIncomplete) return;
|
||||
autoTriggeredRef.current = true;
|
||||
onAutoTriggered?.();
|
||||
handleStart();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [autoTrigger, isFormIncomplete]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="default"
|
||||
disabled={isFormIncomplete || isStarting}
|
||||
onClick={handleStart}
|
||||
>
|
||||
{isStarting ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
Altus
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -143,6 +143,7 @@ const INSURANCE_TYPE_OPTIONS: { value: string; label: string }[] = [
|
||||
{ value: "cca", label: "CCA" },
|
||||
{ value: "bcbs-ma", label: "BCBS MA" },
|
||||
{ value: "metlife", label: "MetLife" },
|
||||
{ value: "altus", label: "Altus" },
|
||||
];
|
||||
|
||||
function getAutoCheck(_dobISO: string): "mh" {
|
||||
|
||||
@@ -26,6 +26,7 @@ const SITE_KEY_OPTIONS = [
|
||||
{ value: "CCA", label: "CCA (CCA)" },
|
||||
{ value: "BCBS_MA", label: "BCBS MA (BCBS_MA)" },
|
||||
{ value: "METLIFE", label: "MetLife Dental (METLIFE)" },
|
||||
{ value: "ALTUS", label: "Altus Dental (ALTUS)" },
|
||||
];
|
||||
|
||||
export function CredentialForm({ onClose, userId, defaultValues }: CredentialFormProps) {
|
||||
|
||||
@@ -121,6 +121,7 @@ const PREAUTH_SITE_KEYS: Record<string, string> = {
|
||||
UNITEDSCO: "UNITED_SCO",
|
||||
BCBS: "BCBS", // paper ADA form, filled and saved to Documents > PreAuth
|
||||
BCBSMA: "BCBS",
|
||||
ALTUS: "ALTUS", // paper ADA form, like BCBS
|
||||
};
|
||||
const toPreauthSiteKey = (siteKey: string): string | undefined =>
|
||||
PREAUTH_SITE_KEYS[siteKey.replace(/_/g, "").toUpperCase()];
|
||||
@@ -1198,6 +1199,8 @@ export default function AppointmentsPage() {
|
||||
autoCheck = "cca";
|
||||
} else if (p.includes("metlife") || p.includes("met life")) {
|
||||
autoCheck = "metlife";
|
||||
} else if (p.includes("altus")) {
|
||||
autoCheck = "altus";
|
||||
}
|
||||
|
||||
if (autoCheck === "other-providers") {
|
||||
@@ -1740,8 +1743,8 @@ export default function AppointmentsPage() {
|
||||
autoSubmit: true,
|
||||
}));
|
||||
}
|
||||
// BCBS is a paper form filled and saved in the background — come straight back here
|
||||
if (/^bcbs/i.test(siteKey)) sessionStorage.setItem("ai_claim_return_to_schedule", "1");
|
||||
// BCBS / Altus are paper forms filled and saved in the background — come straight back here
|
||||
if (/^(bcbs|altus)/i.test(siteKey)) sessionStorage.setItem("ai_claim_return_to_schedule", "1");
|
||||
else sessionStorage.removeItem("ai_claim_return_to_schedule");
|
||||
setAiClaimModalOpen(false);
|
||||
setLocation(`/claims?appointmentId=${appointmentId}`);
|
||||
|
||||
@@ -39,6 +39,7 @@ import { DualPdfPreviewModal } from "@/components/insurance-status/dual-pdf-prev
|
||||
import { useLocation } from "wouter";
|
||||
import { DdmaEligibilityButton } from "@/components/insurance-status/ddma-buton-modal";
|
||||
import { MetLifeEligibilityButton } from "@/components/insurance-status/metlife-button-modal";
|
||||
import { AltusEligibilityButton } from "@/components/insurance-status/altus-button-modal";
|
||||
import { DeltaIndivEligibilityButton } from "@/components/insurance-status/deltaindiv-button-modal";
|
||||
import { DeltaInsEligibilityButton } from "@/components/insurance-status/deltains-button-modal";
|
||||
import { TuftsSCOEligibilityButton } from "@/components/insurance-status/tufts-sco-button-modal";
|
||||
@@ -119,6 +120,8 @@ export default function InsuranceStatusPage() {
|
||||
const isFormIncomplete = !memberId || !dateOfBirth;
|
||||
// United Ucard can also search by first name + last name + DOB when there's no member ID.
|
||||
const isUcardFormIncomplete = !dateOfBirth || (!memberId && !(firstName && lastName));
|
||||
// Altus searches by Member ID, or by Subscriber Last Name when the ID is unknown; First Name + DOB always required.
|
||||
const isAltusFormIncomplete = !dateOfBirth || !firstName?.trim() || (!memberId && !lastName?.trim());
|
||||
const [isCheckingEligibilityStatus, setIsCheckingEligibilityStatus] =
|
||||
useState(false);
|
||||
const [isCheckingEligibilityAppointment, setIsCheckingEligibilityAppointment] =
|
||||
@@ -176,7 +179,7 @@ export default function InsuranceStatusPage() {
|
||||
const [cmspAccumulatorPdfId, setCmspAccumulatorPdfId] = useState<number | string | null>(null);
|
||||
const [cmspAccumulatorFilename, setCmspAccumulatorFilename] = useState<string | null>(null);
|
||||
|
||||
const pendingAutoCheck = useRef<"mh" | "mh-history" | "cmsp" | "ddma" | "delta-ins" | "delta-indiv" | "united-sco" | "united-ucard" | "tufts-sco" | "cca" | "bcbs-ma" | "metlife" | null>(null);
|
||||
const pendingAutoCheck = useRef<"mh" | "mh-history" | "cmsp" | "ddma" | "delta-ins" | "delta-indiv" | "united-sco" | "united-ucard" | "tufts-sco" | "cca" | "bcbs-ma" | "metlife" | "altus" | null>(null);
|
||||
const [triggerTarget, setTriggerTarget] = useState<string | null>(null);
|
||||
const pendingScrollTo = useRef<string | null>(null);
|
||||
const [prefillTick, setPrefillTick] = useState(0);
|
||||
@@ -756,7 +759,7 @@ export default function InsuranceStatusPage() {
|
||||
// Auto-trigger after prefill (from chatbot or schedule page "Check Eligibility")
|
||||
useEffect(() => {
|
||||
if (!pendingAutoCheck.current || !dateOfBirth) return;
|
||||
if (!memberId && !(pendingAutoCheck.current === "united-ucard" && firstName && lastName)) return;
|
||||
if (!memberId && !((pendingAutoCheck.current === "united-ucard" || pendingAutoCheck.current === "altus") && firstName && lastName)) return;
|
||||
const check = pendingAutoCheck.current;
|
||||
pendingAutoCheck.current = null;
|
||||
if (check === "mh" || check === "mh-history") {
|
||||
@@ -917,7 +920,7 @@ export default function InsuranceStatusPage() {
|
||||
const autoCheck = params.get("autoCheck") as typeof pendingAutoCheck.current;
|
||||
const scrollTo = params.get("scrollTo");
|
||||
|
||||
const knownAutoChecks = ["mh", "mh-history", "cmsp", "ddma", "delta-ins", "delta-indiv", "united-sco", "united-ucard", "tufts-sco", "cca", "bcbs-ma", "metlife"] as const;
|
||||
const knownAutoChecks = ["mh", "mh-history", "cmsp", "ddma", "delta-ins", "delta-indiv", "united-sco", "united-ucard", "tufts-sco", "cca", "bcbs-ma", "metlife", "altus"] as const;
|
||||
if (autoCheck && (knownAutoChecks as readonly string[]).includes(autoCheck)) {
|
||||
pendingAutoCheck.current = autoCheck;
|
||||
} else if (scrollTo) {
|
||||
@@ -1010,7 +1013,7 @@ export default function InsuranceStatusPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="firstName">First Name <span className="text-muted-foreground font-normal">(Optional)</span></Label>
|
||||
<Label htmlFor="firstName">First Name <span className="text-muted-foreground font-normal">(required by Altus)</span></Label>
|
||||
<Input
|
||||
id="firstName"
|
||||
placeholder="Enter first name"
|
||||
@@ -1266,14 +1269,28 @@ export default function InsuranceStatusPage() {
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
Aetna
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
disabled={isFormIncomplete}
|
||||
>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
Altus
|
||||
</Button>
|
||||
<div title={!isLicensed ? "License required" : undefined} className={!isLicensed ? "opacity-40 pointer-events-none" : ""}>
|
||||
<AltusEligibilityButton
|
||||
memberId={memberId}
|
||||
dateOfBirth={dateOfBirth}
|
||||
firstName={firstName}
|
||||
lastName={lastName}
|
||||
isFormIncomplete={isAltusFormIncomplete}
|
||||
autoTrigger={triggerTarget === "altus"}
|
||||
onAutoTriggered={() => setTriggerTarget(null)}
|
||||
onPdfReady={async (pdfId, fallbackFilename) => {
|
||||
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const claimed = await tryClaimFromChatbot(selectedPatient?.id);
|
||||
void tryAppointmentFromChatbot();
|
||||
if (!claimed) {
|
||||
setPreviewPdfId(pdfId);
|
||||
setPreviewFallbackFilename(fallbackFilename ?? `eligibility_altus_${memberId}.pdf`);
|
||||
setPreviewOpen(true);
|
||||
}
|
||||
processNextInQueue();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div title={!isLicensed ? "License required" : undefined} className={!isLicensed ? "opacity-40 pointer-events-none" : ""}>
|
||||
<MetLifeEligibilityButton
|
||||
memberId={memberId}
|
||||
|
||||
@@ -8,6 +8,7 @@ import rawUnitedUcardCodeTable from "@/assets/data/procedureCodesUnitedUcard.jso
|
||||
import rawTuftsSCOCodeTable from "@/assets/data/procedureCodesTuftsSCO.json";
|
||||
import rawDeltainsCodeTable from "@/assets/data/procedureCodesDeltains.json";
|
||||
import rawBCBSCodeTable from "@/assets/data/procedureCodesBCBS.json";
|
||||
import rawAltusCodeTable from "@/assets/data/procedureCodesAltus.json";
|
||||
import { PROCEDURE_COMBOS } from "./procedureCombos";
|
||||
|
||||
/* ----------------------------- Types ----------------------------- */
|
||||
@@ -27,6 +28,7 @@ const UNITEDUCARD_CODE_TABLE = rawUnitedUcardCodeTable as CodeRow[];
|
||||
const TUFTSSCO_CODE_TABLE = rawTuftsSCOCodeTable as CodeRow[];
|
||||
const DELTAINS_CODE_TABLE = rawDeltainsCodeTable as CodeRow[];
|
||||
const BCBS_CODE_TABLE = rawBCBSCodeTable as CodeRow[];
|
||||
const ALTUS_CODE_TABLE = rawAltusCodeTable as CodeRow[];
|
||||
|
||||
export type ClaimFormLike = {
|
||||
serviceDate: string; // form-level service date
|
||||
@@ -87,6 +89,15 @@ const BCBS_CODE_MAP: Map<string, CodeRow> = (() => {
|
||||
return m;
|
||||
})();
|
||||
|
||||
const ALTUS_CODE_MAP: Map<string, CodeRow> = (() => {
|
||||
const m = new Map<string, CodeRow>();
|
||||
for (const r of ALTUS_CODE_TABLE) {
|
||||
const k = normalizeCode(String(r["Procedure Code"] || ""));
|
||||
if (k && !m.has(k)) m.set(k, r);
|
||||
}
|
||||
return m;
|
||||
})();
|
||||
|
||||
const UNITEDDH_CODE_MAP: Map<string, CodeRow> = (() => {
|
||||
const m = new Map<string, CodeRow>();
|
||||
for (const r of UNITEDDH_CODE_TABLE) {
|
||||
@@ -129,6 +140,7 @@ function getCodeMap(insuranceSiteKey?: string): Map<string, CodeRow> {
|
||||
if (k === "cca") return CCA_CODE_MAP;
|
||||
if (k === "ddma") return DDMA_CODE_MAP;
|
||||
if (k === "bcbs" || k === "bcbsma") return BCBS_CODE_MAP;
|
||||
if (k === "altus") return ALTUS_CODE_MAP;
|
||||
if (k === "deltains") return DELTAINS_CODE_MAP;
|
||||
if (k === "unitedsco" || k === "uniteddh" || k === "dentalhub") return UNITEDDH_CODE_MAP;
|
||||
if (k === "uniteducard" || k === "ucard") return UNITEDUCARD_CODE_MAP;
|
||||
|
||||
Reference in New Issue
Block a user