Inspiration

In India, most criminal cases begin with an FIR, or First Information Report. When someone reports a serious crime at a police station, an officer writes down what they say, reads it back, and the complainant signs it. That document starts the investigation and follows the case to court.

The weak point is the writing down. The complainant speaks, often in Gujarati, Tamil or Hindi, and someone else types it, usually in formal legal language the complainant may not read well. Details drop out. Words get reworded. Sometimes things get added. By the time the complainant signs, they may be signing a version of events they never told.

AI is already arriving at this step. In the US, tools draft police reports from body-camera audio. In May 2026, Hyderabad City Police launched AI-CopWriter, which records complaints in 10 Indian languages and turns them into FIR drafts in seconds. Tools like these are built for speed. That is exactly the step where a model can reword a statement, guess at intent, or fill gaps with details nobody said. In a police record, that is a serious failure, because the citizen's words are the evidence.

Try it yourself (Live Demo)

Judges: The system is live. You can test the citizen intake and the reviewer audit trail.

1. As a citizen: Go to firsaathi.me, sign up, choose a language, and record a short made-up complaint. You'll receive a case ID (FS-...) and a private key (FSC-...). 2. As a constable: Sign in at firsaathi.me/officer with the demo reviewer account to open the six-lane workspace.

  • Email: [email protected]
  • Password: Demo-DHesmjyssABzYJtU1X-Saathi!
  • Sample case: FS-DEMO1KYC with citizen key FSC-4EAE19860BAFE381477347A01A5D6FE410D3DD68

(Alternative routing: fir.khushit.com. All demo data is synthetic.)

What it does

The AI cannot write. It can only quote.

FIR Saathi is a working prototype of a police-station intake tool built on the opposite idea. Instead of generating a finished FIR, it never writes the citizen's statement at all. Their words stay exactly as spoken, and everything the AI produces has to point back to an exact quote.

  • The citizen chooses their language. Ten options: English, Hindi, Gujarati, Marathi, Bengali, Tamil, Telugu, Kannada, Malayalam and Punjabi. The system never guesses.
  • They speak, and the transcript is locked. Audio is transcribed in memory with Whisper Large v3 and never saved to disk or cloud storage. The transcript becomes the fixed source record for the case.
  • The citizen holds the key. They receive a public case ID (FS-...) and a private access key (FSC-...). The database stores only a SHA-256 hash of the key.
  • Officers work in a verifiable six-lane audit view: Immutable Source, Citizen Context, Citizen Corrections, AI Aids, Officer Corrections and Audit History. Nothing is merged into the original statement, and every change is logged with a timestamp.
  • Legal sections are suggestions, not decisions. Possible sections of the Bharatiya Nyaya Sanhita (BNS) come only from a fixed catalogue, and each one is tied to a quote from the citizen. The officer can override any suggestion.

Example: One statement, start to finish

The citizen says (Hindi): "कल शाम करीब सात बजे, स्टेशन रोड के पास, दो लोग बाइक पर आए और मेरा फ़ोन छीन कर भाग गए।" (Yesterday around 7 pm, near Station Road, two people came on a bike and snatched my phone.)

What the AI extracts, and what survives the deterministic check:

  • Time: "कल शाम करीब सात बजे" → accepted, exact quote found
  • Place: "स्टेशन रोड के पास" → accepted, exact quote found
  • Event: "फ़ोन छीन कर भाग गए" → accepted, exact quote found
  • Vehicle: "black motorcycle" → rejected. The citizen only said "बाइक" (bike). The color and type were the model's guess, so the field is dropped by the quote normalizer.

Possible legal section: BNS 304 (snatching), linked to the quote "मेरा फ़ोन छीन कर भाग गए", marked REVIEW for the officer to confirm or override.

Follow-up: The system asks "Was anyone with you?" The answer is stored separately in Citizen Context and never added to the original statement.

How I built it

  • Frontend: React 19, TypeScript, Tailwind CSS 4, Wouter, GSAP
  • Backend: Node 22, Express 4, tRPC 11 with Zod validation on every request
  • Database: Supabase Postgres with Row-Level Security and private storage buckets
  • AI: Groq API running whisper-large-v3 for transcription and openai/gpt-oss-20b for field extraction
  • Hosting: a self-hosted Raspberry Pi 5 running as a systemd service, served at firsaathi.me through a Cloudflare Tunnel
  • CI/CD: GitHub Actions with a self-hosted ARM64 runner on the same Pi

AI use, declared: Inside the product: whisper-large-v3 and openai/gpt-oss-20b, both through the Groq API. Neither model writes the citizen's statement, and every extraction is checked by deterministic code before an officer sees it. While building: I used Manus, an AI agent, to build the user interface. I designed the architecture, and I built and set up everything else myself: the backend, the quote-enforcement normalizer, the database and access rules, the tests, and the Raspberry Pi deployment. I can explain how every part of the code works.

Challenges I ran into

The hard problem was stopping the model from inventing things. Telling it to "only use facts from the transcript" is not enough. So I made the rules checkable in code.

  • Every field needs a quote. For each field it fills, the extractor must attach an exact, word-for-word excerpt from the transcript. A plain string check verifies it.
  • Fixed field list. The schema accepts only pre-declared field names, so the model cannot create new categories.
  • Follow-ups stay separate. If something important is missing, the system can ask one optional follow-up question. The answer goes into Citizen Context and is never spliced into the original statement.

Accomplishments I'm proud of

  • It's live. The full stack runs at firsaathi.me on hardware I configured, and it deploys automatically from GitHub.
  • The guardrail is verifiable. Anyone can read the normalizer and the tests and confirm the model cannot add unquoted details.
  • It held up when attacked. In adversarial testing, every unsafe legal suggestion the model produced was caught before reaching the officer.

What I learned

  • 56 tests pass across quote enforcement, schema limits and the tRPC routers.
  • I tried to break it. I wrote 10 hostile prompts aimed at prompt injection and made-up charges. 6 caused the model to return broken JSON, which the system rejects outright. 4 returned valid output. 2 of those contained unsafe legal suggestions that weren't marked for review, and the normalizer caught both. Nothing unsafe got through.
  • Speed isn't the point. Adams et al. (2024) found that AI assistance did not make police report writing faster. I'm not claiming time savings. The goal is a record that matches what the citizen actually said.
  • A quote proves where words came from, not what they mean. "He did not hit me" contains the words "hit me." That's why every suggestion shows its source quote and the full statement always stays visible in Lane 1.

What's next

  • Better speech recognition for regional accents and dialects, including on low-power edge devices.
  • Clearer audit views for station supervisors.
  • Data Residency: The Pi and Cloudflare setup is a zero-budget prototype deployment, not a proposal for how a police department should run this. Complaint audio currently goes to Groq, a US provider, which wouldn't be acceptable for real Indian police data. Transcription sits behind one interface, so a production version would run self-hosted Indic models such as AI4Bharat or Bhashini on Indian infrastructure.

Built With

  • arm64
  • civic-tech
  • cloudflare-tunnel
  • express.js
  • github-actions
  • groq
  • indic
  • manus
  • node-22
  • postgresql
  • raspberry-pi-5
  • react-19
  • responsible-ai
  • supabase
  • systemd
  • tailwind-css
  • trpc
  • typescript
  • vitest
  • whisper
  • zod
Share this project:

Updates

Submission history