NagarikAI
Architecture

One photo. Seven agents. Under ten seconds.

A citizen reports a civic issue. Specialised AI agents classify, dedup, route, verify, schedule (MILP), audit the fix, and forecast tomorrow's hotspots. Every agent's decision is auditable; an LLM never makes a load-bearing call without a deterministic gate verifying it.

0%
Fewer km driven by crews
0
Real BBMP issues loaded
0/16
Injection attempts blocked
0.871
Rainfall-model R²

The citizen flow, end-to-end

What happens between “snap a photo or video” and “crew shows up”.

1 · Report
/report — photo + tap
2 · 7-agent loop
Vision → Dedup → Triage → Verify → MILP → Audit → Predict
3 · Resolved
Tracked live, +XP awarded

The 7 agents

01
Vision
Gemini 2.5 Flash

Reads your photo or short video → identifies what's wrong (pothole? streetlight? sewage?), how bad it is (severity 1-5), and how confident the model is.

02
Dedup
PostGIS

Checks if anyone within 50 metres already reported the same thing. If yes, merges into that ticket so the crew isn't sent twice.

03
Triage
Claude Haiku 4.5 + guardrails

The LLM proposes which department to route to. A deterministic SOP table verifies the proposal — every disagreement is logged and the safe default wins.

04
Verification
Community + XP

Notifies the 5 nearest citizens. After 3 confirmations, status is promoted to VERIFIED and the dispatcher picks it up.

05
Scheduler
Google OR-Tools (MILP)

THIS is the math. Re-solves the entire crew dispatch problem for tomorrow each time a new issue arrives — picks crews + stop order to minimize severity-weighted delays + km driven.

06
Resolution
CLIP + custom CNN

When the crew uploads an after-photo, two layers audit it: CLIP confirms same location, the pothole CNN confirms it's actually fixed. Catches fake closures.

07
Insights
HistGradientBoosting

Feeds the rainfall × ward panel that predicts next-30-day hotspots. Lets dispatchers pre-position crews before complaints flood in.

How MILP actually solves dispatch

The single most important agent — and the easiest to misunderstand.

At any moment Bengaluru has hundreds of open civic issues and a dozen BBMP crews. The naive answer is FIFO — assign each new issue to the first crew with capacity, ignore the map. That's how dispatch works today.

MILP rearranges the day every time a new issue lands. It picks which crew visits which issues in what order, weighing the cost of:

  • Severity × lateness — a sev-5 sewage overflow is penalised 5× more than a sev-1 garbage report when running past SLA
  • Total km driven — bunches geographically close stops into one route
  • Unserved issues — pays a heavy penalty for tickets the day can't fit

Subject to: each crew's skill (Roads ≠ Water), capacity (~10 stops/day), and shift hours. Solver: Google OR-Tools, branch-and-cut + guided local search, 15-second cap.

That's why your Hongasandra report showed scheduled_for: ('7cae7e1c…', 0) — crew 7cae, stop position 0. The MILP literally moved your high-severity issue to the front of the crew's day.

Real BBMP backtest
LoadFIFO kmMILP kmΔ
120 / 12 crews87452−94%
250 / 12 crews1,509104−93%
800 / 12 crews1,019107−89.5%
Same crews, same budget, smarter ordering. Reproducible via scripts/run_real_backtest.py.

Predictive insights — forecasting tomorrow's hotspots

What the 7th agent feeds, and how it closes the loop back to MILP.

Every report logged into a panel of ward × month × rainfall_mm × rainfall_lag1. We trained a HistGradientBoosting regressor on 14,580 real ward-months of Bengaluru data (2021–2025):

log(road_complaints + 1) ~
   ward_FE + month_FE + rainfall_mm + rainfall_mm_lag1

In plain words: given a ward, the calendar month, today's rainfall and last month's rainfall, how many pothole complaints will this ward see next month?

The model hits R² = 0.871 on the 2025 hold-out year. The map's hotspot heatmap is its output — wards in red are predicted to spike, so the dispatcher pre-positions crews there instead of waiting for the citizen flood.

Closes the loop: your report nudges its ward's count up → predictor raises the heatmap there → MILP weights that ward heavier in tomorrow's solve — even before more reports come in.

What feeds the model
  • 14,580 ward-month observations (real)
  • 60 months of Bengaluru rainfall (real IMD-style panel)
  • 243 KGIS ward polygons
  • Every new report appended live by the Insights agent
Reproducible via scripts/build_hotspots.py → writes data/processed/hotspots.geojson consumed by /map.

Hub & spoke — how a ticket leaves NagarikAI

We aren't a parallel BBMP. NagarikAI is the citizen front door + AI triage; each department keeps using whatever software they already have. Once Triage picks a department, delivery.py pushes the ticket out via that department's preferred channel. The supervisor dashboard is the fallback for departments that don't have their own system.

The hub (us)
NagarikAI
  • · Citizen submit (photo / video / location)
  • · 7-agent triage loop
  • · MILP scheduler
  • · delivery.py dispatches to spoke
  • · sla_watcher.py escalates on silence
routes via
WhatsAppBBMP Roads · BBMP SWM
AiSensy / Gupshup Business API → supervisor's phone
EmailBWSSB · BESCOM Streetlight
SMTP → complaints@ inbox the dept already monitors
WebhookBBMP Horticulture
POST signed JSON to dept's existing complaint API
In-app onlyBBMP Helpdesk · Town Planning
No external system — supervisor watches /supervisor

Escalation ladder — what happens when the dept goes silent

Level 0
Nominal. Sent via the dept's primary channel. Acknowledgement expected within SLA.
Level 1
SLA breached. Re-dispatch to supervisor's personal phone. Citizen notified.
Level 2
24h after L1 with no ack. Ward councillor looped in.
Level 3
72h after L2. RTI auto-draft generated; citizen one-tap-files.

Runs every 60s in apps/api/nagarik/jobs/sla_watcher.py. Every state change writes a citizen notification — they always know where their ticket stands.

Tech stack at a glance

Frontend
Next.js 14 · Tailwind · Framer Motion · Mapbox GL
Backend
FastAPI · SQLAlchemy 2 · LangGraph · Alembic
Database
PostgreSQL 16 + PostGIS + pgvector
AI
Gemini 2.5 Flash · Claude Haiku 4.5 · CLIP · pothole CNN
Math
Google OR-Tools (MILP CVRPTW) · sklearn HistGradientBoosting
Auth
JWT HS256 · PBKDF2-SHA256 (200k iters) · slowapi rate limit
Geo
EXIF GPS (HEIC) · Nominatim · 243 KGIS ward polygons
i18n
Gemini build-time + runtime cache (EN · हि · ಕ)
Chain
Polygon Amoy · AuditAnchor.sol · CivicBadge soulbound ERC-721
Built for the Coding Ninjas Community Hero challenge. Full implementation provenance in docs/PITCH.md, REAL_DATA.md, and INBOUND_CHANNELS.md.