Appointment Summary View

For requests or help with our API
Post Reply
rinse-dental
Posts: 107
Joined: Wed Apr 06, 2022 12:04 pm

Appointment Summary View

Post by rinse-dental » Tue Dec 05, 2023 6:21 pm

Hello - I have a relatively straight forward set of use cases but it's proven to be quite taxing. That is, to view a summary for a given appointment. For example, for a planned or upcoming visit, I'd like to display the appointment basics (datetime, provider name, apttypename, and duration), a financial summary (total cost, insurance or discount plan estimate, and the patient's estimated portion), and a list of the procedures scheduled (procedure code, layman/description, and procedure level financial summary).

I believe this sort of summary would require at least 5-6 separate APIs/calls coupled with a significant amount of app side logic. I think it's more for a completed visit when you consider payplans.

I've been able to hack a solution together via short queries but it's quite complicated and potentially fragile, duplicating (and likely getting wrong) a lot of logic found within Open Dental or some of the existing APIs.

Any advice on a more efficient way to tackle this? Or, perhaps it's possible to beef up the primary API objects, like Patients and Appointments, to help improve client side complexity and performance? For example, the following would go along way for my and may other use cases.

Here are my hacked together short queries... the first to get a list of visits and their summaries, the second to get financial details for a specific visit.

Get Patient Visits

Code: Select all

{
  "SqlCommand": "select apt.patnum ,apt.aptnum ,apt.nextaptnum ,papt.aptnum as plannedAptNum ,papt.itemorder as plannedPriority ,apt.appointmenttypenum as AptTypeNum ,CASE WHEN apt.appointmenttypenum = 0 THEN 'Treatment' ELSE atype.appointmenttypename END as AptTypeName ,apt.aptstatus ,CASE WHEN apt.aptstatus = 1 THEN 'Booked' WHEN apt.aptstatus = 2 THEN 'Done' WHEN apt.aptstatus = 3 THEN 'Cancelled' WHEN apt.aptstatus = 5 THEN 'Missed' WHEN apt.aptstatus = 6 THEN 'Planned' END as aptstatusDisplay ,apt.confirmed ,(select itemname from definition where defnum = apt.confirmed) as confirmedDisplay ,apt.unschedstatus ,(select itemname from definition where defnum = apt.unschedstatus) as unschedstatusDisplay ,apt.isnewpatient ,apt.ishygiene ,apt.op ,apt.provnum as dentist ,(select concat(fname, ' ', lname, ' ',suffix) from provider where provnum = apt.provnum) as dentistDisplay ,apt.provhyg as hygienist ,ifnull((select concat(fname, ' ', lname, ' ',suffix) from provider where provnum = apt.provhyg ),'') as hygienistDisplay ,CASE WHEN apt.aptstatus = 6 OR (apt.aptstatus = 3 AND apt.nextaptnum > 0) THEN date_add(curdate(), interval 99 year) ELSE apt.aptdatetime END as aptdatetime ,apt.pattern ,LENGTH(apt.pattern)*5 as duration from appointment apt INNER JOIN procedurelog pl on pl.aptnum = apt.aptnum left join appointmenttype atype on atype.appointmenttypenum = apt.appointmenttypenum LEFT JOIN plannedappt papt on apt.nextaptnum = papt.aptnum OR apt.aptnum = papt.aptnum LEFT JOIN (SELECT s.aptnum,s.nextaptnum,s.aptstatus FROM appointment s INNER JOIN plannedappt ON plannedappt.aptnum = s.nextaptnum WHERE s.aptstatus <> 6) capt ON capt.nextaptnum = apt.aptnum where apt.aptstatus in (1,2,3,5,6) and capt.aptnum is null and apt.patnum = <pmsId> group by apt.aptnum order by aptdatetime asc, papt.itemorder asc"
}
Get Visit Details

Code: Select all

{
  "SqlCommand": "select apt.patnum ,apt.aptnum ,apt.nextaptnum ,papt.aptnum as plannedAptNum ,papt.itemorder as plannedPriority ,apt.appointmenttypenum as AptTypeNum ,atype.appointmenttypename as AptTypeName ,apt.aptstatus ,CASE WHEN apt.aptstatus = 1 THEN 'Booked' WHEN apt.aptstatus = 2 THEN 'Done' WHEN apt.aptstatus = 3 THEN 'Cancelled' WHEN apt.aptstatus = 5 THEN 'Missed' WHEN apt.aptstatus = 6 THEN 'Planned' END as aptstatusDisplay ,apt.confirmed ,(select itemname from definition where defnum = apt.confirmed) as confirmedDisplay ,apt.unschedstatus ,(select itemname from definition where defnum = apt.unschedstatus) as unschedstatusDisplay ,apt.isnewpatient ,apt.ishygiene ,apt.op ,apt.provnum as dentist ,(select concat(fname, ' ', lname, ' ',suffix) from provider where provnum = apt.provnum) as dentistDisplay ,apt.provhyg as hygienist ,ifnull((select concat(fname, ' ', lname, ' ',suffix) from provider where provnum = apt.provhyg ),'') as hygienistDisplay ,CASE WHEN apt.aptstatus = 6 THEN date_add(curdate(), interval 99 year) ELSE apt.aptdatetime END as aptdatetime ,apt.pattern ,LENGTH(apt.pattern)*5 as duration ,apt.insplan1 ,(SELECT CONCAT(CASE WHEN d.itemname is null THEN c.carriername ELSE d.itemname END, CASE WHEN ip.groupnum <> '' THEN CONCAT(' (',ip.groupnum,')') ELSE '' END) FROM insplan ip INNER JOIN carrier c ON c.carriernum = ip.carriernum LEFT JOIN definition d ON d.defnum = c.carriergroupname WHERE ip.plannum = apt.insplan1) as insurance_plan1 ,apt.insplan2 ,(SELECT CONCAT(CASE WHEN d.itemname is null THEN c.carriername ELSE d.itemname END, CASE WHEN ip.groupnum <> '' THEN CONCAT(' (',ip.groupnum,')') ELSE '' END) FROM insplan ip INNER JOIN carrier c ON c.carriernum = ip.carriernum LEFT JOIN definition d ON d.defnum = c.carriergroupname WHERE ip.plannum = apt.insplan2) as insurance_plan2 ,SUM(pq_p.ProcFee) as TotalFees /* -- get estimate totals (for posterity) */ ,SUM(pq_p.DeductibleEst) + IFNULL(claimvisit.Deductible,0) as DeductibleEst ,IF(SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0) > SUM(pq_p.ProcFee), SUM(pq_p.ProcFee), SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0)) as InsuranceEst ,SUM(pq_p.WriteoffEst) + IFNULL(claimvisit.WriteOffEst,0) as WriteOffEst ,IF( ROUND(SUM(pq_p.ProcFee) - IFNULL(claimvisit.WriteOffEst,0) - SUM(pq_p.WriteoffEst) - IF(SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0) > SUM(pq_p.ProcFee), SUM(pq_p.ProcFee), SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0)),2) < 0 , 0 , ROUND(SUM(pq_p.ProcFee) - IFNULL(claimvisit.WriteOffEst,0) - SUM(pq_p.WriteoffEst) - IF(SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0) > SUM(pq_p.ProcFee), SUM(pq_p.ProcFee), SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0)),2) ) as CoPayEst /* -- get actuals */ ,SUM(pq_p.Deductible) + IFNULL(claimvisit.Deductible,0) as Deductible ,SUM(pq_p.insuranceCoverage) + IFNULL(claimvisit.InsuranceCoverage,0) as InsuranceCoverage ,SUM(pq_p.Writeoff) + IFNULL(claimvisit.WriteOff,0) as WriteOff ,ROUND(SUM(pq_p.ProcFee) - SUM(pq_p.Writeoff) - IFNULL(claimvisit.WriteOff,0) - IFNULL(claimvisit.InsuranceCoverage,0) - SUM(pq_p.insuranceCoverage),2) as CoPay /* -- get any adjustments and patient payments */ ,SUM(pq_p.Adjustments) + SUM(distinct IFNULL(adjvisit.visitadjamt,0)) as Adjustments ,SUM(pq_p.PatPaid) as patientPaid ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsurancePaid,0)) + IFNULL(claimvisit.InsurancePaid,0) ELSE 0 END as insurancePaidtoRinse ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsuranceOutstanding,0)) + IFNULL(claimvisit.InsOutstanding,0) ELSE 0 END as insurancePaidtoPatient_DeltaDental ,ROUND(SUM(pq_p.patientOwes) - (IFNULL(claimvisit.WriteOff,0) - IFNULL(claimvisit.InsuranceCoverage,0)) + SUM(distinct IFNULL(adjvisit.visitadjamt,0)),2) as patientOwes ,ROUND(SUM(CASE WHEN apt.aptdatetime < pq_p.datedue THEN (pq_p.ProcFee - pq_p.Writeoff - pq_p.insuranceCoverage) ELSE (pq_p.ProcFee - pq_p.baseest) END),2) as recallEst /* -- old get patient owes ,ROUND(SUM(pq_p.ProcFee) - SUM(pq_p.Writeoff) - IFNULL(claimvisit.WriteOff,0) - SUM(pq_p.insuranceCoverage) - IFNULL(claimvisit.InsuranceCoverage,0) - SUM(pq_p.PatPaid) + SUM(pq_p.Adjustments) + SUM(distinct IFNULL(adjvisit.visitadjamt,0)),2) as PatientOwesOld */ ,COUNT(DISTINCT CASE WHEN pq_p.ClaimStatus <> '' THEN pq_p.ClaimStatus END) as claim_count ,TRIM(LEADING ',' FROM CASE WHEN GROUP_CONCAT(DISTINCT pq_p.NoBillIns) = 1 THEN 'No Bill' ELSE (CASE WHEN COUNT(DISTINCT pq_p.ClaimStatus) > 1 THEN GROUP_CONCAT(DISTINCT CASE WHEN pq_p.ClaimStatus <> 'Estimate' THEN pq_p.ClaimStatus END) ELSE CONCAT(IFNULL(claimvisit.ClaimStatus, ''), IFNULL(GROUP_CONCAT(DISTINCT pq_p.ClaimStatus),'')) END) END) as claim_status ,TRIM(LEADING ',' FROM CONCAT(IFNULL(claimvisit.InsNote, ''), IFNULL(GROUP_CONCAT(DISTINCT pq_p.InsNote),''))) as InsNote ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsurancePaid,0)) + IFNULL(claimvisit.InsurancePaid,0) ELSE 0 END as InsurancePaid ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsuranceOutstanding,0)) + IFNULL(claimvisit.InsOutstanding,0) ELSE 0 END as InsuranceOutstanding_DeltaDental /* -- get patient owes check columns */ ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsuranceToRinse,0)) + IFNULL(claimvisit.InsuranceToRinse,0) ELSE 0 END as InsuranceToRinse ,SUM(pq_p.PatCCPayments) as PatCCPayments ,SUM(pq_p.PayPlan) as PayPlanPrincipal ,group_concat(DISTINCT pq_p.datedue) as datedue ,SUM(pq_p.baseest) as baseest ,CONCAT('[',GROUP_CONCAT('\"',IFNULL(pq_p.getCodeDesc,''),'\"'),']') as aptProcCodeArray ,CONCAT('[', GROUP_CONCAT(CONCAT( '{\"procCode\": \"',pq_p.ProcCode ,'\", \"procDesc\": \"', IFNULL(pq_p.getCodeDesc,'') ,'\", \"ProcPriority\": \"', IFNULL(pq_p.ProcPriority,99) ,'\", \"ProcLocation\": \"', IFNULL(pq_p.ProcLocation,'') ,'\", \"ProcCondition\": \"', IFNULL(pq_p.ProcCondition,'') ,'\", \"NoBillIns\": \"', IFNULL(pq_p.NoBillIns,0) ,'\"}')),']') as aptProcCodeObject from appointment apt left join appointmenttype atype on atype.appointmenttypenum = apt.appointmenttypenum LEFT JOIN plannedappt papt on apt.nextaptnum = papt.aptnum OR apt.aptnum = papt.aptnum LEFT JOIN (SELECT s.aptnum,s.nextaptnum,s.aptstatus FROM appointment s INNER JOIN plannedappt ON plannedappt.aptnum = s.nextaptnum WHERE s.aptstatus <> 6) capt ON capt.nextaptnum = apt.aptnum /*##### START JOINs >>> GET VISIT financials ######*/ /* ==== get visit level adjustments */ LEFT JOIN (SELECT patnum, procdate, SUM(COALESCE(adjamt,0)) as visitadjamt FROM adjustment WHERE procnum = 0 GROUP BY adjnum) adjvisit ON adjvisit.procdate = DATE(apt.aptdatetime) AND adjvisit.patnum = apt.patnum /* ==== get procedure level financial details */ INNER JOIN ( SELECT plog.patnum as ProcPatNum ,CASE WHEN plog.aptnum = '' THEN plog.plannedaptnum ELSE plog.aptnum END as ProcAptNum ,plog.procstatus as ProcStatus ,code.proccode as ProcCode ,code.descript as CodeDescipt ,code.laymanterm as LaymanTerm ,CASE WHEN code.laymanterm = '' THEN code.descript ELSE code.laymanterm END as getCodeDesc ,CASE WHEN code.treatarea = 1 THEN CONCAT(plog.toothnum, ': ', plog.surf) WHEN code.treatarea = 2 THEN plog.toothnum WHEN code.treatarea = 3 THEN '' WHEN code.treatarea = 4 AND code.areaalsotoothrange = '' THEN plog.surf WHEN code.treatarea = 4 AND code.areaalsotoothrange <> '' THEN CONCAT(plog.surf, ': ', plog.toothrange) WHEN code.treatarea = 5 THEN plog.surf WHEN code.treatarea = 6 THEN plog.surf WHEN code.treatarea = 7 THEN plog.toothrange ELSE '' END as ProcLocation ,(SELECT itemname FROM definition WHERE defnum = plog.dx) as ProcCondition ,(SELECT itemname FROM definition WHERE defnum = plog.priority) as ProcPriority ,plog.procfee as ProcFee /* get the pre-visit estimated costs */ ,ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, 0, IFNULL(claimproc.DeductibleEst,0)),2) as deductibleEst ,ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, plog.procfee , IFNULL(claimproc.InsuranceEst,0)),2) as insuranceEst /* WriteoffsEst, in this case, is a sum of all estimated discounts */ ,plog.DiscountPlanAmt as discountPlanAmt ,plog.Discount as discount ,(CASE WHEN plog.DiscountPlanAmt = 0 THEN ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, 0, IFNULL(claimproc.WriteoffEst,0)),2) ELSE plog.DiscountPlanAmt END) + plog.Discount as writeoffEst ,plog.procfee - ((CASE WHEN plog.DiscountPlanAmt = 0 THEN ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, 0, IFNULL(claimproc.WriteoffEst,0)),2) ELSE plog.DiscountPlanAmt END) + plog.Discount) - ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, plog.procfee , IFNULL(claimproc.InsuranceEst,0)),2) as patientEst /* get current costs. Considers previsit (estimate), post visit (claim sent/not received), and received/paid. Note that for discount plans/memberships, the savings is accounted for in the writeoff pre-visit and then adjustments after the procedure is marked complete */ ,ROUND(IFNULL(claimproc.Deductible,0),2) as deductible ,ROUND(IFNULL(claimproc.InsuranceCoverage,0),2) as insuranceCoverage ,CASE WHEN plog.DiscountPlanAmt > 0 AND plog.procstatus = 1 THEN plog.DiscountPlanAmt ELSE IFNULL(claimproc.Writeoff,0) END as writeoff ,ROUND(IFNULL(adjproc.adjamt,0),2) as adjustments /* Get patient payments or payment plans made/setup at the procedure level */ ,ROUND(IFNULL(patpayproc.patpay,0) + SUM(COALESCE(payplan.principal,0)),2) as PatPaid ,ROUND(SUM(COALESCE(payplan.principal,0)),2) as PayPlan ,ROUND(IFNULL(patpayproc.patpay,0)) as PatCCPayments /* Get patient owed amount at procedure level - this considers full claim lifecycle */ ,ROUND(plog.procfee - IFNULL(claimproc.InsuranceToRinse,0) - (CASE WHEN plog.DiscountPlanAmt > 0 AND plog.procstatus = 1 THEN plog.DiscountPlanAmt ELSE IFNULL(claimproc.Writeoff,0) END) + IFNULL(adjproc.adjamt,0) - (IFNULL(patpayproc.patpay,0) + SUM(COALESCE(payplan.principal,0))) - IFNULL(claimproc.insurancePayPlan,0),2) as patientOwes ,ROUND(IFNULL(claimproc.InsurancePaid,0),2) as InsurancePaid ,ROUND(IFNULL(claimproc.InsOutstanding,0),2) as InsuranceOutstanding /* Insurance Coverage is the estimated amount Rinse was expected to get. InsOutstanding is the amount expected less the amount paid. ,ROUND(IFNULL(claimproc.InsuranceCoverage,0) - IFNULL(claimproc.InsurancePaid,0),2) as InsOutstanding */ ,ROUND(IFNULL(claimproc.insurancePayPlan,0),2) as insurancePayPlan ,ROUND(IFNULL(claimproc.InsuranceToRinse,0),2) as insuranceToRinse ,IF(recall.datedue IS NULL,'', recall.datedue) as datedue ,ROUND(IFNULL(claimproc.baseest,0),2) as baseest ,IFNULL(claimproc.ClaimCount,0) as ClaimCount ,claimproc.ClaimStatus ,claimproc.InsNote ,claimproc.NoBillIns ,claimproc.CarrierGroupNum as CarrierGroup /* Get any insurance or membership display name, priority is membership, then carrier group, then carrier name */ ,CASE WHEN plog.DiscountPlanAmt > 0 THEN (SELECT dp.Description FROM DiscountPlanSub dps INNER JOIN discountplan dp ON dp.DiscountPlanNum = dps.DiscountPlanNum AND plog.procdate BETWEEN dps.DateEffective AND dps.DateTerm WHERE dps.patnum = plog.patnum) WHEN claimproc.CarrierGroupName IS NOT NULL THEN claimproc.CarrierGroupName ELSE claimproc.CarrierName END as carrierName FROM procedurelog plog INNER JOIN procedurecode code ON code.codenum = plog.codenum LEFT JOIN (select r.patnum ,r.recalltypenum ,rt.description ,rt.procedures ,r.datedue from recalltype rt inner join recall r on rt.recalltypenum = r.recalltypenum group by r.patnum, r.recalltypenum ) recall ON recall.patnum = plog.patnum AND find_in_set(code.proccode, recall.procedures) /* ==== get procedure level adjustments */ LEFT JOIN (SELECT procnum, SUM(COALESCE(adjamt,0)) as adjamt FROM adjustment WHERE procnum <> 0 GROUP BY procnum) adjproc ON adjproc.procnum = plog.procnum /* ==== get procedure level payplan */ LEFT JOIN (SELECT procnum, SUM(COALESCE(principal,0)) as principal FROM payplancharge WHERE chargetype = 1 GROUP BY procnum) payplan ON payplan.procnum = plog.procnum /* ==== get procedure level payments matched on procedure CODE */ LEFT JOIN (SELECT procnum, SUM(COALESCE(splitamt,0)) as patpay FROM paysplit WHERE procnum <> 0 GROUP BY procnum) patpayproc ON patpayproc.procnum = plog.procnum AND patpayproc.procnum <> IFNULL(payplan.procnum,0) /* ==== get procedure level payments matched on procedure DATE */ LEFT JOIN (SELECT patnum, procdate, SUM(COALESCE(splitamt,0)) as patpay FROM paysplit WHERE procnum = 0 GROUP BY patnum, procdate) patpayvisit ON patpayvisit.procdate = plog.procdate AND patpayvisit.patnum = plog.patnum /* ==== get procedure level claim info */ LEFT JOIN (SELECT c.procnum as ProcNum ,GROUP_CONCAT(DISTINCT carriergroupname) as CarrierGroupNum ,GROUP_CONCAT(DISTINCT defcar.ItemName) as CarrierGroupName ,GROUP_CONCAT(DISTINCT CarrierName) as CarrierName ,GROUP_CONCAT(DISTINCT c.status) as ClaimStatusCode ,GROUP_CONCAT(DISTINCT CASE WHEN c.status = 0 THEN 'Submitted' WHEN c.status = 1 THEN 'Received' WHEN c.status = 2 THEN 'PreAuth' WHEN c.status = 3 THEN 'Adjustment' WHEN c.status = 4 THEN 'Supplemental' WHEN c.status = 6 THEN 'Estimate' ELSE 'Other' END) as ClaimStatus /* --- get claim level estimates only (used for posterity) */ ,SUM(CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE c.dedapplied END) as DeductibleEst /* -- Use inspayest as the way to track outsanding amount from delta dental >> see InsOutstanding -- InsEstTotal (and override) to track the original insurance estimates */ ,SUM(COALESCE(CASE WHEN c.InsEstTotalOverride <> -1 THEN c.InsEstTotalOverride ELSE c.insesttotal END,0)) as insuranceEst ,SUM((COALESCE(CASE WHEN c.writeoffestoverride <> -1 THEN c.writeoffestoverride WHEN c.writeoffest <> -1 THEN c.writeoffest ELSE 0 END,0))) as WriteoffEst /* -- get check columns */ /* --- get claim level actuals */ ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE 0 END) WHEN c.status = 1 THEN COALESCE(c.dedapplied,0) ELSE 0 END) as Deductible ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.writeoffestoverride <> -1 THEN COALESCE(c.writeoffestoverride,0) WHEN c.writeoffest <> -1 THEN COALESCE(c.writeoffest ,0) ELSE c.writeoff END) WHEN c.status = 1 THEN COALESCE(c.writeoff,0) ELSE 0 END) as Writeoff ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.InsPayEst,0) ELSE 0 END) as InsuranceCoverage ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.inspayamt,0) ELSE 0 END) as InsuranceToRinse ,SUM((COALESCE(c.inspayamt,0))) as InsurancePaid ,SUM((COALESCE(c.baseest,0))) as baseest /* if insurance paid something then assume the claim is paid and no insurance is outstanding */ ,SUM(CASE WHEN c.status IN (0,6) THEN 0 ELSE (CASE WHEN c.inspayamt > 0 THEN 0 ELSE (COALESCE(c.InsPayEst,0) - COALESCE(c.inspayamt,0)) END) END) as InsOutstanding ,SUM(payplan.principal) as insurancePayPlan /* --- get claim level notes */ ,COUNT(DISTINCT c.claimnum) as ClaimCount ,GROUP_CONCAT(DISTINCT c.nobillins) as NoBillIns ,GROUP_CONCAT(DISTINCT CASE WHEN c.estimatenote <> '' THEN c.estimatenote END) as InsNote FROM claimproc c INNER JOIN procedurelog p ON p.procnum = c.procnum INNER JOIN insplan ip ON ip.plannum = c.plannum INNER JOIN carrier car ON car.carriernum = ip.carriernum LEFT JOIN (SELECT payplannum, SUM(COALESCE(principal,0)) as principal FROM payplancharge WHERE chargetype = 1 GROUP BY payplannum) payplan ON payplan.payplannum = c.payplannum LEFT JOIN definition defcar ON defcar.defnum = car.carriergroupname WHERE c.procnum <> 0 AND c.status NOT IN (2,9) AND p.procstatus IN (1,2) GROUP BY c.procnum) claimproc ON claimproc.ProcNum = plog.procnum WHERE plog.procstatus in (1,2) GROUP BY plog.procnum) pq_p ON pq_p.ProcAptNum = apt.aptnum /* ----- get any misc claims that are not assigned to a proccode -- metlife example */ LEFT JOIN ( SELECT c.patnum ,c.procdate ,c.procnum as ProcNum ,GROUP_CONCAT(DISTINCT carriergroupname) as CarrierGroupNum ,GROUP_CONCAT(DISTINCT CarrierName) as CarrierName ,GROUP_CONCAT(DISTINCT c.status) as ClaimStatusCode ,GROUP_CONCAT(DISTINCT CASE WHEN c.status = 0 THEN 'Submitted' WHEN c.status = 1 THEN 'Received' WHEN c.status = 2 THEN 'PreAuth' WHEN c.status = 3 THEN 'Adjustment' WHEN c.status = 4 THEN 'Supplemental' WHEN c.status = 6 THEN 'Estimate' ELSE 'Other' END) as ClaimStatus /* --- get claim level estimates only (used for posterity) */ ,SUM(CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE c.dedapplied END) as DeductibleEst /* -- Use inspayest as the way to track outsanding amount from delta dental >> see InsOutstanding -- InsEstTotal (and override) to track the original insurance estimates */ ,SUM(COALESCE(CASE WHEN c.InsEstTotalOverride <> -1 THEN c.InsEstTotalOverride ELSE c.insesttotal END,0)) as insuranceEst ,SUM((COALESCE(CASE WHEN c.writeoffestoverride <> -1 THEN c.writeoffestoverride WHEN c.writeoffest <> -1 THEN c.writeoffest ELSE 0 END,0))) as WriteoffEst /* -- get check columns */ /* --- get claim level actuals */ ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE 0 END) WHEN c.status = 1 THEN COALESCE(c.dedapplied,0) ELSE 0 END) as Deductible ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.writeoffestoverride <> -1 THEN COALESCE(c.writeoffestoverride,0) WHEN c.writeoffest <> -1 THEN COALESCE(c.writeoffest ,0) ELSE c.writeoff END) WHEN c.status = 1 THEN COALESCE(c.writeoff,0) ELSE 0 END) as Writeoff ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.InsPayEst,0) ELSE 0 END) as InsuranceCoverage ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.inspayamt,0) ELSE 0 END) as InsuranceToRinse ,SUM((COALESCE(c.inspayamt,0))) as InsurancePaid /* if insurance paid something then assume the claim is paid and no insurance is outstanding */ ,SUM(CASE WHEN c.inspayamt > 0 THEN 0 ELSE COALESCE(c.InsPayEst,0) - COALESCE(c.inspayamt,0) END) as InsOutstanding /* --- get claim level notes */ ,COUNT(DISTINCT c.claimnum) as ClaimCount ,GROUP_CONCAT(DISTINCT c.nobillins) as NoBillIns ,GROUP_CONCAT(DISTINCT CASE WHEN c.estimatenote <> '' THEN c.estimatenote END) as InsNote FROM claimproc c INNER JOIN insplan ip ON ip.plannum = c.plannum INNER JOIN carrier car ON car.carriernum = ip.carriernum WHERE c.procnum = 0 AND c.status NOT IN (2,3,9) GROUP BY c.patnum, c.procdate) claimvisit ON (claimvisit.patnum = apt.patnum AND DATE_FORMAT(claimvisit.procdate, '%Y-%d-%M')= DATE_FORMAT(apt.aptdatetime,'%Y-%d-%M')) /*##### END JOINS >>> GET VISIT FINANCIALS ######*/ where apt.aptstatus in (1,2,3,5,6) and capt.aptnum is null /*and apt.patnum = 1*/ and apt.aptnum = <aptnum> group by apt.aptnum order by aptdatetime asc, papt.itemorder asc"
}


SLeon
Posts: 558
Joined: Mon Mar 01, 2021 10:00 am

Re: Appointment Summary View

Post by SLeon » Wed Dec 06, 2023 9:07 am

Good morning,

These are great questions and thank you for supplying the queries you are using.

Get Patient Visits

I ran your Patient Visits query in a local environment and the results are extremely similar to the output of the Appointments GET (multiple) endpoint with the optional PatNum parameter. Most of the differences I see are related to how you want to display/format the data in your application. Such as "FName LName Suffix" of the provider instead of the ProvNum or provAbbr values the API returns. Your application likely caches data that rarely changes (such as provider details) and you could use that to alter the API results for display. This also applies to the AppointmentType and Definition values you touch in this query. I am guessing you aren't particularly concerned about this layer.

However, I do see that currently the API does not provide a way to view the link between appointments and planned appointments, and your query JOINS to resolve that. We can develop PlannedAppts GET with an optional PatNum parameter, so you could get the linkages between your Appointments GET (multiple) results and the "plannedPriorty" (plannedappt.ItemOrder) you are after.

Get Visit Details

I was unable to run this query locally. The information you are obtaining with it is vast and is similar in scope to what Open Dental runs when loading the Account Module (querying >10 tables). Open Dental obtains information on these tables via query and then programmatically collates them into what you see in the Account Module grid. Each subsequent form (such as Claim Edit) performs a similar data grab, as many tables are needed to display the full picture (e.g. claim, claimproc, procedurelog, claimpayment, insplan, inssub, patplan, and claimtracking for Claim Edit). How you choose to collate the data will vary on the needs of your application.

As far as additional resources available via the API, the AccountModules resource is extremely useful. Aging for the patient's account is ran in each call, so the data returned is to-the-second accurate. The AccountModules GET ServiceDateView endpoint may be useful for your use case, as it shows a breakdown per account item (procedure, claimproc, payplancharge, etc.).

rinse-dental
Posts: 107
Joined: Wed Apr 06, 2022 12:04 pm

Re: Appointment Summary View

Post by rinse-dental » Wed Dec 06, 2023 5:54 pm

Thanks! You're right, caching items that change less frequently will solve some of my issues - or at least some portion of the complexity I'm working through.

re: Get Visit Details... Yes, this is the problem I'm attempting to solve. Unfortunately, it appears the API you referenced wouldn't help me for scheduled or planned visits but could potentially help with completed visits. I'm curious if there could be a mashup between the procedurelogs, claimprocs, and/or ServiceDateView APIs to produce a procedure level summary of cost.

Below is the query that I ran locally and worked for me... you'll need to find and set the aptnum which can be found at the end the query (ctrl+F '5854'). I've also included a sample return. I'm trying to get cute and create a comprehensive visit object using MySQL which is... hard. It may make more sense to create a procedure object that provides the financial roll up I can then use for the visit level.

Code: Select all

select apt.patnum ,apt.aptnum ,apt.nextaptnum ,papt.aptnum as plannedAptNum ,papt.itemorder as plannedPriority ,apt.appointmenttypenum as AptTypeNum ,atype.appointmenttypename as AptTypeName ,apt.aptstatus ,CASE WHEN apt.aptstatus = 1 THEN 'Booked' WHEN apt.aptstatus = 2 THEN 'Done' WHEN apt.aptstatus = 3 THEN 'Cancelled' WHEN apt.aptstatus = 5 THEN 'Missed' WHEN apt.aptstatus = 6 THEN 'Planned' END as aptstatusDisplay ,apt.confirmed ,(select itemname from definition where defnum = apt.confirmed) as confirmedDisplay ,apt.unschedstatus ,(select itemname from definition where defnum = apt.unschedstatus) as unschedstatusDisplay ,apt.isnewpatient ,apt.ishygiene ,apt.op ,apt.provnum as dentist ,(select concat(fname, ' ', lname, ' ',suffix) from provider where provnum = apt.provnum) as dentistDisplay ,apt.provhyg as hygienist ,ifnull((select concat(fname, ' ', lname, ' ',suffix) from provider where provnum = apt.provhyg ),'') as hygienistDisplay ,CASE WHEN apt.aptstatus = 6 THEN date_add(curdate(), interval 99 year) ELSE apt.aptdatetime END as aptdatetime ,apt.pattern ,LENGTH(apt.pattern)*5 as duration ,apt.insplan1 ,(SELECT CONCAT(CASE WHEN d.itemname is null THEN c.carriername ELSE d.itemname END, CASE WHEN ip.groupnum <> '' THEN CONCAT(' (',ip.groupnum,')') ELSE '' END) FROM insplan ip INNER JOIN carrier c ON c.carriernum = ip.carriernum LEFT JOIN definition d ON d.defnum = c.carriergroupname WHERE ip.plannum = apt.insplan1) as insurance_plan1 ,apt.insplan2 ,(SELECT CONCAT(CASE WHEN d.itemname is null THEN c.carriername ELSE d.itemname END, CASE WHEN ip.groupnum <> '' THEN CONCAT(' (',ip.groupnum,')') ELSE '' END) FROM insplan ip INNER JOIN carrier c ON c.carriernum = ip.carriernum LEFT JOIN definition d ON d.defnum = c.carriergroupname WHERE ip.plannum = apt.insplan2) as insurance_plan2 ,SUM(pq_p.ProcFee) as TotalFees /* -- get estimate totals (for posterity) */ ,SUM(pq_p.DeductibleEst) + IFNULL(claimvisit.Deductible,0) as DeductibleEst ,IF(SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0) > SUM(pq_p.ProcFee), SUM(pq_p.ProcFee), SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0)) as InsuranceEst ,SUM(pq_p.WriteoffEst) + IFNULL(claimvisit.WriteOffEst,0) as WriteOffEst ,IF( ROUND(SUM(pq_p.ProcFee) - IFNULL(claimvisit.WriteOffEst,0) - SUM(pq_p.WriteoffEst) - IF(SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0) > SUM(pq_p.ProcFee), SUM(pq_p.ProcFee), SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0)),2) < 0 , 0 , ROUND(SUM(pq_p.ProcFee) - IFNULL(claimvisit.WriteOffEst,0) - SUM(pq_p.WriteoffEst) - IF(SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0) > SUM(pq_p.ProcFee), SUM(pq_p.ProcFee), SUM(pq_p.insuranceEst) + IFNULL(claimvisit.insuranceEst,0)),2) ) as CoPayEst /* -- get actuals */ ,SUM(pq_p.Deductible) + IFNULL(claimvisit.Deductible,0) as Deductible ,SUM(pq_p.insuranceCoverage) + IFNULL(claimvisit.InsuranceCoverage,0) as InsuranceCoverage ,SUM(pq_p.Writeoff) + IFNULL(claimvisit.WriteOff,0) as WriteOff ,ROUND(SUM(pq_p.ProcFee) - SUM(pq_p.Writeoff) - IFNULL(claimvisit.WriteOff,0) - IFNULL(claimvisit.InsuranceCoverage,0) - SUM(pq_p.insuranceCoverage),2) as CoPay /* -- get any adjustments and patient payments */ ,SUM(pq_p.Adjustments) + SUM(distinct IFNULL(adjvisit.visitadjamt,0)) as Adjustments ,SUM(pq_p.PatPaid) as patientPaid ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsurancePaid,0)) + IFNULL(claimvisit.InsurancePaid,0) ELSE 0 END as insurancePaidtoRinse ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsuranceOutstanding,0)) + IFNULL(claimvisit.InsOutstanding,0) ELSE 0 END as insurancePaidtoPatient_DeltaDental ,ROUND(SUM(pq_p.patientOwes) - (IFNULL(claimvisit.WriteOff,0) - IFNULL(claimvisit.InsuranceCoverage,0)) + SUM(distinct IFNULL(adjvisit.visitadjamt,0)),2) as patientOwes ,ROUND(SUM(CASE WHEN apt.aptdatetime < pq_p.datedue THEN (pq_p.ProcFee - pq_p.Writeoff - pq_p.insuranceCoverage) ELSE (pq_p.ProcFee - pq_p.baseest) END),2) as recallEst /* -- old get patient owes ,ROUND(SUM(pq_p.ProcFee) - SUM(pq_p.Writeoff) - IFNULL(claimvisit.WriteOff,0) - SUM(pq_p.insuranceCoverage) - IFNULL(claimvisit.InsuranceCoverage,0) - SUM(pq_p.PatPaid) + SUM(pq_p.Adjustments) + SUM(distinct IFNULL(adjvisit.visitadjamt,0)),2) as PatientOwesOld */ ,COUNT(DISTINCT CASE WHEN pq_p.ClaimStatus <> '' THEN pq_p.ClaimStatus END) as claim_count ,TRIM(LEADING ',' FROM CASE WHEN GROUP_CONCAT(DISTINCT pq_p.NoBillIns) = 1 THEN 'No Bill' ELSE (CASE WHEN COUNT(DISTINCT pq_p.ClaimStatus) > 1 THEN GROUP_CONCAT(DISTINCT CASE WHEN pq_p.ClaimStatus <> 'Estimate' THEN pq_p.ClaimStatus END) ELSE CONCAT(IFNULL(claimvisit.ClaimStatus, ''), IFNULL(GROUP_CONCAT(DISTINCT pq_p.ClaimStatus),'')) END) END) as claim_status ,TRIM(LEADING ',' FROM CONCAT(IFNULL(claimvisit.InsNote, ''), IFNULL(GROUP_CONCAT(DISTINCT pq_p.InsNote),''))) as InsNote ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsurancePaid,0)) + IFNULL(claimvisit.InsurancePaid,0) ELSE 0 END as InsurancePaid ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsuranceOutstanding,0)) + IFNULL(claimvisit.InsOutstanding,0) ELSE 0 END as InsuranceOutstanding_DeltaDental /* -- get patient owes check columns */ ,CASE WHEN apt.aptstatus = 2 THEN SUM(IFNULL(pq_p.InsuranceToRinse,0)) + IFNULL(claimvisit.InsuranceToRinse,0) ELSE 0 END as InsuranceToRinse ,SUM(pq_p.PatCCPayments) as PatCCPayments ,SUM(pq_p.PayPlan) as PayPlanPrincipal ,group_concat(DISTINCT pq_p.datedue) as datedue ,SUM(pq_p.baseest) as baseest ,CONCAT('[',GROUP_CONCAT('\"',IFNULL(pq_p.getCodeDesc,''),'\"'),']') as aptProcCodeArray ,CONCAT('[', GROUP_CONCAT(CONCAT( '{\"procCode\": \"',pq_p.ProcCode ,'\", \"procDesc\": \"', IFNULL(pq_p.getCodeDesc,'') ,'\", \"ProcPriority\": \"', IFNULL(pq_p.ProcPriority,99) ,'\", \"ProcLocation\": \"', IFNULL(pq_p.ProcLocation,'') ,'\", \"ProcCondition\": \"', IFNULL(pq_p.ProcCondition,'') ,'\", \"NoBillIns\": \"', IFNULL(pq_p.NoBillIns,0) ,'\"}')),']') as aptProcCodeObject from appointment apt left join appointmenttype atype on atype.appointmenttypenum = apt.appointmenttypenum LEFT JOIN plannedappt papt on apt.nextaptnum = papt.aptnum OR apt.aptnum = papt.aptnum LEFT JOIN (SELECT s.aptnum,s.nextaptnum,s.aptstatus FROM appointment s INNER JOIN plannedappt ON plannedappt.aptnum = s.nextaptnum WHERE s.aptstatus <> 6) capt ON capt.nextaptnum = apt.aptnum /*##### START JOINs >>> GET VISIT financials ######*/ /* ==== get visit level adjustments */ LEFT JOIN (SELECT patnum, procdate, SUM(COALESCE(adjamt,0)) as visitadjamt FROM adjustment WHERE procnum = 0 GROUP BY adjnum) adjvisit ON adjvisit.procdate = DATE(apt.aptdatetime) AND adjvisit.patnum = apt.patnum /* ==== get procedure level financial details */ INNER JOIN ( SELECT plog.patnum as ProcPatNum ,CASE WHEN plog.aptnum = '' THEN plog.plannedaptnum ELSE plog.aptnum END as ProcAptNum ,plog.procstatus as ProcStatus ,code.proccode as ProcCode ,code.descript as CodeDescipt ,code.laymanterm as LaymanTerm ,CASE WHEN code.laymanterm = '' THEN code.descript ELSE code.laymanterm END as getCodeDesc ,CASE WHEN code.treatarea = 1 THEN CONCAT(plog.toothnum, ': ', plog.surf) WHEN code.treatarea = 2 THEN plog.toothnum WHEN code.treatarea = 3 THEN '' WHEN code.treatarea = 4 AND code.areaalsotoothrange = '' THEN plog.surf WHEN code.treatarea = 4 AND code.areaalsotoothrange <> '' THEN CONCAT(plog.surf, ': ', plog.toothrange) WHEN code.treatarea = 5 THEN plog.surf WHEN code.treatarea = 6 THEN plog.surf WHEN code.treatarea = 7 THEN plog.toothrange ELSE '' END as ProcLocation ,(SELECT itemname FROM definition WHERE defnum = plog.dx) as ProcCondition ,(SELECT itemname FROM definition WHERE defnum = plog.priority) as ProcPriority ,plog.procfee as ProcFee /* get the pre-visit estimated costs */ ,ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, 0, IFNULL(claimproc.DeductibleEst,0)),2) as deductibleEst ,ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, plog.procfee , IFNULL(claimproc.InsuranceEst,0)),2) as insuranceEst /* WriteoffsEst, in this case, is a sum of all estimated discounts */ ,plog.DiscountPlanAmt as discountPlanAmt ,plog.Discount as discount ,(CASE WHEN plog.DiscountPlanAmt = 0 THEN ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, 0, IFNULL(claimproc.WriteoffEst,0)),2) ELSE plog.DiscountPlanAmt END) + plog.Discount as writeoffEst ,plog.procfee - ((CASE WHEN plog.DiscountPlanAmt = 0 THEN ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, 0, IFNULL(claimproc.WriteoffEst,0)),2) ELSE plog.DiscountPlanAmt END) + plog.Discount) - ROUND(IF(IFNULL(claimproc.InsuranceEst,0) >= plog.procfee, plog.procfee , IFNULL(claimproc.InsuranceEst,0)),2) as patientEst /* get current costs. Considers previsit (estimate), post visit (claim sent/not received), and received/paid. Note that for discount plans/memberships, the savings is accounted for in the writeoff pre-visit and then adjustments after the procedure is marked complete */ ,ROUND(IFNULL(claimproc.Deductible,0),2) as deductible ,ROUND(IFNULL(claimproc.InsuranceCoverage,0),2) as insuranceCoverage ,CASE WHEN plog.DiscountPlanAmt > 0 AND plog.procstatus = 1 THEN plog.DiscountPlanAmt ELSE IFNULL(claimproc.Writeoff,0) END as writeoff ,ROUND(IFNULL(adjproc.adjamt,0),2) as adjustments /* Get patient payments or payment plans made/setup at the procedure level */ ,ROUND(IFNULL(patpayproc.patpay,0) + SUM(COALESCE(payplan.principal,0)),2) as PatPaid ,ROUND(SUM(COALESCE(payplan.principal,0)),2) as PayPlan ,ROUND(IFNULL(patpayproc.patpay,0)) as PatCCPayments /* Get patient owed amount at procedure level - this considers full claim lifecycle */ ,ROUND(plog.procfee - IFNULL(claimproc.InsuranceToRinse,0) - (CASE WHEN plog.DiscountPlanAmt > 0 AND plog.procstatus = 1 THEN plog.DiscountPlanAmt ELSE IFNULL(claimproc.Writeoff,0) END) + IFNULL(adjproc.adjamt,0) - (IFNULL(patpayproc.patpay,0) + SUM(COALESCE(payplan.principal,0))) - IFNULL(claimproc.insurancePayPlan,0),2) as patientOwes ,ROUND(IFNULL(claimproc.InsurancePaid,0),2) as InsurancePaid ,ROUND(IFNULL(claimproc.InsOutstanding,0),2) as InsuranceOutstanding /* Insurance Coverage is the estimated amount Rinse was expected to get. InsOutstanding is the amount expected less the amount paid. ,ROUND(IFNULL(claimproc.InsuranceCoverage,0) - IFNULL(claimproc.InsurancePaid,0),2) as InsOutstanding */ ,ROUND(IFNULL(claimproc.insurancePayPlan,0),2) as insurancePayPlan ,ROUND(IFNULL(claimproc.InsuranceToRinse,0),2) as insuranceToRinse ,IF(recall.datedue IS NULL,'', recall.datedue) as datedue ,ROUND(IFNULL(claimproc.baseest,0),2) as baseest ,IFNULL(claimproc.ClaimCount,0) as ClaimCount ,claimproc.ClaimStatus ,claimproc.InsNote ,claimproc.NoBillIns ,claimproc.CarrierGroupNum as CarrierGroup /* Get any insurance or membership display name, priority is membership, then carrier group, then carrier name */ ,CASE WHEN plog.DiscountPlanAmt > 0 THEN (SELECT dp.Description FROM DiscountPlanSub dps INNER JOIN discountplan dp ON dp.DiscountPlanNum = dps.DiscountPlanNum AND plog.procdate BETWEEN dps.DateEffective AND dps.DateTerm WHERE dps.patnum = plog.patnum) WHEN claimproc.CarrierGroupName IS NOT NULL THEN claimproc.CarrierGroupName ELSE claimproc.CarrierName END as carrierName FROM procedurelog plog INNER JOIN procedurecode code ON code.codenum = plog.codenum LEFT JOIN (select r.patnum ,r.recalltypenum ,rt.description ,rt.procedures ,r.datedue from recalltype rt inner join recall r on rt.recalltypenum = r.recalltypenum group by r.patnum, r.recalltypenum ) recall ON recall.patnum = plog.patnum AND find_in_set(code.proccode, recall.procedures) /* ==== get procedure level adjustments */ LEFT JOIN (SELECT procnum, SUM(COALESCE(adjamt,0)) as adjamt FROM adjustment WHERE procnum <> 0 GROUP BY procnum) adjproc ON adjproc.procnum = plog.procnum /* ==== get procedure level payplan */ LEFT JOIN (SELECT procnum, SUM(COALESCE(principal,0)) as principal FROM payplancharge WHERE chargetype = 1 GROUP BY procnum) payplan ON payplan.procnum = plog.procnum /* ==== get procedure level payments matched on procedure CODE */ LEFT JOIN (SELECT procnum, SUM(COALESCE(splitamt,0)) as patpay FROM paysplit WHERE procnum <> 0 GROUP BY procnum) patpayproc ON patpayproc.procnum = plog.procnum AND patpayproc.procnum <> IFNULL(payplan.procnum,0) /* ==== get procedure level payments matched on procedure DATE */ LEFT JOIN (SELECT patnum, procdate, SUM(COALESCE(splitamt,0)) as patpay FROM paysplit WHERE procnum = 0 GROUP BY patnum, procdate) patpayvisit ON patpayvisit.procdate = plog.procdate AND patpayvisit.patnum = plog.patnum /* ==== get procedure level claim info */ LEFT JOIN (SELECT c.procnum as ProcNum ,GROUP_CONCAT(DISTINCT carriergroupname) as CarrierGroupNum ,GROUP_CONCAT(DISTINCT defcar.ItemName) as CarrierGroupName ,GROUP_CONCAT(DISTINCT CarrierName) as CarrierName ,GROUP_CONCAT(DISTINCT c.status) as ClaimStatusCode ,GROUP_CONCAT(DISTINCT CASE WHEN c.status = 0 THEN 'Submitted' WHEN c.status = 1 THEN 'Received' WHEN c.status = 2 THEN 'PreAuth' WHEN c.status = 3 THEN 'Adjustment' WHEN c.status = 4 THEN 'Supplemental' WHEN c.status = 6 THEN 'Estimate' ELSE 'Other' END) as ClaimStatus /* --- get claim level estimates only (used for posterity) */ ,SUM(CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE c.dedapplied END) as DeductibleEst /* -- Use inspayest as the way to track outsanding amount from delta dental >> see InsOutstanding -- InsEstTotal (and override) to track the original insurance estimates */ ,SUM(COALESCE(CASE WHEN c.InsEstTotalOverride <> -1 THEN c.InsEstTotalOverride ELSE c.insesttotal END,0)) as insuranceEst ,SUM((COALESCE(CASE WHEN c.writeoffestoverride <> -1 THEN c.writeoffestoverride WHEN c.writeoffest <> -1 THEN c.writeoffest ELSE 0 END,0))) as WriteoffEst /* -- get check columns */ /* --- get claim level actuals */ ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE 0 END) WHEN c.status = 1 THEN COALESCE(c.dedapplied,0) ELSE 0 END) as Deductible ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.writeoffestoverride <> -1 THEN COALESCE(c.writeoffestoverride,0) WHEN c.writeoffest <> -1 THEN COALESCE(c.writeoffest ,0) ELSE c.writeoff END) WHEN c.status = 1 THEN COALESCE(c.writeoff,0) ELSE 0 END) as Writeoff ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.InsPayEst,0) ELSE 0 END) as InsuranceCoverage ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.inspayamt,0) ELSE 0 END) as InsuranceToRinse ,SUM((COALESCE(c.inspayamt,0))) as InsurancePaid ,SUM((COALESCE(c.baseest,0))) as baseest /* if insurance paid something then assume the claim is paid and no insurance is outstanding */ ,SUM(CASE WHEN c.status IN (0,6) THEN 0 ELSE (CASE WHEN c.inspayamt > 0 THEN 0 ELSE (COALESCE(c.InsPayEst,0) - COALESCE(c.inspayamt,0)) END) END) as InsOutstanding ,SUM(payplan.principal) as insurancePayPlan /* --- get claim level notes */ ,COUNT(DISTINCT c.claimnum) as ClaimCount ,GROUP_CONCAT(DISTINCT c.nobillins) as NoBillIns ,GROUP_CONCAT(DISTINCT CASE WHEN c.estimatenote <> '' THEN c.estimatenote END) as InsNote FROM claimproc c INNER JOIN procedurelog p ON p.procnum = c.procnum INNER JOIN insplan ip ON ip.plannum = c.plannum INNER JOIN carrier car ON car.carriernum = ip.carriernum LEFT JOIN (SELECT payplannum, SUM(COALESCE(principal,0)) as principal FROM payplancharge WHERE chargetype = 1 GROUP BY payplannum) payplan ON payplan.payplannum = c.payplannum LEFT JOIN definition defcar ON defcar.defnum = car.carriergroupname WHERE c.procnum <> 0 AND c.status NOT IN (2,9) AND p.procstatus IN (1,2) GROUP BY c.procnum) claimproc ON claimproc.ProcNum = plog.procnum WHERE plog.procstatus in (1,2) GROUP BY plog.procnum) pq_p ON pq_p.ProcAptNum = apt.aptnum /* ----- get any misc claims that are not assigned to a proccode -- metlife example */ LEFT JOIN ( SELECT c.patnum ,c.procdate ,c.procnum as ProcNum ,GROUP_CONCAT(DISTINCT carriergroupname) as CarrierGroupNum ,GROUP_CONCAT(DISTINCT CarrierName) as CarrierName ,GROUP_CONCAT(DISTINCT c.status) as ClaimStatusCode ,GROUP_CONCAT(DISTINCT CASE WHEN c.status = 0 THEN 'Submitted' WHEN c.status = 1 THEN 'Received' WHEN c.status = 2 THEN 'PreAuth' WHEN c.status = 3 THEN 'Adjustment' WHEN c.status = 4 THEN 'Supplemental' WHEN c.status = 6 THEN 'Estimate' ELSE 'Other' END) as ClaimStatus /* --- get claim level estimates only (used for posterity) */ ,SUM(CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE c.dedapplied END) as DeductibleEst /* -- Use inspayest as the way to track outsanding amount from delta dental >> see InsOutstanding -- InsEstTotal (and override) to track the original insurance estimates */ ,SUM(COALESCE(CASE WHEN c.InsEstTotalOverride <> -1 THEN c.InsEstTotalOverride ELSE c.insesttotal END,0)) as insuranceEst ,SUM((COALESCE(CASE WHEN c.writeoffestoverride <> -1 THEN c.writeoffestoverride WHEN c.writeoffest <> -1 THEN c.writeoffest ELSE 0 END,0))) as WriteoffEst /* -- get check columns */ /* --- get claim level actuals */ ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.dedestoverride <> -1 THEN COALESCE(c.dedestoverride,0) WHEN c.dedest <> -1 THEN COALESCE(c.dedest,0) ELSE 0 END) WHEN c.status = 1 THEN COALESCE(c.dedapplied,0) ELSE 0 END) as Deductible ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.writeoffestoverride <> -1 THEN COALESCE(c.writeoffestoverride,0) WHEN c.writeoffest <> -1 THEN COALESCE(c.writeoffest ,0) ELSE c.writeoff END) WHEN c.status = 1 THEN COALESCE(c.writeoff,0) ELSE 0 END) as Writeoff ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.InsPayEst,0) ELSE 0 END) as InsuranceCoverage ,SUM(CASE WHEN c.status IN (0,6) THEN (CASE WHEN c.InsEstTotalOverride <> -1 THEN COALESCE(c.InsEstTotalOverride,0) ELSE COALESCE(c.insesttotal,0) END) WHEN c.status = 1 THEN COALESCE(c.inspayamt,0) ELSE 0 END) as InsuranceToRinse ,SUM((COALESCE(c.inspayamt,0))) as InsurancePaid /* if insurance paid something then assume the claim is paid and no insurance is outstanding */ ,SUM(CASE WHEN c.inspayamt > 0 THEN 0 ELSE COALESCE(c.InsPayEst,0) - COALESCE(c.inspayamt,0) END) as InsOutstanding /* --- get claim level notes */ ,COUNT(DISTINCT c.claimnum) as ClaimCount ,GROUP_CONCAT(DISTINCT c.nobillins) as NoBillIns ,GROUP_CONCAT(DISTINCT CASE WHEN c.estimatenote <> '' THEN c.estimatenote END) as InsNote FROM claimproc c INNER JOIN insplan ip ON ip.plannum = c.plannum INNER JOIN carrier car ON car.carriernum = ip.carriernum WHERE c.procnum = 0 AND c.status NOT IN (2,3,9) GROUP BY c.patnum, c.procdate) claimvisit ON (claimvisit.patnum = apt.patnum AND DATE_FORMAT(claimvisit.procdate, '%Y-%d-%M')= DATE_FORMAT(apt.aptdatetime,'%Y-%d-%M')) /*##### END JOINS >>> GET VISIT FINANCIALS ######*/ where apt.aptstatus in (1,2,3,5,6) and capt.aptnum is null /*and apt.patnum = 1*/ and apt.aptnum = 5854 group by apt.aptnum order by aptdatetime asc, papt.itemorder asc
Here is an example return:

