Inspiration
Citizen water-quality monitoring usually happens on whatever schedule a volunteer has free time, which means the moments a stream changes the most, right before and right after a storm, may not get checked. OneAquaHealth already has the pieces that matter: a Citizen Science App for recording observations, a Resilience Map for showing site history, a forecasting app with precedent for weather-driven tooling. What FirstFlush adds: it tells a volunteer when a visit is worth making, and turns the two checks they take around a storm into an actual before/after comparison instead of two disconnected data points.
What it does
FirstFlush watches free Open-Meteo forecasts for a qualifying rain event at a site (configurable rainfall total, dry-spell requirement). Once one's detected with enough lead time, it sends the volunteer an in-app heads-up , Before / After / Both / Skip, gated so each choice is only offered when it's actually still possible (no picking "before" once the rain's already started). The volunteer records a short bank-side visual check: water colour, clarity, foam, odour, litter, algae, flow. Each check's role (before/after/recovery/routine) is assigned automatically from its timing against the event, not from what the volunteer clicked.
Once an after-check exists, FirstFlush pairs it with the freshest before-check (or, if there wasn't a fresh one, automatically falls back to the site's last routine observation within 21 days so a missed before-check doesn't waste the after-check). Any check 48–96 hours later is auto-tagged as a recovery observation. The pair gets a 1–3 sentence, plain-language AI summary of what changed (Groq, with a deterministic fallback template when there's no API key or the call fails) — always labelled "AI draft — for researcher confirmation" until a human confirms it, and explicitly forbidden from making a causal, health, or "predicts pollution" claim. The whole pair exports as a FHIR R4B Bundle.
There's also a demo mode: an admin-controlled clock lets a full rain-event cycle — heads-up through recovery check — be walked through live without waiting for real rain, plus a historical-replay view that runs the same rule over 12 months of real Open-Meteo archive data for Coimbra, to show how often it would actually have fired.
How we built it
Python 3.12, FastAPI, SQLModel over SQLite. Server-rendered Jinja2 + HTMX + Leaflet for the UI.
Groq for the AI summary, with a template as a fallback. fhir.resources to build and validate the FHIR R4B export.
A single Clock abstraction (RealClock / ControllableClock) drives both real time and the demo's admin-controlled clock. Deployed on Render.
The SQLite file just gets wiped and reseeded (python -m scripts.demo_reset) rather than persisted.
Challenges we ran into
Understanding and implementing FHIR was fun but challenging.
Accomplishments that we're proud of
- The baseline-fallback mechanism: a volunteer who only manages an after-check doesn't lose the comparison, because the pairing engine automatically substitutes the site's last routine observation. It's a small design decision that meaningfully raises how often a usable "before" exists at all.
- The AI summary is actually constrained, not just prompted nicely — both the Groq prompt and the deterministic fallback template are tested directly to confirm neither one ever drifts into a causal, health, or "predicts pollution" claim.
What's next for FirstFlush
- Map the observations with real fields in Citizen Science App.
- Calibrate the rain-event thresholds against real site history instead of today's unvalidated demo defaults.
- Wire up live weather polling on a schedule — the detection code already exists
(
app/events.py::poll_all_sites), it just isn't scheduled yet — so this runs as a live deployment, not only a demo.
Log in or sign up for Devpost to join the conversation.