Inspiration

A woman walks into a police station. Someone is threatening to leak her photographs unless she pays fifty thousand rupees. The officer tells her there is nothing he can do. She goes home.

Two things just went wrong, and she knows about neither.

First, the officer was right, but for the wrong reason. Online blackmail is not a local police matter. PECA 2016 assigns electronic crimes to the FIA Cybercrime Wing. She was standing in the wrong building.

Second, and this is the one that matters: when the police refuse to register an FIR, CrPC §22-A(6) gives her a judge — an ex officio Justice of the Peace — who can order them to do it. That remedy has existed the entire time. She has never heard of it, and nobody at that counter was going to tell her.

That is where most legal journeys in Pakistan end. Not at a verdict. At a counter, with a shrug.

The problem was never a shortage of legal text. Pakistan has plenty of law. The problem is that the people most likely to be wronged — domestic workers, first-time complainants, women facing harassment, blackmail victims — are exactly the people least equipped to read a statute or push back against an officer who has decided not to help them. HaqNama is built around that exact dead end.

What it does

A citizen describes what happened to them in their own words. Typed or spoken. Urdu, Roman Urdu, or English. They get back four things:

  • The law — the exact sections that apply, quoted and openable in the app.
  • The route — not "consult a lawyer," but the specific authority: police station, FIA, Ombudsperson, or Justice of the Peace, shown as a colour-coded "GO HERE" card with what to bring.
  • The escalation path — what to do when that authority turns them away. This is the feature the whole app exists for.
  • Document reading — photograph a contract or legal notice and get its dangerous clauses flagged, by number, in plain language.
The citizen says HaqNama answers
"Someone broke into my house and stole my laptop" Local police, FIR under CrPC §154, PPC §397/§391/§453
"A man is threatening to leak my photos on WhatsApp" FIA Cybercrime, not the police. PECA §21/§20/§16
"My manager keeps making sexual comments at work" Ombudsperson, no court needed. Harassment Act §5/§2
"The officer refused to register my case" Ex officio Justice of the Peace. CrPC §22-A(6)
"I hit a man with my car and he sued me" Accused-party guidance: PPC §279/§337G, bail, compounding under §338E

Look at rows two and three. The intuitive answer, the one every Pakistani would give you, is go to the police. In both cases that answer is wrong and costs the victim days they may not have. Encoding that is the whole job.

How we built it

Every act of reasoning is done by Gemma 4. gemma-4-31b-it handles triage and answer generation; gemma-4-26b-a4b-it reads photographed documents directly, natively multimodal, no OCR stage. The only non-Gemma models just convert modalities: multilingual-e5-large embeds text for search, and Whisper turns speech into text. No legal judgement is ever made outside Gemma.

We used RAG, not fine-tuning, and deliberately. Fine-tuning a model on statute law invites it to paraphrase the law from memory, which for a legal tool is a bug, not a feature. Instead HaqNama is a GraphRAG system with two different retrievers:

  1. Semantic (FAISS + multilingual-e5-large) finds which law applies.
  2. Structural (Neo4j knowledge graph) answers which authority owns it and what happens when they refuse. Authorities are nodes; routing and escalation are typed edges (Police -ESCALATES_TO-> Justice of the Peace, reason: CrPC §22-A(6)).

A vector store alone can tell you what §379 says. It cannot tell you that §379 chains to CrPC §154, which belongs to a police station, which escalates to a Sessions Judge when it refuses you. Those are relationships, not semantics, and relationships belong in a graph.

The finding that became the product. Pure semantic search fails at the core task, and the reason is the reason the app exists. Measured on our index:

"police refuse to register my FIR what can I do"
  → CrPC §57, §349, Preamble.   Noise. The remedy isn't there.

"ex-officio Justice of the Peace, refusal to register FIR"
  → CrPC §22-A.   Score 0.91. The exact remedy, first hit.

Same need, same law, the only difference is vocabulary. And the person who needs §22-A is, by definition, the person who does not know the phrase "ex officio Justice of the Peace." So before anything is embedded, Gemma detects the intent and appends the statutory vocabulary the index is built on. Lived experience goes in, statutory language comes out, then we search.

The stack: Flutter (one codebase → iOS, Android, Web), FastAPI (Python 3.11), Neo4j, FAISS, Supabase with Postgres Row Level Security, Whisper via Groq for Urdu voice, and Gemma 4 served through OpenRouter. Corpus: Pakistan Penal Code, CrPC 1898, PECA 2016, and the Harassment Act 2010.

Challenges we ran into

We tried to run Gemma locally first, and the hardware said no. We began on a 2019 Intel MacBook Pro, CPU only, because an app that holds descriptions of crimes against real people should ideally send them nowhere. We got it working and learned three things fast: the 26B-A4B MoE ran ~3× faster than the dense 12B (7.3 tok/s), because on CPU speed tracks active parameters; Gemma, a reasoning model, once thought itself to death, 112 seconds, 700 tokens of thinking, and a zero-character answer, until we set think: false; and Ollama silently truncates context past 4096, which can quietly drop the retrieved statutes from the prompt entirely. Then the Mac gave out under load, thermal-throttling and crashing too often to demo, so we moved to OpenRouter. It was hardware, not a design choice. But the local attempt is why Gemma does the smallest possible amount of the thinking, which keeps a fully offline future real rather than rhetorical.

"Hi" summoned a police station. Retrieval originally ran on every message, so a greeting matched its nearest statutory neighbour and confidently routed the user to the police, for a case that did not exist. That is funny until you remember who is typing into this app: someone frightened, opening it for the first time. Fixed with a triage stage, locked with a test literally named "saying 'Hi' does NOT summon a police station."

A car accident was refused as out-of-scope while PPC §279/§337G sat in the index at 0.92 similarity. Our triage prompt described the Penal Code too narrowly. Fixing it is what exposed the victim-vs-accused axis: when the user is the accused, the routing card is suppressed entirely and the answer inverts to the offence they face, bail, and compounding. Telling that person to go file an FIR would be worse than useless.

A provider returned HTTP 200 and silently dropped an image. During document analysis the API returned a clean success while the model asked us to "please upload the document." Status codes can't catch this; token counts can. An included image costs hundreds of prompt tokens, so we now check the response's prompt-token count and retry on a fallback vision model when the image never arrived.

Accomplishments that we're proud of

  • The escalation path. Most legal tools stop at "here is the law." HaqNama is the only one we know of that answers what to do when the authority refuses you, the exact moment most real cases die.
  • The intent-expansion insight. Turning "the police won't help me" into "ex officio Justice of the Peace, CrPC §22-A" is not a UX nicety. It is the bridge between a citizen's words and a statute book they were never able to read.
  • The accused-party inversion. Same corpus, same graph, opposite answer, so the app never tells a driver who caused an accident to go file a complaint against himself.
  • 24 automated tests, including 12 live contract tests that assert what a citizen actually depends on: blackmail routes to the FIA, a refusal produces §22-A, Urdu in gives Urdu out, and the accused are never mis-routed.
  • Row Level Security on every conversation. These rows describe crimes against real people, so the database itself, keyed on auth.uid(), refuses to return another user's data. That guarantee belongs in the database, not a where clause someone can forget.

What we learned

The biggest lesson was that the hard part of legal AI is not the law, it is the vocabulary gap. The statute and the remedy already exist; the citizen simply does not have the words to reach them. Once we understood that, the whole architecture followed: Gemma translates lived experience into statutory language, retrieval and the graph do the actual legal reasoning, and Gemma phrases the result back in the citizen's own language.

We also learned to make Gemma do less. Left to reason freely it burned its entire budget and returned nothing; the system only became reliable once we gave the deterministic work (which law, which authority) to the graph and left Gemma to triage, decompose, and phrase. And we learned that in a tool people turn to when they are frightened, the failures that matter most are the quiet ones: a greeting that summons a police station, an image silently dropped, a context window truncated without a warning. None of those crash. They just lie, and a legal tool cannot afford to.

What's next for HaqNama

  • Bring the offline path back on hardware that can carry it. Gemma 4's open weights mean that option never closes, it only waits for better silicon than a 2019 Mac.
  • Expand the corpus into family law, tenancy, and consumer protection, where the document-analysis feature is already surfacing demand.
  • Province-aware routing with real office addresses, helplines, and jurisdiction.
  • Partnerships with legal-aid organisations, for the cases the app correctly identifies as needing a human lawyer.

HaqNama gives legal information, not legal representation, and it says so on every screen.

Built With

Share this project:

Updates

Submission history