Code: Select all

{
  "data": [
    {
      "patnum": 1,
      "aptnum": 1,
      "nextaptnum": 0,
      "plannedAptNum": null,
      "plannedPriority": null,
      "AptTypeNum": 1,
      "AptTypeName": "Dental Checkup",
      "aptstatus": 2,
      "aptstatusDisplay": "Done",
      "confirmed": 366,
      "confirmedDisplay": "Out the Door",
      "unschedstatus": 0,
      "unschedstatusDisplay": null,
      "isnewpatient": 1,
      "ishygiene": 1,
      "op": 1,
      "dentist": 8,
      "dentistDisplay": "John Smith",
      "hygienist": 10,
      "hygienistDisplay": "Jane Doe",
      "aptdatetime": "2023-11-15T10:00:00",
      "pattern": "///XXXXXXXXX//////",
      "duration": 90,
      "insplan1": 0,
      "insurance_plan1": null,
      "insplan2": 0,
      "insurance_plan2": null,
      "TotalFees": 704,
      "DeductibleEst": 0,
      "InsuranceEst": 0,
      "WriteOffEst": 405,
      "CoPayEst": 299,
      "Deductible": 0,
      "InsuranceCoverage": 0,
      "WriteOff": 0,
      "CoPay": 704,
      "Adjustments": -405,
      "patientPaid": 299,
      "insurancePaidtoRinse": 0,
      "insurancePaidtoPatient_DeltaDental": 0,
      "patientOwes": 0,
      "recallEst": 704,
      "claim_count": 0,
      "claim_status": "",
      "InsNote": "",
      "InsurancePaid": 0,
      "InsuranceOutstanding_DeltaDental": 0,
      "InsuranceToRinse": 0,
      "PatCCPayments": 299,
      "PayPlanPrincipal": 0,
      "datedue": ",2024-02-15,2024-11-15",
      "baseest": 0,
      "aptProcCodeArray": [
        "Comprehensive Dental Checkup",
        "Digital X-Rays (4) - Bitewings", << get layperson terms if exist, otherwise get descript
        "Digital X-Ray (PA)",
        "Digital X-Ray (PA)",
        "Teeth Cleaning",
        "Gum health check",
        "Digital X-Ray - 3D CT scan",
        "3D Scan",
        "Rinse Membership Essentials",
        "Ortho consultation",
      ],
      "aptProcCodeObject": [
        {
          "procCode": "D0150",
          "procDesc": "Comprehensive Dental Checkup",
          "ProcPriority": "1",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D0274",
          "procDesc": "Digital X-Rays (4) - Bitewings",
          "ProcPriority": "1",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D0220",
          "procDesc": "Digital X-Ray (PA)",
          "ProcPriority": "1",
          "ProcLocation": "8",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D0230",
          "procDesc": "Digital X-Ray (PA)",
          "ProcPriority": "1",
          "ProcLocation": "24",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D1110",
          "procDesc": "Teeth Cleaning",
          "ProcPriority": "1",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "FMP",
          "procDesc": "Gum health check",
          "ProcPriority": "1",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D0367",
          "procDesc": "Digital X-Ray - 3D CT scan",
          "ProcPriority": "1",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D0351",
          "procDesc": "3D Scan",
          "ProcPriority": "1",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "R0002",
          "procDesc": "Rinse Membership Essentials",
          "ProcPriority": "99",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D8660",
          "procDesc": "Ortho consulation",
          "ProcPriority": "99",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        },
        {
          "procCode": "D8660",
          "procDesc": "Ortho consulation",
          "ProcPriority": "99",
          "ProcLocation": "",
          "ProcCondition": "",
          "NoBillIns": "0"
        }
      ]
    }
  ]
}

