Which Patients Will Ghost? Predicting Dental No-Shows Without the AI Hype

9 min read
Which Patients Will Ghost? Predicting Dental No-Shows Without the AI Hype

Every empty chair is revenue you can’t get back. The patient who booked three weeks ago and silently doesn’t turn up costs a dental practice the full value of that slot — and the advice every practice owner hears is the same: text everyone a reminder.

I wanted to know if that advice actually holds up, and whether you can do better than spraying reminders at the whole book. So I took 110,527 real appointments — a public dataset of clinic visits, each labeled with whether the patient showed up — and ran it through plain, boring machine learning. No large language model, no AI chatbot, none of the 2026 hype. Just the kind of predictive modeling that has worked on spreadsheet-shaped data for twenty years, sitting inside the database you already run.

The data had a trap in it, an obvious lever everyone ignores, and a clear answer to “is this worth doing.” Let me walk through all three the way I found them.

The trap: reminders look like they cause no-shows

The dataset records whether each patient received an SMS reminder. So the first thing I checked was the obvious one: do reminders help? Here’s what the raw numbers say.

Left: patients with no reminder no-showed 16.7%, patients who got a reminder no-showed 27.6%. Right: within each booking-lead-time bucket, the reminder group no-shows the same or less.

Look at the left panel. Patients who got a reminder no-showed 27.6% of the time; patients who got no reminder no-showed only 16.7%. Taken at face value, reminders don’t just fail — they appear to make things worse. A practice owner reading that would reasonably conclude the reminders are a waste of money.

That conclusion is wrong, and the reason it’s wrong is the whole point of measuring properly. Reminders in this data weren’t sent at random — they were mostly sent for appointments booked far in advance, and those appointments were already the most likely to be forgotten. The reminder wasn’t causing no-shows; it was a marker for the long-lead bookings that ghost anyway. When you compare like-for-like — same booking lead time, reminder versus no reminder (the right panel) — the reminder group no-shows the same or less in every bucket. Reminders help. The raw number just hid it behind a confound.

This is the difference between looking at a dashboard and actually modeling the thing. The naive read had the right data and the wrong answer.

The lever everyone ignores: booking lead time

Once lead time turned out to be lurking behind the reminder result, I looked at it head-on. It’s the most useful chart in this whole post:

No-show rate by how far ahead the appointment was booked: same-day 5%, 1-3 days 23%, 4-7 days 25%, 8-14 days 31%, 15-30 days 33%, 30+ days 33%.

A same-day appointment almost never gets missed — under 5%. Book two to four weeks out and you’re looking at one in three. Nothing else in the dataset — not age, not income support, not chronic conditions — comes close to this signal. The single most powerful thing a practice can do about no-shows isn’t a fancier reminder; it’s shortening the gap between booking and visit: shorter waitlists, same-week slots for high-risk patients, a quick confirmation step for anything booked far out.

The other reliable signal is history: patients who’ve ghosted before ghost again (about 30% versus 15% for those with a clean record). That one’s intuitive — and it’s a single query over each patient’s past visits, the kind of feature your database computes for free.

Can you actually predict it? Yes — and a one-liner gets you most of the way

Knowing the drivers, I built the predictor as a ranked list: score every upcoming appointment by how likely it is to be missed. I deliberately raced three approaches of increasing fanciness against each other, scoring all of them on appointments from a later time period than they were trained on — because in real life you only ever have the past to learn from.

The feature that does most of the work is just lead time, and computing it is one line:

-- how many days between booking and the appointment — the strongest predictor
appointment_day::date - scheduled_day::date AS lead_time_days

Here’s how the three approaches ranked, measured by AUC — a standard 0.5-to-1.0 score for “how well does this sort risky from safe,” where 0.5 is a coin flip:

ApproachHow fancyAUC
Sort by lead time aloneone line of SQL0.69
Logistic regressionclassic stats, 13 features0.68
Gradient-boosted treesthe “real” ML model0.74

Two honest things jump out. First, sorting by a single column already gets you to 0.69 — most of the way there, deployable this afternoon, no model to train or maintain. Second, unlike a lot of “do you really need the fancy thing” stories, here the fancy thing genuinely earns a bit: the gradient-boosted model adds a real five points over the simple rule. (The middle option, a plain linear model, actually did slightly worse than the one-liner — piling features into the wrong kind of model isn’t the same as improving it.)

None of this is an LLM. It’s gradient-boosted decision trees — a technique from the 2010s that quietly runs a huge share of the predictive work in industry, and is exactly right for “predict a yes/no outcome from columns in a table.” The 2026 instinct to reach for a giant language model here would be slower, costlier, and worse.

The part that actually matters: targeting, not predicting

