Red-Box
Anonymous civic reports in, grounded government intelligence out.
Citizens file potholes, water, garbage, and safety issues without exposing identity on the public feed.
Officers see clusters, ward trends, and briefings that are required to match stored counts — not invented metrics.
anonymous-reporting · civic-tech · langgraph · groq · rag · ward-analytics
What it does
| Citizen | Government |
|---|---|
| File a report with location | KPI tiles from stored complaint counts |
| Public cards always say Anonymous citizen | Duplicate clusters (same street, paraphrased filings) |
| Pre-submit safety check | Ward chart, briefing, Ask, CSV / JSON / PDF |
| Status updates on the feed | Override category / department when review is needed |
Identity (author_id) is stored for the account that filed. It is never returned on public complaint DTOs.
Architecture
flowchart LR
subgraph Browser
L[City landing /]
A[CivicPulse /app/]
end
subgraph Node
E[Express :3001]
J[(civic.json)]
end
subgraph Python
G[LangGraph :8001]
KB[departments.md]
end
subgraph Models
Groq[Groq LLM]
Oll[Ollama optional]
OR[OpenRouter fallback]
end
L -->|redirects| A
A -->|/api| E
E --> J
E -->|classify / cluster / ask| G
G --> KB
G --> Groq
G --> Oll
G --> OR
If the Python worker is down, Express still classifies and clusters with local heuristics so the demo never hard-fails.
flowchart TD
R[New report] --> M[Moderate]
M -->|block vulgar / abuse| X[Reject with reason]
M -->|ok| C[Classify category + department]
C --> S[Embed + similar complaints]
S --> RAG[Retrieve department notes]
RAG --> RT[Route + needs_review]
RT --> ST[(Persist civic.json)]
ST --> GOV[Gov briefing / Ask narrates SQL-shaped totals]
LLM chain: Groq → Ollama → OpenRouter → heuristic
Embeddings: Ollama (nomic-embed-text) → OpenRouter → local 384-d hash (same as Express)
pie showData
title Example mix on a seeded civic store
"Roads / potholes" : 34
"Water / drainage" : 22
"Garbage" : 18
"Street lights" : 14
"Safety / other" : 12
The pie is illustrative of the product mix. Live KPI numbers in the government panel come from
server/data/civic.json, not this diagram.
Quick start
./start-all.sh
| Surface | URL |
|---|---|
| City landing | http://localhost:5173/ |
| App | http://localhost:5173/app/ |
| API health | http://localhost:3001/api/health |
| AI worker | http://127.0.0.1:8001 |
Copy server/.env.example → server/.env. Optional keys:
| Variable | Role |
|---|---|
GROQ_API_KEY |
Primary LLM (console.groq.com — Groq, not xAI Grok) |
OPENROUTER_API_KEY |
Cloud fallback |
JWT_SECRET |
Change before any public deploy |
AI_SERVICE_URL |
Default http://127.0.0.1:8001 |
# split processes
cd server && cp .env.example .env && npm install && npm run dev
cd ai && python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt && python server.py
cd client && npm install && npm run dev
Optional Postgres (schema only; the running API uses the JSON civic store):
docker compose up -d postgres
Demo accounts
| Username | Password | Role |
|---|---|---|
citizen_demo |
password123 |
File and track reports |
gov_demo |
password123 |
Intelligence panel |
Suggested path:
- Log in as
citizen_demo, file a pothole with a location. - File two paraphrases of the same stretch — they should share a cluster.
- Log in as
gov_demo→ Intelligence: wards, clusters, briefing, Ask. - Export CSV/JSON from live store counts. Open a
needs_reviewitem if present.
Landing shortcuts: register-complaint.html → compose, view-complaint.html → search, open-thoughts.html → feed. The 3D city landing is bundled in landing/ and copied into client/dist at build (sibling ../card/apple-3d-card still works for local dev).
Repository layout
Website/
├── api/ Vercel serverless entry (Express)
├── landing/ City landing (bundled for deploy)
├── client/ React + Vite (base /app/, landing plugin)
├── server/ Express API + civic JSON store
├── ai/ LangGraph worker, Groq, RAG over kb/
├── config/ Civic taxonomy
├── scripts/ API / AI verification
├── docker-compose.yml
└── start-all.sh
HTTP surface (high level)
| Method | Path | Notes |
|---|---|---|
GET |
/api/health |
Process up |
POST |
/api/auth/login |
JWT |
GET |
/api/complaints |
Public list, anonymous authors |
POST |
/api/complaints |
Create (auth) |
POST |
/api/complaints/moderate |
Pre-submit check |
GET |
/api/gov/overview |
Role: government |
GET |
/api/gov/briefing |
Grounded narrative |
POST |
/api/gov/ask |
Q&A over stored totals |
PATCH |
/api/gov/complaints/:id |
Human override |
Deploy on Vercel
The repo includes vercel.json — import it in Vercel with framework preset Other.
| Path | Served by |
|---|---|
/ |
Static landing (landing/ → client/dist at build) |
/app/ |
Vite SPA (base: /app/) |
/api/* |
Express serverless (api/index.ts → server/dist/app.js) |
Environment variables (see .env.vercel.example):
DATABASE_URL— Postgres connection string (required on Vercel for persistent data)JWT_SECRET— long random stringGROQ_API_KEY— primary LLMOPENROUTER_API_KEY— optional fallbackAI_SERVICE_URL— optional external LangGraph worker (Pythonai/is not deployed on Vercel)
npm run install:all
npm run build
npx vercel dev # local Vercel parity
Production data: Set DATABASE_URL (Vercel Postgres, Neon, or Supabase). Schema auto-applies on boot; demo users and civic seed load when empty. Without DATABASE_URL, the API uses JSON files in server/data/ (local dev only).
Production checklist (self-hosted)
- Set a long random
JWT_SECRET. Do not ship the demo password as the only auth story. - Put Express behind HTTPS; Vite
baseis/app/so reverse-proxy/(landing) and/app/(SPA) plus/apito:3001. npm run buildat the repo root compiles server + client (client/dist/app).- Keep
GROQ_API_KEY(and any OpenRouter key) in the host secret store, never in git. - Civic persistence is PostgreSQL when
DATABASE_URLis set (server/sql/schema.sql). JSON files inserver/data/are the offline fallback.
# sketch — adjust root paths
location /api/ { proxy_pass http://127.0.0.1:3001; }
location /app/ { alias /var/www/civicpulse/client/dist/app/; try_files $uri $uri/ /app/index.html; }
location / { root /var/www/civicpulse/landing; }
License
Private project unless you add a license file. Demo credentials are for local evaluation only.
Log in or sign up for Devpost to join the conversation.