SLeon
Posts: 558
Joined: Mon Mar 01, 2021 10:00 am

Re: Appointment Summary View

Post by SLeon » Mon Dec 11, 2023 12:48 pm

I am still unable to run your query (I replaced the aptnum in both this and the previous one) so I do appreciate you sharing the return payload.

Our API methods are analogous to either SQL queries to a single table (Appointments GET) or a report shown in Open Dental's UI (AccountModules GET ServiceDateView). Because there is not a way in Open Dental to view the range of data you are after in one place, the API cannot either. I am happy to add PlannedAppts GET to our development list. Are there other tables we could expose via the API to help you compile this information?

rinse-dental
Posts: 107
Joined: Wed Apr 06, 2022 12:04 pm

Re: Appointment Summary View

Post by rinse-dental » Fri Jan 05, 2024 12:01 am

Super strange that the query doesn't run for you... 🤷‍♂️

Yes, it would be helpful if you'd consider including the following in your dev list:
  • Add planned priority to Appointments GET (multiple) - I can combine this with AptStatus filter to get a good list of planned apts
  • Add planned priority to Appointments GET (single)
  • Add PatPlan field to InsPlans GET
Also, if it's possible - a TreatmentPlanModules GET PatProcs - similar to AccountModules GET PatientBalances would be incredibly useful. This would allow us to provide more efficient/accurate views treatment plan AND scheduled and planned appointments.
/TreatPlanModules/[PatNum]/PatProcs?EstimateAsOf=[Date]
  • ProcNum
  • AptNum
  • TP date
  • PriorityString
  • PriorityDefNum
  • Fee
  • Allowed
  • PriIns
  • SecIns
  • PatOwes
  • EstimateNote

