ConsulDent
ConsulDent is a clinical support tool for dentists. I build it with a practising dentist — my co-founder, and my closest friend — who runs a clinic and brings the cases, the vocabulary, and the veto. It's live on the App Store, in paid use, and still changing shape based on what the current cohort asks for.
~50 dentists reached · 3 test cohorts · payments active
Where this came from
I spent a lot of time in my co-founder's clinic. Not interviewing her — just being there, watching her work over a couple of years before any of this was a product.
Two things stood out.
The first is that dentistry hasn't been touched much by any of this yet. The tooling is old, the workflows are paper-shaped, and most of the dentists I met weren't really aware of how fast things were moving elsewhere. That's not a criticism of them; they're busy treating people. But the gap between what's possible and what's in use is enormous.
The second is the mess. There are documents everywhere — guidelines, product inserts, protocols, notes from a course someone attended. Notes get written and lost. Sometimes they don't get written at all, because the patient is in the chair and there's no free hand. When a dentist needs to check something and the colleague who'd know isn't in that day, they ask whoever is around, or they go with what they remember.
The obvious thought is: they could just ask an AI. And they can't, really — not for anything that matters. A model that answers confidently without telling you where the answer came from is useless in a clinic. The dentist has no way to check it, and "the AI said so" isn't a defensible reason for a treatment decision. What's missing isn't an answer. It's an answer you can trace.
That's the product in one line: a place a dentist can ask something and get back an answer with the source attached.
What it does
Four things, and they came from watching what goes wrong rather than from a feature plan.
Verified answers with sources. Clinical questions answered from a curated corpus of dental textbooks and guidelines, with numbered inline citations that resolve to real reference metadata — authors, organisation, year, a link where one exists. If there's no source, there's no claim.
Drug information and dosage calculation. Against a hand-verified dosing database, not model recall. The model's only job is recognising what's being asked; the arithmetic — weight-based paediatric dosing, local anaesthetic maximum-dose limits — is deterministic code. This is where a confident wrong answer does the most damage, so the language model is kept away from the numbers.
Documents. Generating and organising the paperwork that currently gets scattered: consent forms, consultation notes, a post-operative instruction library, treatment records.
Patient-facing material. Explanations and brochures a patient can actually read, produced from the same clinical basis, so the dentist isn't rewriting the same explanation for the twentieth time.
From desk to chairside
ConsulDent started as a web app — a server-rendered Django product you'd use at a desk. The longer-term ambition behind it was ambient listening: the system hears the appointment, and the notes and patient material fall out of the conversation instead of being typed afterwards. We built the first slice of that — hands-free voice capture, press-and-hold, because you can't type with gloves on — and staged the rest.
Then the usage data disagreed with the plan. The moments dentists actually reached for the product were chairside and between patients, on a phone. Desk usage was the minority.
So we moved to mobile: a native app on the App Store talking to the same Django backend over a JSON API. The web panel still exists, but mobile is where the product lives now, and the ambient listening work waits until the mobile habit is established rather than the other way around.
Why it isn't a chat box
The easy build was a chat window over a retrieval pipeline. We didn't do that, and the reason is about the clinic rather than the technology.
Chat moves work onto the dentist. She has to phrase the question well, judge whether a fluent answer is actually grounded, then turn a conversation into something she can file or hand to a patient. Between two appointments, that doesn't happen.
So the tasks produce artifacts, not transcripts. Each one has a shape — a consent form, a post-op instruction sheet, a patient explanation, a dosage calculation — with references attached. That makes the output checkable, reusable, and honest about what the system does and doesn't do.
Voice input exists for the dull reason above: gloves. Speech is the only input the clinic allows during treatment.
How it's built
One Django codebase serves everything — server-rendered pages for the web panel, and a JSON API with JWT auth for the mobile app. Two paths share one corpus: an offline ingestion side that writes, and a clinic-time query side that only reads.
Ingestion runs offline as a resumable pipeline. PDFs are validated, extracted with an OCR fallback for scanned sources, chunked along their own structure, embedded, and uploaded incrementally. A registry tracks every processed source so runs can resume, and per-source deletion means a bad document can be pulled from the corpus without touching the rest.
At query time, a single router call does four jobs at once: decides whether the question needs clarification, a direct reply, or retrieval; classifies the clinical intent — comparison, dosage, procedure, post-op; and rewrites the Turkish question into two or three English search queries covering different facets of it. Those run against the vector store in parallel, results are deduplicated, and the answer is composed in Turkish with inline numbered citations, streamed token by token so the dentist sees something immediately rather than waiting for a complete response.
The rewrite
The first production pipeline was around 3,000 lines: a planner that narrated its reasoning, answers generated as structured JSON and validated against a small domain-specific markup of paragraphs, tables and callouts, follow-up suggestion chips, and a retry loop for when the model drifted out of Turkish. It produced good demos and slow answers.
The current one is about a fifth of that size. Consolidated prompts — roughly 400 tokens where there used to be 900 — full chunk context instead of truncated excerpts, one strong language instruction instead of a retry loop, and markdown streamed as tokens instead of JSON rendered after the fact.
The lesson wasn't that structure is bad. It's that structure belongs where it pays. Documents and dosage calculations are still schema-validated, because those are artifacts someone files or hands to a patient. The conversational answer became a stream, because nobody files a conversation.
Vector storage
Vectors live in Postgres with pgvector — a managed Supabase instance queried through a single similarity-search function — rather than in a dedicated vector database.
The usual options were Pinecone, Qdrant, Weaviate. Postgres means one well-understood store: SQL against chunk metadata, ordinary tooling, no second database technology to operate. At the corpus size we're working with, that simplicity is worth more than raw retrieval performance.
There's a ceiling. pgvector won't match a purpose-built index at tens of millions of vectors. If the corpus grows past what one instance handles comfortably, this gets revisited.
Chunking
The first version split documents into fixed-size chunks with overlap, which is what every tutorial does.
It broke in a way specific to clinical text. A dosage table would get separated from the heading naming the patient group it applies to. Contraindications would end up in a different chunk from the indication they belong to. Retrieval then returned sections that were topically correct and clinically wrong, which is worse than returning nothing.
The fix was chunking along the document's own structure — chunks keyed to headings, carrying their section path as metadata that later steps can check. It costs ingestion time, because every new source format needs mapping work before it enters the corpus, so the library grows more slowly than a naive pipeline would allow.
Drug data
The drug side deliberately doesn't use retrieval. There's a small, hand-verified dosing database — each drug goes through a verification worklist before it ships — plus Turkish brand-name mappings and a local anaesthetics reference. Questions that mention a drug are caught by intent detection and routed to deterministic calculators. A dentist asking for a paediatric amoxicillin dose gets arithmetic over verified numbers with the reference attached.
What we measure
Saying the cohort shapes the product only means something if you can see the cohort, so the product is instrumented fairly heavily.
Every meaningful action is logged as a typed event with a platform tag — around thirty of them. Queries submitted and what intent they were classified as. Follow-up chips clicked. Sources opened, and which ones. Citations copied. Voice transcriptions. Documents generated, by type. These aggregate into internal dashboards that answer the questions I actually have: which of the four features gets used, at what time of day, on which device, and whether a dentist who generated a document last week came back this week.
Two things the data has already told us that I would have guessed wrong.
The first is where people use it — chairside on a phone, not at a desk. That's what moved the product to mobile.
The second is which features earn their place. Some things I was confident about get opened rarely. Things I filed as secondary turn out to be why someone opens the app at all. I'm not going to pretend I predicted that; the event stream told me, and the roadmap changed.
Payments
In-app purchases through the App Store, handled with RevenueCat, with entitlements driven by webhooks — including the unglamorous cases, like not revoking access the moment someone cancels, because they've paid through the end of the period. Not the interesting part of the project, but worth stating plainly: people pay for this.
What's been hard
Answer quality is the permanent fight. Not a problem that got solved — an ongoing one. Embeddings are good at topical similarity and completely indifferent to clinical correctness, so the paragraph about the adult protocol will rank beautifully against a question about a child. The chunking rework helped. So did multi-query retrieval and the rule that a claim without a source doesn't ship. But there's no version of this where we declare it done. Every cohort surfaces answers that are technically defensible and practically useless, and we work through them.
The corpus and the users don't share a language. Questions arrive in Turkish clinical shorthand; the best sources are mostly English textbook sections. The pipeline handles this explicitly — questions get rewritten into English search queries, retrieval runs in English, composition comes back in Turkish with the terminology intact. It works, but getting there needed case-by-case review, my co-founder going through outputs one at a time on top of running a clinic. Her review time has been the scarcest resource in the project.
Habit is a harder opponent than accuracy. A dentist who has asked a colleague for twenty years doesn't switch because the retrieval is well-designed. Anything that needs two free hands loses instantly. Most of what the cohorts are testing is whether the thing survives contact with a real working day.
Where it stands
Live on the App Store, around fifty dentists across three test cohorts, payments running. The mobile release moved usage toward the moments the product was designed for.
The current cohort is less about proving the retrieval works and more about finding out which of the four features people actually reach for. That answer has already shifted once and will probably shift again.
Not built yet: clinic-level accounts, multiple dentists sharing a workspace. That's the obvious next thing and it stays unbuilt until a clinic asks for it.