Tahadhari
Tahadhari is Swahili for "caution" or "warning," which is exactly what the project delivers: weather warnings translated into clear, occupation-specific action, sent straight to people's phones over Telegram.
Inspiration
In much of East Africa, meteorological warnings exist, but they don't reach the people who most need to act on them, and when they do, they're often generic ("heavy rainfall expected in your region") rather than actionable. A farmer, a fisherman, and a matatu driver in the same ward need three completely different pieces of advice from the same rainfall forecast. We wanted to close that last mile: take raw hazard data and turn it into a message that tells someone exactly what to do, in language that respects who they are and what they do for a living.
What it does
Tahadhari ingests rainfall and hazard data, matches it against a person's registered ward or road corridor and occupation, and sends a personalized alert over Telegram. A farmer gets guidance on protecting crops and drainage, a driver gets a flood prediction for their specific route with a map, a commuter gets a status update. New users self-register through a button-driven Telegram onboarding flow (county, then town, then occupation, then key asset), so no app install or form-filling is required, just a chat.
How we built it
The system is split into two independently deployable pieces: a FastAPI + SQLAlchemy backend (app/) that owns the data model (alerts, profiles, message templates, road segments, flood predictions) and an ai_layer/ package that talks to it purely over HTTP, the same way any external client would. That separation meant we could iterate on the LLM logic without ever touching the database layer directly.
The AI layer does three real jobs. It geocodes a ward or corridor name with Google Maps, pulls live rainfall from Open-Meteo for that exact location, and asks an LLM (OpenAI's gpt-4o-mini) to weave the structured alert, the live weather, and the person's occupation into one coherent, human message, complete with a why-it-matters sentence and concrete action tips, not just a filled-in template. It also classifies free-text WhatsApp/Telegram replies into feedback categories using structured output, and pulls hazard layers from ICPAC's GeoNode WFS service to seed alerts from real geospatial data.
We migrated the database from local SQLite to Supabase Postgres mid-build, wired up a JWT plus API-key auth layer with three trust tiers (public, service, admin), and set up CI gated on pytest with an 80% coverage floor across both packages. Every PR into main has to clear that bar before it merges.
Challenges we ran into
The biggest surprise was ICPAC's own data: we expected a simple live rainfall-alert feed, but their GeoNode catalog turned out to be mostly static and historical geospatial layers. The seasonal-forecast layers stopped at 2018, and the newer hazard-index layers were raster, not WFS-queryable. Rather than block the pipeline on ICPAC publishing something we couldn't control, we built the poller generically: any WFS type_name in, alerts out, with field mapping fully config-driven, so swapping in a real live layer later is a config change, not a rewrite.
The other recurring challenge was designing every external call (LLM, geocoding, weather, ICPAC, delivery) to degrade gracefully. A geocoding failure shouldn't stop an alert from going out; it should just fall back to the plain templated message instead of live-weather-enriched text. That principle, never let an enrichment failure become a silence, ended up shaping almost every service in ai_layer/.
What we learned
We learned to treat the AI layer as a client of the API, not a bolted-on feature. That boundary made testing trivial, since the AI-layer test suite could mock a single HTTP client instead of an entire ORM. We also learned, the hard way, that it's worth verifying a data source actually exposes what its documentation implies, with a direct query, before designing a whole system around it.
Built With
- fastapi
- google-maps
- open-meteo-forecast
- openai
- python
- supabase
- telegram
Log in or sign up for Devpost to join the conversation.