EPFO Member Portal - Rebuild

Project Overview
- Name: EPFO Member Portal (rebuild)
- Type: Full rebuild of India's Employees' Provident Fund Organisation member portal - React/Vite frontend plus a Cloudflare Worker API
- Context: Built solo for the "Build What Moves India" hackathon
- Purpose: Make a citizen's own retirement-savings record legible - not a redesign of the interface, a fix to what a member can understand
- Live: deployed at
mrutyunjaypatil.dev/epfo/, with demo credentials on the sign-in screen
The thesis
EPFO's problem is not that it is slow. It is illegible: it knows everything about a member's money and tells them almost none of it. In a real logged-in session the passbook showed ₹0 employee and ₹0 employer contribution for four straight years and explained why on a different page; a status called "Settled" did not mean the money had arrived; a rejection remark read SEEKING CONFIRMATION FROM THE EMPLOYER REGARDING GENUINITY; an address rendered as NA, NA, NA, NA, -1. This rebuild is organised around one idea: explain everything, before it costs the member something. Every feature has to pass one test - does this make a citizen's afternoon easier - and anything that only holds the institution accountable is out of scope.
Key Capabilities
Service parity, not a redesign
- Thirty-five member services across forty-one routes - claims (Form 31, 19, 10C), transfers, pension (Form 10D), Scheme Certificate surrender, KYC, e-nomination, exit marking, joint declaration, profile management, and more - each backed by real service-eligibility, validation, and stage-progression rules.
- Every route renders something clean. The failure mode a reviewer must never hit is a broken screen, not a thin one.
- Works with the model switched off. Every service runs from bundled synthetic fixtures with no Worker and no network call; the Worker only adds the AI assistant on top, and losing it costs nothing else.
The audit layer
- A deterministic TypeScript audit engine, ported from a Python reference implementation, checks a member's record against itself: months an employer deducted but never deposited, an account stranded under an employer left a decade ago, a name mismatch that would reject any claim filed against it.
- The verbatim EPFO remark sits beside its meaning - the real string, original casing and typos, in monospace, labelled "EPFO remark" / "Meaning", neutrally - the single most memorable design decision in the build.
- The pension threshold, computed and not asserted. 120 months of counted service is the line between a one-time payment and a monthly pension for life; a demo account with 106 counted months gains exactly the 14 it needs - 10 from an unlinked account, 4 from unpaid months - from the two things the audit says it is owed.
- A
missingmonth reads as a hole in the record, not an error message.
The assistant, scoped to one record
- A language model explains the record where the member is standing and cites the lines it read, but it sees exactly one account:
buildContexttakes aVerifiedSession, a branded type only the token verifier can produce, so a handler that tried to read a record named in a request body would not compile - enforced in the Worker, not merely asked of the model. - No endpoint names a login. A signed session token carries the login; the Worker verifies it, loads the one record it names, and streams the answer.
- A deterministic fallback answers from the record when no model is reachable, so the assistant degrades rather than disappears.
- Multiple Workers AI accounts behind one interface - the free tier's daily neuron allowance is spent across numbered account pairs, with an account that reports itself empty skipped for ten minutes rather than retried, so
worker/ai-pool.tsgives every caller more headroom without anything else in the Worker knowing there is more than one account.
Government-service voice and accessibility
- No product brand. The portal reads as a government service throughout, with a prototype notice stating it is not an official Government of India service and that all data is synthetic - no State Emblem, seal, or tricolour anywhere.
- The product states facts and never argues - banned language includes "plain language," "we found," "unlike," "finally," first-person plural, and exclamation marks.
- Accessibility gates in the release suite: one level-one heading per route, every form control labelled, focus moving to the page heading and to the error summary, dialogue Escape and focus return, and no control in the main content without a destination.
Architecture (high level)
text
Service eligibility, validation, reference numbers, and stage progression are plain TypeScript with no React import, so they are tested directly and React components decide only where a result appears. A request submitted from any service is written to a typed client-side repository so it survives navigation and a refresh; reference numbers are a pure function of service, member, office, year, and a per-member sequence, so a screenshot taken today matches a test run tomorrow.
Release gate
pnpm verify is the one release command, run the same locally and in CI: typecheck, unit tests, an assistant evaluation set, the frontend build, a Worker dry-run build, an initial-bundle-size budget (320 KB, tighter than the 500 KB the build itself warns at, because the synthetic records are a 380 KB dynamic import that must stay out of the first load), and a Playwright browser gate. The browser gate builds nothing itself - it starts vite preview over the production dist/ and the real Worker through wrangler dev, so it drives the exact bundle that ships against the exact Worker that serves it, and it fails outright if the Worker cannot start rather than silently passing on the browser's own fallback. It covers sign-in, a claim from purpose through receipt to status, a transfer through its stage history, cross-account request denial, public lookups, and the full accessibility gate list above, at both desktop and a 390px viewport.
Technical Stack
- Frontend: React 19, Vite, TypeScript, React Router 7, react-markdown with remark-gfm for the assistant's streamed answers
- API / edge: Cloudflare Workers, the Workers AI binding via
workers-ai-providerand the VercelaiSDK, Zod validation - Model:
@cf/meta/llama-3.3-70b-instruct-fp8-fast, chosen empirically against this portal's own prompts after the catalogue's reasoning models spent their token budget "thinking" and returned empty answers - Testing: Vitest (unit and an assistant evaluation suite run on every commit), Playwright (the production browser release gate)
- Auth: signed session tokens verified in the Worker - hackathon-scoped, not production identity infrastructure
- Deployment: Cloudflare Worker asset deployment, routed to
mrutyunjaypatil.dev/epfo/*alongside the existing portfolio at the apex domain - Reference engine: a Python audit engine (
engine/) as the source of truth, ported line-for-line into the shipped TypeScript engine rather than reimplemented from scratch