The schedule was full. The month still came up short
Production to collections, every step is a named measure
Six operatories, two doctors, three hygienists. One screen now shows where the money goes between work performed and money deposited, which chair hours sit empty every week, and which accepted treatment never made it onto the schedule.
PMS exports (ledger, procedures, adjustments, payments, schedule) through Power Query into Power Pivot. Net collection rate, adjustment rate by plan, production per chair hour and pending treatment are all measures over one date table.
01How it worked before us
The practice was healthy. Good team, loyal patients, twenty years in the same building. The owner's problem was not the business, it was the instruments: she was flying on numbers she did not trust.
Before our work:
- Production and collections were two unrelated numbers. The practice management software reported both, on different screens, for different date ranges. Nobody could hold the bridge between them in their head.
- Write-offs were treated as weather. Contractual adjustments happened, everyone knew they happened, and no one had ever looked at them by insurance plan.
- Treatment was diagnosed and then forgotten. A patient accepts a crown, does not schedule that day, and quietly disappears from view. There was no list of those patients.
- Hygiene recall ran on memory. The front desk chased who they remembered. A patient eight months overdue looked exactly like one seen last week.
- Provider performance was a feeling. Whose schedule actually produced more per chair hour had never been measured.
An independent two doctor practice on a standard PMS with a clearinghouse and an outside accountant. Nothing was missing from the software; the problem was that every number lived in its own report with its own date logic. Specifically:
- No shared date table. Production was pulled by procedure date, payments by deposit date, adjustments by posting date. Any comparison between them was apples to oranges.
- The denominator was never examined. The PMS reports a collection rate against net production, which is already reduced by write-offs nobody had reviewed. A 96% rate can sit on top of a badly negotiated fee schedule.
- Capacity was not modelled. Six operatories multiplied by opening hours is the entire manufacturing capacity of the business, and unlike most costs it cannot be scaled down on a slow Tuesday. No report expressed it.
- Diagnosed and completed procedures were never joined. Both sets existed in the ledger. The difference between them, which is the recoverable money, had no report.
- Insurance aging sat outside the picture. Money already earned, parked in claims, appeared in no summary the owner read.
02What we built
No software change. The practice kept its PMS, its clearinghouse and its accountant. We used the exports the software already produces and built a model that refreshes on one click.
The bridge from production to cash
This is the first thing the owner asked for and the thing that changed the most. Every step between work performed at full fee and money in the account is a named line with a number attached.
Two lines usually surprise people. Contractual adjustments are almost always larger than the team assumes, and once they are broken out by plan the conversation about which contracts to renegotiate becomes concrete instead of theoretical. Insurance still in aging is money already earned and sitting somewhere; when it is a visible number rather than a stack of claims, somebody owns it.
Chairs are the constraint, so measure the chairs
A dental practice sells chair time. The model reports utilization the way a factory would: share of available chair time actually booked, by day and hour.
The pattern is typical and it is actionable in a way a monthly average never is. Friday afternoons and the first hour of Monday are structurally soft; midweek mornings are full. Knowing that turns a recall call from "when can you come in" into "we have Thursday at 8 or Friday at 3", and the soft slots fill first.
Paired with utilization is production per chair hour, the number that lets you compare a hygiene column to a doctor column honestly, and one provider to another without arguing about who works harder.
Treatment that was accepted and never scheduled
This is usually the largest recoverable number in the practice and it is invisible by default. The model cross references diagnosed procedures against completed and scheduled ones, and produces a working list.
| Patient | Diagnosed treatment | Days pending | Value | Benefit left | Priority |
|---|
| Recall bucket | Patients | Avg hygiene value | Value at risk | Typical reactivation | Recoverable |
|---|
| Provider | Chair hours | Production | Per chair hour | Adjustment rate | Case acceptance | Rebook rate |
|---|
* Rebook rate is the share of patients leaving with their next appointment already scheduled.
The benefit column is what makes this list work in practice. A patient with $900 of unused annual benefit and a crown pending in November is a genuinely useful phone call, for them and for the practice. In January the same call is a much harder conversation, so the list sorts itself accordingly.
What it made possible
- The month closes with a picture, not a hunt. One refresh and the bridge, the chairs and the pending lists are current.
- Plan conversations became concrete. Adjustment rate by payer turns "this plan feels bad" into a number in a contract discussion.
- The front desk works a list. Recall and pending treatment are sorted by recoverable value, not by memory.
- Provider reviews use the same numbers every quarter. Per chair hour, adjustment rate, case acceptance, rebook rate.
Architecture
dropped to one folder Google Sheets straight
into Power Query
The one structural decision that makes everything else possible: a single date table, with every fact table joined to it by its own business date. Production hangs off procedure date, payments off deposit date, adjustments off posting date. Only then can the bridge be a single row of measures in one context.
Net collection rate, with the denominator in the open
The rate itself is trivial. The point is to compute and display the write-off separately, so the denominator is never quietly assumed:
-- Net collection rate: of what was collectible, how much we actually got VAR GrossProduction = SUM( 'Procedures'[FeeAtFullSchedule] ) VAR ContractualAdj = SUM( 'Adjustments'[ContractualWriteOff] ) VAR NetProduction = GrossProduction - ContractualAdj VAR Collected = SUM( 'Payments'[Amount] ) RETURN DIVIDE( Collected, NetProduction )
Alongside it the adjustment rate against gross production, sliceable by plan. That single cut is what turns a payer conversation into a negotiation:
-- Adjustment rate against gross production (slice by plan) DIVIDE( SUM( 'Adjustments'[ContractualWriteOff] ), SUM( 'Procedures'[FeeAtFullSchedule] ) )
Capacity as a denominator
Available chair time is not in any export, it is a parameter: operatories multiplied by open hours in the period, from the Google Sheets input. Utilization and production per chair hour both hang off it:
-- Available chair hours in the current context VAR OpenHours = SUMX( 'Calendar', RELATED( 'Hours'[OpenHoursOnDay] ) ) VAR Operatories = SELECTEDVALUE( 'Settings'[OperatoryCount] ) RETURN OpenHours * Operatories
-- Production per chair hour (works for a doctor or a hygiene column) DIVIDE( [Gross production], SUMX( 'Appointments', 'Appointments'[ScheduledMinutes] ) / 60 )
Utilization uses booked minutes over available minutes in the same context, which is what lets the heatmap resolve to a single day and hour without a separate report.
Unscheduled treatment: a join, not a report
The recoverable list is the difference between two states of the same procedure table. Diagnosed, minus anything completed or currently on the schedule:
-- Value of treatment diagnosed and neither completed nor scheduled CALCULATE( SUM( 'Procedures'[FeeAtFullSchedule] ), 'Procedures'[Status] = "Diagnosed", ISBLANK( 'Procedures'[CompletedDate] ), ISBLANK( 'Procedures'[ScheduledDate] ) )
Priority is where the model earns its keep. Remaining annual benefit comes from plan maximums minus benefit already used in the current benefit year, and it decays to zero on the plan reset date:
-- Remaining annual benefit, current benefit year VAR PlanMax = SELECTEDVALUE( 'Plans'[AnnualMaximum] ) VAR UsedYTD = CALCULATE( SUM( 'Payments'[InsurancePortion] ), DATESBETWEEN( 'Calendar'[Date], [Benefit year start], MAX( 'Calendar'[Date] ) ) ) RETURN MAX( PlanMax - UsedYTD, 0 )
Sorting the list by remaining benefit and days pending, rather than by treatment value alone, is what makes the front desk trust it. The expensive implant with no benefit left is a slower conversation than the crown with $900 expiring in six weeks.
Recall as a set of buckets
Recall status is derived, not stored: last hygiene visit plus the interval from the input sheet, compared to today. Buckets, a reactivation rate per bucket taken from the practice's own history, and a recoverable number:
| Component | Where it comes from |
|---|---|
| Months overdue | last hygiene visit + recall interval vs today |
| Average hygiene value | completed hygiene procedures ÷ visits |
| Reactivation rate | historical: share of patients in the bucket who returned |
| Recoverable | patients × value × reactivation rate |
Refresh path
Scheduled PMS reports land in a folder, the claims file lands next to them, Power Query reads the folder rather than named files. One "Refresh all" and the whole month is current, including anything the office manager changed in the input sheet.
03Timeline and outcome
| Stage | From start |
|---|---|
| Audit: PMS exports, fee schedules, plan participation | Week 1 |
| Production to collections bridge, adjustments by plan | Week 2 |
| Chair utilization, production per chair hour, provider view | Week 3 |
| Unscheduled treatment and recall working lists | Weeks 4-5 |
How much of the pending treatment and overdue recall converts depends on your team and your patient base. We size the opportunity during the audit, from your own exports, before any work starts.
A similar problem on your side?
Tell us and in 30 minutes we will figure out what is possible.
