Inspiration
A family member once received a medicine from a local pharmacy that looked slightly off — different texture, faded print, slightly wrong smell. We had no way to confirm if it was genuine. We called the manufacturer's helpline. They couldn't help. We searched online. Nothing useful came up.
That stuck with me. A pharmacist in a small Indian town dispenses hundreds of medicines a day with no practical tool to verify authenticity. The official CDSCO route exists, but it's not built for the counter. And roughly 1 in 4 drugs in developing markets are substandard or outright fake — patients are unknowingly putting their trust in boxes that could contain chalk or worse.
MedShield started as a simple question: what if a chemist could just type in a batch number and know?
What we built
MedShield is an AI agent that verifies a medicine's authenticity before it reaches a patient. The chemist types in a drug name and batch number. The agent runs an 11-step verification — checking the batch against a verified registry, cross-referencing the manufacturer's production capacity, scanning for existing CDSCO NSQ (Not of Standard Quality) alerts, and detecting whether a counterfeit cluster has been spotted nearby using geospatial search. It ends with one of five verdicts: GENUINE, UNVERIFIED, SUSPICIOUS, LIKELY_COUNTERFEIT, or NSQ_FLAGGED — each with plain-English reasons and clear next steps.
The defining design decision was a verification-first security posture: a medicine is only called GENUINE when its exact batch is positively proven in a verified registry. If we can't confirm it, the verdict is UNVERIFIED — not a pass. A counterfeiter can print any real brand name on a fake box; "the name looks right" is not enough.
When a suspicious verdict is returned, the agent automatically generates a downloadable CDSCO complaint PDF the chemist can file immediately.
How we built it
- Agent framework: Google ADK with Gemini 2.5 Flash-Lite. The agent orchestrates 8 custom domain tools (batch validation, capacity checks, registry verification, geospatial cluster search, complaint generation) plus a live MongoDB MCP server for arbitrary database queries.
- Database: MongoDB Atlas (M0 free tier) — a single database serves
four very different query modes: exact compound lookups (batch + manufacturer),
fuzzy text search (
$textindex on medicine names), 768-dim vector search (gemini-embedding-001, cosine similarity, Atlas Vector Search) for semantic matching of suspicious reports, and geospatial cluster detection (2dsphereindex +$geoNear) for finding nearby counterfeit sightings. - Real data: 253,973 real Indian medicines from a public dataset, real CDSCO NSQ alert PDFs parsed with PyMuPDF, supplemented with reproducible seed scripts (fixed RNG) for demo cases.
- UI: Streamlit with a pydeck map of suspicious reports across India.
- PDF: ReportLab for CDSCO complaint generation.
- Deployment: Docker on Google Cloud Run (bundles Node.js so the MongoDB MCP server runs in-container alongside the Python app).
Challenges
The hardest problem was making the agent's verdict tamper-proof. When
the LLM calls tools, it can pass its own inferred values as arguments — for
example, it might derive manufacturer_name = "GSK" from a batch prefix
instead of using the full registered name "Glaxo SmithKline Pharmaceuticals
Ltd." If the verdict function trusted those arguments blindly, a
well-formed genuine batch would fail a name comparison and return
LIKELY_COUNTERFEIT. The fix was to have calculate_risk_score re-derive
facts from the database itself, treating the model's arguments only as
hints. The agent cannot be tricked into a wrong verdict by its own imprecise
tool calls.
Geospatial + vector in one database: wiring MongoDB's $geoNear
aggregation and Atlas Vector Search into the same query flow (without
indexing conflicts) required careful index design — the two indexes live on
different fields of the same collection.
The Streamlit threading deadlock: ADK's Runner uses asyncio, but
Streamlit runs each user interaction in a new thread. Sharing a single
event loop across threads caused deadlocks. The fix was a persistent
background thread owning the event loop, with run_coroutine_threadsafe
bridging the two worlds.
What we learned
Building this made us think hard about the difference between "nothing suspicious found" and "confirmed genuine" — those are not the same thing, and most systems conflate them. The verification-first posture felt philosophically correct for anything touching patient safety: if you can't prove it, say so honestly. UNVERIFIED is not a failure mode; it is the correct, honest answer.
We also learned how powerful MongoDB Atlas becomes when you stop thinking of it as a key-value store and start combining its query capabilities — a single collection can answer a fuzzy text match, a geospatial cluster query, and a semantic vector search, with no extra infrastructure.
Log in or sign up for Devpost to join the conversation.