Medicine supply for public health facilities, reported over WhatsApp
Translation pending
HealthPython · TypeScriptGitHub @akshat333-debugOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Medicines, beds and staff for India's primary health centres, reported on WhatsApp in 7 languages: stock-out forecasts on real HMIS data, OR-Tools transfers, referrals and deputations, officer-approved. Google Cloud (Gemini, Chirp 2, Vertex AI). Code for Communities Track 3 finalist.
Taken from the repository README
Loading the full README.
Read the full README ↗AushadhiNet turns WhatsApp messages from nurses into a live, district-wide picture of medicines, beds and staff, then tells officers exactly what to ask the state for and what to move, today.
Build with AI: Code for Communities, Problem Statement 03 (Smart Health & Supply Chain Resilience). Team Visible, Track 3 finalist.
| ▶️ Demo video (3:51, latest build) | Watch the demo |
| 🌐 Live app (Google Cloud Run) | aushadhinet-frontend-449585498230.asia-south1.run.app |
| 🌐 Submitted link (same app, Render frontend) | aushadhinet-frontend.onrender.com |
| 📊 Pitch deck | docs/pitch/AushadhiNet-pitch.pdf |
| ⚙️ API health | https://aushadhinet-backend-449585498230.asia-south1.run.app/health |
India's primary health centres run out of ORS, zinc, iron tablets and antibiotics while a clinic a few kilometres away holds spare stock. Beds overflow at one PHC while the next has room. Stock, beds and attendance are reported late, on paper, and nobody compares facilities in time to act.
| Google AI | What it does in AushadhiNet (all live) |
|---|---|
| Gemini (vision) | Reads handwritten register photos into medicine + quantity: 98.1% medicine, 100% quantity on 206 test lines |
| Chirp 2 (Speech-to-Text) | Transcribes nurses' voice notes and detects the language |
| Vertex AI embeddings | Matches medicine names typed in 7 scripts to the National List of Essential Medicines |
| Gemini function calling | Officer agent over read-only, jurisdiction-checked tools; any answer with a number no tool returned is dropped |
| Cloud Text-to-Speech + Translate | Spoken order alerts in each facility's language |
| Google OR-Tools | Min-cost-flow transfers; CP-SAT referrals and staff deputations |
| Cloud Run, Firestore, BigQuery, Pub/Sub, Cloud KMS, Secret Manager, Cloud Build | Serverless hosting, live state, history, message queue, signed orders, secrets, one-command deploy |
Works end to end, live: report → confirm → forecast → escalate / draft → approve → KMS sign → WhatsApp alert → SENT / RECEIVED.
Safe by design: no model output is saved without a human YES; the AI has no approve or send tool (prompt injection has nothing to call); orders are cryptographically signed.
Engineered to hold: 442 automated tests (425 backend + 17 browser tests against the live site); retries, double approvals and stale messages are covered by tests.
| The brief asks for | AushadhiNet |
|---|---|
| Real-time medicine stock visibility | WhatsApp text, voice or photo, 7 languages |
| Bed availability | BEDS 4/6 on the same number; facilities 90% full flagged with referral suggestions |
| Staff attendance | Location check-in; more than 200 m from the facility is flagged; deputation suggestions for uncovered roles |
| Forecast demand | Next-week demand per facility and medicine, from the facility's own counts |
| Early stock-out warnings | Morning briefing; district-wide shortages escalated to the state with quantities |
| Cross-district redistribution | OR-Tools drafts within and across districts; a state officer approves cross-district moves |
| Shared modelling across states | Federated learning: Maharashtra and Meghalaya train one model sharing updates, never rows |
| Result | Value |
|---|---|
| Forecasting on real HMIS government data (held-out window) | 19% better than the naive baseline (error 0.871 vs 1.078) |
| 26-week replay of the policy we deploy, 760 facilities | Stock-out weeks -4.9% |
| Same replay with demand read from nurses' own counts | Stock-out weeks -1.8%, unmet units -1.06% |
| Live today (Nashik) | 4 medicines escalated to the state with quantities; transfer drafts covering every facility that can be helped |
We report the results as measured: rebalancing spreads scarce stock, it does not add supply. That is why district-wide gaps go straight to the state as a supply request.
BEDS 4/6 for beds; a shared location to check in.ml/forecast/reported_use.py: two counts a day or more apart, net of transfers in and out; an empty shelf can only raise it), else last ledger week's use (lag-1 naive, which beat LightGBM at this grain). A facility new to the ledger is forecast from its own use, with a wide range. Stock-out probability uses live stock. LightGBM's 19% gain is a separate, district-month benchmark on real HMIS data.SENT <code>, whatever counts it reports meanwhile; the receiver replies RECEIVED <code>. Each order is KMS-signed and both facilities are alerted in their language; if the alerts fail, the order card says so and offers Resend. Referrals and deputations (role and head count signed into the order) are approved the same way.| Nurse on WhatsApp | Officer's morning briefing |
|---|---|
| Beds and staff today | Agent refuses a prompt injection |
| Metric | Value |
|---|---|
| Live demo (simulated pilot: real facility list, synthetic stock) | Nashik: 760 facilities x 16 essential HMIS medicines, plus beds and staff at its 186 PHCs, CHCs and hospitals |
| In the national directory (MoHFW, 2016; listed, not onboarded) | 1,90,609 facilities, 678 districts, 36 states and UTs |
| Medicines escalated to the state (short district-wide, with quantities) | 4 |
| Transfer drafts (live, measured 2026-10-26) | Nashik alone: 77, each at least 10 units, at most 5 donors per need (68% of the units an unconstrained run moves in 160 drafts). State-wide: 58 more cross the line from Dhule's labelled demo stock |
| Forecast error vs naive baseline (offline: real HMIS, district-month, held-out window; not the model the app serves) | 0.871 vs 1.078, 19% better |
| 26-week replay (offline simulation, 760 facilities), policy the app deploys (at least 10 units, at most 5 donors); objective: fewer facility-weeks with unmet demand | Demand from the ledger: stock-out weeks -4.9% (10,774 to 10,244), unmet units +0.41%, fill rate 67.83% to 67.70%, 6,016 transfers. Demand read from weekly counts, as the app now does: stock-out weeks -1.8% (10,584), unmet units -1.06%, fill rate 68.17%, 4,170 transfers. Neither reaches the objective by much; the count version serves more demand but helps fewer facilities (303 better, 196 worse; ledger: 400 better, 124 worse), and makes vitamin A syrup worse (417 to 517 stock-out weeks). Per medicine and facility: eval/reports/replay_breakdown.json. Unconstrained policy, earlier run: -6.0%, +0.25% unmet, 14,815 transfers. Rebalancing spreads scarcity; it adds no supply |
| Federation (offline experiment, 4 states, data-poor Meghalaya) | Local-only 0.993, federated 0.996, centralised 1.000 WAPE: a tie, no accuracy gain. Connected demo (2 processes, 1 round): served model's WAPE 0.90 on Meghalaya's latest validation month |
| Gemini register reading (206 synthetic lines, 4 scripts) | 98.1% medicine, 100% quantity |
| Languages, end to end (UI, WhatsApp replies, voice, agent) | 7 (en, hi, mr, bn, ta, te, kn) |
| Automated tests | 442 (425 backend + 17 Playwright E2E, run against the live site) |
ORS 50 Zinc 120; tap the mic and say it instead; or send a photo of a handwritten list. Every read waits for YES. Tap BEDS 4/6, then the pin button to check in. Switch language to Marathi and send ओआरएस ४०.flowchart LR
N[Nurse on WhatsApp<br/>7 languages] -->|text / voice / photo| I[Ingest<br/>parser + Chirp 2 + Gemini]
N -->|BEDS 4/6, shared location| K[Beds + geofenced check-ins]
I -->|voice, photo, unsure text| C[One-tap confirm card]
C --> L[(Stock ledger)]
I -->|confident| L
L --> F[Forecast<br/>demand + stock-out risk]
F --> E[District-wide gap<br/>supply request to state]
F --> O[OR-Tools solver<br/>draft transfers]
K --> R[Referral + deputation<br/>suggestions]
O --> A{District officer<br/>approves}
R --> A
G[Guardrailed agent<br/>explain + draft only] -.-> A
A -->|signed order| W[WhatsApp alerts<br/>both facilities]
F --> P[Public view<br/>district totals only]
Google Cloud: Cloud Run, Firestore, BigQuery, Pub/Sub, Cloud KMS, Secret Manager, Vertex AI
(embeddings), Gemini, Speech-to-Text (Chirp 2), all provisioned by Terraform and deployed by Cloud
Build. Also Google OR-Tools, LightGBM, FastAPI, Next.js 15, Tailwind, Leaflet + OpenStreetMap,
Playwright. Local/Google provider seam: every Google service has a local twin, so the whole app runs
with zero cloud credentials (docker compose, or any Docker host via render.yaml).
| Week | What happens |
|---|---|
| 1 | terraform apply provisions the state's 37 resources in its Google Cloud project; Cloud Build deploys. This deployment took one day. |
| 2 | Register one block's nurse WhatsApp numbers (alerts go only to registered numbers outside demo mode) and load its issue history; set PUBLIC_API_URL and connect a Twilio WhatsApp number (voice alerts are served from the API for Twilio to fetch); create officer accounts in Firebase Auth (verification is in code, no sign-in screen yet). |
| 3 | Nurses report daily by text, voice or photo; real register photos measure extraction accuracy. |
| 4 | The officer approves transfers and sends supply requests; compare stock-out weeks with a matched block not yet using it, over the same weeks (year-on-year comparisons mix in demand and reporting changes). |
Docs: project.md (requirements), architecture.md, state.md (status and results), docs/build-log.md (build log), docs/api/endpoints.md (API), docs/finale/qa.md (judge Q&A), docs/finale/demo-script.md, tasklist.md (open items).
Running on Google Cloud (project aushadhinet, region asia-south1): Cloud Run (backend and
frontend), Firestore (live stock, contacts, orders), BigQuery (stock history), Pub/Sub (WhatsApp
message queue, push to the backend), Cloud KMS (signed transfer orders), Secret Manager (Gemini
key), Vertex AI (multilingual embeddings for medicine names), Gemini (register photos), Artifact
Registry and Cloud Build. Provisioned with Terraform (infra/terraform/state-module).
DEMO_MODE keeps demo sign-in and the WhatsApp simulator for judges; a real rollout uses Firebase
Auth and Twilio, which are wired but not configured.
Requires: Python 3.12+, uv, Node 20+.
# 1. Backend
uv sync --extra ml --extra dev # add --extra gcp only for cloud mode
cp .env.example .env # defaults are all local-mode; no keys needed
uv run python -m eval.seal_generator # rebuilds data/synthetic/, fails if hash != protocol.lock
uv run pytest -q # 425 tests, about 5 minutes
uv run uvicorn backend.app:app --reload --port 8000
# 2. Frontend (separate terminal)
cd frontend
npm install
npm run dev # http://localhost:3000
On startup (local mode) the backend seeds Nashik's 760 facilities with the latest week of the sealed
synthetic ledger and gives each a dev WhatsApp number. Demo walkthrough at http://localhost:3000:
/sim): pick a facility, send ORS 0. The reply confirms what was recorded.
Send BEDS 4/6 and tap the pin to check in./agent): "Which facilities are at risk?", "Why is Sinnar Phata at risk for calcium?",
"Propose transfers" (drafts only)./orders): Propose transfers, approve one. Both facilities get a WhatsApp text and
voice note, visible in the simulator's outbox for that facility./public is the no-login transparency view (district counts only).
Languages: English, हिन्दी, मराठी, বাংলা, தமிழ், తెలుగు, ಕನ್ನಡ (switcher in the top bar). The whole UI,
medicine names, dates, WhatsApp replies, confirmation cards and local-mode agent answers follow
the chosen language, and reports can be typed in any of these scripts (ओआरएस 50, झिंक ५).
Translations were written for this project and should be reviewed by native speakers before a
real rollout. Ambiguous names such as "IFA" (four products) always get a confirmation card.
infra/docker-compose.yml runs the same two services in containers (backend on locked
dependencies, frontend as a production next build):
docker compose -f infra/docker-compose.yml up --build
On every start the backend regenerates the synthetic ledger and checks it against the seal in
eval/protocol.lock; it refuses to serve if the hash differs. First start takes a minute or two.
Verified on 2026-09-19: the stack came up healthy (backend about 860 MB RAM) and all 11 Playwright
tests passed against the containers.
The evaluation depends on real public data (data/raw/, downloaded and checksum-verified — see
data/README.md) and a sealed synthetic ledger (data/synthetic/, gitignored,
regenerated deterministically):
uv run python -m eval.seal_generator # regenerates data/synthetic/*.parquet, verifies the hash
# already locked in eval/protocol.lock; refuses silently
# if the output has changed since sealing (use --force
# only if the change is a deliberate, reviewed one)
uv run python -m eval.run_final # real-HMIS frozen test: LightGBM vs naive baselines
uv run python -m eval.run_replay # solver vs no-transfer status quo, frozen synthetic test window
uv run python -m eval.run_replay --live # the same, with the deployed policy (>= 10 units, <= 5 donors)
uv run python -m eval.run_replay --breakdown # deployed policy: ledger demand vs demand from counts, per medicine and facility
uv run python -m eval.run_federated_eval # local-only vs federated vs centralised (Meghalaya)
uv run python -m ml.federated.demo # two state processes, one FedProx round, model served at /public/federated
Every run appends to eval/reports/runs.jsonl — nothing is ever overwritten, so the full history of
every scored attempt is auditable. Current honest results (see docs/build-log.md for full detail and the real
numbers behind these headlines):
Forecasting (real HMIS, Maharashtra, frozen test Sep 2019 – Feb 2020, 2,501 district-drug-months):
LightGBM WAPE 0.871 vs lag-1 naive 1.078 and median-of-3 0.959 (19% better than naive). This is
the second scored attempt; the first (L2 objective, WAPE 1.110, lost to naive) stays in runs.jsonl.
The fix (L1 objective, since WAPE rewards the median) was chosen on the validation window only
(eval/tune_val.py).
Federation (Meghalaya, validation split, 541 rows): log-space linear head. Local-only 0.993,
federated 0.996, centralised 1.000; all about 21% better than lag-1 naive (1.261). Federation
ties local-only, it does not beat it. Chosen on an inner split of train (eval/tune_federated.py)
so this validation number was not tuned against. Earlier raw-space run: 1.122 / 1.182 / 1.336.
What it is: an in-process experiment (the connected two-process demo is ml/federated/demo.py), not
a deployment across state projects; Meghalaya also trains
(a data-poor participating state, not an unseen one); noise multiplier 0, so no differential-privacy
guarantee is claimed. Fixed 2026-10-26: the local-only arm scaled validation with validation's own
statistics (now its training statistics: 0.9928 became 0.9931), and shared statistics are now
built from per-state counts, sums and sums of squares rather than pooled rows (identical numbers).
App forecasts (facility × week, synthetic ledger) use lag-1 naive, not LightGBM. On the last 13
training weeks naive scores WAPE 0.220 vs 0.239 for the best LightGBM variant (residual on
median-of-3) and 0.307 for the monthly model's settings, so the app uses whichever measured best at its
grain. (Re-measured after the drug-mapping re-seal; the pre-seal numbers were 0.243 / 0.255 / 0.397.)
These facility-week numbers and the replay's cover_weeks choice were made before the held-out
block was enforced: the facility panel had no block column and the lock spells it differently from
the directory, so Trimbak was never excluded. It is enforced from 2026-10-26 (eval/splits.py).
Stock-out replay (AC6, synthetic Nashik ledger, frozen 26-week test window, 760 facilities x 16
items). Objective, fixed before the runs: fewer facility-weeks with unmet demand. All arms run
through eval/replay_sim.py with identical pre-drawn demand, shocks and delays; policies see only
reported history. cover_weeks=2 was chosen on the 26 weeks before the test window
(eval/tune_replay.py).
uv run pytest -q # backend + ml + eval: 425 tests
cd frontend && npx playwright test # frontend e2e, needs both dev servers (playwright.config.ts starts them)
backend/ FastAPI app: domain models, providers (local/google), ingest, agent, action, API, runtime wiring
ml/ data parsers, sealed synthetic generator, forecasting, optimisation, federated learning
eval/ the frozen evaluation protocol and every scoring script
frontend/ Next.js officer PWA, WhatsApp simulator, public transparency view
infra/ Dockerfiles, docker-compose, Terraform (per-state GCP project), Cloud Build
data/ provenance, licences, manifest, and the raw open-licence source files
docs/ API reference, build log, finale Q&A and demo script, pitch deck and video, screenshots, Review-1 document
tests/ mirrors every package above, plus frontend/tests/e2e
backend/providers/), so the entire system runs with zero credentials. Swapping to real Gemini,
Chirp, Firestore, BigQuery, etc. is an .env change (AUSHADHI_MODE=cloud), not a rewrite.eval/protocol.lock), and the
synthetic data generator is sealed with a content hash — an automated test
(tests/test_isolation.py) fails the build if any forecasting code ever imports the generator.DEMO_MODE); Firebase Auth verification exists in the backend but
no officer accounts or sign-in screen are set up. Alerts go to the contacts registry; demo mode
falls back to derived dev numbers for facilities with no registered number.SENT <code> or Mark sent), received. Batches, expiry and cold
chain are modelled in the solver, but the live data has no batch or cold-chain fields, so FEFO is
tested in unit tests only. Travel times are straight-line estimates.TWILIO_LIVE_NUMBERS would get real sends in demo mode. Card replies accept YES or 1.scripts/generate_labelled_registers.py's docstring).
Gemini scores 98.1% medicine / 100% quantity on them (state.md); a real-photo number still needs
real register photos.terraform apply in
infra/terraform/state-module, add the secret values, gcloud builds submit --config infra/cloudbuild.yaml, terraform apply -var backend_url=... for Pub/Sub push, then
.eval/reports/replay_breakdown.json.
Rerun on 28 Oct after an empty shelf stopped lowering the fallback demand on a first reading; the
run before that fix (-1.9%, -1.07%, 310 better, 194 worse) is kept in replay_breakdown_0c90044.json.
Nothing was tuned on this window: the harmed medicines are reported, not fitted away.Redistribution spreads scarce stock; it does not add supply. None of these is a large win, and the live estimator trades fewer facilities helped for more demand served.
AUSHADHI_MODE=cloud python -m backend.runtime seed