diff --git a/apps/Backend/src/queue/processors/tuftsSCOClaimProcessor.ts b/apps/Backend/src/queue/processors/tuftsSCOClaimProcessor.ts new file mode 100644 index 00000000..d0e07c09 --- /dev/null +++ b/apps/Backend/src/queue/processors/tuftsSCOClaimProcessor.ts @@ -0,0 +1,145 @@ +/** + * Processor for "tuftssco-claim-submit" jobs. + * Opens a claim on the Tufts SCO (DentaQuest) provider portal via Selenium. + * + * Flow: + * 1. POST /tuftssco-claim to Python agent → get session_id + * 2. Emit selenium:tuftssco_claim_started to frontend + * 3. Poll until completed/error + * 4. Emit result + */ +import { + forwardToSeleniumTuftsSCOClaimAgent, + getSeleniumTuftsSCOClaimSessionStatus, +} from "../../services/seleniumTuftsSCOClaimClient"; +import { io } from "../../socket"; +import { storage } from "../../storage"; + +function log(tag: string, msg: string, ctx?: any) { + console.log(`${new Date().toISOString()} [${tag}] ${msg}`, ctx ?? ""); +} + +function emitToSocket(socketId: string | undefined, event: string, payload: any) { + if (!socketId || !io) return; + try { + const socket = io.sockets.sockets.get(socketId); + if (socket) socket.emit(event, payload); + } catch (_) {} +} + +async function pollUntilDone( + sessionId: string, + socketId: string | undefined, + jobId: string, + pollTimeoutMs = 10 * 60 * 1000 +): Promise { + const maxAttempts = 1200; + const pollIntervalMs = 500; + const maxTransientErrors = 12; + let transientErrors = 0; + let lastOtpEmit = 0; + const deadline = Date.now() + pollTimeoutMs; + + for (let attempt = 0; attempt < maxAttempts; attempt++) { + if (Date.now() > deadline) { + throw new Error(`Tufts SCO claim polling timeout for session ${sessionId}`); + } + try { + const st = await getSeleniumTuftsSCOClaimSessionStatus(sessionId); + const status: string = st?.status ?? "unknown"; + log("tuftssco-claim-processor", `poll attempt=${attempt}`, { sessionId, status }); + transientErrors = 0; + + if (status === "waiting_for_otp") { + if (Date.now() - lastOtpEmit > 5000) { + emitToSocket(socketId, "selenium:otp_required", { + session_id: sessionId, + jobId, + message: "OTP required. Please enter the OTP shown by the Tufts SCO portal.", + }); + lastOtpEmit = Date.now(); + } + await new Promise((r) => setTimeout(r, pollIntervalMs)); + continue; + } + + if (status === "completed") return st.result; + if (status === "error" || status === "not_found") { + throw new Error(st?.message || `Tufts SCO claim session ended with status: ${status}`); + } + await new Promise((r) => setTimeout(r, pollIntervalMs)); + } catch (err: any) { + const isTerminal = + err?.response?.status === 404 || + (typeof err?.message === "string" && + (err.message.includes("not_found") || err.message.includes("polling timeout"))); + if (isTerminal) throw err; + transientErrors++; + if (transientErrors > maxTransientErrors) { + throw new Error(`Too many transient errors polling Tufts SCO claim session ${sessionId}`); + } + const backoff = Math.min(30_000, 500 * Math.pow(2, transientErrors - 1)); + await new Promise((r) => setTimeout(r, backoff)); + } + } + throw new Error(`Tufts SCO claim polling exhausted all attempts for session ${sessionId}`); +} + +export interface TuftsSCOClaimProcessorInput { + enrichedPayload: any; + userId: number; + claimId?: number; + socketId?: string; +} + +export async function runTuftsSCOClaimProcessor( + input: TuftsSCOClaimProcessorInput, + jobId: string +): Promise<{ status: string; pdf_url?: string; claimNumber?: string }> { + const { enrichedPayload, userId, claimId, socketId } = input; + + log("tuftssco-claim-processor", "starting Python agent session", { claimId }); + const agentResp = await forwardToSeleniumTuftsSCOClaimAgent(enrichedPayload); + + if (!agentResp?.session_id) { + throw new Error("Python agent did not return a session_id for Tufts SCO claim"); + } + + const sessionId = agentResp.session_id as string; + log("tuftssco-claim-processor", "got session_id", { sessionId }); + + emitToSocket(socketId, "selenium:tuftssco_claim_started", { session_id: sessionId, jobId }); + + const seleniumResult = await pollUntilDone(sessionId, socketId, jobId); + + if (!seleniumResult || seleniumResult.status === "error") { + throw new Error(seleniumResult?.message ?? "Tufts SCO claim session returned an error"); + } + + const claimNumber: string | undefined = seleniumResult.claimNumber ?? undefined; + const pdf_url: string | undefined = seleniumResult.pdf_url ?? undefined; + + if (claimId) { + try { + const updates: Record = { status: "REVIEW" }; + if (claimNumber) updates.claimNumber = claimNumber; + await storage.updateClaim(claimId, updates); + log("tuftssco-claim-processor", "claim record updated", { claimId, claimNumber }); + } catch (e) { + log("tuftssco-claim-processor", "failed to update claim record (non-fatal)", { error: e }); + } + } + + emitToSocket(socketId, "selenium:tuftssco_claim_completed", { + jobId, + claimId, + claimNumber, + pdf_url, + message: claimNumber + ? `Tufts SCO claim submitted — Claim #: ${claimNumber}` + : (seleniumResult?.message ?? "Tufts SCO claim submitted successfully"), + }); + + log("tuftssco-claim-processor", "done", { claimId, claimNumber }); + return { status: "success", pdf_url, claimNumber }; +} diff --git a/apps/Backend/src/routes/feeSchedule.ts b/apps/Backend/src/routes/feeSchedule.ts index 2715342e..f31f341b 100644 --- a/apps/Backend/src/routes/feeSchedule.ts +++ b/apps/Backend/src/routes/feeSchedule.ts @@ -9,6 +9,10 @@ const SCHEDULE_FILES: Record = { MASSHEALTH: "procedureCodesMH.json", CCA: "procedureCodesCCA.json", DDMA: "procedureCodesDDMA.json", + TUFTSSCO: "procedureCodesTuftsSCO.json", + TUFTS_SCO: "procedureCodesTuftsSCO.json", + UNITEDDH: "procedureCodesUnitedDH.json", + UNITED_SCO: "procedureCodesUnitedDH.json", }; function getSchedulePath(siteKey: string): string | null { diff --git a/apps/Backend/src/routes/insuranceStatusTuftsSCOClaim.ts b/apps/Backend/src/routes/insuranceStatusTuftsSCOClaim.ts new file mode 100644 index 00000000..68720fa1 --- /dev/null +++ b/apps/Backend/src/routes/insuranceStatusTuftsSCOClaim.ts @@ -0,0 +1,94 @@ +import { Router, Request, Response } from "express"; +import { storage } from "../storage"; +import { enqueueSeleniumJob } from "../queue/jobRunner"; +import { forwardOtpToSeleniumTuftsSCOClaimAgent } from "../services/seleniumTuftsSCOClaimClient"; +import { io } from "../socket"; + +const router = Router(); + +/** + * POST /tuftssco-claim + * + * Enqueues a Tufts SCO (DentaQuest) claim submission job. + * + * Body fields (JSON): + * data — claim payload (memberId, dateOfBirth, serviceDate, serviceLines, patientName, etc.) + * socketId — socket.io client id + * claimId — existing claim DB id (optional) + * + * Response: { status: "queued", jobId: "…" } + */ +router.post("/tuftssco-claim", async (req: Request, res: Response): Promise => { + if (!req.user?.id) { + return res.status(401).json({ error: "Unauthorized: user info missing" }); + } + + try { + const claimData = + typeof req.body.data === "string" + ? JSON.parse(req.body.data) + : req.body.data ?? req.body ?? {}; + + // Fetch Tufts SCO (DentaQuest) credentials + const credentials = await storage.getInsuranceCredentialByUserAndSiteKey( + req.user.id, + "TUFTS_SCO" + ); + if (!credentials) { + return res.status(404).json({ + error: "No Tufts SCO credentials found. Please add them on the Settings page.", + }); + } + + const enrichedPayload = { + claim: { + ...claimData, + dentaquestUsername: credentials.username, + dentaquestPassword: credentials.password, + }, + }; + + const socketId: string | undefined = req.body.socketId; + const claimId: number | undefined = claimData.claimId + ? Number(claimData.claimId) + : undefined; + + const jobId = enqueueSeleniumJob({ + jobType: "tuftssco-claim-submit", + userId: req.user.id, + socketId, + enrichedPayload, + claimId, + }); + + return res.json({ status: "queued", jobId }); + } catch (err: any) { + console.error("[tuftssco-claim route] error:", err); + return res.status(500).json({ + error: err.message || "Failed to enqueue Tufts SCO claim job", + }); + } +}); + +/** + * POST /claims/tuftssco-claim/selenium/submit-otp + * Body: { session_id, otp, socketId? } + */ +router.post("/tuftssco-claim/selenium/submit-otp", async (req: Request, res: Response): Promise => { + const { session_id: sessionId, otp, socketId } = req.body; + if (!sessionId || !otp) { + return res.status(400).json({ error: "session_id and otp are required" }); + } + try { + const r = await forwardOtpToSeleniumTuftsSCOClaimAgent(sessionId, otp); + if (socketId && io) { + io.to(socketId).emit("selenium:otp_submitted", { session_id: sessionId }); + } + return res.json(r); + } catch (err: any) { + console.error("[tuftssco-claim] submit-otp failed:", err?.message); + return res.status(500).json({ error: err?.message || "Failed to forward OTP" }); + } +}); + +export default router; diff --git a/apps/Backend/src/services/seleniumTuftsSCOClaimClient.ts b/apps/Backend/src/services/seleniumTuftsSCOClaimClient.ts new file mode 100644 index 00000000..9ad5e799 --- /dev/null +++ b/apps/Backend/src/services/seleniumTuftsSCOClaimClient.ts @@ -0,0 +1,35 @@ +import axios from "axios"; + +const SELENIUM_BASE = process.env.SELENIUM_SERVICE_URL ?? "http://localhost:5002"; + +/** + * POST /tuftssco-claim + * Returns { status: "started", session_id: "" } + */ +export async function forwardToSeleniumTuftsSCOClaimAgent( + data: Record +): Promise<{ status: string; session_id: string }> { + const resp = await axios.post(`${SELENIUM_BASE}/tuftssco-claim`, data); + return resp.data; +} + +/** + * GET /session/{sid}/status + */ +export async function getSeleniumTuftsSCOClaimSessionStatus( + sessionId: string +): Promise> { + const resp = await axios.get(`${SELENIUM_BASE}/session/${sessionId}/status`); + return resp.data; +} + +/** + * POST /submit-otp + */ +export async function forwardOtpToSeleniumTuftsSCOClaimAgent( + sessionId: string, + otp: string +): Promise> { + const resp = await axios.post(`${SELENIUM_BASE}/submit-otp`, { session_id: sessionId, otp }); + return resp.data; +} diff --git a/apps/Frontend/src/assets/data/procedureCodesDDMA.json b/apps/Frontend/src/assets/data/procedureCodesDDMA.json new file mode 100644 index 00000000..5bf3865b --- /dev/null +++ b/apps/Frontend/src/assets/data/procedureCodesDDMA.json @@ -0,0 +1,1196 @@ +[ + { + "Procedure Code": "D0120", + "Description": "Periodic oral evaluation - established patient", + "PriceLTEQ21": "24", + "PriceGT21": "24" + }, + { + "Procedure Code": "D0140", + "Description": "Limited oral evaluation - problem focused", + "PriceLTEQ21": 90, + "PriceGT21": 90 + }, + { + "Procedure Code": "D0145", + "Description": "Oral evaluation for a patient under three years of age and counseling with primary caregiver", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D0150", + "Description": "Comprehensive oral evaluation - new or established patient", + "PriceLTEQ21": "41", + "PriceGT21": "41" + }, + { + "Procedure Code": "D0180", + "Description": "Comprehensive periodontal evaluation - new or established patient", + "PriceLTEQ21": "37", + "PriceGT21": "37" + }, + { + "Procedure Code": "D0190", + "Description": "Screening of a patient (PHDH only)", + "PriceLTEQ21": "20", + "PriceGT21": "20" + }, + { + "Procedure Code": "D0191", + "Description": "Assessment of a patient (PHDH only)", + "PriceLTEQ21": "20", + "PriceGT21": "20" + }, + { + "Procedure Code": "D0210", + "Description": "Intraoral - complete series of radiographic images", + "PriceLTEQ21": "76", + "PriceGT21": "76" + }, + { + "Procedure Code": "D0220", + "Description": "Intraoral - periapical, first radiographic image", + "PriceLTEQ21": 60, + "PriceGT21": 60 + }, + { + "Procedure Code": "D0230", + "Description": "Intraoral - periapical, each additional radiographic image", + "PriceLTEQ21": "13", + "PriceGT21": "13" + }, + { + "Procedure Code": "D0240", + "Description": "Intraoral - occlusal radiographic image", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D0270", + "Description": "Bitewing - single radiographic image", + "PriceLTEQ21": "14", + "PriceGT21": "14" + }, + { + "Procedure Code": "D0272", + "Description": "Bitewings - two radiographic images", + "PriceLTEQ21": "25", + "PriceGT21": "25" + }, + { + "Procedure Code": "D0273", + "Description": "Bitewings - three radiographic images", + "PriceLTEQ21": "27", + "PriceGT21": "27" + }, + { + "Procedure Code": "D0274", + "Description": "Bitewings - four radiographic images", + "PriceLTEQ21": "36", + "PriceGT21": "36" + }, + { + "Procedure Code": "D0330", + "Description": "Panoramic radiographic image", + "PriceLTEQ21": "69", + "PriceGT21": "69" + }, + { + "Procedure Code": "D0340", + "Description": "Cephalometric radiograph image (Oral surgeon only)", + "PriceLTEQ21": "74", + "PriceGT21": "74" + }, + { + "Procedure Code": "D0364", + "Description": "Less than one jaw", + "Price": "350" + }, + { + "Procedure Code": "D0365", + "Description": "Mand", + "Price": "350" + }, + { + "Procedure Code": "D0366", + "Description": "Max", + "Price": "350" + }, + { + "Procedure Code": "D0367", + "Description": "", + "Price": "400" + }, + { + "Procedure Code": "D0368", + "Description": "include TMJ", + "Price": "375" + }, + { + "Procedure Code": "D0380", + "Description": "Less than one jaw", + "Price": "300" + }, + { + "Procedure Code": "D0381", + "Description": "Mand", + "Price": "300" + }, + { + "Procedure Code": "D0382", + "Description": "Max", + "Price": "300" + }, + { + "Procedure Code": "D0383", + "Description": "", + "Price": "350" + }, + { + "Procedure Code": "D1110", + "Description": "Prophylaxis – adult, 14 yo or older", + "PriceLTEQ21": "60", + "PriceGT21": "60" + }, + { + "Procedure Code": "D1120", + "Description": "Prophylaxis – child, 0-13 yo", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1206", + "Description": "Topical application of fluoride varnish", + "PriceLTEQ21": "26", + "PriceGT21": "26" + }, + { + "Procedure Code": "D1208", + "Description": "Topical application of fluoride – excluding varnish", + "PriceLTEQ21": "29", + "PriceGT21": "29" + }, + { + "Procedure Code": "D1351", + "Description": "Sealant – per tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1354", + "Description": "Application of caries arresting medicament - per tooth", + "PriceLTEQ21": "15", + "PriceGT21": "15" + }, + { + "Procedure Code": "D1510", + "Description": "Space maintainer – fixed,unilateral – per quadrant", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1516", + "Description": "Space maintainer- fixed- bilateral, maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1517", + "Description": "Space maintainer- fixed- bilateral, mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1520", + "Description": "Space maintainer – removable- unilateral- per quadrant", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1526", + "Description": "Space maintainer- removable- bilateral, maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1527", + "Description": "Space maintainer- removable- bilateral, mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1575", + "Description": "Distal shoe space maintainer - fixed- unilateral- Per Quadrant I.C", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1701", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – first dose SARSCOV2 COVID-19 VAC mRNA 30mcg/0.3mL IM DOSE 1", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1702", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – second dose SARSCOV2 COVID-19 VAC mRNA 30mcg/0.3mL IM DOSE 2", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1707", + "Description": "Janssen Covid-19 vaccine administration SARSCOV2 COVID-19 VAC Ad26 5x1010 VP/.5mL IM SINGLE DOSE", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1708", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – third dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1709", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – booster dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1712", + "Description": "Janssen Covid-19 vaccine administration - booster dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1713", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration tris-sucrose pediatric – first dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1714", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration tris-sucrose pediatric – second dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1999", + "Description": "", + "Price": "50" + }, + { + "Procedure Code": "D2140", + "Description": "Amalgam-one surface, primary or permanent", + "PriceLTEQ21": "62", + "PriceGT21": "62" + }, + { + "Procedure Code": "D2150", + "Description": "Amalgam-two surfaces, primary or permanent", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D2955", + "Description": "post renoval", + "Price": "350" + }, + { + "Procedure Code": "D4910", + "Description": "perio maintains", + "Price": "250" + }, + { + "Procedure Code": "D5510", + "Description": "Repair broken complete denture base (QUAD)", + "Price": "400" + }, + { + "Procedure Code": "D6010", + "Description": "Surgical placement of implant body", + "Price": "1600" + }, + { + "Procedure Code": "D6056", + "Description": "pre fab abut", + "Price": "750" + }, + { + "Procedure Code": "D6057", + "Description": "custom abut", + "Price": "800" + }, + { + "Procedure Code": "D6058", + "Description": "porcelain, implant crown, ceramic crown", + "Price": "1400" + }, + { + "Procedure Code": "D6059", + "Description": "", + "Price": "1400" + }, + { + "Procedure Code": "D6100", + "Description": "", + "Price": "320" + }, + { + "Procedure Code": "D6110", + "Description": "implant", + "Price": "1600" + }, + { + "Procedure Code": "D6242", + "Description": "noble metal. For united", + "Price": "1400" + }, + { + "Procedure Code": "D6245", + "Description": "porcelain, not for united", + "Price": "1400" + }, + { + "Procedure Code": "D7910", + "Description": "suture, small wound up to 5 mm", + "Price": "400" + }, + { + "Procedure Code": "D7950", + "Description": "max", + "Price": "800" + }, + { + "Procedure Code": "D2160", + "Description": "Amalgam-three surfaces, primary or permanent", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2161", + "Description": "Amalgam-four or more surfaces, primary or permanent", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2330", + "Description": "Resin-based composite – one surface, anterior", + "PriceLTEQ21": "72", + "PriceGT21": "72" + }, + { + "Procedure Code": "D2331", + "Description": "Resin-based composite – two surfaces, anterior", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2332", + "Description": "Resin-based composite – three surfaces, anterior", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2335", + "Description": "Resin-based composite – four or more surfaces or involving incisal angle (anterior)", + "PriceLTEQ21": "146", + "PriceGT21": "146" + }, + { + "Procedure Code": "D2390", + "Description": "Resin-based composite crown, anterior", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2391", + "Description": "Resin-based composite – one surface, posterior", + "PriceLTEQ21": "62", + "PriceGT21": "62" + }, + { + "Procedure Code": "D2392", + "Description": "Resin-based composite – two surfaces, posterior", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D2393", + "Description": "Resin-based composite – three surfaces, posterior", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2394", + "Description": "Resin-based composite – four or more surfaces, posterior", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2710", + "Description": "Crown – resin-based composite (indirect)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2740", + "Description": "Crown – porcelain/ceramic", + "PriceLTEQ21": "729", + "PriceGT21": "729" + }, + { + "Procedure Code": "D2750", + "Description": "Crown – porcelain fused to high noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2751", + "Description": "Crown – porcelain fused to predominantly base metal", + "PriceLTEQ21": "613", + "PriceGT21": "613" + }, + { + "Procedure Code": "D2752", + "Description": "Crown – porcelain fused to noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2790", + "Description": "Crown – full cast high noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2910", + "Description": "Re-cement or re-bond inlay, onlay or partial coverage restoration", + "PriceLTEQ21": "57", + "PriceGT21": "57" + }, + { + "Procedure Code": "D2920", + "Description": "Re-cement or re-bond crown", + "PriceLTEQ21": 150, + "PriceGT21": 150 + }, + { + "Procedure Code": "D2929", + "Description": "Prefabricated porcelain/ceramic crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2930", + "Description": "Prefabricated stainless steel crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2931", + "Description": "Prefabricated stainless steel crown – permanent tooth", + "PriceLTEQ21": "171", + "PriceGT21": "171" + }, + { + "Procedure Code": "D2932", + "Description": "Prefabricated resin crown", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2934", + "Description": "Prefabricated esthetic coated stainless steel crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2950", + "Description": "Core buildup, including any pins when required", + "PriceLTEQ21": "164", + "PriceGT21": "164" + }, + { + "Procedure Code": "D2951", + "Description": "Pin retention – per tooth, in addition to restoration", + "PriceLTEQ21": "27", + "PriceGT21": "27" + }, + { + "Procedure Code": "D2954", + "Description": "Prefabricated post and core in addition to crown", + "PriceLTEQ21": "191", + "PriceGT21": "191" + }, + { + "Procedure Code": "D2980", + "Description": "Crown repair necessitated by restorative material failure", + "PriceLTEQ21": "115", + "PriceGT21": "115" + }, + { + "Procedure Code": "D2999", + "Description": "Unspecified restorative procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D3120", + "Description": "Pulp cap – indirect (excluding final restoration)", + "PriceLTEQ21": "34", + "PriceGT21": "34" + }, + { + "Procedure Code": "D3220", + "Description": "Therapeutic pulpotomy (excluding final restoration) – removal of pulp coronal to the dentinocemental junction and application of medicament", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D3310", + "Description": "Endodontic therapy, anterior (excluding final restoration)", + "PriceLTEQ21": "544", + "PriceGT21": "544" + }, + { + "Procedure Code": "D3320", + "Description": "Endodontic therapy, premolar tooth (excluding final restoration)", + "PriceLTEQ21": "639", + "PriceGT21": "639" + }, + { + "Procedure Code": "D3330", + "Description": "Endodontic therapy, molar tooth (excluding final restoration)", + "PriceLTEQ21": "829", + "PriceGT21": "829" + }, + { + "Procedure Code": "D3346", + "Description": "Retreatment of previous root canal therapy – anterior", + "PriceLTEQ21": "456", + "PriceGT21": "456" + }, + { + "Procedure Code": "D3347", + "Description": "Retreatment of previous root canal therapy – premolar", + "PriceLTEQ21": "538", + "PriceGT21": "538" + }, + { + "Procedure Code": "D3348", + "Description": "Retreatment of previous root canal therapy – molar", + "PriceLTEQ21": "613", + "PriceGT21": "613" + }, + { + "Procedure Code": "D3410", + "Description": "Apicoectomy – anterior", + "PriceLTEQ21": "407", + "PriceGT21": "407" + }, + { + "Procedure Code": "D3421", + "Description": "Apicoectomy – premolar (first root)", + "PriceLTEQ21": "460", + "PriceGT21": "460" + }, + { + "Procedure Code": "D3425", + "Description": "Apicoectomy – molar (first root)", + "PriceLTEQ21": "598", + "PriceGT21": "598" + }, + { + "Procedure Code": "D3426", + "Description": "Apicoectomy (each additional root)", + "PriceLTEQ21": "230", + "PriceGT21": "230" + }, + { + "Procedure Code": "D4210", + "Description": "Gingivectomy or gingivoplasty - Four or more contiguous teeth or bounded teeth spaces per quadrant", + "PriceLTEQ21": "307", + "PriceGT21": "307" + }, + { + "Procedure Code": "D4211", + "Description": "Gingivectomy or gingivoplasty - one to three contiguous teeth or bounded teeth spaces per quadrant", + "PriceLTEQ21": "111", + "PriceGT21": "111" + }, + { + "Procedure Code": "D4341", + "Description": "Periodontal scaling and root planing - four or more teeth per quadrant", + "PriceLTEQ21": "134", + "PriceGT21": "134" + }, + { + "Procedure Code": "D4342", + "Description": "Periodontal scaling and root planing - one to three teeth, per quadrant", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D4346", + "Description": "Scaling in presence of generalized moderate or severe gingival inflammation – full mouth, after oral evaluation", + "PriceLTEQ21": "60", + "PriceGT21": "60" + }, + { + "Procedure Code": "D5110", + "Description": "Complete denture – maxillary", + "PriceLTEQ21": "730", + "PriceGT21": "730" + }, + { + "Procedure Code": "D5120", + "Description": "Complete denture – mandibular", + "PriceLTEQ21": "730", + "PriceGT21": "730" + }, + { + "Procedure Code": "D5130", + "Description": "Immediate denture – maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5140", + "Description": "Immediate denture - mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5211", + "Description": "Maxillary partial denture - resin base (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "556", + "PriceGT21": "556" + }, + { + "Procedure Code": "D5212", + "Description": "Mandibular partial denture - resin base (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "595", + "PriceGT21": "595" + }, + { + "Procedure Code": "D5213", + "Description": "Maxillary partial denture- cast metal framework with resin denture bases (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5214", + "Description": "Mandibular partial denture - cast metal framework with resin denture bases (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5225", + "Description": "Maxillary partial denture- flexible base", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5226", + "Description": "Mandibular partial denture- flexible base", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5511", + "Description": "Repair broken complete denture base, mandibular", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D5512", + "Description": "Repair broken complete denture base, maxillary", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D5520", + "Description": "Replace missing or broken teeth - complete denture (each tooth)", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5611", + "Description": "Repair broken resin partial denture base, mandibular", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5612", + "Description": "Repair broken resin partial denture base, maxillary", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5621", + "Description": "Repair broken cast partial denture base, mandibular", + "PriceLTEQ21": "104", + "PriceGT21": "104" + }, + { + "Procedure Code": "D5622", + "Description": "Repair broken cast partial denture base, maxillary", + "PriceLTEQ21": "104", + "PriceGT21": "104" + }, + { + "Procedure Code": "D5630", + "Description": "Repair or replace broken retentive/clasping materials – per tooth", + "PriceLTEQ21": "99", + "PriceGT21": "99" + }, + { + "Procedure Code": "D5640", + "Description": "Replace broken teeth - per tooth", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5650", + "Description": "Add tooth to existing partial denture", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D5660", + "Description": "Add clasp to existing partial denture per tooth", + "PriceLTEQ21": "98", + "PriceGT21": "98" + }, + { + "Procedure Code": "D5730", + "Description": "Reline complete maxillary denture (direct)", + "PriceLTEQ21": "158", + "PriceGT21": "158" + }, + { + "Procedure Code": "D5731", + "Description": "Reline lower complete mandibular denture (direct)", + "PriceLTEQ21": "173", + "PriceGT21": "173" + }, + { + "Procedure Code": "D5740", + "Description": "Reline maxillary partial denture(chairside)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5741", + "Description": "Reline mandibular partial denture(chairside)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5750", + "Description": "Reline complete maxillary denture (indirect)", + "PriceLTEQ21": "214", + "PriceGT21": "214" + }, + { + "Procedure Code": "D5751", + "Description": "Reline complete mandibular denture (indirect)", + "PriceLTEQ21": "215", + "PriceGT21": "215" + }, + { + "Procedure Code": "D5760", + "Description": "Reline maxillary partial denture (laboratory)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5761", + "Description": "Reline mandibular partial denture (laboratory)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6241", + "Description": "Pontic-porcelain fused metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6751", + "Description": "Retainer crown-porcelain fused to metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6930", + "Description": "Re-cement or re-bond fixed partial denture", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6980", + "Description": "Fixed partial denture repair", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6999", + "Description": "Fixed prosthodontic procedure", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D7111", + "Description": "Extraction, coronal remnants - primary tooth", + "PriceLTEQ21": "75", + "PriceGT21": "75" + }, + { + "Procedure Code": "D7140", + "Description": "Extraction, erupted tooth or exposed root (elevation and/or forceps removal)", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D7210", + "Description": "Extraction, erupted tooth requiring removal of bone and/or sectioning of tooth, and including elevation of mucoperiosteal flap if indicated", + "PriceLTEQ21": "149", + "PriceGT21": "149" + }, + { + "Procedure Code": "D7220", + "Description": "Removal of impacted tooth - soft tissue", + "PriceLTEQ21": "191", + "PriceGT21": "191" + }, + { + "Procedure Code": "D7230", + "Description": "Removal of impacted tooth - partially bony", + "PriceLTEQ21": "249", + "PriceGT21": "249" + }, + { + "Procedure Code": "D7240", + "Description": "Removal of impacted tooth - completely bony", + "PriceLTEQ21": "295", + "PriceGT21": "295" + }, + { + "Procedure Code": "D7250", + "Description": "Surgical removal of residual tooth roots (cutting procedure)", + "PriceLTEQ21": "144", + "PriceGT21": "144" + }, + { + "Procedure Code": "D7251", + "Description": "Coronectomy- intentional partial tooth removal, impacted teeth only", + "PriceLTEQ21": "134", + "PriceGT21": "134" + }, + { + "Procedure Code": "D7270", + "Description": "Tooth reimplantation and/or stabilization of accidentally evulsed or displaced tooth", + "PriceLTEQ21": "106", + "PriceGT21": "106" + }, + { + "Procedure Code": "D7280", + "Description": "Surgical access of an unerupted tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D7283", + "Description": "Placement of device to facilitate eruption of impacted tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D7310", + "Description": "Alveoloplasty in conjunction with extractions-four or more teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "142", + "PriceGT21": "142" + }, + { + "Procedure Code": "D7311", + "Description": "Alveoloplasty in conjunction with extractions - one to three teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "128", + "PriceGT21": "128" + }, + { + "Procedure Code": "D7320", + "Description": "Alveoloplasty not in conjunction with extractions- four or more teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "187", + "PriceGT21": "187" + }, + { + "Procedure Code": "D7321", + "Description": "Alveoloplasty not in conjunction with extractions - one to three teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "149", + "PriceGT21": "149" + }, + { + "Procedure Code": "D7340", + "Description": "Vestibuloplasty - ridge extension (second epithelialization)", + "PriceLTEQ21": "747", + "PriceGT21": "747" + }, + { + "Procedure Code": "D7350", + "Description": "Vestibuloplasty - ridge extension (Oral surgeon only)", + "PriceLTEQ21": "943", + "PriceGT21": "943" + }, + { + "Procedure Code": "D7410", + "Description": "Radical excision - lesion diameter up to 1.25cm", + "PriceLTEQ21": "115", + "PriceGT21": "115" + }, + { + "Procedure Code": "D7411", + "Description": "Excision of benign lesion greater than 1.25 cm", + "PriceLTEQ21": "208", + "PriceGT21": "208" + }, + { + "Procedure Code": "D7450", + "Description": "Removal of benign odontogenic cyst or tumor - lesion diameter up to 1.25 cm", + "PriceLTEQ21": "248", + "PriceGT21": "248" + }, + { + "Procedure Code": "D7451", + "Description": "Removal of benign odontogenic cyst or tumor - lesion diameter greater than 1.25 cm", + "PriceLTEQ21": "288", + "PriceGT21": "288" + }, + { + "Procedure Code": "D7460", + "Description": "Removal of benign nonodontogenic cyst or tumor - lesion diameter up to 1.25 cm", + "PriceLTEQ21": "121", + "PriceGT21": "121" + }, + { + "Procedure Code": "D7461", + "Description": "Removal of benign nonodontogenic cyst or tumor - lesion diameter greater than 1.25 cm", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7471", + "Description": "Removal of lateral exostosis (maxilla or mandible) (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7472", + "Description": "Removal of torus palatinus (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7473", + "Description": "Removal of torus mandibularis (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7961", + "Description": "Buccal/labial frenectomy (frenulectomy)", + "PriceLTEQ21": "107", + "PriceGT21": "107" + }, + { + "Procedure Code": "D7962", + "Description": "Lingual frenectomy (frenulectomy)", + "PriceLTEQ21": "107", + "PriceGT21": "107" + }, + { + "Procedure Code": "D7963", + "Description": "Frenuloplasty", + "PriceLTEQ21": "416", + "PriceGT21": "416" + }, + { + "Procedure Code": "D7970", + "Description": "Excision of hyperplastic tissue - per arch", + "PriceLTEQ21": "246", + "PriceGT21": "246" + }, + { + "Procedure Code": "D7999", + "Description": "Unspecified oral surgery procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D8010", + "Description": "Limited orthodontic treamtnent of the primary transition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8020", + "Description": "Limited orthodontic treatment of the transitional dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8030", + "Description": "Limited orthodontic treatment of the adolescent dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8040", + "Description": "Limited orthodontic treatment of the adult dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8070", + "Description": "Comprehensive orthodontic treatment of the transitional dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8080", + "Description": "Comprehensive orthodontic treatment of the adolescent dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8090", + "Description": "Comprehensive orthodontic treatment of the adult dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8660", + "Description": "Pre-orthodontic treatment examination to monitor growth and development (records fee) (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8670", + "Description": "Periodic orthodontic treatment visit (Orthodontist only)", + "PriceLTEQ21": "215", + "PriceGT21": "215" + }, + { + "Procedure Code": "D8680", + "Description": "Orthodontic retention (removal of appliances, construction and placement of retainer(s)) (Orthodontist only)", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D8703", + "Description": "Replacement of lost or broken retainer- maxillary (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8704", + "Description": "Replacement of lost or broken retainer- mandibular (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8999", + "Description": "Unspecified orthodontic procedure, by report (Orthodontist only) I.C I.C** Y Y**", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9110", + "Description": "Palliative treatment of dental pain – per visit", + "PriceLTEQ21": "36", + "PriceGT21": "36" + }, + { + "Procedure Code": "D9222", + "Description": "Deep sedation/general anesthesia – first 15 minutes", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D9223", + "Description": "Deep sedation/general anesthesia – each additional 15- minute increment", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D9230", + "Description": "Analgesia, anxiolysis, inhalation of nitrous oxide", + "PriceLTEQ21": "15", + "PriceGT21": "15" + }, + { + "Procedure Code": "D9248", + "Description": "Nonintravenous conscious sedation", + "PriceLTEQ21": "45", + "PriceGT21": "45" + }, + { + "Procedure Code": "D9310", + "Description": "Consultation- Diagnostic service provided by dentist or physician other than requesting dentist or physician (Specialist only)", + "PriceLTEQ21": "63", + "PriceGT21": "63" + }, + { + "Procedure Code": "D9410", + "Description": "House/extended care facility call, once per facility per day", + "PriceLTEQ21": "39", + "PriceGT21": "39" + }, + { + "Procedure Code": "D9450", + "Description": "Rural add-on encounter payment", + "PriceLTEQ21": "31", + "PriceGT21": "31" + }, + { + "Procedure Code": "D9920", + "Description": "Behavior management, by report", + "PriceLTEQ21": "86", + "PriceGT21": "86" + }, + { + "Procedure Code": "D9930", + "Description": "Treatment of complications (postsurgical) - unusual circumstances, by report", + "PriceLTEQ21": "30", + "PriceGT21": "30" + }, + { + "Procedure Code": "D9941", + "Description": "Fabrication of athletic mouthguard", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9944", + "Description": "Occlusal guard - hard appliance, full arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9945", + "Description": "Occlusal guard - soft appliance, full arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9946", + "Description": "Occlusal guard - hard appliance, partial arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9999", + "Description": "Unspecified adjunctive procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + } +] \ No newline at end of file diff --git a/apps/Frontend/src/assets/data/procedureCodesTuftsSCO.json b/apps/Frontend/src/assets/data/procedureCodesTuftsSCO.json new file mode 100644 index 00000000..5bf3865b --- /dev/null +++ b/apps/Frontend/src/assets/data/procedureCodesTuftsSCO.json @@ -0,0 +1,1196 @@ +[ + { + "Procedure Code": "D0120", + "Description": "Periodic oral evaluation - established patient", + "PriceLTEQ21": "24", + "PriceGT21": "24" + }, + { + "Procedure Code": "D0140", + "Description": "Limited oral evaluation - problem focused", + "PriceLTEQ21": 90, + "PriceGT21": 90 + }, + { + "Procedure Code": "D0145", + "Description": "Oral evaluation for a patient under three years of age and counseling with primary caregiver", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D0150", + "Description": "Comprehensive oral evaluation - new or established patient", + "PriceLTEQ21": "41", + "PriceGT21": "41" + }, + { + "Procedure Code": "D0180", + "Description": "Comprehensive periodontal evaluation - new or established patient", + "PriceLTEQ21": "37", + "PriceGT21": "37" + }, + { + "Procedure Code": "D0190", + "Description": "Screening of a patient (PHDH only)", + "PriceLTEQ21": "20", + "PriceGT21": "20" + }, + { + "Procedure Code": "D0191", + "Description": "Assessment of a patient (PHDH only)", + "PriceLTEQ21": "20", + "PriceGT21": "20" + }, + { + "Procedure Code": "D0210", + "Description": "Intraoral - complete series of radiographic images", + "PriceLTEQ21": "76", + "PriceGT21": "76" + }, + { + "Procedure Code": "D0220", + "Description": "Intraoral - periapical, first radiographic image", + "PriceLTEQ21": 60, + "PriceGT21": 60 + }, + { + "Procedure Code": "D0230", + "Description": "Intraoral - periapical, each additional radiographic image", + "PriceLTEQ21": "13", + "PriceGT21": "13" + }, + { + "Procedure Code": "D0240", + "Description": "Intraoral - occlusal radiographic image", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D0270", + "Description": "Bitewing - single radiographic image", + "PriceLTEQ21": "14", + "PriceGT21": "14" + }, + { + "Procedure Code": "D0272", + "Description": "Bitewings - two radiographic images", + "PriceLTEQ21": "25", + "PriceGT21": "25" + }, + { + "Procedure Code": "D0273", + "Description": "Bitewings - three radiographic images", + "PriceLTEQ21": "27", + "PriceGT21": "27" + }, + { + "Procedure Code": "D0274", + "Description": "Bitewings - four radiographic images", + "PriceLTEQ21": "36", + "PriceGT21": "36" + }, + { + "Procedure Code": "D0330", + "Description": "Panoramic radiographic image", + "PriceLTEQ21": "69", + "PriceGT21": "69" + }, + { + "Procedure Code": "D0340", + "Description": "Cephalometric radiograph image (Oral surgeon only)", + "PriceLTEQ21": "74", + "PriceGT21": "74" + }, + { + "Procedure Code": "D0364", + "Description": "Less than one jaw", + "Price": "350" + }, + { + "Procedure Code": "D0365", + "Description": "Mand", + "Price": "350" + }, + { + "Procedure Code": "D0366", + "Description": "Max", + "Price": "350" + }, + { + "Procedure Code": "D0367", + "Description": "", + "Price": "400" + }, + { + "Procedure Code": "D0368", + "Description": "include TMJ", + "Price": "375" + }, + { + "Procedure Code": "D0380", + "Description": "Less than one jaw", + "Price": "300" + }, + { + "Procedure Code": "D0381", + "Description": "Mand", + "Price": "300" + }, + { + "Procedure Code": "D0382", + "Description": "Max", + "Price": "300" + }, + { + "Procedure Code": "D0383", + "Description": "", + "Price": "350" + }, + { + "Procedure Code": "D1110", + "Description": "Prophylaxis – adult, 14 yo or older", + "PriceLTEQ21": "60", + "PriceGT21": "60" + }, + { + "Procedure Code": "D1120", + "Description": "Prophylaxis – child, 0-13 yo", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1206", + "Description": "Topical application of fluoride varnish", + "PriceLTEQ21": "26", + "PriceGT21": "26" + }, + { + "Procedure Code": "D1208", + "Description": "Topical application of fluoride – excluding varnish", + "PriceLTEQ21": "29", + "PriceGT21": "29" + }, + { + "Procedure Code": "D1351", + "Description": "Sealant – per tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1354", + "Description": "Application of caries arresting medicament - per tooth", + "PriceLTEQ21": "15", + "PriceGT21": "15" + }, + { + "Procedure Code": "D1510", + "Description": "Space maintainer – fixed,unilateral – per quadrant", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1516", + "Description": "Space maintainer- fixed- bilateral, maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1517", + "Description": "Space maintainer- fixed- bilateral, mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1520", + "Description": "Space maintainer – removable- unilateral- per quadrant", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1526", + "Description": "Space maintainer- removable- bilateral, maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1527", + "Description": "Space maintainer- removable- bilateral, mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1575", + "Description": "Distal shoe space maintainer - fixed- unilateral- Per Quadrant I.C", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1701", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – first dose SARSCOV2 COVID-19 VAC mRNA 30mcg/0.3mL IM DOSE 1", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1702", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – second dose SARSCOV2 COVID-19 VAC mRNA 30mcg/0.3mL IM DOSE 2", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1707", + "Description": "Janssen Covid-19 vaccine administration SARSCOV2 COVID-19 VAC Ad26 5x1010 VP/.5mL IM SINGLE DOSE", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1708", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – third dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1709", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – booster dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1712", + "Description": "Janssen Covid-19 vaccine administration - booster dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1713", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration tris-sucrose pediatric – first dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1714", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration tris-sucrose pediatric – second dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1999", + "Description": "", + "Price": "50" + }, + { + "Procedure Code": "D2140", + "Description": "Amalgam-one surface, primary or permanent", + "PriceLTEQ21": "62", + "PriceGT21": "62" + }, + { + "Procedure Code": "D2150", + "Description": "Amalgam-two surfaces, primary or permanent", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D2955", + "Description": "post renoval", + "Price": "350" + }, + { + "Procedure Code": "D4910", + "Description": "perio maintains", + "Price": "250" + }, + { + "Procedure Code": "D5510", + "Description": "Repair broken complete denture base (QUAD)", + "Price": "400" + }, + { + "Procedure Code": "D6010", + "Description": "Surgical placement of implant body", + "Price": "1600" + }, + { + "Procedure Code": "D6056", + "Description": "pre fab abut", + "Price": "750" + }, + { + "Procedure Code": "D6057", + "Description": "custom abut", + "Price": "800" + }, + { + "Procedure Code": "D6058", + "Description": "porcelain, implant crown, ceramic crown", + "Price": "1400" + }, + { + "Procedure Code": "D6059", + "Description": "", + "Price": "1400" + }, + { + "Procedure Code": "D6100", + "Description": "", + "Price": "320" + }, + { + "Procedure Code": "D6110", + "Description": "implant", + "Price": "1600" + }, + { + "Procedure Code": "D6242", + "Description": "noble metal. For united", + "Price": "1400" + }, + { + "Procedure Code": "D6245", + "Description": "porcelain, not for united", + "Price": "1400" + }, + { + "Procedure Code": "D7910", + "Description": "suture, small wound up to 5 mm", + "Price": "400" + }, + { + "Procedure Code": "D7950", + "Description": "max", + "Price": "800" + }, + { + "Procedure Code": "D2160", + "Description": "Amalgam-three surfaces, primary or permanent", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2161", + "Description": "Amalgam-four or more surfaces, primary or permanent", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2330", + "Description": "Resin-based composite – one surface, anterior", + "PriceLTEQ21": "72", + "PriceGT21": "72" + }, + { + "Procedure Code": "D2331", + "Description": "Resin-based composite – two surfaces, anterior", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2332", + "Description": "Resin-based composite – three surfaces, anterior", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2335", + "Description": "Resin-based composite – four or more surfaces or involving incisal angle (anterior)", + "PriceLTEQ21": "146", + "PriceGT21": "146" + }, + { + "Procedure Code": "D2390", + "Description": "Resin-based composite crown, anterior", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2391", + "Description": "Resin-based composite – one surface, posterior", + "PriceLTEQ21": "62", + "PriceGT21": "62" + }, + { + "Procedure Code": "D2392", + "Description": "Resin-based composite – two surfaces, posterior", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D2393", + "Description": "Resin-based composite – three surfaces, posterior", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2394", + "Description": "Resin-based composite – four or more surfaces, posterior", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2710", + "Description": "Crown – resin-based composite (indirect)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2740", + "Description": "Crown – porcelain/ceramic", + "PriceLTEQ21": "729", + "PriceGT21": "729" + }, + { + "Procedure Code": "D2750", + "Description": "Crown – porcelain fused to high noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2751", + "Description": "Crown – porcelain fused to predominantly base metal", + "PriceLTEQ21": "613", + "PriceGT21": "613" + }, + { + "Procedure Code": "D2752", + "Description": "Crown – porcelain fused to noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2790", + "Description": "Crown – full cast high noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2910", + "Description": "Re-cement or re-bond inlay, onlay or partial coverage restoration", + "PriceLTEQ21": "57", + "PriceGT21": "57" + }, + { + "Procedure Code": "D2920", + "Description": "Re-cement or re-bond crown", + "PriceLTEQ21": 150, + "PriceGT21": 150 + }, + { + "Procedure Code": "D2929", + "Description": "Prefabricated porcelain/ceramic crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2930", + "Description": "Prefabricated stainless steel crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2931", + "Description": "Prefabricated stainless steel crown – permanent tooth", + "PriceLTEQ21": "171", + "PriceGT21": "171" + }, + { + "Procedure Code": "D2932", + "Description": "Prefabricated resin crown", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2934", + "Description": "Prefabricated esthetic coated stainless steel crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2950", + "Description": "Core buildup, including any pins when required", + "PriceLTEQ21": "164", + "PriceGT21": "164" + }, + { + "Procedure Code": "D2951", + "Description": "Pin retention – per tooth, in addition to restoration", + "PriceLTEQ21": "27", + "PriceGT21": "27" + }, + { + "Procedure Code": "D2954", + "Description": "Prefabricated post and core in addition to crown", + "PriceLTEQ21": "191", + "PriceGT21": "191" + }, + { + "Procedure Code": "D2980", + "Description": "Crown repair necessitated by restorative material failure", + "PriceLTEQ21": "115", + "PriceGT21": "115" + }, + { + "Procedure Code": "D2999", + "Description": "Unspecified restorative procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D3120", + "Description": "Pulp cap – indirect (excluding final restoration)", + "PriceLTEQ21": "34", + "PriceGT21": "34" + }, + { + "Procedure Code": "D3220", + "Description": "Therapeutic pulpotomy (excluding final restoration) – removal of pulp coronal to the dentinocemental junction and application of medicament", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D3310", + "Description": "Endodontic therapy, anterior (excluding final restoration)", + "PriceLTEQ21": "544", + "PriceGT21": "544" + }, + { + "Procedure Code": "D3320", + "Description": "Endodontic therapy, premolar tooth (excluding final restoration)", + "PriceLTEQ21": "639", + "PriceGT21": "639" + }, + { + "Procedure Code": "D3330", + "Description": "Endodontic therapy, molar tooth (excluding final restoration)", + "PriceLTEQ21": "829", + "PriceGT21": "829" + }, + { + "Procedure Code": "D3346", + "Description": "Retreatment of previous root canal therapy – anterior", + "PriceLTEQ21": "456", + "PriceGT21": "456" + }, + { + "Procedure Code": "D3347", + "Description": "Retreatment of previous root canal therapy – premolar", + "PriceLTEQ21": "538", + "PriceGT21": "538" + }, + { + "Procedure Code": "D3348", + "Description": "Retreatment of previous root canal therapy – molar", + "PriceLTEQ21": "613", + "PriceGT21": "613" + }, + { + "Procedure Code": "D3410", + "Description": "Apicoectomy – anterior", + "PriceLTEQ21": "407", + "PriceGT21": "407" + }, + { + "Procedure Code": "D3421", + "Description": "Apicoectomy – premolar (first root)", + "PriceLTEQ21": "460", + "PriceGT21": "460" + }, + { + "Procedure Code": "D3425", + "Description": "Apicoectomy – molar (first root)", + "PriceLTEQ21": "598", + "PriceGT21": "598" + }, + { + "Procedure Code": "D3426", + "Description": "Apicoectomy (each additional root)", + "PriceLTEQ21": "230", + "PriceGT21": "230" + }, + { + "Procedure Code": "D4210", + "Description": "Gingivectomy or gingivoplasty - Four or more contiguous teeth or bounded teeth spaces per quadrant", + "PriceLTEQ21": "307", + "PriceGT21": "307" + }, + { + "Procedure Code": "D4211", + "Description": "Gingivectomy or gingivoplasty - one to three contiguous teeth or bounded teeth spaces per quadrant", + "PriceLTEQ21": "111", + "PriceGT21": "111" + }, + { + "Procedure Code": "D4341", + "Description": "Periodontal scaling and root planing - four or more teeth per quadrant", + "PriceLTEQ21": "134", + "PriceGT21": "134" + }, + { + "Procedure Code": "D4342", + "Description": "Periodontal scaling and root planing - one to three teeth, per quadrant", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D4346", + "Description": "Scaling in presence of generalized moderate or severe gingival inflammation – full mouth, after oral evaluation", + "PriceLTEQ21": "60", + "PriceGT21": "60" + }, + { + "Procedure Code": "D5110", + "Description": "Complete denture – maxillary", + "PriceLTEQ21": "730", + "PriceGT21": "730" + }, + { + "Procedure Code": "D5120", + "Description": "Complete denture – mandibular", + "PriceLTEQ21": "730", + "PriceGT21": "730" + }, + { + "Procedure Code": "D5130", + "Description": "Immediate denture – maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5140", + "Description": "Immediate denture - mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5211", + "Description": "Maxillary partial denture - resin base (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "556", + "PriceGT21": "556" + }, + { + "Procedure Code": "D5212", + "Description": "Mandibular partial denture - resin base (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "595", + "PriceGT21": "595" + }, + { + "Procedure Code": "D5213", + "Description": "Maxillary partial denture- cast metal framework with resin denture bases (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5214", + "Description": "Mandibular partial denture - cast metal framework with resin denture bases (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5225", + "Description": "Maxillary partial denture- flexible base", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5226", + "Description": "Mandibular partial denture- flexible base", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5511", + "Description": "Repair broken complete denture base, mandibular", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D5512", + "Description": "Repair broken complete denture base, maxillary", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D5520", + "Description": "Replace missing or broken teeth - complete denture (each tooth)", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5611", + "Description": "Repair broken resin partial denture base, mandibular", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5612", + "Description": "Repair broken resin partial denture base, maxillary", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5621", + "Description": "Repair broken cast partial denture base, mandibular", + "PriceLTEQ21": "104", + "PriceGT21": "104" + }, + { + "Procedure Code": "D5622", + "Description": "Repair broken cast partial denture base, maxillary", + "PriceLTEQ21": "104", + "PriceGT21": "104" + }, + { + "Procedure Code": "D5630", + "Description": "Repair or replace broken retentive/clasping materials – per tooth", + "PriceLTEQ21": "99", + "PriceGT21": "99" + }, + { + "Procedure Code": "D5640", + "Description": "Replace broken teeth - per tooth", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5650", + "Description": "Add tooth to existing partial denture", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D5660", + "Description": "Add clasp to existing partial denture per tooth", + "PriceLTEQ21": "98", + "PriceGT21": "98" + }, + { + "Procedure Code": "D5730", + "Description": "Reline complete maxillary denture (direct)", + "PriceLTEQ21": "158", + "PriceGT21": "158" + }, + { + "Procedure Code": "D5731", + "Description": "Reline lower complete mandibular denture (direct)", + "PriceLTEQ21": "173", + "PriceGT21": "173" + }, + { + "Procedure Code": "D5740", + "Description": "Reline maxillary partial denture(chairside)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5741", + "Description": "Reline mandibular partial denture(chairside)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5750", + "Description": "Reline complete maxillary denture (indirect)", + "PriceLTEQ21": "214", + "PriceGT21": "214" + }, + { + "Procedure Code": "D5751", + "Description": "Reline complete mandibular denture (indirect)", + "PriceLTEQ21": "215", + "PriceGT21": "215" + }, + { + "Procedure Code": "D5760", + "Description": "Reline maxillary partial denture (laboratory)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5761", + "Description": "Reline mandibular partial denture (laboratory)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6241", + "Description": "Pontic-porcelain fused metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6751", + "Description": "Retainer crown-porcelain fused to metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6930", + "Description": "Re-cement or re-bond fixed partial denture", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6980", + "Description": "Fixed partial denture repair", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6999", + "Description": "Fixed prosthodontic procedure", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D7111", + "Description": "Extraction, coronal remnants - primary tooth", + "PriceLTEQ21": "75", + "PriceGT21": "75" + }, + { + "Procedure Code": "D7140", + "Description": "Extraction, erupted tooth or exposed root (elevation and/or forceps removal)", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D7210", + "Description": "Extraction, erupted tooth requiring removal of bone and/or sectioning of tooth, and including elevation of mucoperiosteal flap if indicated", + "PriceLTEQ21": "149", + "PriceGT21": "149" + }, + { + "Procedure Code": "D7220", + "Description": "Removal of impacted tooth - soft tissue", + "PriceLTEQ21": "191", + "PriceGT21": "191" + }, + { + "Procedure Code": "D7230", + "Description": "Removal of impacted tooth - partially bony", + "PriceLTEQ21": "249", + "PriceGT21": "249" + }, + { + "Procedure Code": "D7240", + "Description": "Removal of impacted tooth - completely bony", + "PriceLTEQ21": "295", + "PriceGT21": "295" + }, + { + "Procedure Code": "D7250", + "Description": "Surgical removal of residual tooth roots (cutting procedure)", + "PriceLTEQ21": "144", + "PriceGT21": "144" + }, + { + "Procedure Code": "D7251", + "Description": "Coronectomy- intentional partial tooth removal, impacted teeth only", + "PriceLTEQ21": "134", + "PriceGT21": "134" + }, + { + "Procedure Code": "D7270", + "Description": "Tooth reimplantation and/or stabilization of accidentally evulsed or displaced tooth", + "PriceLTEQ21": "106", + "PriceGT21": "106" + }, + { + "Procedure Code": "D7280", + "Description": "Surgical access of an unerupted tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D7283", + "Description": "Placement of device to facilitate eruption of impacted tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D7310", + "Description": "Alveoloplasty in conjunction with extractions-four or more teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "142", + "PriceGT21": "142" + }, + { + "Procedure Code": "D7311", + "Description": "Alveoloplasty in conjunction with extractions - one to three teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "128", + "PriceGT21": "128" + }, + { + "Procedure Code": "D7320", + "Description": "Alveoloplasty not in conjunction with extractions- four or more teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "187", + "PriceGT21": "187" + }, + { + "Procedure Code": "D7321", + "Description": "Alveoloplasty not in conjunction with extractions - one to three teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "149", + "PriceGT21": "149" + }, + { + "Procedure Code": "D7340", + "Description": "Vestibuloplasty - ridge extension (second epithelialization)", + "PriceLTEQ21": "747", + "PriceGT21": "747" + }, + { + "Procedure Code": "D7350", + "Description": "Vestibuloplasty - ridge extension (Oral surgeon only)", + "PriceLTEQ21": "943", + "PriceGT21": "943" + }, + { + "Procedure Code": "D7410", + "Description": "Radical excision - lesion diameter up to 1.25cm", + "PriceLTEQ21": "115", + "PriceGT21": "115" + }, + { + "Procedure Code": "D7411", + "Description": "Excision of benign lesion greater than 1.25 cm", + "PriceLTEQ21": "208", + "PriceGT21": "208" + }, + { + "Procedure Code": "D7450", + "Description": "Removal of benign odontogenic cyst or tumor - lesion diameter up to 1.25 cm", + "PriceLTEQ21": "248", + "PriceGT21": "248" + }, + { + "Procedure Code": "D7451", + "Description": "Removal of benign odontogenic cyst or tumor - lesion diameter greater than 1.25 cm", + "PriceLTEQ21": "288", + "PriceGT21": "288" + }, + { + "Procedure Code": "D7460", + "Description": "Removal of benign nonodontogenic cyst or tumor - lesion diameter up to 1.25 cm", + "PriceLTEQ21": "121", + "PriceGT21": "121" + }, + { + "Procedure Code": "D7461", + "Description": "Removal of benign nonodontogenic cyst or tumor - lesion diameter greater than 1.25 cm", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7471", + "Description": "Removal of lateral exostosis (maxilla or mandible) (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7472", + "Description": "Removal of torus palatinus (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7473", + "Description": "Removal of torus mandibularis (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7961", + "Description": "Buccal/labial frenectomy (frenulectomy)", + "PriceLTEQ21": "107", + "PriceGT21": "107" + }, + { + "Procedure Code": "D7962", + "Description": "Lingual frenectomy (frenulectomy)", + "PriceLTEQ21": "107", + "PriceGT21": "107" + }, + { + "Procedure Code": "D7963", + "Description": "Frenuloplasty", + "PriceLTEQ21": "416", + "PriceGT21": "416" + }, + { + "Procedure Code": "D7970", + "Description": "Excision of hyperplastic tissue - per arch", + "PriceLTEQ21": "246", + "PriceGT21": "246" + }, + { + "Procedure Code": "D7999", + "Description": "Unspecified oral surgery procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D8010", + "Description": "Limited orthodontic treamtnent of the primary transition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8020", + "Description": "Limited orthodontic treatment of the transitional dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8030", + "Description": "Limited orthodontic treatment of the adolescent dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8040", + "Description": "Limited orthodontic treatment of the adult dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8070", + "Description": "Comprehensive orthodontic treatment of the transitional dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8080", + "Description": "Comprehensive orthodontic treatment of the adolescent dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8090", + "Description": "Comprehensive orthodontic treatment of the adult dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8660", + "Description": "Pre-orthodontic treatment examination to monitor growth and development (records fee) (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8670", + "Description": "Periodic orthodontic treatment visit (Orthodontist only)", + "PriceLTEQ21": "215", + "PriceGT21": "215" + }, + { + "Procedure Code": "D8680", + "Description": "Orthodontic retention (removal of appliances, construction and placement of retainer(s)) (Orthodontist only)", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D8703", + "Description": "Replacement of lost or broken retainer- maxillary (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8704", + "Description": "Replacement of lost or broken retainer- mandibular (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8999", + "Description": "Unspecified orthodontic procedure, by report (Orthodontist only) I.C I.C** Y Y**", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9110", + "Description": "Palliative treatment of dental pain – per visit", + "PriceLTEQ21": "36", + "PriceGT21": "36" + }, + { + "Procedure Code": "D9222", + "Description": "Deep sedation/general anesthesia – first 15 minutes", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D9223", + "Description": "Deep sedation/general anesthesia – each additional 15- minute increment", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D9230", + "Description": "Analgesia, anxiolysis, inhalation of nitrous oxide", + "PriceLTEQ21": "15", + "PriceGT21": "15" + }, + { + "Procedure Code": "D9248", + "Description": "Nonintravenous conscious sedation", + "PriceLTEQ21": "45", + "PriceGT21": "45" + }, + { + "Procedure Code": "D9310", + "Description": "Consultation- Diagnostic service provided by dentist or physician other than requesting dentist or physician (Specialist only)", + "PriceLTEQ21": "63", + "PriceGT21": "63" + }, + { + "Procedure Code": "D9410", + "Description": "House/extended care facility call, once per facility per day", + "PriceLTEQ21": "39", + "PriceGT21": "39" + }, + { + "Procedure Code": "D9450", + "Description": "Rural add-on encounter payment", + "PriceLTEQ21": "31", + "PriceGT21": "31" + }, + { + "Procedure Code": "D9920", + "Description": "Behavior management, by report", + "PriceLTEQ21": "86", + "PriceGT21": "86" + }, + { + "Procedure Code": "D9930", + "Description": "Treatment of complications (postsurgical) - unusual circumstances, by report", + "PriceLTEQ21": "30", + "PriceGT21": "30" + }, + { + "Procedure Code": "D9941", + "Description": "Fabrication of athletic mouthguard", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9944", + "Description": "Occlusal guard - hard appliance, full arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9945", + "Description": "Occlusal guard - soft appliance, full arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9946", + "Description": "Occlusal guard - hard appliance, partial arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9999", + "Description": "Unspecified adjunctive procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + } +] \ No newline at end of file diff --git a/apps/Frontend/src/assets/data/procedureCodesUnitedDH.json b/apps/Frontend/src/assets/data/procedureCodesUnitedDH.json new file mode 100644 index 00000000..5bf3865b --- /dev/null +++ b/apps/Frontend/src/assets/data/procedureCodesUnitedDH.json @@ -0,0 +1,1196 @@ +[ + { + "Procedure Code": "D0120", + "Description": "Periodic oral evaluation - established patient", + "PriceLTEQ21": "24", + "PriceGT21": "24" + }, + { + "Procedure Code": "D0140", + "Description": "Limited oral evaluation - problem focused", + "PriceLTEQ21": 90, + "PriceGT21": 90 + }, + { + "Procedure Code": "D0145", + "Description": "Oral evaluation for a patient under three years of age and counseling with primary caregiver", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D0150", + "Description": "Comprehensive oral evaluation - new or established patient", + "PriceLTEQ21": "41", + "PriceGT21": "41" + }, + { + "Procedure Code": "D0180", + "Description": "Comprehensive periodontal evaluation - new or established patient", + "PriceLTEQ21": "37", + "PriceGT21": "37" + }, + { + "Procedure Code": "D0190", + "Description": "Screening of a patient (PHDH only)", + "PriceLTEQ21": "20", + "PriceGT21": "20" + }, + { + "Procedure Code": "D0191", + "Description": "Assessment of a patient (PHDH only)", + "PriceLTEQ21": "20", + "PriceGT21": "20" + }, + { + "Procedure Code": "D0210", + "Description": "Intraoral - complete series of radiographic images", + "PriceLTEQ21": "76", + "PriceGT21": "76" + }, + { + "Procedure Code": "D0220", + "Description": "Intraoral - periapical, first radiographic image", + "PriceLTEQ21": 60, + "PriceGT21": 60 + }, + { + "Procedure Code": "D0230", + "Description": "Intraoral - periapical, each additional radiographic image", + "PriceLTEQ21": "13", + "PriceGT21": "13" + }, + { + "Procedure Code": "D0240", + "Description": "Intraoral - occlusal radiographic image", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D0270", + "Description": "Bitewing - single radiographic image", + "PriceLTEQ21": "14", + "PriceGT21": "14" + }, + { + "Procedure Code": "D0272", + "Description": "Bitewings - two radiographic images", + "PriceLTEQ21": "25", + "PriceGT21": "25" + }, + { + "Procedure Code": "D0273", + "Description": "Bitewings - three radiographic images", + "PriceLTEQ21": "27", + "PriceGT21": "27" + }, + { + "Procedure Code": "D0274", + "Description": "Bitewings - four radiographic images", + "PriceLTEQ21": "36", + "PriceGT21": "36" + }, + { + "Procedure Code": "D0330", + "Description": "Panoramic radiographic image", + "PriceLTEQ21": "69", + "PriceGT21": "69" + }, + { + "Procedure Code": "D0340", + "Description": "Cephalometric radiograph image (Oral surgeon only)", + "PriceLTEQ21": "74", + "PriceGT21": "74" + }, + { + "Procedure Code": "D0364", + "Description": "Less than one jaw", + "Price": "350" + }, + { + "Procedure Code": "D0365", + "Description": "Mand", + "Price": "350" + }, + { + "Procedure Code": "D0366", + "Description": "Max", + "Price": "350" + }, + { + "Procedure Code": "D0367", + "Description": "", + "Price": "400" + }, + { + "Procedure Code": "D0368", + "Description": "include TMJ", + "Price": "375" + }, + { + "Procedure Code": "D0380", + "Description": "Less than one jaw", + "Price": "300" + }, + { + "Procedure Code": "D0381", + "Description": "Mand", + "Price": "300" + }, + { + "Procedure Code": "D0382", + "Description": "Max", + "Price": "300" + }, + { + "Procedure Code": "D0383", + "Description": "", + "Price": "350" + }, + { + "Procedure Code": "D1110", + "Description": "Prophylaxis – adult, 14 yo or older", + "PriceLTEQ21": "60", + "PriceGT21": "60" + }, + { + "Procedure Code": "D1120", + "Description": "Prophylaxis – child, 0-13 yo", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1206", + "Description": "Topical application of fluoride varnish", + "PriceLTEQ21": "26", + "PriceGT21": "26" + }, + { + "Procedure Code": "D1208", + "Description": "Topical application of fluoride – excluding varnish", + "PriceLTEQ21": "29", + "PriceGT21": "29" + }, + { + "Procedure Code": "D1351", + "Description": "Sealant – per tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1354", + "Description": "Application of caries arresting medicament - per tooth", + "PriceLTEQ21": "15", + "PriceGT21": "15" + }, + { + "Procedure Code": "D1510", + "Description": "Space maintainer – fixed,unilateral – per quadrant", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1516", + "Description": "Space maintainer- fixed- bilateral, maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1517", + "Description": "Space maintainer- fixed- bilateral, mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1520", + "Description": "Space maintainer – removable- unilateral- per quadrant", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1526", + "Description": "Space maintainer- removable- bilateral, maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1527", + "Description": "Space maintainer- removable- bilateral, mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1575", + "Description": "Distal shoe space maintainer - fixed- unilateral- Per Quadrant I.C", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D1701", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – first dose SARSCOV2 COVID-19 VAC mRNA 30mcg/0.3mL IM DOSE 1", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1702", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – second dose SARSCOV2 COVID-19 VAC mRNA 30mcg/0.3mL IM DOSE 2", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1707", + "Description": "Janssen Covid-19 vaccine administration SARSCOV2 COVID-19 VAC Ad26 5x1010 VP/.5mL IM SINGLE DOSE", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1708", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – third dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1709", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration – booster dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1712", + "Description": "Janssen Covid-19 vaccine administration - booster dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1713", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration tris-sucrose pediatric – first dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1714", + "Description": "Pfizer-BioNTech Covid-19 vaccine administration tris-sucrose pediatric – second dose", + "PriceLTEQ21": "45.87", + "PriceGT21": "45.87" + }, + { + "Procedure Code": "D1999", + "Description": "", + "Price": "50" + }, + { + "Procedure Code": "D2140", + "Description": "Amalgam-one surface, primary or permanent", + "PriceLTEQ21": "62", + "PriceGT21": "62" + }, + { + "Procedure Code": "D2150", + "Description": "Amalgam-two surfaces, primary or permanent", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D2955", + "Description": "post renoval", + "Price": "350" + }, + { + "Procedure Code": "D4910", + "Description": "perio maintains", + "Price": "250" + }, + { + "Procedure Code": "D5510", + "Description": "Repair broken complete denture base (QUAD)", + "Price": "400" + }, + { + "Procedure Code": "D6010", + "Description": "Surgical placement of implant body", + "Price": "1600" + }, + { + "Procedure Code": "D6056", + "Description": "pre fab abut", + "Price": "750" + }, + { + "Procedure Code": "D6057", + "Description": "custom abut", + "Price": "800" + }, + { + "Procedure Code": "D6058", + "Description": "porcelain, implant crown, ceramic crown", + "Price": "1400" + }, + { + "Procedure Code": "D6059", + "Description": "", + "Price": "1400" + }, + { + "Procedure Code": "D6100", + "Description": "", + "Price": "320" + }, + { + "Procedure Code": "D6110", + "Description": "implant", + "Price": "1600" + }, + { + "Procedure Code": "D6242", + "Description": "noble metal. For united", + "Price": "1400" + }, + { + "Procedure Code": "D6245", + "Description": "porcelain, not for united", + "Price": "1400" + }, + { + "Procedure Code": "D7910", + "Description": "suture, small wound up to 5 mm", + "Price": "400" + }, + { + "Procedure Code": "D7950", + "Description": "max", + "Price": "800" + }, + { + "Procedure Code": "D2160", + "Description": "Amalgam-three surfaces, primary or permanent", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2161", + "Description": "Amalgam-four or more surfaces, primary or permanent", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2330", + "Description": "Resin-based composite – one surface, anterior", + "PriceLTEQ21": "72", + "PriceGT21": "72" + }, + { + "Procedure Code": "D2331", + "Description": "Resin-based composite – two surfaces, anterior", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2332", + "Description": "Resin-based composite – three surfaces, anterior", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2335", + "Description": "Resin-based composite – four or more surfaces or involving incisal angle (anterior)", + "PriceLTEQ21": "146", + "PriceGT21": "146" + }, + { + "Procedure Code": "D2390", + "Description": "Resin-based composite crown, anterior", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2391", + "Description": "Resin-based composite – one surface, posterior", + "PriceLTEQ21": "62", + "PriceGT21": "62" + }, + { + "Procedure Code": "D2392", + "Description": "Resin-based composite – two surfaces, posterior", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D2393", + "Description": "Resin-based composite – three surfaces, posterior", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D2394", + "Description": "Resin-based composite – four or more surfaces, posterior", + "PriceLTEQ21": "116", + "PriceGT21": "116" + }, + { + "Procedure Code": "D2710", + "Description": "Crown – resin-based composite (indirect)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2740", + "Description": "Crown – porcelain/ceramic", + "PriceLTEQ21": "729", + "PriceGT21": "729" + }, + { + "Procedure Code": "D2750", + "Description": "Crown – porcelain fused to high noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2751", + "Description": "Crown – porcelain fused to predominantly base metal", + "PriceLTEQ21": "613", + "PriceGT21": "613" + }, + { + "Procedure Code": "D2752", + "Description": "Crown – porcelain fused to noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2790", + "Description": "Crown – full cast high noble metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2910", + "Description": "Re-cement or re-bond inlay, onlay or partial coverage restoration", + "PriceLTEQ21": "57", + "PriceGT21": "57" + }, + { + "Procedure Code": "D2920", + "Description": "Re-cement or re-bond crown", + "PriceLTEQ21": 150, + "PriceGT21": 150 + }, + { + "Procedure Code": "D2929", + "Description": "Prefabricated porcelain/ceramic crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2930", + "Description": "Prefabricated stainless steel crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2931", + "Description": "Prefabricated stainless steel crown – permanent tooth", + "PriceLTEQ21": "171", + "PriceGT21": "171" + }, + { + "Procedure Code": "D2932", + "Description": "Prefabricated resin crown", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2934", + "Description": "Prefabricated esthetic coated stainless steel crown – primary tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D2950", + "Description": "Core buildup, including any pins when required", + "PriceLTEQ21": "164", + "PriceGT21": "164" + }, + { + "Procedure Code": "D2951", + "Description": "Pin retention – per tooth, in addition to restoration", + "PriceLTEQ21": "27", + "PriceGT21": "27" + }, + { + "Procedure Code": "D2954", + "Description": "Prefabricated post and core in addition to crown", + "PriceLTEQ21": "191", + "PriceGT21": "191" + }, + { + "Procedure Code": "D2980", + "Description": "Crown repair necessitated by restorative material failure", + "PriceLTEQ21": "115", + "PriceGT21": "115" + }, + { + "Procedure Code": "D2999", + "Description": "Unspecified restorative procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D3120", + "Description": "Pulp cap – indirect (excluding final restoration)", + "PriceLTEQ21": "34", + "PriceGT21": "34" + }, + { + "Procedure Code": "D3220", + "Description": "Therapeutic pulpotomy (excluding final restoration) – removal of pulp coronal to the dentinocemental junction and application of medicament", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D3310", + "Description": "Endodontic therapy, anterior (excluding final restoration)", + "PriceLTEQ21": "544", + "PriceGT21": "544" + }, + { + "Procedure Code": "D3320", + "Description": "Endodontic therapy, premolar tooth (excluding final restoration)", + "PriceLTEQ21": "639", + "PriceGT21": "639" + }, + { + "Procedure Code": "D3330", + "Description": "Endodontic therapy, molar tooth (excluding final restoration)", + "PriceLTEQ21": "829", + "PriceGT21": "829" + }, + { + "Procedure Code": "D3346", + "Description": "Retreatment of previous root canal therapy – anterior", + "PriceLTEQ21": "456", + "PriceGT21": "456" + }, + { + "Procedure Code": "D3347", + "Description": "Retreatment of previous root canal therapy – premolar", + "PriceLTEQ21": "538", + "PriceGT21": "538" + }, + { + "Procedure Code": "D3348", + "Description": "Retreatment of previous root canal therapy – molar", + "PriceLTEQ21": "613", + "PriceGT21": "613" + }, + { + "Procedure Code": "D3410", + "Description": "Apicoectomy – anterior", + "PriceLTEQ21": "407", + "PriceGT21": "407" + }, + { + "Procedure Code": "D3421", + "Description": "Apicoectomy – premolar (first root)", + "PriceLTEQ21": "460", + "PriceGT21": "460" + }, + { + "Procedure Code": "D3425", + "Description": "Apicoectomy – molar (first root)", + "PriceLTEQ21": "598", + "PriceGT21": "598" + }, + { + "Procedure Code": "D3426", + "Description": "Apicoectomy (each additional root)", + "PriceLTEQ21": "230", + "PriceGT21": "230" + }, + { + "Procedure Code": "D4210", + "Description": "Gingivectomy or gingivoplasty - Four or more contiguous teeth or bounded teeth spaces per quadrant", + "PriceLTEQ21": "307", + "PriceGT21": "307" + }, + { + "Procedure Code": "D4211", + "Description": "Gingivectomy or gingivoplasty - one to three contiguous teeth or bounded teeth spaces per quadrant", + "PriceLTEQ21": "111", + "PriceGT21": "111" + }, + { + "Procedure Code": "D4341", + "Description": "Periodontal scaling and root planing - four or more teeth per quadrant", + "PriceLTEQ21": "134", + "PriceGT21": "134" + }, + { + "Procedure Code": "D4342", + "Description": "Periodontal scaling and root planing - one to three teeth, per quadrant", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D4346", + "Description": "Scaling in presence of generalized moderate or severe gingival inflammation – full mouth, after oral evaluation", + "PriceLTEQ21": "60", + "PriceGT21": "60" + }, + { + "Procedure Code": "D5110", + "Description": "Complete denture – maxillary", + "PriceLTEQ21": "730", + "PriceGT21": "730" + }, + { + "Procedure Code": "D5120", + "Description": "Complete denture – mandibular", + "PriceLTEQ21": "730", + "PriceGT21": "730" + }, + { + "Procedure Code": "D5130", + "Description": "Immediate denture – maxillary", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5140", + "Description": "Immediate denture - mandibular", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5211", + "Description": "Maxillary partial denture - resin base (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "556", + "PriceGT21": "556" + }, + { + "Procedure Code": "D5212", + "Description": "Mandibular partial denture - resin base (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "595", + "PriceGT21": "595" + }, + { + "Procedure Code": "D5213", + "Description": "Maxillary partial denture- cast metal framework with resin denture bases (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5214", + "Description": "Mandibular partial denture - cast metal framework with resin denture bases (including retentive/clasping materials, rests and teeth)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5225", + "Description": "Maxillary partial denture- flexible base", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5226", + "Description": "Mandibular partial denture- flexible base", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5511", + "Description": "Repair broken complete denture base, mandibular", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D5512", + "Description": "Repair broken complete denture base, maxillary", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D5520", + "Description": "Replace missing or broken teeth - complete denture (each tooth)", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5611", + "Description": "Repair broken resin partial denture base, mandibular", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5612", + "Description": "Repair broken resin partial denture base, maxillary", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5621", + "Description": "Repair broken cast partial denture base, mandibular", + "PriceLTEQ21": "104", + "PriceGT21": "104" + }, + { + "Procedure Code": "D5622", + "Description": "Repair broken cast partial denture base, maxillary", + "PriceLTEQ21": "104", + "PriceGT21": "104" + }, + { + "Procedure Code": "D5630", + "Description": "Repair or replace broken retentive/clasping materials – per tooth", + "PriceLTEQ21": "99", + "PriceGT21": "99" + }, + { + "Procedure Code": "D5640", + "Description": "Replace broken teeth - per tooth", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D5650", + "Description": "Add tooth to existing partial denture", + "PriceLTEQ21": "92", + "PriceGT21": "92" + }, + { + "Procedure Code": "D5660", + "Description": "Add clasp to existing partial denture per tooth", + "PriceLTEQ21": "98", + "PriceGT21": "98" + }, + { + "Procedure Code": "D5730", + "Description": "Reline complete maxillary denture (direct)", + "PriceLTEQ21": "158", + "PriceGT21": "158" + }, + { + "Procedure Code": "D5731", + "Description": "Reline lower complete mandibular denture (direct)", + "PriceLTEQ21": "173", + "PriceGT21": "173" + }, + { + "Procedure Code": "D5740", + "Description": "Reline maxillary partial denture(chairside)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5741", + "Description": "Reline mandibular partial denture(chairside)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5750", + "Description": "Reline complete maxillary denture (indirect)", + "PriceLTEQ21": "214", + "PriceGT21": "214" + }, + { + "Procedure Code": "D5751", + "Description": "Reline complete mandibular denture (indirect)", + "PriceLTEQ21": "215", + "PriceGT21": "215" + }, + { + "Procedure Code": "D5760", + "Description": "Reline maxillary partial denture (laboratory)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D5761", + "Description": "Reline mandibular partial denture (laboratory)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6241", + "Description": "Pontic-porcelain fused metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6751", + "Description": "Retainer crown-porcelain fused to metal", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6930", + "Description": "Re-cement or re-bond fixed partial denture", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6980", + "Description": "Fixed partial denture repair", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D6999", + "Description": "Fixed prosthodontic procedure", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D7111", + "Description": "Extraction, coronal remnants - primary tooth", + "PriceLTEQ21": "75", + "PriceGT21": "75" + }, + { + "Procedure Code": "D7140", + "Description": "Extraction, erupted tooth or exposed root (elevation and/or forceps removal)", + "PriceLTEQ21": "77", + "PriceGT21": "77" + }, + { + "Procedure Code": "D7210", + "Description": "Extraction, erupted tooth requiring removal of bone and/or sectioning of tooth, and including elevation of mucoperiosteal flap if indicated", + "PriceLTEQ21": "149", + "PriceGT21": "149" + }, + { + "Procedure Code": "D7220", + "Description": "Removal of impacted tooth - soft tissue", + "PriceLTEQ21": "191", + "PriceGT21": "191" + }, + { + "Procedure Code": "D7230", + "Description": "Removal of impacted tooth - partially bony", + "PriceLTEQ21": "249", + "PriceGT21": "249" + }, + { + "Procedure Code": "D7240", + "Description": "Removal of impacted tooth - completely bony", + "PriceLTEQ21": "295", + "PriceGT21": "295" + }, + { + "Procedure Code": "D7250", + "Description": "Surgical removal of residual tooth roots (cutting procedure)", + "PriceLTEQ21": "144", + "PriceGT21": "144" + }, + { + "Procedure Code": "D7251", + "Description": "Coronectomy- intentional partial tooth removal, impacted teeth only", + "PriceLTEQ21": "134", + "PriceGT21": "134" + }, + { + "Procedure Code": "D7270", + "Description": "Tooth reimplantation and/or stabilization of accidentally evulsed or displaced tooth", + "PriceLTEQ21": "106", + "PriceGT21": "106" + }, + { + "Procedure Code": "D7280", + "Description": "Surgical access of an unerupted tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D7283", + "Description": "Placement of device to facilitate eruption of impacted tooth", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D7310", + "Description": "Alveoloplasty in conjunction with extractions-four or more teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "142", + "PriceGT21": "142" + }, + { + "Procedure Code": "D7311", + "Description": "Alveoloplasty in conjunction with extractions - one to three teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "128", + "PriceGT21": "128" + }, + { + "Procedure Code": "D7320", + "Description": "Alveoloplasty not in conjunction with extractions- four or more teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "187", + "PriceGT21": "187" + }, + { + "Procedure Code": "D7321", + "Description": "Alveoloplasty not in conjunction with extractions - one to three teeth or tooth spaces, per quadrant", + "PriceLTEQ21": "149", + "PriceGT21": "149" + }, + { + "Procedure Code": "D7340", + "Description": "Vestibuloplasty - ridge extension (second epithelialization)", + "PriceLTEQ21": "747", + "PriceGT21": "747" + }, + { + "Procedure Code": "D7350", + "Description": "Vestibuloplasty - ridge extension (Oral surgeon only)", + "PriceLTEQ21": "943", + "PriceGT21": "943" + }, + { + "Procedure Code": "D7410", + "Description": "Radical excision - lesion diameter up to 1.25cm", + "PriceLTEQ21": "115", + "PriceGT21": "115" + }, + { + "Procedure Code": "D7411", + "Description": "Excision of benign lesion greater than 1.25 cm", + "PriceLTEQ21": "208", + "PriceGT21": "208" + }, + { + "Procedure Code": "D7450", + "Description": "Removal of benign odontogenic cyst or tumor - lesion diameter up to 1.25 cm", + "PriceLTEQ21": "248", + "PriceGT21": "248" + }, + { + "Procedure Code": "D7451", + "Description": "Removal of benign odontogenic cyst or tumor - lesion diameter greater than 1.25 cm", + "PriceLTEQ21": "288", + "PriceGT21": "288" + }, + { + "Procedure Code": "D7460", + "Description": "Removal of benign nonodontogenic cyst or tumor - lesion diameter up to 1.25 cm", + "PriceLTEQ21": "121", + "PriceGT21": "121" + }, + { + "Procedure Code": "D7461", + "Description": "Removal of benign nonodontogenic cyst or tumor - lesion diameter greater than 1.25 cm", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7471", + "Description": "Removal of lateral exostosis (maxilla or mandible) (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7472", + "Description": "Removal of torus palatinus (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7473", + "Description": "Removal of torus mandibularis (Oral surgeon only)", + "PriceLTEQ21": "143", + "PriceGT21": "143" + }, + { + "Procedure Code": "D7961", + "Description": "Buccal/labial frenectomy (frenulectomy)", + "PriceLTEQ21": "107", + "PriceGT21": "107" + }, + { + "Procedure Code": "D7962", + "Description": "Lingual frenectomy (frenulectomy)", + "PriceLTEQ21": "107", + "PriceGT21": "107" + }, + { + "Procedure Code": "D7963", + "Description": "Frenuloplasty", + "PriceLTEQ21": "416", + "PriceGT21": "416" + }, + { + "Procedure Code": "D7970", + "Description": "Excision of hyperplastic tissue - per arch", + "PriceLTEQ21": "246", + "PriceGT21": "246" + }, + { + "Procedure Code": "D7999", + "Description": "Unspecified oral surgery procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + }, + { + "Procedure Code": "D8010", + "Description": "Limited orthodontic treamtnent of the primary transition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8020", + "Description": "Limited orthodontic treatment of the transitional dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8030", + "Description": "Limited orthodontic treatment of the adolescent dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8040", + "Description": "Limited orthodontic treatment of the adult dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8070", + "Description": "Comprehensive orthodontic treatment of the transitional dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8080", + "Description": "Comprehensive orthodontic treatment of the adolescent dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8090", + "Description": "Comprehensive orthodontic treatment of the adult dentition (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8660", + "Description": "Pre-orthodontic treatment examination to monitor growth and development (records fee) (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8670", + "Description": "Periodic orthodontic treatment visit (Orthodontist only)", + "PriceLTEQ21": "215", + "PriceGT21": "215" + }, + { + "Procedure Code": "D8680", + "Description": "Orthodontic retention (removal of appliances, construction and placement of retainer(s)) (Orthodontist only)", + "PriceLTEQ21": "85", + "PriceGT21": "85" + }, + { + "Procedure Code": "D8703", + "Description": "Replacement of lost or broken retainer- maxillary (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8704", + "Description": "Replacement of lost or broken retainer- mandibular (Orthodontist only)", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D8999", + "Description": "Unspecified orthodontic procedure, by report (Orthodontist only) I.C I.C** Y Y**", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9110", + "Description": "Palliative treatment of dental pain – per visit", + "PriceLTEQ21": "36", + "PriceGT21": "36" + }, + { + "Procedure Code": "D9222", + "Description": "Deep sedation/general anesthesia – first 15 minutes", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D9223", + "Description": "Deep sedation/general anesthesia – each additional 15- minute increment", + "PriceLTEQ21": "90", + "PriceGT21": "90" + }, + { + "Procedure Code": "D9230", + "Description": "Analgesia, anxiolysis, inhalation of nitrous oxide", + "PriceLTEQ21": "15", + "PriceGT21": "15" + }, + { + "Procedure Code": "D9248", + "Description": "Nonintravenous conscious sedation", + "PriceLTEQ21": "45", + "PriceGT21": "45" + }, + { + "Procedure Code": "D9310", + "Description": "Consultation- Diagnostic service provided by dentist or physician other than requesting dentist or physician (Specialist only)", + "PriceLTEQ21": "63", + "PriceGT21": "63" + }, + { + "Procedure Code": "D9410", + "Description": "House/extended care facility call, once per facility per day", + "PriceLTEQ21": "39", + "PriceGT21": "39" + }, + { + "Procedure Code": "D9450", + "Description": "Rural add-on encounter payment", + "PriceLTEQ21": "31", + "PriceGT21": "31" + }, + { + "Procedure Code": "D9920", + "Description": "Behavior management, by report", + "PriceLTEQ21": "86", + "PriceGT21": "86" + }, + { + "Procedure Code": "D9930", + "Description": "Treatment of complications (postsurgical) - unusual circumstances, by report", + "PriceLTEQ21": "30", + "PriceGT21": "30" + }, + { + "Procedure Code": "D9941", + "Description": "Fabrication of athletic mouthguard", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9944", + "Description": "Occlusal guard - hard appliance, full arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9945", + "Description": "Occlusal guard - soft appliance, full arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9946", + "Description": "Occlusal guard - hard appliance, partial arch", + "PriceLTEQ21": "NC", + "PriceGT21": "NC" + }, + { + "Procedure Code": "D9999", + "Description": "Unspecified adjunctive procedure, by report", + "PriceLTEQ21": "IC", + "PriceGT21": "IC" + } +] \ No newline at end of file diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index 6ca23857..e2ac4988 100755 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -1202,16 +1202,26 @@ export function ClaimForm({ ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; - const createdClaim = await onSubmit({ - ...formToCreateClaim, - serviceLines: filteredServiceLines, - staffId: appointmentStaffId ?? Number(staff?.id), - patientId, - insuranceProvider: "Tufts SCO", - appointmentId: appointmentIdToUse!, - claimFiles: claimFilesMeta, - ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), - }); + let createdClaim: any; + try { + createdClaim = await onSubmit({ + ...formToCreateClaim, + serviceLines: filteredServiceLines, + staffId: appointmentStaffId ?? Number(staff?.id), + patientId, + insuranceProvider: "Tufts SCO", + appointmentId: appointmentIdToUse!, + claimFiles: claimFilesMeta, + ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), + }); + } catch (err: any) { + toast({ + title: "Failed to save claim", + description: err?.message || "An error occurred saving the claim.", + variant: "destructive", + }); + return; + } onHandleForTuftsSCOSeleniumClaim({ ...form, @@ -1228,6 +1238,27 @@ export function ClaimForm({ onClose(); }; + const handleClaimAll = () => { + const siteKey = (form.insuranceSiteKey || deriveInsuranceSiteKey(form.insuranceProvider || "")).toUpperCase(); + if (siteKey === "MH" || siteKey === "MASSHEALTH") { + runWithPriceCheck(() => handleMHSubmit()); + } else if (siteKey === "CCA") { + runWithPriceCheck(handleCCAClaim); + } else if (siteKey === "DDMA") { + runWithPriceCheck(handleDDMAClaim); + } else if (siteKey === "TUFTSSCO" || siteKey === "TUFTS_SCO") { + runWithPriceCheck(handleTuftsSCOClaim); + } else if (siteKey === "UNITEDSCO" || siteKey === "UNITEDDH" || siteKey === "UNITED_SCO") { + runWithPriceCheck(handleUnitedDHClaim); + } else { + toast({ + title: "No automated claim for this insurance", + description: `Insurance type "${form.insuranceSiteKey || "unknown"}" does not have an automated claim. Please use one of the buttons below.`, + variant: "destructive", + }); + } + }; + const handleCCAPreAuth = async () => { const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); @@ -2146,6 +2177,15 @@ export function ClaimForm({ ) : ( + <> +
+ +
+ )} diff --git a/apps/SeleniumService/dentaquest_browser_manager.py b/apps/SeleniumService/dentaquest_browser_manager.py index a173a598..d88a075a 100644 --- a/apps/SeleniumService/dentaquest_browser_manager.py +++ b/apps/SeleniumService/dentaquest_browser_manager.py @@ -43,99 +43,43 @@ class DentaQuestBrowserManager: def clear_session_on_startup(self): """ - Clear session cookies from Chrome profile on startup. - This forces a fresh login after PC restart. - Preserves device trust tokens (LocalStorage, IndexedDB) to avoid OTPs. + Clear only login cookies on startup to force credential re-entry after restart. + NEVER clears Local Storage or IndexedDB — those hold the DentaQuest device trust + token that allows the portal to skip OTP for recognised devices. """ - print("[DentaQuest BrowserManager] Clearing session on startup...") - + print("[DentaQuest BrowserManager] Clearing login cookies on startup (preserving device trust)...") + try: - # Clear the credentials tracking file + # Reset credentials tracking so the next login re-saves the hash if os.path.exists(self._credentials_file): os.remove(self._credentials_file) print("[DentaQuest BrowserManager] Cleared credentials tracking file") - - # Clear session-related files from Chrome profile - # These are the files that store login session cookies + + # Only remove cookie / login-data files — these expire the session so the + # user must re-enter credentials, but the device trust token is untouched. session_files = [ "Cookies", "Cookies-journal", "Login Data", "Login Data-journal", - "Web Data", - "Web Data-journal", ] - for filename in session_files: - filepath = os.path.join(self.profile_dir, "Default", filename) - if os.path.exists(filepath): - try: - os.remove(filepath) - print(f"[DentaQuest BrowserManager] Removed {filename}") - except Exception as e: - print(f"[DentaQuest BrowserManager] Could not remove {filename}: {e}") - - # Also try root level (some Chrome versions) - for filename in session_files: - filepath = os.path.join(self.profile_dir, filename) - if os.path.exists(filepath): - try: - os.remove(filepath) - print(f"[DentaQuest BrowserManager] Removed root {filename}") - except Exception as e: - print(f"[DentaQuest BrowserManager] Could not remove root {filename}: {e}") - - # Clear Session Storage (contains login state) - session_storage_dir = os.path.join(self.profile_dir, "Default", "Session Storage") - if os.path.exists(session_storage_dir): - try: - shutil.rmtree(session_storage_dir) - print("[DentaQuest BrowserManager] Cleared Session Storage") - except Exception as e: - print(f"[DentaQuest BrowserManager] Could not clear Session Storage: {e}") - - # Clear Local Storage (may contain auth tokens) - local_storage_dir = os.path.join(self.profile_dir, "Default", "Local Storage") - if os.path.exists(local_storage_dir): - try: - shutil.rmtree(local_storage_dir) - print("[DentaQuest BrowserManager] Cleared Local Storage") - except Exception as e: - print(f"[DentaQuest BrowserManager] Could not clear Local Storage: {e}") - - # Clear IndexedDB (may contain auth tokens) - indexeddb_dir = os.path.join(self.profile_dir, "Default", "IndexedDB") - if os.path.exists(indexeddb_dir): - try: - shutil.rmtree(indexeddb_dir) - print("[DentaQuest BrowserManager] Cleared IndexedDB") - except Exception as e: - print(f"[DentaQuest BrowserManager] Could not clear IndexedDB: {e}") + for base in [os.path.join(self.profile_dir, "Default"), self.profile_dir]: + filepath = os.path.join(base, filename) + if os.path.exists(filepath): + try: + os.remove(filepath) + print(f"[DentaQuest BrowserManager] Removed {filename}") + except Exception as e: + print(f"[DentaQuest BrowserManager] Could not remove {filename}: {e}") - # Clear browser caches (prevents Chrome crash from corrupted cache) - cache_dirs = [ - os.path.join(self.profile_dir, "Default", "Cache"), - os.path.join(self.profile_dir, "Default", "Code Cache"), - os.path.join(self.profile_dir, "Default", "GPUCache"), - os.path.join(self.profile_dir, "Default", "Service Worker"), - os.path.join(self.profile_dir, "Cache"), - os.path.join(self.profile_dir, "Code Cache"), - os.path.join(self.profile_dir, "GPUCache"), - os.path.join(self.profile_dir, "ShaderCache"), - ] - for cache_dir in cache_dirs: - if os.path.exists(cache_dir): - try: - shutil.rmtree(cache_dir) - print(f"[DentaQuest BrowserManager] Cleared {os.path.basename(cache_dir)}") - except Exception as e: - print(f"[DentaQuest BrowserManager] Could not clear {os.path.basename(cache_dir)}: {e}") + # Local Storage, IndexedDB, and Session Storage are intentionally + # NOT cleared — they contain the DentaQuest device trust token that + # prevents OTP from being required on every login. - # Set flag to clear session via JavaScript after browser opens self._needs_session_clear = True - - print("[DentaQuest BrowserManager] Session cleared - will require fresh login") - + print("[DentaQuest BrowserManager] Startup clear done — device trust preserved, OTP not required") + except Exception as e: print(f"[DentaQuest BrowserManager] Error clearing session: {e}") diff --git a/apps/SeleniumService/helpers_tuftssco_claim.py b/apps/SeleniumService/helpers_tuftssco_claim.py new file mode 100644 index 00000000..1c710ca9 --- /dev/null +++ b/apps/SeleniumService/helpers_tuftssco_claim.py @@ -0,0 +1,344 @@ +import os +import time +import asyncio +from typing import Dict, Any +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import WebDriverException, TimeoutException + +from selenium_TuftsSCO_claimSubmitWorker import AutomationTuftsSCOClaimSubmit + +sessions: Dict[str, Dict[str, Any]] = {} + +SESSION_OTP_TIMEOUT = int(os.getenv("SESSION_OTP_TIMEOUT", "120")) + + +def make_session_entry() -> str: + import uuid + sid = str(uuid.uuid4()) + sessions[sid] = { + "status": "created", + "created_at": time.time(), + "last_activity": time.time(), + "bot": None, + "driver": None, + "otp_event": asyncio.Event(), + "otp_value": None, + "result": None, + "message": None, + } + return sid + + +async def cleanup_session(sid: str, message: str | None = None): + s = sessions.get(sid) + if not s: + return + try: + if s.get("status") not in ("completed", "error", "not_found"): + s["status"] = "error" + if message: + s["message"] = message + ev = s.get("otp_event") + if ev and not ev.is_set(): + ev.set() + finally: + sessions.pop(sid, None) + print(f"[helpers_tuftssco_claim] cleaned session {sid}") + + +async def _remove_session_later(sid: str, delay: int = 20): + await asyncio.sleep(delay) + await cleanup_session(sid) + + +def _minimize_browser(bot): + try: + if bot and bot.driver: + try: + bot.driver.get("about:blank") + except Exception: + pass + try: + bot.driver.minimize_window() + print("[TuftsSCO Claim] Browser minimized after error") + return + except Exception: + pass + try: + bot.driver.set_window_position(-10000, -10000) + print("[TuftsSCO Claim] Browser moved off-screen after error") + except Exception: + pass + except Exception as e: + print(f"[TuftsSCO Claim] Could not hide browser: {e}") + + +async def start_tuftssco_claim_run(sid: str, data: dict, url: str): + """ + Run the Tufts SCO (DentaQuest) claim workflow. + Login/OTP handling mirrors helpers_ddma_claim.py. + Claim steps call selenium_TuftsSCO_claimSubmitWorker. + """ + s = sessions.get(sid) + if not s: + return {"status": "error", "message": "session not found"} + + s["status"] = "running" + s["last_activity"] = time.time() + + bot = None + try: + bot = AutomationTuftsSCOClaimSubmit(data) + bot.config_driver() + + s["bot"] = bot + s["driver"] = bot.driver + s["last_activity"] = time.time() + + try: + bot.driver.maximize_window() + bot.driver.get(url) + await asyncio.sleep(1) + except Exception as e: + s["status"] = "error" + s["message"] = f"Navigation failed: {e}" + await cleanup_session(sid) + return {"status": "error", "message": s["message"]} + + # --- Login --- + try: + login_result = bot.login(url) + except WebDriverException as wde: + s["status"] = "error" + s["message"] = f"Selenium driver error during login: {wde}" + await cleanup_session(sid, s["message"]) + return {"status": "error", "message": s["message"]} + except Exception as e: + s["status"] = "error" + s["message"] = f"Unexpected error during login: {e}" + await cleanup_session(sid, s["message"]) + return {"status": "error", "message": s["message"]} + + # ── Already logged in ──────────────────────────────────────────────── + if isinstance(login_result, str) and login_result == "ALREADY_LOGGED_IN": + print("[TuftsSCO Claim] Session persisted — skipping OTP") + s["status"] = "running" + s["message"] = "Session persisted" + + # ── OTP required ───────────────────────────────────────────────────── + elif isinstance(login_result, str) and login_result == "OTP_REQUIRED": + s["status"] = "waiting_for_otp" + s["message"] = "OTP required for login — please enter OTP" + s["last_activity"] = time.time() + + driver = s["driver"] + max_polls = SESSION_OTP_TIMEOUT + login_success = False + + print(f"[TuftsSCO Claim OTP] Polling for OTP completion (up to {SESSION_OTP_TIMEOUT}s)...") + + for poll in range(max_polls): + await asyncio.sleep(1) + s["last_activity"] = time.time() + + try: + # a) App submitted OTP via /submit-otp endpoint + otp_value = s.get("otp_value") + if otp_value: + print(f"[TuftsSCO Claim OTP poll {poll+1}] OTP received from app, typing...") + try: + otp_input = driver.find_element(By.XPATH, + "//input[contains(@name,'otp') or contains(@name,'code') or @type='tel' or " + "contains(@aria-label,'Verification') or contains(@placeholder,'code') or " + "contains(@placeholder,'Code')]" + ) + otp_input.clear() + otp_input.send_keys(otp_value) + try: + verify_btn = driver.find_element(By.XPATH, + "//button[@type='button' and @aria-label='Verify']") + verify_btn.click() + except Exception: + try: + verify_btn = driver.find_element(By.XPATH, + "//button[contains(text(),'Verify') or contains(text(),'Submit') or @type='submit']") + verify_btn.click() + except Exception: + otp_input.send_keys("\n") + print("[TuftsSCO Claim OTP] OTP submitted") + s["otp_value"] = None + await asyncio.sleep(3) + except Exception as type_err: + print(f"[TuftsSCO Claim OTP] Failed to type OTP: {type_err}") + + # b) Check URL — if past OTP page, login succeeded + current_url = driver.current_url.lower() + print(f"[TuftsSCO Claim OTP poll {poll+1}/{max_polls}] URL: {current_url[:70]}...") + + if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url: + try: + WebDriverWait(driver, 5).until( + EC.presence_of_element_located((By.XPATH, + '//input[@placeholder="Search by member ID"]')) + ) + print("[TuftsSCO Claim OTP] Member search found — login successful!") + login_success = True + break + except TimeoutException: + print("[TuftsSCO Claim OTP] On member page but element not found, continuing...") + + # Check if OTP input still visible + try: + driver.find_element(By.XPATH, + "//input[contains(@name,'otp') or contains(@name,'code') or @type='tel' or " + "contains(@aria-label,'Verification') or contains(@placeholder,'code')]" + ) + print(f"[TuftsSCO Claim OTP poll {poll+1}] OTP input still visible — waiting...") + except Exception: + if "login" in current_url or "onboarding" in current_url: + print("[TuftsSCO Claim OTP] OTP input gone, navigating to members page...") + try: + driver.get("https://providers.dentaquest.com/members") + await asyncio.sleep(2) + except Exception: + pass + + except Exception as poll_err: + print(f"[TuftsSCO Claim OTP poll {poll+1}] Error: {poll_err}") + + if not login_success: + try: + print("[TuftsSCO Claim OTP] Final attempt — navigating to members page...") + driver.get("https://providers.dentaquest.com/members") + await asyncio.sleep(3) + WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, + '//input[@placeholder="Search by member ID"]')) + ) + print("[TuftsSCO Claim OTP] Member search found — login successful!") + login_success = True + except TimeoutException: + s["status"] = "error" + s["message"] = "OTP timeout — login not completed" + await cleanup_session(sid) + return {"status": "error", "message": "OTP not completed in time"} + except Exception as final_err: + s["status"] = "error" + s["message"] = f"OTP verification failed: {final_err}" + await cleanup_session(sid) + return {"status": "error", "message": s["message"]} + + if login_success: + s["status"] = "running" + s["message"] = "Login successful after OTP" + print("[TuftsSCO Claim OTP] Proceeding to claim steps...") + + # ── Login succeeded without OTP ─────────────────────────────────────── + elif isinstance(login_result, str) and login_result == "SUCCESS": + print("[TuftsSCO Claim] Login succeeded without OTP") + s["status"] = "running" + s["message"] = "Login succeeded" + + # ── Login error ─────────────────────────────────────────────────────── + elif isinstance(login_result, str) and login_result.startswith("ERROR"): + s["status"] = "error" + s["message"] = login_result + await cleanup_session(sid) + return {"status": "error", "message": login_result} + + # --- Claim steps --- + for step_name, step_fn in [ + ("step1_search_patient", bot.step1_search_patient), + ("step2_open_member_page", bot.step2_open_member_page), + ("step3_click_create_claim", bot.step3_click_create_claim), + ("step4_fill_claim_form", bot.step4_fill_claim_form), + ("step5_attach_files", bot.step5_attach_files), + ("step6_click_next", bot.step6_click_next), + ("step7_submit_claim", bot.step7_submit_claim), + ]: + result = step_fn() + print(f"[TuftsSCO Claim] {step_name} result: {result}") + if isinstance(result, str) and result.startswith("ERROR"): + s["status"] = "error" + s["message"] = result + _minimize_browser(bot) + asyncio.create_task(_remove_session_later(sid, 30)) + return {"status": "error", "message": result} + + # --- Step 8: PDF + claim number --- + step8_result = bot.step8_save_confirmation_pdf() + print(f"[TuftsSCO Claim] step8 result: {step8_result}") + if isinstance(step8_result, str) and step8_result.startswith("ERROR"): + print(f"[TuftsSCO Claim] step8 warning (non-fatal): {step8_result}") + step8_result = {} + + pdf_path = step8_result.get("pdf_path") if isinstance(step8_result, dict) else None + pdf_url = None + if pdf_path: + import os as _os + filename = _os.path.basename(pdf_path) + port = _os.getenv("PORT", "5002") + url_host = _os.getenv("HOST", "localhost") + pdf_url = f"http://{url_host}:{port}/downloads/{filename}" + print(f"[TuftsSCO Claim] pdf_url: {pdf_url}") + + claim_number = step8_result.get("claimNumber") if isinstance(step8_result, dict) else None + + result = { + "status": "success", + "message": "Tufts SCO claim submitted successfully", + "claimNumber": claim_number, + "pdf_url": pdf_url, + } + s["status"] = "completed" + s["result"] = result + s["message"] = "completed" + + # Close browser window (session preserved in profile) + try: + from dentaquest_browser_manager import get_browser_manager as _gbm + _gbm().quit_driver() + print("[TuftsSCO Claim] Browser closed - session preserved in profile") + except Exception as close_err: + print(f"[TuftsSCO Claim] Could not close browser (non-fatal): {close_err}") + + asyncio.create_task(_remove_session_later(sid, 60)) + return result + + except Exception as e: + if s: + s["status"] = "error" + s["message"] = f"worker exception: {e}" + await cleanup_session(sid) + return {"status": "error", "message": f"worker exception: {e}"} + + +def submit_otp(sid: str, otp: str) -> Dict[str, Any]: + s = sessions.get(sid) + if not s: + return {"status": "error", "message": "session not found"} + if s.get("status") != "waiting_for_otp": + return {"status": "error", "message": f"session not waiting for otp (state={s.get('status')})"} + s["otp_value"] = otp + s["last_activity"] = time.time() + try: + s["otp_event"].set() + except Exception: + pass + return {"status": "ok", "message": "otp accepted"} + + +def get_session_status(sid: str) -> Dict[str, Any]: + s = sessions.get(sid) + if not s: + return {"status": "not_found"} + return { + "session_id": sid, + "status": s.get("status"), + "message": s.get("message"), + "created_at": s.get("created_at"), + "last_activity": s.get("last_activity"), + "result": s.get("result") if s.get("status") in ("completed", "error") else None, + } diff --git a/apps/SeleniumService/selenium_TuftsSCO_claimSubmitWorker.py b/apps/SeleniumService/selenium_TuftsSCO_claimSubmitWorker.py new file mode 100644 index 00000000..b38fe9b3 --- /dev/null +++ b/apps/SeleniumService/selenium_TuftsSCO_claimSubmitWorker.py @@ -0,0 +1,905 @@ +""" +Tufts SCO (DentaQuest) Claim Submission Worker. +Portal: providers.dentaquest.com + +Step 1 & 2 mirror the DentaQuest eligibility worker (same portal, same selectors). +Step 3–8 mirror the DDMA claim worker (identical claim-form UI on both portals). +""" +from selenium.common.exceptions import TimeoutException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +import time +import os +import base64 + +from dentaquest_browser_manager import get_browser_manager + +MEMBERS_URL = "https://providers.dentaquest.com/members" + +_SERVICE_DIR = os.path.dirname(os.path.abspath(__file__)) +_BACKEND_CWD = os.path.normpath(os.path.join(_SERVICE_DIR, "..", "Backend")) + + +class AutomationTuftsSCOClaimSubmit: + def __init__(self, data): + self.headless = False + self.driver = None + + claim = data.get("claim", {}) if isinstance(data, dict) else {} + + self.memberId = claim.get("memberId", "") + self.dateOfBirth = claim.get("dateOfBirth", "") + self.firstName = claim.get("firstName", "") + self.lastName = claim.get("lastName", "") + self.serviceDate = claim.get("serviceDate", "") + self.serviceLines = claim.get("serviceLines", []) + self.claimFiles = claim.get("claimFiles", []) + self.patientName = claim.get("patientName", "") + self.remarks = claim.get("remarks", "") + + self.dentaquest_username = claim.get("dentaquestUsername", "") + self.dentaquest_password = claim.get("dentaquestPassword", "") + + self.download_dir = get_browser_manager().download_dir + os.makedirs(self.download_dir, exist_ok=True) + + def config_driver(self): + self.driver = get_browser_manager().get_driver(self.headless) + + def _force_logout(self): + try: + print("[TuftsSCO Claim login] Forcing logout due to credential change...") + browser_manager = get_browser_manager() + try: + self.driver.get("https://providers.dentaquest.com/") + time.sleep(2) + for selector in [ + "//button[contains(text(),'Log out') or contains(text(),'Logout') or contains(text(),'Sign out')]", + "//a[contains(text(),'Log out') or contains(text(),'Logout')]", + "//button[@aria-label='Log out' or @aria-label='Logout']", + "//*[contains(@class,'logout') or contains(@class,'signout')]", + ]: + try: + btn = WebDriverWait(self.driver, 3).until(EC.element_to_be_clickable((By.XPATH, selector))) + btn.click() + print("[TuftsSCO Claim login] Clicked logout button") + time.sleep(2) + break + except TimeoutException: + continue + except Exception as e: + print(f"[TuftsSCO Claim login] Could not click logout button: {e}") + try: + self.driver.delete_all_cookies() + print("[TuftsSCO Claim login] Cleared all cookies") + except Exception as e: + print(f"[TuftsSCO Claim login] Error clearing cookies: {e}") + browser_manager.clear_credentials_hash() + return True + except Exception as e: + print(f"[TuftsSCO Claim login] Error during forced logout: {e}") + return False + + def _is_maintenance_page(self) -> bool: + try: + body = self.driver.find_element(By.TAG_NAME, "body").text.lower() + markers = ["temporarily unable to service", "maintenance downtime", "capacity problems"] + return any(m in body for m in markers) + except Exception: + return False + + def login(self, url): + wait = WebDriverWait(self.driver, 30) + browser_manager = get_browser_manager() + + try: + if self.dentaquest_username and browser_manager.credentials_changed(self.dentaquest_username): + self._force_logout() + self.driver.get(url) + time.sleep(2) + + try: + current_url = self.driver.current_url + print(f"[TuftsSCO Claim login] Current URL: {current_url}") + if "dashboard" in current_url.lower() or "member" in current_url.lower(): + try: + WebDriverWait(self.driver, 3).until( + EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) + ) + print("[TuftsSCO Claim login] Already logged in") + return "ALREADY_LOGGED_IN" + except TimeoutException: + pass + except Exception as e: + print(f"[TuftsSCO Claim login] Error checking current state: {e}") + + self.driver.get(url) + time.sleep(3) + + if self._is_maintenance_page(): + return "ERROR: DentaQuest portal is in maintenance mode" + + current_url = self.driver.current_url.lower() + print(f"[TuftsSCO Claim login] After navigation URL: {current_url}") + + if "dashboard" in current_url or "member" in current_url: + try: + WebDriverWait(self.driver, 3).until( + EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) + ) + print("[TuftsSCO Claim login] Already on dashboard") + return "ALREADY_LOGGED_IN" + except TimeoutException: + pass + + # Dismiss "Authentication flow continued in another tab" modal if present + try: + ok_button = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((By.XPATH, + "//button[normalize-space(text())='Ok' or normalize-space(text())='OK' " + "or normalize-space(text())='Continue']" + )) + ) + ok_button.click() + print("[TuftsSCO Claim login] Dismissed modal") + time.sleep(3) + try: + WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) + ) + print("[TuftsSCO Claim login] Already authenticated after modal dismiss") + return "ALREADY_LOGGED_IN" + except TimeoutException: + pass + except TimeoutException: + pass + + # Check for OTP input on page + try: + WebDriverWait(self.driver, 3).until( + EC.presence_of_element_located((By.XPATH, + "//input[@type='tel' or contains(@placeholder,'code') or " + "contains(@aria-label,'Verification') or contains(@name,'otp')]")) + ) + print("[TuftsSCO Claim login] OTP input found on arrival") + return "OTP_REQUIRED" + except TimeoutException: + pass + + # Fill login credentials + try: + username_field = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, + "//input[@type='email' or @name='username' or @id='username' or " + "@name='Email' or @placeholder='Email' or @placeholder='Username' or @type='text']")) + ) + username_field.clear() + username_field.send_keys(self.dentaquest_username) + print(f"[TuftsSCO Claim login] Entered username") + + password_field = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//input[@type='password']")) + ) + password_field.clear() + password_field.send_keys(self.dentaquest_password) + print("[TuftsSCO Claim login] Entered password") + + signin_btn = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, + "//button[@type='submit'] | //input[@type='submit'] | " + "//button[contains(text(),'Sign') or contains(text(),'Log')]")) + ) + signin_btn.click() + print("[TuftsSCO Claim login] Clicked Sign in") + + if self.dentaquest_username: + browser_manager.save_credentials_hash(self.dentaquest_username) + + # Wait for OTP input to appear + try: + WebDriverWait(self.driver, 30).until( + EC.presence_of_element_located((By.XPATH, + "//input[@type='tel' or contains(@placeholder,'code') or contains(@placeholder,'Code') or " + "contains(@aria-label,'Verification') or contains(@aria-label,'verification') or " + "contains(@name,'otp') or contains(@name,'code')]" + )) + ) + print("[TuftsSCO Claim login] OTP required after sign-in") + return "OTP_REQUIRED" + except TimeoutException: + pass + + current_url = self.driver.current_url.lower() + if "dashboard" in current_url or "member" in current_url: + print("[TuftsSCO Claim login] Login succeeded without OTP") + return "SUCCESS" + + print(f"[TuftsSCO Claim login] Unexpected state — URL: {current_url}") + return "SUCCESS" + + except Exception as e: + return f"ERROR: Login failed - {e}" + + except Exception as e: + return f"ERROR: Login exception - {e}" + + # ── Step 1: Search patient (mirrors DentaQuest eligibility step1) ────────── + + def step1_search_patient(self): + """Navigate to member search and find the patient by Member ID + DOB.""" + wait = WebDriverWait(self.driver, 30) + + def replace_with_sendkeys(el, value): + el.click() + time.sleep(0.05) + el.send_keys(Keys.CONTROL, "a") + el.send_keys(Keys.BACKSPACE) + el.send_keys(value) + + try: + print(f"[TuftsSCO Claim step1] Current URL: {self.driver.current_url}") + print(f"[TuftsSCO Claim step1] Searching memberId={self.memberId} dob={self.dateOfBirth}") + + if self._is_maintenance_page(): + return "ERROR: DentaQuest portal is in maintenance mode" + + time.sleep(2) + + # Parse DOB + try: + dob_parts = self.dateOfBirth.split("-") + dob_year = dob_parts[0] + dob_month = dob_parts[1].zfill(2) + dob_day = dob_parts[2].zfill(2) + print(f"[TuftsSCO Claim step1] Parsed DOB: {dob_month}/{dob_day}/{dob_year}") + except Exception as e: + return f"ERROR: step1 DOB parse failed: {e}" + + # 1. Select Location from dropdown (required field on DentaQuest portal) + try: + trigger = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((By.XPATH, + '//button[@data-testid="member-search_location_select-btn"]')) + ) + trigger.click() + print("[TuftsSCO Claim step1] Clicked location dropdown") + time.sleep(0.5) + first_option = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((By.XPATH, "(//li[@role='option'])[1]")) + ) + opt_text = first_option.get_attribute("aria-label") or first_option.text.strip() + first_option.click() + print(f"[TuftsSCO Claim step1] Selected location: {opt_text[:60]}") + time.sleep(0.3) + except TimeoutException: + print("[TuftsSCO Claim step1] Warning: Location dropdown not found (continuing)") + except Exception as e: + print(f"[TuftsSCO Claim step1] Warning: Location select failed: {e}") + + # 2. Fill DOB + try: + dob_container = wait.until(EC.presence_of_element_located( + (By.XPATH, "//div[@data-testid='member-search_date-of-birth']") + )) + month_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']") + day_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']") + year_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']") + replace_with_sendkeys(month_elem, dob_month) + time.sleep(0.1) + replace_with_sendkeys(day_elem, dob_day) + time.sleep(0.1) + replace_with_sendkeys(year_elem, dob_year) + print(f"[TuftsSCO Claim step1] Filled DOB: {dob_month}/{dob_day}/{dob_year}") + except Exception as e: + print(f"[TuftsSCO Claim step1] Warning: Could not fill DOB: {e}") + + time.sleep(0.3) + + # 3. Fill Member ID + if self.memberId: + try: + member_id_input = wait.until(EC.presence_of_element_located( + (By.XPATH, '//input[@placeholder="Search by member ID"]') + )) + member_id_input.clear() + member_id_input.send_keys(self.memberId) + print(f"[TuftsSCO Claim step1] Entered Member ID: {self.memberId}") + time.sleep(0.2) + except Exception as e: + print(f"[TuftsSCO Claim step1] Warning: Could not fill Member ID: {e}") + + time.sleep(0.3) + + # 4. Click Search + try: + search_btn = wait.until(EC.element_to_be_clickable( + (By.XPATH, '//button[@data-testid="member-search_search-button"]') + )) + search_btn.click() + print("[TuftsSCO Claim step1] Clicked Search button") + except TimeoutException: + try: + search_btn = self.driver.find_element(By.XPATH, '//button[contains(text(),"Search")]') + search_btn.click() + print("[TuftsSCO Claim step1] Clicked Search button (fallback)") + except Exception: + ActionChains(self.driver).send_keys(Keys.RETURN).perform() + print("[TuftsSCO Claim step1] Pressed Enter to search") + + # Wait for results or no-results + WebDriverWait(self.driver, 15).until( + EC.any_of( + EC.presence_of_element_located((By.XPATH, "//tbody//tr")), + EC.presence_of_element_located((By.XPATH, + '//*[contains(@data-testid,"no-results") or contains(text(),"No results") ' + 'or contains(text(),"No member found") or contains(text(),"Nothing was found")]' + )), + ) + ) + time.sleep(4) + + # Check for no-results + try: + no_results = self.driver.find_element(By.XPATH, + '//*[contains(@data-testid,"no-results") or contains(text(),"No results") ' + 'or contains(text(),"No member found")]' + ) + if no_results and no_results.is_displayed(): + return "ERROR: No patient found with given search criteria" + except Exception: + pass + + print("[TuftsSCO Claim step1] Search completed") + return "SUCCESS" + + except Exception as e: + print(f"[TuftsSCO Claim step1] Exception: {e}") + return f"ERROR: step1 failed: {e}" + + # ── Step 2: Open member information page ─────────────────────────────────── + + def step2_open_member_page(self): + """Click patient name link → Member Information page, wait for Create claim button.""" + try: + try: + WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//tbody//tr")) + ) + time.sleep(2) + except TimeoutException: + print("[TuftsSCO Claim step2] Warning: Results table not found within timeout") + + # Find member-details URL from first row + detail_url = None + for selector in [ + "(//table//tbody//tr)[1]//td[1]//a", + "(//tbody//tr)[1]//a[contains(@href,'member-details')]", + "(//tbody//tr)[1]//a[contains(@href,'member')]", + "//a[contains(@href,'member-details')]", + ]: + try: + link_el = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.XPATH, selector)) + ) + href = link_el.get_attribute("href") + if href and "member" in href: + detail_url = href + print(f"[TuftsSCO Claim step2] Found detail URL: {href}") + break + except Exception: + continue + + if not detail_url: + return "ERROR: step2 failed: could not find member link" + + self.driver.get(detail_url) + print(f"[TuftsSCO Claim step2] Navigating to: {detail_url}") + + try: + WebDriverWait(self.driver, 15).until( + lambda d: "member" in d.current_url + ) + print(f"[TuftsSCO Claim step2] Member Information page loaded: {self.driver.current_url}") + except TimeoutException: + print(f"[TuftsSCO Claim step2] Warning — URL: {self.driver.current_url}") + + try: + WebDriverWait(self.driver, 15).until( + EC.presence_of_element_located((By.XPATH, "//button[@aria-label='Create claim']")) + ) + print("[TuftsSCO Claim step2] 'Create claim' button found") + except TimeoutException: + print("[TuftsSCO Claim step2] Warning: 'Create claim' button not found within timeout") + + time.sleep(2) + return "SUCCESS" + + except Exception as e: + print(f"[TuftsSCO Claim step2] Exception: {e}") + return f"ERROR: step2 failed: {e}" + + # ── Step 3: Click "Create claim" button ──────────────────────────────────── + + def step3_click_create_claim(self): + """Click the 'Create claim' button on the Member Information page.""" + try: + print(f"[TuftsSCO Claim step3] Current URL: {self.driver.current_url}") + handles_before = set(self.driver.window_handles) + + self.driver.execute_script("window.scrollTo(0, 0);") + time.sleep(0.5) + + all_btns = self.driver.find_elements(By.XPATH, "//button") + print(f"[TuftsSCO Claim step3] Buttons on page: {[b.get_attribute('aria-label') or b.text for b in all_btns]}") + + btn = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, + "//button[@aria-label='Create claim' and @data-react-aria-pressable='true']" + )) + ) + print(f"[TuftsSCO Claim step3] Found 'Create claim': displayed={btn.is_displayed()}, enabled={btn.is_enabled()}") + + self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn) + time.sleep(0.5) + + self.driver.execute_script(""" + var el = arguments[0]; + el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true})); + """, btn) + print("[TuftsSCO Claim step3] Dispatched pointer+click events on 'Create claim'") + + time.sleep(2) + + # Switch to new tab if one opened + handles_after = set(self.driver.window_handles) + new_handles = handles_after - handles_before + if new_handles: + self.driver.switch_to.window(new_handles.pop()) + print("[TuftsSCO Claim step3] Switched to new tab") + + print(f"[TuftsSCO Claim step3] Post-click URL: {self.driver.current_url}") + + try: + WebDriverWait(self.driver, 20).until( + EC.any_of( + EC.presence_of_element_located((By.XPATH, "//input[contains(@id,'procedureCode')]")), + EC.presence_of_element_located((By.XPATH, "//span[@data-type='month' and @contenteditable='true']")), + EC.presence_of_element_located((By.XPATH, + "//*[contains(text(),'date of service') or contains(text(),'Date of service') " + "or contains(text(),'Procedure code')]" + )), + ) + ) + print("[TuftsSCO Claim step3] Claim form loaded") + except TimeoutException: + page_text = self.driver.execute_script("return document.body.innerText;")[:400] + print(f"[TuftsSCO Claim step3] Claim form not detected — page: {page_text}") + + time.sleep(1) + return "SUCCESS" + + except Exception as e: + print(f"[TuftsSCO Claim step3] Exception: {e}") + return f"ERROR: step3 failed: {e}" + + # ── Step 4: Fill service date and procedure lines ────────────────────────── + + def _parse_service_date(self): + s = str(self.serviceDate or "").strip() + if not s: + return None, None, None + if "-" in s: + parts = s.split("-") + if len(parts) == 3 and len(parts[0]) == 4: + return parts[1].zfill(2), parts[2].zfill(2), parts[0] + if len(parts) == 3 and len(parts[2]) == 4: + return parts[0].zfill(2), parts[1].zfill(2), parts[2] + if "/" in s: + parts = s.split("/") + if len(parts) == 3: + return parts[0].zfill(2), parts[1].zfill(2), parts[2] + return None, None, None + + def _fill_spinbutton(self, label_fragment, value): + for sel in [ + f"//span[@contenteditable='true' and contains(@aria-label,'{label_fragment}')]", + f"//span[@data-type='{label_fragment}' and @contenteditable='true']", + ]: + try: + elem = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.XPATH, sel)) + ) + elem.click() + elem.send_keys(Keys.CONTROL, "a") + elem.send_keys(Keys.BACKSPACE) + elem.send_keys(value) + time.sleep(0.1) + print(f"[TuftsSCO Claim step4] Filled spinbutton '{label_fragment}' = {value!r}") + return True + except Exception: + continue + print(f"[TuftsSCO Claim step4] Warning: spinbutton '{label_fragment}' not found") + return False + + def _fill_combobox(self, inp, value, label="field"): + try: + inp.click() + inp.send_keys(Keys.CONTROL + "a") + inp.send_keys(Keys.DELETE) + inp.send_keys(str(value)) + time.sleep(0.5) + listbox_id = inp.get_attribute("aria-controls") or "" + try: + if listbox_id: + option = WebDriverWait(self.driver, 4).until( + EC.element_to_be_clickable((By.XPATH, + f"//*[@id='{listbox_id}']//*[@role='option'][1]" + )) + ) + else: + option = WebDriverWait(self.driver, 4).until( + EC.element_to_be_clickable((By.XPATH, + f"//*[@role='listbox']//*[@role='option' and contains(normalize-space(.),'{value}')]" + f" | //*[@role='listbox']//*[@role='option'][1]" + )) + ) + option.click() + print(f"[TuftsSCO Claim step4] {label}: selected '{value}'") + except TimeoutException: + inp.send_keys(Keys.TAB) + print(f"[TuftsSCO Claim step4] {label}: typed '{value}' (no dropdown)") + except Exception as e: + print(f"[TuftsSCO Claim step4] Warning: could not fill {label}: {e}") + + def _fill_text_input(self, inp, value, label="field"): + try: + inp.click() + inp.send_keys(Keys.CONTROL + "a") + inp.send_keys(Keys.DELETE) + inp.send_keys(str(value)) + time.sleep(0.1) + print(f"[TuftsSCO Claim step4] {label}: typed '{value}'") + except Exception as e: + print(f"[TuftsSCO Claim step4] Warning: could not fill {label}: {e}") + + def step4_fill_claim_form(self): + """Fill service date then all procedure line fields.""" + try: + month, day, year = self._parse_service_date() + + # Service date (once, at the top of the form) + if month and day and year: + print(f"[TuftsSCO Claim step4] Filling service date: {month}/{day}/{year}") + try: + dos_container = WebDriverWait(self.driver, 8).until( + EC.presence_of_element_located((By.XPATH, + "//*[@data-testid and contains(@data-testid,'date-of-service')] | " + "//*[contains(@aria-label,'Select date of service')]/ancestor::div[1]" + )) + ) + month_el = dos_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']") + day_el = dos_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']") + year_el = dos_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']") + for elem, val in [(month_el, month), (day_el, day), (year_el, year)]: + elem.click() + elem.send_keys(Keys.CONTROL, "a") + elem.send_keys(Keys.BACKSPACE) + elem.send_keys(val) + time.sleep(0.05) + print("[TuftsSCO Claim step4] Service date filled") + except Exception: + self._fill_spinbutton("month", month) + self._fill_spinbutton("day", day) + self._fill_spinbutton("year", year) + else: + print(f"[TuftsSCO Claim step4] No valid service date: {self.serviceDate!r}") + + time.sleep(0.3) + + active_lines = [ln for ln in self.serviceLines if str(ln.get("procedureCode") or "").strip()] + print(f"[TuftsSCO Claim step4] {len(active_lines)} service line(s)") + + for idx, line in enumerate(active_lines): + code = str(line.get("procedureCode") or "").strip().upper() + tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip() + arch = str(line.get("arch") or "").strip() + quad = str(line.get("quad") or line.get("quadrant") or "").strip() + surface = str(line.get("toothSurface") or line.get("surface") or "").strip().upper() + billed = str(line.get("totalBilled") or line.get("billedAmount") or line.get("fee") or "").strip() + billed = billed.replace("$", "").strip() + + print(f"[TuftsSCO Claim step4] Line {idx}: code={code} tooth={tooth!r} arch={arch!r} " + f"quad={quad!r} surface={surface!r} billed={billed!r}") + + # Click "Add a procedure" for lines after the first + if idx > 0: + try: + add_btn = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, + "//button[.//span[contains(text(),'Add a procedure')]] | " + "//button[contains(normalize-space(text()),'Add a procedure')] | " + "//*[contains(text(),'Add a procedure') and @role='button']" + )) + ) + self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn) + add_btn.click() + print(f"[TuftsSCO Claim step4] Clicked 'Add a procedure' for line {idx}") + time.sleep(1) + except Exception as e: + print(f"[TuftsSCO Claim step4] Could not click 'Add a procedure': {e}") + + # Procedure code + if code: + try: + proc_inputs = self.driver.find_elements(By.XPATH, + "//input[contains(@id,'procedureCode') and contains(@id,'-input')]" + ) + proc_inp = proc_inputs[idx] if idx < len(proc_inputs) else proc_inputs[-1] + self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_inp) + self._fill_combobox(proc_inp, code, f"procedureCode[{idx}]") + time.sleep(0.5) + except Exception as e: + print(f"[TuftsSCO Claim step4] Could not fill procedure code: {e}") + + # Tooth + if tooth: + try: + tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']") + if idx < len(tooth_inputs): + self._fill_combobox(tooth_inputs[idx], tooth, f"tooth[{idx}]") + time.sleep(0.3) + except Exception as e: + print(f"[TuftsSCO Claim step4] Could not fill tooth: {e}") + + # Arch + if arch: + try: + arch_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Arch']") + if idx < len(arch_inputs): + self._fill_combobox(arch_inputs[idx], arch, f"arch[{idx}]") + time.sleep(0.3) + except Exception as e: + print(f"[TuftsSCO Claim step4] Could not fill arch: {e}") + + # Quad + if quad: + try: + quad_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Quad']") + if idx < len(quad_inputs): + self._fill_combobox(quad_inputs[idx], quad, f"quad[{idx}]") + time.sleep(0.3) + except Exception as e: + print(f"[TuftsSCO Claim step4] Could not fill quad: {e}") + + # Surface (free-text — type directly, dismiss listbox with Escape) + if surface: + try: + surface_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Surface']") + if idx < len(surface_inputs): + surf_inp = surface_inputs[idx] + surf_inp.click() + surf_inp.send_keys(Keys.CONTROL + "a") + surf_inp.send_keys(Keys.DELETE) + surf_inp.send_keys(surface) + time.sleep(0.3) + surf_inp.send_keys(Keys.ESCAPE) + print(f"[TuftsSCO Claim step4] surface[{idx}]: typed '{surface}'") + time.sleep(0.2) + except Exception as e: + print(f"[TuftsSCO Claim step4] Could not fill surface: {e}") + + # Billed amount + if billed: + try: + billed_inputs = self.driver.find_elements(By.XPATH, + "//input[@aria-label='Enter billed amount']" + ) + if idx < len(billed_inputs): + self._fill_text_input(billed_inputs[idx], billed, f"billedAmount[{idx}]") + time.sleep(0.2) + except Exception as e: + print(f"[TuftsSCO Claim step4] Could not fill billed amount: {e}") + + print("[TuftsSCO Claim step4] Done") + return "SUCCESS" + + except Exception as e: + print(f"[TuftsSCO Claim step4] Exception: {e}") + return f"ERROR: step4 failed: {e}" + + # ── Step 5: Attach files ──────────────────────────────────────────────────── + + def step5_attach_files(self): + """For each claimFile with a filePath, click 'Add a file' and upload it.""" + if not self.claimFiles: + print("[TuftsSCO Claim step5] No files to attach") + return "SUCCESS" + + attached = 0 + for cf in self.claimFiles: + relative_path = cf.get("filePath") or "" + if not relative_path: + print(f"[TuftsSCO Claim step5] Skipping file with no filePath: {cf}") + continue + + abs_path = os.path.normpath(os.path.join(_BACKEND_CWD, relative_path.lstrip("/"))) + if not os.path.isfile(abs_path): + print(f"[TuftsSCO Claim step5] File not found on disk: {abs_path}") + continue + + print(f"[TuftsSCO Claim step5] Attaching: {abs_path}") + try: + add_file_btn = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, + "//button[.//span[contains(text(),'Add a file')]] | " + "//button[contains(normalize-space(text()),'Add a file')] | " + "//*[contains(text(),'Add a file') and (@role='button' or self::label)]" + )) + ) + self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_file_btn) + ActionChains(self.driver).move_to_element(add_file_btn).click().perform() + time.sleep(1) + + file_input = WebDriverWait(self.driver, 8).until( + EC.presence_of_element_located((By.XPATH, "//input[@type='file']")) + ) + self.driver.execute_script("arguments[0].style.display='block';", file_input) + file_input.send_keys(abs_path) + time.sleep(1.5) + print(f"[TuftsSCO Claim step5] Attached: {os.path.basename(abs_path)}") + attached += 1 + except Exception as e: + print(f"[TuftsSCO Claim step5] Could not attach {abs_path}: {e}") + + print(f"[TuftsSCO Claim step5] Attached {attached}/{len(self.claimFiles)} file(s)") + return "SUCCESS" + + # ── Step 6: Click "Next step" ────────────────────────────────────────────── + + def step6_click_next(self): + """Click the 'Next step' button (React Aria — dispatches pointer events directly).""" + try: + print(f"[TuftsSCO Claim step6] Current URL: {self.driver.current_url}") + btn = WebDriverWait(self.driver, 15).until( + EC.element_to_be_clickable((By.XPATH, + "//button[@data-testid='next-step-btn'] | " + "//button[@aria-label='Next step']" + )) + ) + self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn) + time.sleep(0.5) + self.driver.execute_script(""" + var el = arguments[0]; + el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true})); + """, btn) + print("[TuftsSCO Claim step6] Clicked 'Next step'") + time.sleep(2) + print(f"[TuftsSCO Claim step6] URL after Next: {self.driver.current_url}") + return "SUCCESS" + except Exception as e: + print(f"[TuftsSCO Claim step6] Exception: {e}") + return f"ERROR: step6 failed: {e}" + + # ── Step 7: Acknowledge + submit ──────────────────────────────────────────── + + def step7_submit_claim(self): + """On the claims summary page, tick the acknowledgement checkbox then click Submit claim.""" + try: + print(f"[TuftsSCO Claim step7] Current URL: {self.driver.current_url}") + + checkbox = WebDriverWait(self.driver, 15).until( + EC.presence_of_element_located((By.XPATH, + "//input[@type='checkbox'] | " + "//*[@role='checkbox'] | " + "//label[contains(.,'submitting this claim')]//input | " + "//*[contains(@aria-label,'submitting this claim')]" + )) + ) + self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", checkbox) + time.sleep(0.3) + self.driver.execute_script(""" + var el = arguments[0]; + el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true})); + """, checkbox) + print("[TuftsSCO Claim step7] Checked acknowledgement checkbox") + time.sleep(0.5) + + submit_btn = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((By.XPATH, + "//button[.//span[contains(text(),'Submit claim')]] | " + "//button[contains(normalize-space(text()),'Submit claim')] | " + "//button[@aria-label='Submit claim']" + )) + ) + self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", submit_btn) + time.sleep(0.3) + self.driver.execute_script(""" + var el = arguments[0]; + el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true})); + el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true})); + """, submit_btn) + print("[TuftsSCO Claim step7] Clicked 'Submit claim'") + time.sleep(2) + print(f"[TuftsSCO Claim step7] URL after submit: {self.driver.current_url}") + return "SUCCESS" + + except Exception as e: + print(f"[TuftsSCO Claim step7] Exception: {e}") + return f"ERROR: step7 failed: {e}" + + # ── Step 8: Extract claim number + save confirmation PDF ─────────────────── + + def step8_save_confirmation_pdf(self): + """Wait for the thank-you page, extract the claim number, save page as PDF.""" + import re + try: + WebDriverWait(self.driver, 30).until( + lambda d: "thank" in d.page_source.lower() or "submitted claim" in d.page_source.lower() + ) + time.sleep(2) + print(f"[TuftsSCO Claim step8] Confirmation page URL: {self.driver.current_url}") + + claim_number = None + try: + body_text = self.driver.find_element(By.TAG_NAME, "body").text + match = re.search(r'submitted claim\s+(\d{10,})', body_text, re.IGNORECASE) + if match: + claim_number = match.group(1) + print(f"[TuftsSCO Claim step8] Extracted claim number: {claim_number}") + else: + match = re.search(r'\b(\d{12,})\b', body_text) + if match: + claim_number = match.group(1) + print(f"[TuftsSCO Claim step8] Extracted claim number (fallback): {claim_number}") + except Exception as e: + print(f"[TuftsSCO Claim step8] Could not extract claim number: {e}") + + shared_downloads = os.path.join(_SERVICE_DIR, "downloads") + os.makedirs(shared_downloads, exist_ok=True) + safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.") + safe_claim = ("_" + claim_number[:20]) if claim_number else "" + timestamp = time.strftime("%Y%m%d_%H%M%S") + pdf_filename = f"tuftssco_claim_confirmation_{safe_member}{safe_claim}_{timestamp}.pdf" + pdf_path = os.path.join(shared_downloads, pdf_filename) + + try: + pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", { + "printBackground": True, + "paperWidth": 8.5, + "paperHeight": 11, + "marginTop": 0.4, + "marginBottom": 0.4, + "marginLeft": 0.4, + "marginRight": 0.4, + }) + pdf_bytes = base64.b64decode(pdf_data["data"]) + with open(pdf_path, "wb") as f: + f.write(pdf_bytes) + print(f"[TuftsSCO Claim step8] PDF saved: {pdf_path}") + except Exception as e: + print(f"[TuftsSCO Claim step8] PDF capture failed: {e}") + return f"ERROR: step8 PDF failed: {e}" + + return { + "status": "success", + "pdf_path": pdf_path, + "claimNumber": claim_number, + } + + except Exception as e: + print(f"[TuftsSCO Claim step8] Exception: {e}") + return f"ERROR: step8 failed: {e}"