A risk score is useless on its own. What you do with it is rank your day and spend your limited follow-up effort where it pays. A reminder text is nearly free, so by all means send it to everyone. But the interventions that actually move a stubborn no-show — a personal phone call, asking for a deposit, deliberately overbooking the slot — cost real front-desk time and goodwill. You can only do so many. So the real question isn’t “who will no-show,” it’s “if I can only work 20% of the book, which 20%?”

Capture curve: calling a random 20% of patients reaches 20% of no-shows; sorting by lead time reaches 32%; ranking by the model reaches 39%.

Call a random 20% of patients and, by definition, you reach 20% of the people who’ll ghost. Rank by the model and call the riskiest 20%, and you reach 39% of all no-shows — nearly double the catch for the exact same number of calls. The one-line lead-time sort gets you to 32% — again, most of the value for none of the effort.

What’s that worth? Model it for one busy practice — 1,000 bookings a week, a recovered chair worth $120, a follow-up costing about $8 of staff time, and assume the intervention saves 40% of the no-shows you reach. Same call budget, three different ways to choose who to call:

Net recovered revenue per week: random list $206, sort by lead time $1,226, model ranking $1,865 — about $97,000 per year.

Same number of calls every week. The random list nets a couple hundred dollars; ranking by the model nets close to $1,900 a week — about $97,000 a year for a single practice — because it recovers roughly twice the slots (29 versus 15 a week) for identical effort. The simple lead-time sort captures most of that on its own. The expensive part was never the model; it was calling the wrong people.

Why I’d build this on Postgres, and why there’s no LLM in it

Two deliberate choices worth naming.

It lives in the database you already have. Every number above came out of a single Postgres instance: the features (lead time, a patient’s prior-no-show history) are SQL, and the final risk score can be written back as a column your booking screen reads. There’s no separate analytics platform, no data warehouse, no pipeline to babysit — the same “one system, not four” approach I take to search and recommendations on Postgres.

And not everything is a language model. This is the unglamorous truth behind a lot of “AI for business” projects in 2026: the highest-ROI problems are often not LLM problems. Predicting a yes/no outcome from tabular data is a job decision trees have done well, cheaply, and explainably for years — and explainability matters when a human has to act on the score. The skill isn’t knowing the fanciest tool; it’s measuring which tool the problem actually needs — and being honest when the answer is “the boring one from 2015.”

A few caveats I’d flag before anyone ships this: the data is from public clinics in one country and one year, so the exact numbers won’t transfer — your practice’s drivers should be measured on your bookings. The ROI rests on stated assumptions (slot value, how often a call actually saves a booking); the right move is to pilot on one month and measure the real recovery rate. And a risk score is a prompt for a human, not an excuse to hassle anyone — the goal is to fill chairs, not to profile patients.

But the shape of the answer is robust, and it’s not what the hype would tell you. The win wasn’t a clever AI. It was measuring properly enough to dodge the reminder trap, finding the one lever that mattered, and spending a limited budget on the right 20%.


I help small businesses turn the data they already have into decisions that pay — predictive models, the database to run them, and an honest read on when you need AI and when you very much don’t. If empty slots, churn, or duplicate-ridden customer lists are costing you money, let’s talk.

Related Articles

Data & Search 9 min read
Your Customer List Is Full of Duplicates. Here's How to Find Them in Postgres.

Finding Duplicate Customers in Postgres

Every CRM quietly fills up with the same person entered three different ways — and you pay for it in wasted marketing, wrong reports, and split histories. I measured three ways to find the duplicates on 5,000 labeled records: plain exact match, Postgres fuzzy matching, and AI embeddings. The boring one won — exact numbers, where each method wins, and why you don't need a data-quality platform.

AI Engineering 10 min read
Don't Make an LLM Find Your At-Risk Customers. Make It Explain Them.

Don't Make an LLM Find At-Risk Customers

The tempting move is to hand an LLM your customer data and ask which accounts are about to churn. That's the expensive, unreliable way to do the one thing classic ML is great at. I built the whole pipeline on a real 1M-row e-commerce dataset: a one-line SQL rule already catches 75% of churners in the top-flagged 20%, gradient boosting nudges it up — and the LLM's only job is to explain and route each case. Then I measured whether a free local 27B stays faithful to the model's signals: 0% hallucinated drivers across 109 cards.

Data & Search 15 min read
I Built a Recommendation Engine in Pure Postgres. The Embeddings Lost to ORDER BY count(*).

The Embeddings Lost to ORDER BY count(*)

Everyone reaches for an AI vector database to build 'people also bought.' I did too — then measured it on real Amazon data and watched the fancy approach lose to a few lines of SQL that just count what customers actually buy together. Here's the honest, plain-English comparison: what wins, what doesn't, and why you probably don't need the extra infrastructure — all in one database.

Web Engineering 6 min read
Background Processing in Next.js Part 1

Background Processing in Next.js Part 1

Learn about Background Processing in Next.js Part 1