SLeon
Posts: 558
Joined: Mon Mar 01, 2021 10:00 am

Re: Appointment Summary View

Post by SLeon » Fri Jan 05, 2024 9:03 am

Good morning,

There is no field corresponding to "planned priority" in the appointments table, so we cannot add the field to the Appointments GET (single/multiple) endpoints. This information is in the PlannedAppt table, so I have added this GET to our development list.

There is no corresponding field to PatPlan in the Insplan table. You can use PatPlans GET with a patnum, and then use the resulting InsSubNum in InsSubs GET to get the plan information. We can also add InsSubNum as an optional parameter to InsPlans GET, if that would be helpful.

Much of the Treatment Plan Module information you describe is available in the ProcTPs GET endpoint. Please let us know if there are additional fields in that table you need us to expose.

justine
Posts: 233
Joined: Tue Dec 28, 2021 7:59 am

Re: Appointment Summary View

Post by justine » Tue Jun 25, 2024 1:53 pm

Hello rinse-dental,

Open Dental version 24.2 deprecated the plannedappt table and replaced it with the appointment.ItemOrderPlanned field. Please see the schema and appointments API documentation for additional information. This functions identically to how the plannedappt table did, without having to make any additional API calls.

24.2.4 was released on 25 June 24.

Thanks!

rinse-dental
Posts: 107
Joined: Wed Apr 06, 2022 12:04 pm

Re: Appointment Summary View

Post by rinse-dental » Thu Jul 11, 2024 1:54 pm

Got it. Thanks!

Post Reply