Inspiration

What it does

How we built it# HaqSetu — हक़ सेतु · "the bridge to your rights"

From paper you can't read to the rights you're owed. Built for the rural poor. Not advice. Not a chatbot. A bridge.


💡 Inspiration

HaqSetu exists for rural India's poorest households — farmers, widows, daily-wage labourers, first-generation students. For them, an unclaimed pension or a missed scholarship is not paperwork friction. It decides whether there is food this month, whether a daughter finishes school.

Here is what makes it unbearable: the money already exists. Pensions, rations, scholarships, health cover, guaranteed wage work — all appropriated, all sitting in a budget line with these families' names on it. Year after year, a staggering portion goes unclaimed.

The reason is almost never ineligibility. The reason is that the benefit shows up as a form nobody in the house can read, written in someone else's language, guarded by a process no one ever explained.

What reframed the entire problem for me was seeing that this is not an eligibility failure — it is an uptake failure. Take a scheme \(s\) paying \(B_s\), for which a household qualifies with probability \(\Pr(E_s)\) but actually files with probability \(u_s\). What that family quietly gives up each year comes to

$$ L \;=\; \sum_{s} B_s \cdot \Pr(E_s) \cdot \bigl(1 - u_s\bigr) $$

Look at what the state has already handled. \(B_s\) is funded. \(\Pr(E_s)\) is settled law. The one loose variable is \(u_s\) — and \(u_s\) collapses because of literacy and forms, not because of poverty. Which means this is a software problem wearing a policy problem's clothes.

One person kept pulling me back. Call her Sunita: widowed, two children, no land. By law she is owed a widow's pension, a girl-child scholarship, and subsidised rations. She collects none of the three. The first she has never heard of. The second she cannot read. For the third, a middleman in her village offered to "sort it out" — for a share of her own entitlement.

That is the injustice at the root of this project. The instant somebody else has to read the paper on your behalf, your right quietly turns into a bribe. I had no interest in building one more app that informs people they "may be eligible." I wanted the bridge that carries them the whole distance — to a filled form, ready to hand across the counter.

North Star: No one should lose what they are legally owed simply because they cannot read a form.


🌉 What it does

Speak one sentence. Photograph one document. In your own language. HaqSetu returns a single artefact: the Claim Dossier.

  1. 📄 "What this paper means" — photograph any official document and get it back decoded in your language: what it is, what it demands of you, when it's due, and whether you're looking at an opportunity, a debt, or a scam.
  2. 💰 "What you're owed but aren't claiming" — every benefit you qualify for, ranked, each carrying a plain-language "why you qualify" that names the precise eligibility rule it satisfied.
  3. ✅ "Your ready-to-file paperwork" — the actual government form, completed, as a real PDF; a "bring these" document checklist; and where to submit it, by when.

Talking to it is merely the way in. What comes out is a verifiable packet — and that is precisely why HaqSetu is not a chatbot.


🏗️ How I built it

One architectural commitment shapes everything else: language models sit at the perimeter; a deterministic rules engine holds the centre. Models handle what only models can — untangling messy speech, reading a creased and shadowed page, restating a bureaucratic clause in someone's mother tongue. A model is never permitted to decide who qualifies.

 VOICE ─┐
        ├─▶ [1] INTAKE        speech/text → provenance-carrying facts   (model)
 PHOTO ─┘
              [2] DECODER      document image → meaning, deadline, risk  (model, vision)
              [3] ENTITLEMENT  one agent per scheme, concurrently        (code decides; model explains)
              [4] ACTION       fills the real form, field by field       (model selects; code fills)
              [5] VERIFIER     every claim cites its rule; every field its fact  (code gate)
                    ▼
              📦 THE CLAIM DOSSIER

Stack: TypeScript and Express behind a five-stage agent pipeline; pdf-lib writing into genuine fillable government-style PDFs; zod schemas policing every model boundary, so malformed output is rejected at the seam instead of quietly propagating. The client is a phone-sized, icon-led, voice-first React app in Hindi, Marathi, and English. Eligibility rules are data, not code — five benefits, five real fillable forms, one JSON file each. Adding or amending a scheme is an edit to a text file. No code change. Certainly no retraining.

I built it as a vertical slice first: one scheme carried the entire distance from spoken sentence to filled PDF, with a passing test, before a second scheme was allowed in. Breadth came only after the thin path was provably solid.

Eligibility is a boolean program, not an opinion

Every scheme \(s\) owns a rule set \(R_s\). Qualification is a plain conjunction of checkable predicates over the citizen profile \(p\):

$$ E_s(p) \;=\; \bigwedge_{r \,\in\, R_s} r(p), \qquad r(p) \in {\text{true}, \text{false}} $$

The Uttar Pradesh Widow Pension, for example, reduces to four predicates — widowhood, minimum age, state residence, and an income ceiling that is nothing more exotic than

$$ r_{\text{income}}(p) \;=\; \bigl[\, I(p) \le \tau_s \,\bigr], \qquad \tau_s = ₹2{,}00{,}000 $$

These are ordinary boolean evaluations in server/engine/evaluate.ts, not model judgements. The verdict is therefore reproducible, auditable, and byte-identical across runs. The model never even observes the qualify/don't-qualify branch — it only phrases the why for a rule that code has already matched.

"I don't know" is not "no"

A subtlety that turned out to matter enormously in the field. When a required fact is simply absent from what the citizen told us, the engine must not read that as a failed test. So the verdict is three-valued, not binary:

$$ V_s(p) = \begin{cases} \textsf{missing_info} & \text{if some } r \in R_s \text{ has no backing fact}\[4pt] \textsf{not_eligible} & \text{else if } \exists\, r \in R_s : r(p) = \text{false}\[4pt] \textsf{eligible} & \text{else} \end{cases} $$

Note the precedence: \(\textsf{missing_info}\) is checked before \(\textsf{not_eligible}\). Silence about a fact can never harden into a rejection. Instead the dossier converts the gap into a question worth asking, which is the difference between telling a widow "you don't qualify" and telling her "bring your income certificate."

The Verifier is the moat

server/engine/verifier.ts runs last, and it enforces exactly one invariant before a dossier is allowed out: every element must arrive carrying a non-empty citation.

$$ \forall\, e \in \mathcal{D} \;:\; \operatorname{cite}(e) \neq \varnothing $$

In practice that means no entitlement is displayed without a matched rule, and no form field is populated without a source fact. The Verifier independently re-checks each matched rule against the real profile and discards any filled field whose provenance fails to trace back to a genuine extracted fact. In this domain a fabricated "you qualify" puts a person on a bus they cannot afford, to be turned away and shamed at a counter. The gate is not a nicety. It is the product.

Why the fan-out is genuine concurrency

The entitlement stage launches one agent per scheme in parallel, rather than folding everything into a single sprawling prompt. With per-scheme latency \(t_s\), the cost of breadth stops being a sum and becomes a maximum:

$$ T_{\text{parallel}} = \max_{s} t_s \qquad\text{rather than}\qquad T_{\text{serial}} = \sum_{s} t_s $$

Widening coverage therefore costs the user essentially no extra waiting — which matters a great deal when the person holding the phone is standing in a queue with a toddler on one hip.


📚 What I learned

  • The artefact is the product; the conversation is just the doorway. My hardest discipline was refusing to let this drift into a chatbot. One question — does this move a real person closer to money they are owed? — killed a great many clever features and rescued the project.
  • Trust is something you architect, not something you disclaim. No prompt will ever get you to "never hallucinates eligibility." You have to make it structurally unreachable. Every disclaimer I felt tempted to write turned out to be a design defect requesting a fix in code.
  • Constrain the model instead of pleading with it. Schema-enforced output, plus tool calling shaped so the model chooses among candidates rather than authoring values, converted a fragile demo into something dependable. Models are at their strongest when the output space is narrow and typed.
  • Provenance must be born alongside the fact. Once each extracted fact carried its own origin and confidence from the instant of extraction, the citation invariant became nearly free to enforce. Bolting it on afterwards would have been hopeless.
  • Absence of evidence deserves its own status. Collapsing "unknown" into "false" is the single cruellest bug this category of software can ship, and it is invisible in testing unless you deliberately look for it.
  • Depth beats breadth, and it is also more honest. Five schemes carried end-to-end to a filled PDF persuades far more than a claim to cover five hundred.

🧗 Challenges I faced

  • Making hallucination impossible rather than infrequent. The citation rule went through several rewrites before "no citation ⇒ not shown" became a true hard gate, instead of a soft check that logged a warning and shipped the answer anyway. Genuinely hard gates fail loudly and inconveniently — that is what makes them work.
  • Threading provenance through five stages without dropping it. A fact extracted in stage one has to arrive in stage five still knowing where it came from, even after nested-field lookups reshape it. Getting resolution to preserve the source fact's lineage instead of flattening it into a bare value was subtle, and it is what makes the Verifier's job possible at all.
  • Filling real government PDFs. Fitting loosely-extracted facts onto rigid, inconsistently-labelled form fields — while never inventing a value — proved fiddlier than any AI component. Non-Latin scripts broke PDF text encoding outright and needed dedicated work: a Hindi name silently rendering as empty boxes is a form that gets rejected at the window.
  • Teaching the reader to admit defeat. Coaxing schema-valid structure out of a vision model for every document, every time, forced validation at every boundary and a fail-loud policy over pass-it-along. The decoder returns nulls on an illegible page rather than guessing — and teaching it to say "I cannot read this" was substantially harder than teaching it to read.
  • Treating literacy and language as constraints, not preferences. Building so that the conversation is optional while the dossier is the deliverable meant designing around a person who may not read at all, on one basic handset shared by an entire household, on a connection that comes and goes.

🔭 A note on scope (please read before assessing accuracy)

The Uttar Pradesh Widow Pension rules cite the state's SSPY pension portal. The remaining schemes carry clearly-labelled representative demo rules rather than live statutory text. That is a deliberate honesty decision, and the architecture is what makes it a safe one: correcting or adding a scheme is a JSON edit with no retraining whatsoever, so a production deployment would ingest official rules through precisely the same path this demo uses.


HaqSetufrom paper you can't read to the rights you're owed. Built for the rural poor. Not advice. Not a chatbot. A bridge.

Built With

Share this project:

Updates