CourtNetra: retrieval over 18,863,754 judgments
Our own legal research platform, also shipped as NyayLens. Hybrid retrieval across the Supreme Court and all 25 High Courts, 1950 to the present; a Citator that says whether an authority is still good law; and an automated hallucination evaluation that fails the build above 2%. Built and operated solo by our founder, Aniruddh Atrey.
Shipped, not projected. Every figure on this page is from the production system. Ask us for the CI report.
A wrong citation is not a bug
In legal software, a confidently wrong citation is professional negligence, handed to a user in a form they are inclined to trust. That sentence is the whole design brief. Everything below exists because of it.
Fluency is the hazard
The failure mode is not gibberish, which anyone would catch. It is a well-formed citation to a case that says something else, or to no case at all, arriving in exactly the register a correct answer would use.
The reader carries the duty
A practitioner who files it is the one answerable for it, not the vendor. So the answer has to be checkable, which means every claim carries a citation back to the passage it came from or it does not get made.
Overruled reads like good law
A superseded authority is textually indistinguishable from a live one. Similarity search cannot tell them apart, because on the text they are the same kind of object. Something else has to.
So the product question was never how to make the model sound more authoritative. It was the opposite: how to make the system decline, reliably, at the boundary of what the corpus can support, and how to prove week after week that it still does.
18,863,754 judgments, 1950 to the present
Scale is not the achievement here. It is the constraint that makes every other decision on this page necessary: at this size, the cheap version of each choice stops working, and it stops working quietly.
- Supreme Court
- 39,158 judgments
- High Court
- 18,824,596 judgments, across all 25 High Courts
- Coverage
- 1950 to the present
- Embedded
- 687,289 chunks, from the subset selected for vector retrieval
- Embedding model
- Qwen3-Embedding-4B, halfvec(2560)
- Index
- HNSW, in PostgreSQL with pgvector
- Pipeline
- Self-hosted, CUDA GPU
Embedding is a self-hosted job on our own CUDA GPUs rather than an API call, which at 687,289 chunks is a cost decision and a data decision at once: the corpus is public, but the query logs of a practising lawyer are not, and the fewer parties that see them the better.
halfvec(2560) stores each dimension at half precision, which cuts the index footprint against full precision and is what keeps an HNSW graph of this size resident rather than paging. That is not a micro optimization at this scale; it is the difference between a graph traversal and a disk seek per hop.
Retrieval sits on a structured layer built alongside it: a citation network of approximately 165,000 edges, 1,807 statute sections and 6,315 judge profiles. A judiciary knowledge graph is enriched from Wikidata over SPARQL and reconciled against independent sources behind a feature flag, because an unreconciled third-party fact is a liability in exactly the way an uncited answer is.
How a query becomes a cited answer
Six stages. Written out as text rather than drawn, because the text version has to work on its own regardless of what is rendered next to it: with no JavaScript, in a screen reader, and in whatever an answer engine sends to read this page.
A query arrives, and its filters are resolved first
Whatever restriction the user has set is turned into predicates before anything is retrieved, so both retrieval legs run against the same narrowed pool. Filtering after retrieval rather than before it is the specific mistake described further down this page.
It splits into two retrievals, lexical and dense
A tsvector BM25 pass runs first, because it is cheap and it narrows the pool that the expensive leg has to consider. Dense retrieval then runs as a pgvector similarity search over halfvec(2560) embeddings under an HNSW index.
The two ranked lists merge through Reciprocal Rank Fusion
RRF reconciles the legs by rank position rather than by raw score, so neither scoring scale can dominate the other. A case that both legs rank moderately well survives; a case that only one leg loves does not automatically win.
The merged set enters the 6-layer corrective loop
Adaptive reranking, dynamic context assembly and LRU caching, all inside a hard 30-second budget. The budget is a design input, not a timeout bolted on afterwards: a stage that cannot justify its share of 30 seconds does not stay in the pipeline.
The system answers with citations, or it declines
Every claim points back to the passage that supports it. Where the corpus cannot support a claim, declining is the output. Declining is a first-class result here, not an error path, because an answer nobody can check is worse than no answer.
Above the answer stage sits the evaluation gate
Automated hallucination evaluation runs weekly in CI and fails the build above 2%. It sits above the answer stage rather than beside it, because it governs whether that stage is allowed to reach a user at all.
Why hybrid, and not just vectors
Dense retrieval is good at paraphrase and bad at exact tokens. A statute section number, a party name or a neutral citation is exactly the kind of string a lawyer types and exactly the kind an embedding blurs. BM25 does not blur it. Running both and reconciling by rank is cheaper than trying to make one of them do the other job.
Why corrective, and not one pass
A single retrieval pass commits to whatever the first ranking returned. The corrective loop gets to notice that the retrieved set does not actually answer the question, and to do something about it inside the budget rather than generating over a weak context and hoping.
Two things stand between retrieval and a filing
Retrieval finds the most relevant authority. Whether relying on that authority is safe is a different question, and conflating the two is the specific way legal AI hurts people.
Hallucination evaluation, in CI
An automated hallucination evaluation runs weekly in CI and fails the build above 2%.
A threshold that fails a build is a different object from a number on a dashboard. A dashboard is consulted when somebody remembers to look, and after a launch nobody remembers. A gate is consulted on every release, by a machine that does not get tired of the question. Grounding regressions do not reach a user here because they do not reach a release.
This is the mechanism we bring to client work, and it is why we can write a number rather than an adjective.
The Citator
Every authority is classified as good law, distinguished, doubted, partially overruled, or overruled, across a 165,000-entry AIR-SCC-SCR crosswalk.
- Good law
- Distinguished
- Doubted
- Partially overruled
- Overruled
The crosswalk is what makes the classification possible at all: the same judgment is cited differently in AIR, in SCC and in SCR, so without reconciliation a treatment recorded against one citation is invisible against the others.
Filtered queries were slower than unfiltered ones
That is the diagnostic tell, and it is worth more than any number further up this page. A filter removes rows. Removing rows should not cost time. When it does, the query is not running the plan you think it is.
EXPLAIN ANALYZE showed the planner abandoning the HNSW index and falling back to a sequential scan with exact distance computation, on the strength of a wrong row estimate. Exact distance over a corpus this size is precisely the work the index exists to avoid.
Correcting the estimate helped, and it did not solve the problem, which is the part worth understanding. An approximate index and a post-filter interact badly by construction: HNSW returns its ef_search candidates first, and the predicate is applied afterwards. A narrow filter can leave almost nothing standing.
So the query is slow and under-recalled at the same time, and the second failure is the dangerous one. A slow query announces itself. A query that quietly returns four results where it should have returned forty looks like a successful search, and in this product it looks like the case law simply does not exist.
The fix, in parts
- Partial HNSW indexes
- Built on the high-cardinality filter dimensions, so a filtered query has an index that already matches its restriction instead of one it has to fight.
- Corrected planner statistics
- So the estimate stops arguing for a sequential scan on a table where a sequential scan is never the right answer.
- ef_search, measured
- Set deliberately against a held-out relevance set rather than left at whatever value the tutorial used. It is a recall dial, and a recall dial nobody measured is a guess.
- Reordered hybrid legs
- Cheap BM25 narrows the pool before dense search runs, which reduces both the cost of the dense leg and its exposure to the post-filter problem.
None of that is visible in a demo, which is why it is on this page. The numbers above say the system works. This says we know why, and it is the part that transfers to your system rather than staying with ours.
A cache-aware cascade, measured
A cache-aware model cascade cut LLM spend approximately 60% at a 70% cache hit rate.
Cost is a design constraint here, not a cleanup task for later. An architecture that only works at a price nobody will pay has not shipped; it has been demonstrated. At 18,863,754 judgments, a per-query cost that looks trivial in testing becomes the line item that decides whether the product survives its own traffic.
The cascade is cache-aware rather than sitting behind a cache, which is the distinction that made the numbers move. The two are designed against each other: what the cache can serve shapes what the cascade escalates, and both stay inside the same hard 30-second budget as everything else in the pipeline.
Past tense, one named system, measured on it. We do not promise the same reduction on yours; the mechanism transfers, the percentage is a property of your traffic.
What is actually running
Retrieval is the interesting half. The other half is a product that people open on a phone in a courtroom corridor, which imposes its own constraints and does not care how good the ranking is.
- 268 API endpoints on Express and Prisma.
- A 41-page Next.js 14 web app.
- A 37-screen React Native app, live on Google Play, with offline mode and push.
- An auto-fetch engine syncing hearings, orders and cause-lists from 19,660 court endpoints.
- 8-language internationalization across the product.
- A judiciary knowledge graph enriched from Wikidata over SPARQL, reconciled against independent sources behind a feature flag.
Built solo
CourtNetra was built and is operated solo by our founder, Aniruddh Atrey: the retrieval pipeline, the API, the web app, the mobile app and the ingestion.
We are saying it plainly because it cuts both ways and you should have both halves. It means there is no handoff between the person who chose the index and the person who has to explain it, and every decision on this page is still traceable to a reason. It also means we scope engagements to what we can actually deliver, and we will tell you on the first call if yours is not one of them.
That is the same person who would be on your call.
Ask us the second follow-up question.
Bring the one that usually goes unanswered: the query plan, the eval harness, the refusal rate, what happens when the corpus is wrong. Thirty minutes, straight to the engineer who built the system above.
Typical reply within one business day · Engagements start at $2,500