Project 08
Sehat Saarthiਸਿਹਤ ਸਾਰਥੀ
The decision always belongs to the doctor. The saarthi only guides the way.
Nine models built, seven shipped. The two I deleted at midnight are the reason I would trust the seven.

Punjab's health problem is not treatment, it is detection. Roughly half the specialist posts are empty and many rural clinics have no lab at all, so a generalist doctor sees a hundred patients a day and has to decide, from very little, who needs to travel to the district hospital. I wanted to build the thing that helps with that decision — for a health worker on a cheap Android, in Punjabi, over unreliable internet, for zero rupees.
I got nine models working. Chest X-ray, liver, diabetes, heart, breast, anaemia, skin — and two image models for Alzheimer's and diabetic retinopathy. Then I did something that I think matters more than any of the accuracy numbers: I opened my own live app and fed the Alzheimer's module an MRI of a completely healthy brain. It told me 80% dementia.
I deleted it that night. Both image models — including the Alzheimer's one, which was descended from the project that had won me a hackathon at IIT Ropar. The reason I wrote down is the thing I learned: tabular models cannot be fooled by domain shift, because a glucose of 200 is 200 everywhere; image models can. And I answered the obvious objection — why not just add a warning? — with why it does not work. A family still reads "80% dementia". For them, the number itself is the harm.
Everything else in it follows from that. The disclaimer is a required field on the response schema, not a UI convention. There is no language model anywhere near the medical text. If a model fails to load, it falls back to something deliberately meaningless and flags itself, so a broken deployment can never be mistaken for a working screening. And it is honest in writing that it over-flags — high recall bought with low specificity, which it tells the patient in Punjabi.
Why it was hard
Punjab’s health bottleneck is detection, not treatment: roughly 47% of specialist posts vacant, 62% rural staff shortage, many clinics with no lab at all. The user is a generalist doctor seeing a hundred patients a day, or a health worker on a cheap Android with unreliable internet, serving low-literacy Punjabi-speaking families — so a language toggle is itself a literacy test, and the real downstream artefact is a piece of paper the patient carries to the district hospital. On a zero-rupee budget with no GPU, every model is a small-public-dataset baseline. Which made my real problem not accuracy but calibrated honesty.
How it actually works
There is no AI writing anything a patient reads. Every sentence comes from a template a human wrote and checked, in both languages.
I put no language model anywhere in this system. Every sentence a patient reads comes from a reviewed bilingual template, because I locked a rule forbidding free-form generation of medical text before I wrote a line of it.
1 receipt
- content.pyReviewed report blocks loaded from JSON; no generation path exists.
The “this is not a diagnosis” line is not a design choice somebody could quietly drop. A result cannot physically come back without one attached.
The disclaimer is a required field on the response schema, not a UI convention. A result physically cannot be returned without one.
1 receipt
- schemas.py:21probability, label, confidence, recall_note, disclaimer, report, demo — all required.
When something breaks it does not hand back a plausible-looking number. It hands back a deliberately meaningless one and announces that it is broken, the whole way to the screen.
If a model fails to load, it degrades to a deterministic stub that is clinically meaningless — and flags itself as demo through the entire response chain, so a broken deployment can never be mistaken for a working screening.
2 receipts
- registry.py:93StubModel fallback with a logged reason.
- models.py:40A SHA-256 hash of the inputs — deterministic, and deliberately meaningless.
A model that is loud is not a model that is correct, and in a clinic the loud number is the one that does the damage — a family reads “80%” and stops arguing. So no probability reaches the screen until it has been bent back into line with how often that model is actually right.
I calibrated every tabular model isotonically, because uncalibrated confidence on out-of-distribution input is confident nonsense — and in a clinic that is the dangerous failure, not the inaccurate one.
1 receipt
- training/CalibratedClassifierCV with isotonic regression, cv=5, across all five tabular modules.
The Punjabi was written first and the English added after. Gurmukhi is the main line and English the subtitle, on every single element — which is the exact opposite of how these apps normally get built.
I authored the Punjabi rather than translating it. Gurmukhi is the primary string and English the secondary, rendered together on every element, with a self-hosted font and no machine translation anywhere. If the phone has no Punjabi voice, the interface says so instead of silently falling back.
1 receipt
- i18n/strings.tsA single bilingual map, Gurmukhi first.
The real product is a piece of paper. Black on white, both languages, blank lines sized for a pen — because what actually happens in a rural clinic is that somebody is handed something to carry to the district hospital.
The product is the printed referral slip — black on white, bilingual, with blank fields that print as handwriting lines, because the real workflow is paper carried to a district hospital.
1 receipt
- PrintableReferral.tsxPrint-only markup with the boxed result, meaning, next step and disclaimer in both languages.

The numbers
receipts
- registry.ts:29Two working modules are present in the repo but withheld from the live app.
receipts
- metadata.jsonPublished beside the recall, not hidden.
receipts
- MODEL_ANALYSIS.mdMean sick probability minus mean healthy probability.
receipts
- reports/Every file carries reviewed_by: "" pending human review.
receipts
- EXPERIMENTS.md"Refused to force a half-trained module."
The part most people would have skipped
I fed my own live app an MRI of a healthy brain and it returned 80% dementia. I pulled both image models that night — including the Alzheimer’s one descended from the project that won me a hackathon. What I wrote down as the root cause is the line I am proudest of: tabular models cannot be fooled by domain shift, because a glucose reading of 200 is 200 everywhere; image models can. And I answered the obvious objection — why not just add a warning? — with the reason it does not work: a family still sees "80% dementia", and for that family the number itself does the harm.
What it can't do
- Proof of concept, pending clinical validation and regulatory review. Not approved, certified, or fit for unsupervised clinical use — the repository’s own words.
- Trained on small public datasets with no external validation. Domain shift is untested.
- High recall is bought with low specificity. Hepatitis-C specificity is 0.147 at the escalation cutoff, and the app tells the patient in Punjabi that it will sometimes send a healthy person for further testing.
- All nine report texts still await sign-off by a clinician and a Punjabi speaker.
- There are no automated tests in this repository.
- Python
- FastAPI
- scikit-learn
- ONNX Runtime
- React
- Vite
- Hugging Face Spaces
- Noto Sans Gurmukhi
