Built: late September 2026 for Smart India Hackathon 2026 (repo public since 29 Sep).
Status: submitted; the live workstation stays up for anyone to try.
A procurement officer checking a government bid in India opens portal after portal. GST. Income tax. MCA21 for the company record. Udyam for MSME status. EPFO and ESIC for labour compliance. The debarment list. Then the bid documents themselves, to see whether the name on the PDF matches the name on all of the above.
It is slow. Worse, two officers can look at the same bid and reach two different answers.
For Smart India Hackathon 2026 (problem statement SIH26100, from the Ministry of Petroleum and Natural Gas), our team Ace Azael built Procurement Pilot: a workstation that runs fifteen fixed checks on every bid against ten registry sources, scores it out of 100, explains every flag in plain language, and then hands the decision to a named officer.
The design rule fits on one line:
Code decides. AI explains. A named officer signs.
This post is about why that order matters, and what it looks like in code.
Why the model doesn't get to score
The tempting build is obvious: give an LLM the bid, the tender and the registry data, and ask it whether the bid is compliant. It would demo well. It would also fail the one property a government process needs most: the same bid must get the same answer every time, and an auditor must be able to see why.
So every check is plain Python. Each one returns PASS, FLAG or FAIL, plus a sentence naming the real numbers and the source it checked. The score is arithmetic over those results:
def score_and_verdict(checks):
raw = sum(c["points"] for c in checks) # PASS = full weight, FLAG = half, FAIL = 0
fails = [c for c in checks if c["status"] == "FAIL"]
hard = [c["name"] for c in fails if c["name"] in HARD_CAP_CHECKS]
cap = 45 if hard else 72 if fails else None # a hard FAIL caps the score at 45
value = min(raw, cap) if cap is not None else raw
score = int(value + 0.5)
...
The weights add to 100. A FAIL on any of four hard checks (EMD, statutory documents, GST registration, debarment) caps the score at 45 and makes the risk Critical, no matter how good the rest of the bid looks. Any other FAIL caps it at 72. The officer sees a "Why not 100" line listing every point lost and why.
That cap was the most important design decision in the project. Without it, a debarred vendor with perfect paperwork everywhere else could score 92. With it, debarment means the bid is Non-compliant.
The rules that need no registry are real
We could not get live access to government registries in a hackathon. That needs onboarding through API Setu and a GST Suvidha Provider. So every registry lookup reads sample records, and the app labels every one of them as simulated on screen.
But some checks need no lookup at all, and those are real. A GSTIN carries its own check digit:
def gstin_check_digit(first14: str) -> str:
total = 0
for i, ch in enumerate(first14):
product = _CHARS.index(ch) * (1 if i % 2 == 0 else 2)
total += product // 36 + product % 36
return _CHARS[(36 - total % 36) % 36]
A GSTIN that fails this test cannot be a valid registration, whatever the bid claims. The PAN sits inside the GSTIN, and its fourth character tells you the holder type, so a "Private Limited" company filing with an individual's PAN gets flagged. Make in India class thresholds (Class-I at 50% local content, Class-II at 20%), EMD exemptions for verified MSE, Startup and NSIC bidders, and incorporation under 91 days as a shell-company signal are all real rules too.
Being honest about what is simulated is not a weakness in the pitch. It is the pitch. A judge who finds an unlabelled fake will distrust everything else.
Where the AI actually earns its place
The reasoning layer (Sarvam's sarvam-105b, an Indian model) does one job: it reads the check results and writes the explanation an officer would want to read. It has three hard limits:
- It cannot set the score or the verdict. Those are computed before it is called.
- It never sees a full PAN or GSTIN. Identifiers are masked before anything reaches the model:
def mask_ids(text: str) -> str:
"""The model never sees a full PAN or GSTIN: the PAN part is masked in both."""
- It is optional. Without an API key, the app writes a deterministic summary from the same evidence and says so. The demo never depends on Wi-Fi.
Catching what a single bid can't show
Some fraud only appears across bids. For every pair of bids on a tender, Procurement Pilot screens for:
- prices within 0.5% of each other
- submissions within 5 minutes of each other
- shared addresses
- cover bids more than 25% above the benchmark
In our demo data, two transport bidders quoted 0.38% apart and submitted 2 minutes apart. One of them had been incorporated 55 days before bidding and had no ESIC registration. No single-bid check would have connected those facts.
A human signs, on the record
The last step is deliberately not automated. Officer, senior approver and admin roles are enforced by the server, not the UI. Passing a flagged bid needs a senior approver and a written reason of at least 30 characters. Every decision, with name and time, lands in an audit log that an admin can export.
The stack
- FastAPI running as a Vercel Function, Postgres on Neon (SQLite locally)
- JWT sign-in with server-enforced roles
- A PDF reader that pulls GSTIN, PAN, Udyam, CIN, DPIIT number and legal name from a bid document and cross-checks each one
- 26 API tests, plus a parity test that runs the frontend's offline engine against the backend for every sample bid. CI runs both on every push
What I'd tell another team
If your agent touches money, eligibility or safety, split it in two. Let deterministic code own every decision that must be repeatable, and let the model own the part humans are bad at: turning fifteen results into two clear paragraphs. You get an AI feature judges can see, and a system an auditor can trust.
The live build is at procurement-pilot-sih.vercel.app and the code is public under MIT at github.com/victorisaacchinta-eng/procurement-pilot-sih. There is also a 4:52 walkthrough on YouTube.
Student prototype. Not an official government system, and every tender, bid and registry record in it is sample data.
I wrote this with AI assistance, working from the repo. Every number and code excerpt above comes from the repo.

Top comments (0)