All work

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.

The Sehat Saarthi screening menu, headed in Gurmukhi with English beneath, a phulkari diamond border, a banner reading "Proof-of-concept — pending clinical validation", and a note saying voice is not available on this phone.
Gurmukhi first, English second, on every single element — and the honest banner is in the masthead, not buried in a footer. Note the line on the right: when the phone has no Punjabi voice, it says so rather than silently failing.

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
  • 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.
A screening module in Sehat Saarthi showing bilingual input fields with units and ranges.
Each screening is a config entry, not a code path — which is what made nine of them possible on a zero-rupee budget, and what made deleting two of them a one-line change.

The numbers

9 → 7
models built, then deliberately reduced to the ones that could be trusted
receipts
  • registry.ts:29Two working modules are present in the repo but withheld from the live app.
0.997
chest X-ray recall — 389 of 390 caught
receipts
0.543
the specificity behind that recall — it over-flags, and says so
receipts
0.19
test separation on the retinopathy model — the number that got it deleted
receipts
0 of 9
report texts signed off by a clinician — stated openly, not buried
receipts
  • reports/Every file carries reviewed_by: "" pending human review.
5.3 GB
the TB dataset that would not finish downloading — so that module was not shipped at all
receipts

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.
Built with
  • Python
  • FastAPI
  • scikit-learn
  • ONNX Runtime
  • React
  • Vite
  • Hugging Face Spaces
  • Noto Sans Gurmukhi

Westerlund 2

Three thousand stars, about two million years old — and already reshaping everything around them.

The brightest stars in this cluster are burning through their fuel so fast they will be gone in a few million years, while carving the gas that made them into the pillars you can see at the edges. Young, powerful, and genuinely dangerous to their own surroundings.

Westerlund 2 · NASA, ESA, the Hubble Heritage Team (STScI/AURA), A. Nota (ESA/STScI) and the Westerlund 2 Science Team · CC BY 4.0