Skip to content
amritkang165Public

About

Know your ward. Route your complaint. See who's ignoring you. Civic accountability PWA for Delhi's 250 MCD wards — built on AWS for First Commit / Bharat Builds Tour 2026.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

MeraWard banner

MERAWARD

Know your ward. Route your complaint. See who's ignoring you.

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.

tests live lighthouse python react IaC licence

First Commit · Bharat Builds Tour (WeMakeDevs × AWS Builder Center) · Ship It track


Contents

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


The problem

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.

The solution

  1. 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.
  2. Low-friction report — voice, photo, or text → a reviewed formal complaint drafted in Hindi and English, routed to the correct ward office, tracked publicly.
  3. 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.
  4. Public accountability dashboard — complaint heatmap across Delhi, plus wards ranked by Neglect Index.

Architecture

MERAWARD architecture

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 shapely STRtree 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 202 in ~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.

Request flow

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
Loading

Complaint lifecycle

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
Loading

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.


Repository layout

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

Local setup

Backend

python -m pip install -r backend/requirements-dev.txt
python -m pytest backend -q          # 254 tests, no AWS credentials needed

WARDS_GEOJSON_PATH points the ward index at a local GeoJSON file instead of S3 — also how sam local should be run.

Frontend

cd frontend
npm install
cp .env.example .env                 # then set VITE_API_BASE_URL
npm run dev

Ward boundary data

python data/prepare_wards.py data/raw/<source>.geojson data/wards.geojson

Environment variables are documented in .env.example and frontend/.env.example. Never commit a real .env.


Status

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

Performance

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).


Data & demo-mode disclaimer

Complaint data on the deployed demo is generated for demonstration. Every seeded record carries is_demo = true and 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.md for why, and the deployed /about page states it plainly.

AI tools used

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.


Team — Sleepy peeps (4T5AKB)

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

Licence

MIT. Ward boundary data is © DataMeet contributors under CC BY-SA 2.5 IN — see CREDITS.md.

About

Know your ward. Route your complaint. See who's ignoring you. Civic accountability PWA for Delhi's 250 MCD wards — built on AWS for First Commit / Bharat Builds Tour 2026.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages