PYTHON · NLP · AUTOMATION · Private clinic group, Leinster
TriageLensNLP Triage of Inbound Clinic Enquiries
A private clinic group in Leinster received about 1,900 enquiries a week by email and web form into one shared inbox. Reception read every one to decide where it went; urgent items waited behind appointment-change requests. I built a triage classifier that routes the routine 70% automatically and escalates anything clinical or uncertain to a person straight away — deliberately not the other way round.
- −74% median first-response time on routine enquiries
- 96.2% routing accuracy on the held-out sample
- 100% of clinical-flag enquiries routed to a human
01 — Context
The problem.
One shared inbox, nine intents and a strictly first-in-first-out reading order. A prescription query about worsening symptoms sat behind fourteen requests to move an appointment, because nobody could tell them apart without opening each one. Median first response on routine enquiries was 6.2 hours; on the enquiries that mattered most it was no better, which was the real problem.
Any solution had to be conservative by construction. In a clinical setting the cost of misrouting an urgent enquiry is not symmetrical with the cost of a receptionist reading one extra email, and the design had to encode that rather than optimise a single accuracy number.
02 — Method
The approach.
The build, in the order it happened.
-
Defined the intents with reception, not for them
Nine intents drawn from a sample of 3,000 historical enquiries, labelled by two reception staff with a third resolving disagreements. Inter-annotator agreement was measured before any modelling began — if two humans cannot agree on a label, no model will learn it.
-
A clinical-flag layer that runs first
Before classification, a high-recall rule and embedding layer screens for symptom, medication, safeguarding and distress language. Anything it touches goes to a human queue immediately and is never auto-routed, whatever the classifier subsequently says.
-
Classification with an abstain threshold
A linear classifier over sentence embeddings, tuned for precision rather than accuracy. Below the confidence threshold the model abstains and the enquiry goes to the general human queue — abstention is a valid, logged outcome, not a failure.
-
Routing as a reversible service
A FastAPI service applies a queue label via the Graph API and writes an audit row. Every routing decision is visible, reversible by reception in one click, and every reversal is reviewed weekly as training signal.
03 — Outcome
What changed.
- Median first-response time on routine enquiries fell from 6.2 hours to 1.6 hours — a 74% reduction — and urgent items no longer queue behind them.
- Routing accuracy of 96.2% on a held-out sample of 600 enquiries, measured on the 89% the model did not abstain on.
- Every enquiry caught by the clinical-flag layer reached a human on first touch across the twelve-week evaluation, with no auto-routing exceptions.
- Reception time spent sorting the inbox fell by around 14 hours a week and moved to patient-facing work.
04 — The numbers
Median first-response time on routine enquiries.
Tracked as hours, from Wk 1 through Wk 12 — low 1.6, high 6.2.
05 — Screens
What it looks like in use.
Confusion across the nine intents, with abstentions counted separately — a model that declines to answer is behaving correctly here.
The audit log. Every decision is reversible in one click, and every reversal feeds the weekly review.
06 — Stack & role
Built with.
- Python (spaCy, scikit-learn, sentence-transformers)
- FastAPI
- PostgreSQL
- Microsoft Graph API
- Role
- Consulting data scientist — labelling design, model, routing service
- Duration
- 11 weeks
- Client
- Private clinic group, Leinster
- Period
- 2026
07 — Questions
The questions I get asked about this one.
Processing runs inside the clinic’s own tenancy, no enquiry text leaves it, and the training corpus was pseudonymised before labelling. Retention on the audit log matches the clinic’s existing policy rather than adding a new one.
Because a wrong route costs more than an unrouted enquiry. Abstention keeps precision high on the queues that are automated and puts the genuinely ambiguous cases where they belong — in front of a person.
No. The system routes and prioritises; it does not reply. Every patient response is written by a human.