Civic accountability for Delhi's municipal wards — find the ward you're standing in, report by voice, photo, or text, get a formal letter in Hindi and English, and see a public scoreboard of which wards are being ignored.
First Commit · Bharat Builds Tour (WeMakeDevs × AWS Builder Center) · Ship It track
| The problem · The solution | why this exists |
| Architecture · Request flow | how it works |
| Repository layout · Local setup | how to run it |
| Performance · Status | what state it's in |
| Data & demo-mode disclaimer | read this before judging the data |
| AI tools used | required disclosure |
Deeper reading: docs/DECISIONS.md (why things are the way they are) ·
docs/SUBMISSION.md (the writeup) ·
docs/AWS_SETUP.md (deployment runbook) ·
CREDITS.md (data provenance) ·
LEARNING.md · KNOWN_ISSUES.md
Delhi has hundreds of municipal wards. Each has an elected councillor whose remit covers exactly the things that stay broken: potholes, streetlights, garbage, drains, waterlogging.
| Nobody knows their ward | Boundaries were redrawn in 2022 and most people still cite the old zones. There is no simple "which ward am I in?" tool. |
| Complaints go into black holes | Portals accept a complaint and offer no tracking, no escalation, no consequence. People tweet into the void instead. |
| There is no public scoreboard | No ward-level data on open complaints or resolution speed exists publicly, so "which areas get ignored" stays a rumour instead of a number. |
The same pothole survives three monsoons, and holding anyone to account requires knowing who to ask — which almost nobody does.
- Which ward am I in? — GPS or a dropped pin returns your ward, its zone, its councillor where we could source one, and its Ward Neglect Index.
- Low-friction report — voice, photo, or text → a reviewed formal complaint drafted in Hindi and English, routed to the correct ward office, tracked publicly.
- Instant guest profile — one click creates a device-local
user####alias that can be renamed and remembers recent reports. It is optional and is clearly not presented as secure server authentication. - Public accountability dashboard — complaint heatmap across Delhi, plus wards ranked by Neglect Index.
AWS services: Lambda · API Gateway · DynamoDB · S3 · SQS (+DLQ) · Bedrock · SES · EventBridge Scheduler · CloudWatch · Amplify Hosting / CloudFront. IaC is AWS SAM.
Three decisions worth knowing up front:
- Point-in-polygon runs inside the Lambda. 289 ward polygons load from S3 once per
cold start into a
shapelySTRtree held at module level. Warm lookups measure 0.023 ms. A managed search cluster was considered and rejected — OpenSearch Serverless has a 2-OCU floor of roughly $11/day, which would have made our own cost claim false. - SQS sits between the API and the AI draft. The API returns
202in ~2 ms and the user never waits on a model. - Bedrock is optional by design. A deterministic bilingual template composer was written first and returns the same shape, so the model is a config flag rather than a dependency.
sequenceDiagram
autonumber
actor User as Citizen on a phone
participant API as API Gateway
participant Presign as presign_upload
participant Filer as create_complaint
participant S3 as S3
participant DDB as DynamoDB
participant Q as SQS
participant W as draft_and_send
User->>API: POST /complaints/presign
API->>Presign: content_type, content_length
Presign-->>User: signed PUT url
User->>S3: PUT photo — direct, never via Lambda
User->>API: POST /complaints
API->>Filer: photo_key, issue_type, lat, lng
Filer->>Filer: point-in-polygon → ward_id
Filer->>DDB: write OPEN / PENDING
Note over Filer,DDB: write BEFORE enqueue —<br/>the other order races
Filer->>Q: enqueue complaint_id
Filer-->>User: 202 + tracking token
Q->>W: deliver message
W->>DDB: read (strongly consistent)
alt Bedrock available
W->>W: Bedrock Converse → bilingual JSON
else unavailable, throttled, or bad reply
W->>W: deterministic template composer
end
W->>DDB: save draft → DRAFTED
W->>W: deliver to demo outbox / verified inbox
User->>API: GET /complaints/{id} (poll)
API-->>User: the letter, in Hindi and English
stateDiagram-v2
[*] --> OPEN: citizen files a report
OPEN --> ACKNOWLEDGED: acknowledge
OPEN --> RESOLVED: resolve
ACKNOWLEDGED --> RESOLVED: resolve
RESOLVED --> OPEN: reopen — "still broken"
ACKNOWLEDGED --> OPEN: reopen
note right of RESOLVED
Reopening clears resolved_at, so the
ward's index tells the truth again
after a fix that did not hold.
end note
Every transition is authorised by a magic-link token compared in constant time, and every write is conditional on the current status, so two people clicking the same link cannot both win.
backend/ Python 3.13 Lambdas + AWS SAM 254 tests, no AWS needed to run them
src/common/ shared: geometry, index, store, validation, drafting, delivery
src/<fn>/ one package per Lambda — handler path is <fn>.app.handler
frontend/ React 18 + Vite + TS + Tailwind + MapLibre, as a PWA
data/ ward polygon pipeline + the committed wards.geojson
docs/ architecture, decisions log, deployment runbook, submission writeup
python -m pip install -r backend/requirements-dev.txt
python -m pytest backend -q # 254 tests, no AWS credentials neededWARDS_GEOJSON_PATH points the ward index at a local GeoJSON file instead of S3 —
also how sam local should be run.
cd frontend
npm install
cp .env.example .env # then set VITE_API_BASE_URL
npm run devpython data/prepare_wards.py data/raw/<source>.geojson data/wards.geojsonEnvironment variables are documented in .env.example and
frontend/.env.example. Never commit a real .env.
| Live app | https://main.d1s6q0cvldi6dz.amplifyapp.com/ |
| API | https://9f41c4bkel.execute-api.ap-south-1.amazonaws.com |
| Demo video | pending |
| Region | ap-south-1 (Mumbai) |
| Backend | ✅ deployed — 254 tests passing locally |
| Frontend | ✅ deployed on Amplify + CloudFront |
| Ward data | ✅ 289 real polygons live in S3 |
| Demo data | ✅ 240 seeded complaints across 86 wards |
Lighthouse, mobile, simulated throttling, measured against the live deployment — not a local build:
| page | performance | accessibility | best practices | SEO |
|---|---|---|---|---|
/ |
100 | 100 | 100 | 100 |
/about |
96 | 100 | 100 | 100 |
/dashboard |
76 | 100 | 100 | 100 |
/ward |
69 | 100 | 100 | 100 |
Accessibility, best practices and SEO are 100 on every page, with zero console errors.
/ward and /dashboard score lower on performance because they load MapLibre — 801 kB,
and the map is the largest-contentful-paint element. That is the floor for a map page,
not a defect. It is already split into its own chunk that / and /about never fetch,
excluded from service-worker precaching, and preceded by preconnect hints to the tile
origin (which moved the dashboard from 59 to 76).
Complaint data on the deployed demo is generated for demonstration. Every seeded record carries
is_demo = trueand the UI says so on screen. Reports filed through the site are real and are not flagged.
- The Ward Neglect Index scores a ward, never a person. It is computed partly from generated data; attaching such a number to a real, named, elected individual would be indefensible. Councillor details, where shown, are identity only and visually separated.
- A ward with fewer than five reports has no index at all — not zero. Scoring a silent ward as zero would rank the wards nobody reports as the best-run in the city.
- Councillor fields we could not source render as an explicit "not available". We never invent councillor data.
- We do not email real officials. Complaints go to a demo outbox and a verified demo inbox. The live-send path exists in code and is refused at runtime.
- Our ward boundaries are real but not current — the pre-2022 delimitation. See
CREDITS.mdfor why, and the deployed/aboutpage states it plainly.
AI coding tools are permitted under the hackathon rules if disclosed. We used them heavily, and this is the disclosure.
| Tool | Used for |
|---|---|
| Claude (Anthropic), via Claude Code | PRD review and rewrite, architecture critique, backend Lambdas, frontend screens, test authoring, ward-data pipeline, performance work, documentation |
| Amazon Bedrock | A runtime product feature, not a build tool — it drafts the bilingual complaint letter |
Every architectural decision, scope cut and service choice was reviewed and accepted by the team. Commit history is unmodified: no force-pushes, no squashes, no rewritten commit dates, and all three members commit under their own accounts.
| Role | |
|---|---|
| Amrit Kang (@amritkang165) | Team lead · frontend · ward data · submission |
| Kartik Dixit (@Kartikdixit2468) | AWS infrastructure · SAM · delivery pipeline |
| Muneer Alam (@Muneer320) | Backend Lambdas · geospatial · index |
MIT. Ward boundary data is © DataMeet contributors under
CC BY-SA 2.5 IN — see
CREDITS.md.