Legal Research Platform
The hard part of a legal AI is the corpus underneath it. That part is built, measured, and honest about what is still missing.
Who it is for: Law firms, legal publishers, in-house counsel, and any team that has to build retrieval over regulated text where a wrong citation is not a rounding error.
Every figure here was measured, and the method is recorded. A number we did not measure does not appear.
Everyone wants the assistant. The corpus is the work.
A legal research assistant is a retrieval problem wearing a chat interface. The interface takes a week. The corpus underneath it — complete, current, structurally correct, and honest about which of its sentences are law and which are somebody’s opinion about law — takes considerably longer, and it is the only part that determines whether the answers are any good.
So that is the part we built first, and this page describes it at that stage.
Where this stands
The corpus is built, cleaned and indexed for lexical search. The retrieval pool holds 151,216 units, and none of them carry an embedding yet. Semantic search, the chat screen, and the grounded explanation are designed and specified, and are parked on purpose until the evaluation set exists. This page is about what is built.
What is in it
| Count | |
|---|---|
| Laws, including the constitution | 1,020 |
| Articles, including 177 constitutional اصل | 32,103 |
| تبصره modelled as their own records | 14,471 |
| Court decisions | 35,185 |
| Advisory opinions | 13,462 |
| Judicial panel records | 6,406 |
| Scholarly works | 21,870 |
| Commentary passages | 82,540 |
| Topics and topic links | 23,567 · 361,167 |
| Retrieval units | 151,216 |
Operative law never shares a column with commentary. A تبصره is a record of its own, because a lawyer cites one separately and it can be amended while its parent article stands unchanged. Commentary is not quotable unless something marks it so. These sound like schema decisions. They are the difference between a system that cites a statute and a system that cites a textbook’s opinion of a statute in the same typeface.
Getting the text was the cheap part
The harvest ran 183,725 pages in about eighty minutes across eight disposable machines that deleted themselves on exit, on exception, and on interruption. No bans. One to two dollars. The workers were rate-limited per address and instructed to report a block and stop rather than route around it.
Nothing about that is clever. It is polite, it is cheap, and the raw landing zone it produced is treated as durable — every correction since has been a re-parse, never a second crawl.
"How do you know the corpus is any good?"
Because we went looking for the ways it was bad, seven times, and wrote down what we found.
Seven rounds of finding out what was wrong
Fifty-six findings, eighteen of them critical, each one verified against the live database with counts rather than asserted. The ones worth naming:
- 103,423 content rows were invisible to the search index. Notes, commentary and panel records had simply never been indexed. Rebuilt, the retrieval pool went from 106,479 units to 151,039.
- The constitution was missing entirely. The parser recognised «ماده N» and not «اصل N», so 1,461 pages were silently dropped. All 177 اصل were recovered from the raw landing zone, with no gaps.
- Authority ranking was completely flat. Every decision had landed on the same tier and none were marked binding, which meant a binding وحدت رویه ruling ranked exactly like a first-instance judgment. A trial court that happens to use your exact words must never outrank the ruling that settles the question — and vector similarity has no way of knowing that.
- The citation graph was around seventy per cent noise. 24,517 of 34,773 edges were same-law sequential pairs, artifacts of previous-article and next-article navigation links on the source pages. They were marked rather than deleted, so retrieval ignores them and the diagnosis remains reversible.
The most useful round was the one that cancelled a plan
Round four was supposed to merge fragmented duplicate laws. It did not, because the fragmentation diagnosis turned out to be wrong: most of the apparent duplicates were legitimate versions of the same law, and two records that shared all 124 article numbers had identical text in only eight of them. Merging would have destroyed real legal history to make a table look tidier.
The same round refused a deletion filter aimed at rows labelled as duplicate rulings — seventeen of twenty-two were real, settled decisions.
A cleanup pass that deletes nothing is not a failed cleanup pass. It is what a review is for.
The retrieval that is designed, and not turned on
The design is specific and it is written down: a dense arm over 1024-dimension vectors and a lexical arm over a normalised text index, fused with reciprocal rank fusion at k=60, over-fetching 150 candidates per arm, with ties broken by authority tier so that ranking never contradicts the hierarchy of sources. Filters for in-force status, branch of law, and date range are built in and tolerant of nulls, so a filter can never quietly hide the controlling article.
We are not embedding 151,216 units to prove that. The graded evaluation set comes first, then the model bake-off, then the spend — in that order, because the alternative is picking a model on benchmark scores drawn from a domain that is not this one.
Every action leaves a record, or it does not happen
The audit row is written into the same transaction as the action it describes. Not afterwards, not through a queue, not inside a try-except that shrugs. The record commits if and only if the action commits, and a database trigger makes any attempt to update or delete an audit row raise an error. There is no configuration setting that turns it off, in any environment.
Route coverage is a test, not a promise: the suite walks the live route table and fails if any write endpoint is neither mapped to an expected audit action nor explicitly exempted with a written reason.
What it is not
- Not a working legal AI assistant. Not yet, and we will not describe it as one.
- Not a replacement for a lawyer’s judgement, at any phase of this roadmap.
- Not a corpus we are offering for redistribution.
- Not a system with performance numbers. Those arrive with the evaluation set, and they will be published with their method.
Technical decisions
Including what we did not choose, and why.
Publish at corpus-complete rather than embedding 151,216 units to get a demo out.
Alternatives: Generate the embeddings now and ship a chat screen
Embedding the whole corpus is a one-way spend that fixes a model choice before there is any way to tell whether it was the right one. The main Persian embedding benchmark contains no legal dataset, so published scores do not predict this domain. The evaluation set is a deliverable scheduled before retrieval tuning, not after it.
Quarantined 24,517 of 34,773 citation edges instead of deleting them.
Alternatives: Delete the bad edges, Leave the graph as ingested
Roughly seventy per cent of the citation graph turned out to be an artifact of previous-article and next-article navigation links, not a legal citation. Deleting them would have made the diagnosis irreversible. Marking them keeps retrieval clean and keeps the decision open to review.
A further 28,684 reciprocal mirror edges were removed after they were found to inflate hub scores by 1.83 times.
Cancelled a planned merge of fragmented laws after the diagnosis proved wrong.
Alternatives: Merge the duplicate law records as originally planned
The apparent fragmentation was mostly legitimate versioning: two records sharing all 124 article numbers had identical text in only 8 of them. Merging would have destroyed real legal history to make a count look tidier.
Rejected a 64-step tool-calling agent for the case-matching engine.
Alternatives: 64-iteration agent loop, Bounded 8-step pipeline
The best independently measured per-step reliability is about 99.3%, which compounds to roughly 64% end to end at 64 steps against about 89% at 16. Pushing graph traversal and joins into a model loop converts operations that are correct by construction into decisions that are correct most of the time, sixty-four times over.
The replacement is a bounded 8-step pipeline with three to four sequential model calls, where retrieval, graph expansion and verification make no model calls at all.
Wrote the audit row inside the same transaction as the action it records.
Alternatives: Post-commit hook, Message outbox, Best-effort logging
Each alternative reintroduces the exact failure the requirement forbids — an action that happened with no record of it. The audit row commits if and only if the action commits, and a database trigger makes UPDATE and DELETE on the log raise.
Designed hybrid retrieval as a database function rather than around a dedicated vector store.
Alternatives: Qdrant, Weaviate or Milvus alongside PostgreSQL
At this corpus size the vector table and its index fit in memory on a modest machine. A separate vector store adds an operational component, a synchronisation problem and a second consistency model to buy nothing.
Limitations
- Retrieval is not switched on. The pool holds 151,216 units and zero embeddings. There is no chat screen in front of a user, no ranked result list, and no grounded explanation of why a statute matters to a case. Anyone describing this as a working legal AI assistant — including us — would be describing the next phase, not this one.
- No latency, accuracy or ranking figures exist for this system, and we will not quote any. The graded evaluation set is a scheduled deliverable, not a result. A vendor who has numbers at this stage either measured a different system or made them up.
- The corpus was assembled from public legal reference sources. Migrating primary text to official publication sources is planned and is a precondition for any customer deployment, and we would rather say that here than in a procurement meeting.
- About 19.6% of source pages carry the source's own machine-generated summaries. They are captured, flagged, and excluded from the citable set by default — which is correct for a product whose whole promise is never to invent law, and which does shrink what can be quoted.
- Only 8.3% of the article-to-decision edges are grounded in a holding rather than a page-level link. They are usable as a ranking feature and are never auto-included as results.
- Point-in-time statute text returns nothing rather than today's text when the version history is known to be incomplete. That is deliberate, and it means the feature is silent more often than a less careful implementation would be.
- No air-gapped or on-premise deployment of this platform has been delivered. The architecture would support it for everything except the generation model; that is a design property, not a reference.