Case study — KisanGyan
A pan-India, voice-first agricultural advisor serving farmers in eight Indian languages plus English — with no state-specific or language-specific branches anywhere in the codebase.
The challenge
Agricultural advice is only useful if it is locally correct. Soil, rainfall, crop calendars, mandi behaviour, festivals and language all change across district lines, and a recommendation that is right in Nashik can be actively harmful in Ballia.
The obvious way to handle that is to launch in one state, hard-code its assumptions, and generalise later. Every team that does this discovers the same thing eighteen months in: the second state requires rewriting the first one.
The second challenge was the user. Our farmers often have limited literacy, a mid-range Android phone bought on EMI, and connectivity that disappears the moment they walk into their field. An interface that assumes typing, reading and a live connection is an interface most of them will not use twice.
The approach
Decisions that shaped the product
Region as configuration, never as code
States, languages, crops, festivals and feature availability all live in a configuration layer with feature flags. Activating a new state is a data change and a flag flip, not a release. There is no `state == "MH"` conditional anywhere in the codebase — a rule we enforced from the first commit precisely because it is impossible to retrofit.
Voice as the primary interface
Speech recognition and synthesis run behind a provider-agnostic interface, so the engine can change without touching the app. The farmer speaks; Mitra replies aloud. Text exists as a fallback, not as the default — which inverts the usual assumption and drove several architectural decisions, including streaming responses over WebSocket so speech begins before generation finishes.
A persona, not a chatbot
Every farmer-facing model call flows through a single system prompt template assembled with the farmer's profile, their farm history, current weather, local market conditions and the festival calendar. Mitra has consistent honorifics, greetings and a voice per language, and it never refers to itself as 'the AI' or 'the app'. Consistency here is a technical property, enforced by making the template the only path to the model.
Grounded answers, not remembered ones
Agronomic guidance is retrieved before it is generated. Curated knowledge — crop calendars, package-of-practices, pest and disease references, government advisories — is chunked, embedded and searched with a hybrid of keyword and vector matching, and only the retrieved passages are given to the model to answer from. That is what makes the advice auditable and updatable: correcting a recommendation means editing a document, not retraining a model. It also means Mitra can say it does not know, which a model answering from memory will rarely do.
Offline as the default state
The Flutter app treats its local SQLite database as the source of truth and the network as a synchronisation detail. A farmer can photograph a diseased leaf, get a diagnosis, log it in their diary and plan a rotation with the phone in flight mode. Everything reconciles when signal returns.
Inference on the device where it counts
Crop disease detection runs as a TensorFlow Lite model on the handset. That decision removed an image upload from the critical path, made diagnosis instant, cut per-diagnosis cost to zero and — most importantly — made the single most-used feature work without a network.
Strategic advice, not just tactical
The recommendation engine scores candidate crops on soil match, rotation benefit, monsoon fit, market outlook and risk. Rotation history is tracked across seasons against research-backed agronomic rules, and declining soil indicators trigger proactive regeneration suggestions the farmer never asked for. This is the difference between an advisory app and an agronomist.
Architecture
How the system is put together
Layer by layer, with the technology and the reason it is there.
Mobile
Flutter 3.x, Android from API 23, offline-first sqflite store, TFLite inference, ARB localisation across nine languages
API
Python 3.12 + FastAPI, REST and WebSocket, Pydantic v2 schemas, SQLAlchemy 2.0 models, Alembic migrations
Data
PostgreSQL 16 with pgvector for retrieval and PostGIS for geospatial queries; Redis for cache and queues; S3-compatible object storage for media
AI
A large language model behind the Mitra persona, multilingual speech recognition and synthesis, and a retrieval-augmented generation (RAG) layer over the agronomic knowledge base using 768-dimension multilingual embeddings
Workers
Scheduled jobs for advisory nudges, satellite NDVI refresh, soil assessment, market prediction and mandi price ingestion
Integrations
Official and commercial weather feeds, satellite vegetation indices, government soil and market data, SMS OTP, push notifications and messaging channels — each behind an interface so any provider can be swapped
Infrastructure
Docker on an Indian cloud region, Caddy edge, CI-driven staged deploys with migration gates
Operations
Admin console for the operations team: farmer records, outreach campaigns, crop-doctor review and per-language message drafting
Engineering highlights
The parts worth talking about
Nine languages, one codebase, zero branches
The Hindi belt alone spans ten states with wildly different cropping patterns, and Bengal, Odisha, Gujarat, Karnataka and the Telugu states each bring their own crops, calendars and scripts. Because region and language data are configuration, adding one means seeding crops, festivals and rotation rules — not touching application logic.
Notification discipline as an engineering problem
Early weather alerting woke farmers at 1 a.m. and repeated identical messages. We rebuilt the gate: only cyclone warnings pierce 21:00-07:00 quiet hours, alert signatures no longer hash volatile advisory text, and every push passes a per-event dedup check, a same-kind cooldown and a per-farmer daily cap. Push notification quality is a product feature, and it took a schema-free rewrite of the decision layer to get right.
RAG that works across nine languages
Mitra does not answer from model memory. Every agronomic claim is retrieved from a curated knowledge base first and the model answers from those passages — which is what keeps the advice correct and updatable without retraining. A single multilingual embedding space means a question asked in Marathi retrieves knowledge written in Hindi; maintaining nine parallel monolingual indices would have multiplied both the ingestion work and the drift.
Streaming voice over WebSocket
Text-to-speech begins on the first sentence rather than waiting for the full response. On a slow connection that is the difference between a conversation and a form submission.
Takeaways
What we would tell you to do
- 01
Configuration-driven regionalisation is nearly free on day one and nearly impossible on day five hundred.
- 02
On-device inference is not only about latency — it is about cost, privacy and the network not existing.
- 03
Notification restraint earns more retention than notification volume.
- 04
If the persona is the product, make the persona template the only code path to the model.
Next case study
An AI interior visualisation product where the differentiator is not the model — it is the cultural rule engine wrapped around it.
Read the Nivista case studyThis is how we would build yours.
Same approach, same discipline, applied to whatever you are trying to make.