-
-
PharmaShed: where to sample first
-
-
105 places in 5 cities: red = test soon
-
A plain-language answer for each place"
-
An assistant that can only quote model numbers
-
The model, with its untreated pathway
-
Report what you observe
-
Points for useful, honest reports
-
Uncertainty, not a single guess
-
"Nicknames only, opt-in"
-
-
What if? with Drought on ->Live scenarios in the browser
-
11 bundles, 0 errors in the HL7 validator
-
11 bundles, 0 errors in the HL7 validator
Inspiration
Every pill you swallow takes a trip, and some of it ends up in the stream behind your house. In 2025, Rodrigues and colleagues sampled 102 urban streams in five European cities (Coimbra, Toulouse, Benevento, Ghent and Oslo) and found pharmaceuticals at 91 % of them. Yet the standard exposure models, ePiE and GREAT-ER, route medicine only through sewage treatment plant outfalls. For a stream with no plant on it, they predict about zero.
Monitoring budgets are finite, so the real question for an environmental officer is sharper: where should a field team go first? PharmaShed is our answer. It is built directly on that field study: the paper is our starting point, and its measurements are our reality check.
What it does
PharmaShed models the route those tools miss, the untreated pathway: sewer leaks, wrong connections and overflows that bypass treatment. For every monitoring place and medicine it runs a Monte Carlo simulation and reports a sampling-priority score: the share of 1,000 simulated draws where the predicted concentration is above the level where effects are expected (risk quotient above 1). It is an index under stated assumptions, never a probability, and the app never calls a stream safe or unsafe.
$$ \begin{aligned} L &= \frac{P \cdot C \cdot \mathrm{DDD}}{1000} \ E &= L \cdot f_e \ M &= E \cdot \bigl(\lambda + w\,(1-\lambda)(1-R)\bigr) \ \mathrm{PEC} &= \frac{M}{Q \cdot 86\,400 \cdot 1000}\; e^{-k\tau}\cdot 10^{9} \ \mathrm{RQ} &= \frac{\mathrm{PEC}}{\mathrm{PNEC}} \end{aligned} $$
Here L is the grams per day consumed near the site, E the grams per day excreted unchanged, M the grams per day reaching the stream, and PEC the concentration in ng/L. The sampling-priority score is the share of the 1,000 draws in which the risk quotient (RQ) exceeds 1:
$$ \text{score} = \frac{1}{N}\sum_{i=1}^{N} \mathbf{1}!\left[\mathrm{RQ}_i > 1\right] $$
Here λ (leakage), Q (stream flow), τ (travel time) and a consumption multiplier are drawn from stated ranges, and there is no treatment plant at any site (w = 0), so the load is all untreated pathway.
One app, two views. A toggle switches between a plain-language citizen view and an expert view with the maths, checks and data standard.
- Interactive map of 105 places in 5 cities as heat-style glows (red: test soon, amber: worth testing, green: lower priority). Filter by medicine (carbamazepine, atenolol, losartan, irbesartan, paracetamol, citalopram), hover and click for a site page.
- Expert detail: concentration bands against the no-effect level, the distribution of simulated outcomes, a sensitivity "tornado", and the multiplier that would flip a site to another tier.
- What if? Drought, sewer repair or a leakage hotspot: scores recompute live in the browser from stored draws, with a plain-words explanation of why the score moved.
- An AI assistant that cannot invent numbers. It answers only through five tools over the model results, and a numeric guard rewrites any answer containing a number that is not traceable to a tool result.
- Consistency checks against the paper's field data, using predictions frozen before the comparison. Some fail on our placeholder inputs, and the app says so.
- Citizen reports. Signed-in people add what they see in three quick steps (a seven-question checklist, a note, an optional photo). An AI helper tidies the wording only if the person accepts it, and both versions are kept. Photos have location data stripped and are checked for people and number plates. Reports appear as a clearly unverified layer and never change a score.
- Points, badges, a weekly challenge and a leaderboard reward useful, honest reports, with caps and an auditable ledger.
- Trust and safety: reports with personal details, flagged photos, or three flags from different people are held for a moderator who approves or hides them; hiding reverses points and deletes the photo. There is a plain-language privacy note and a one-click Delete my account.
- FHIR output: every prediction is an HL7 FHIR R4 Observation aligned with the OneAquaHealth Implementation Guide, plus Locations and unverified citizen observations (no nickname, no free text). All 11 bundles pass the official HL7 validator with 0 errors.
How we built it
- Engine (Python, NumPy, SciPy): a pure, vectorised Monte Carlo model run once offline; draws stored as compact float32 so scenarios run in the browser. A pre-registered freeze (a git tag) and a firewall rule that only the checks may read the field data.
- API (FastAPI): serves a committed model snapshot, the assistant (OpenAI gpt-5-mini through a six-round tool loop), and the community features on Neon Postgres, Neon Auth (EdDSA tokens verified with the public key list) and a private Neon storage bucket for photos.
- Web (React, TypeScript strict, Zustand, Tailwind, MapLibre GL): hand-written SVG charts, client-side scenario maths, light and dark themes.
- Data: real population around each site (GHS-POP, 500 m buffers) and real OpenStreetMap waterways, with 105 rule-placed synthetic sites.
- Standards: FHIR bundles built from the same results, validated against the OneAquaHealth IG compiled from its FSH source at a pinned commit.
- Quality: 500+ automated tests (engine, API against a real Postgres, web), plus real-browser end-to-end checks of sign-up, reporting, moderation and account deletion.
Challenges we ran into
- Staying honest about illustrative data. Our drug parameters are placeholders. We made that a rule in the code: illustrative values never carry a citation, are badged everywhere (UI and every FHIR resource), and failing consistency checks are shown rather than hidden.
- λ and Q are not separately identifiable from one sample (only their ratio matters), so we carry the joint prior uncertainty through every draw instead of pretending to estimate them.
- An assistant you can trust. Telling a hallucinated number from a rounded or converted tool result needed a careful allowance list.
- Unverified data in a standard built for verified data. The OneAquaHealth profile fixes an observation's status to
final. A citizen report is not final, so we export it aspreliminaryand do not claim that profile. The real validator caught this for us. - Letting the public in safely: metadata-free photos, hold-for-review rules, rate limits, reversible points, and a delete-everything button, all tested against a real database.
Accomplishments that we're proud of
- A model that says something where treatment-plant models say zero, with its limits stated on screen.
- A reality-check pipeline that was frozen before the comparison and reports failures openly.
- FHIR output that passes the official validator with zero errors, including honest handling of unverified citizen data.
- A product that works for both a curious citizen and a technical reviewer, from one data source.
What we learned
Explainability and auditability are not add-ons: they shaped the data model from day one. Storing provenance for every parameter, badging illustrative values, guarding the assistant's numbers and keeping an append-only points ledger all forced us to be clear about what the model knows and what it only assumes. That clarity is the point.
What's next
- Replace the illustrative drug parameters and consumption values with sourced ones (the pipeline recomputes all 630,000 simulations, every chart and every FHIR file).
- Place sites at the study's real sampling points and test the model against the full field data.
- Add continuous integration and a local HAPI FHIR server for a full read and write demo.
- Grow the citizen layer: more cities, better photo checks, and sharing reports with local monitoring teams.
Research and sources
Rodrigues, F., Calapez, A.R., Pereira, A.M.P.T., Silva, L.J.G., Freitas, A., Bouchali, R., De Cock, A., Forio, M.A.E., Goethals, P., Henni, S.H., Loyau, A., Moen, A., Piscopo, N., Schmeller, D.S., da Silva, J.P., Durães, L., Simões, N.E.C., Feio, M.J. (2025). Patterns of pharmaceutical contamination in streams of European cities across urbanisation gradients: Potential impacts on One Health. Journal of Hazardous Materials, 499, 139946. https://doi.org/10.1016/j.jhazmat.2025.139946
Baseline model extended: Oldenkamp, R., Hoeks, S., Čengić, M., Barbarossa, V., et al. (2018). ePiE. Environmental Science & Technology, 52(21), 12494–12503. Leakage prior informed by Fenz, R., et al. (2005). Water Science and Technology, 52(9), 209–217, and the APUSS project. Population: GHS-POP (European Commission JRC). Streams: © OpenStreetMap contributors. Standards: HL7 FHIR R4 and the OneAquaHealth Implementation Guide.
The drug parameters in this prototype are illustrative placeholders, not values from these papers.
Built with
python, fastapi, numpy, scipy, pydantic, react, typescript, vite, tailwindcss, zustand, maplibre-gl, postgresql, neon, openai, fhir, hl7, openstreetmap, vercel, render, pytest, vitest, playwright
Built With
- api
- css
- fastapi
- fhir
- gl
- groq
- hl7
- maplibre
- numpy
- openai-compatible
- openstreetmap
- pydantic
- pytest
- python
- r4
- react
- react-markdown
- ruff
- scipy
- tailwind
- typescript
- uv
- vite
- zustand
Log in or sign up for Devpost to join the conversation.