.?! boundaries first, then accumulates complete sentences up to 800 chars.
The 100-char overlap means a sentence at the boundary of two chunks appears in both — retrieval never misses a provision because of where the chunk boundary fell.
| Why not larger chunks? Larger = more diluted embeddings — mixed topics in one vector score lower on any specific query.
instructor for guaranteed JSON output. If LLM call fails entirely → heuristic fallback scores sentences by legal-term density, picks top 8. You always get something to search with.
asyncio.gather instead of create_task for simplicity. Chose create_task so extraction streams to browser immediately without waiting for Pinecone cold-start latency.
work_injury_benefits_act_cap236.txt).
WIBA s.16 says: "No action shall lie by an employee against an employer for damages for personal injury." This section bars all common-law tort claims against employers — missing it means advising a client to sue when the claim will be struck out.
The problem: A lawyer's case uses vocabulary like "employee was injured · employer was negligent · seeking compensation." WIBA s.16 uses exclusion language: "no action shall lie." These phrases embed in completely different regions of the vector space. Even with WIBA scoping and 7 expanded queries, s.16 consistently scored 0.38 — below the 0.45 threshold.
Diagnosis path: symptom (s.16 missing from output) → checked ingestion (s.16 was indexed, confirmed by direct Pinecone fetch) → checked scores (s.16 scored 0.38) → root cause: embedding space mismatch between case vocabulary and statutory exclusion vocabulary.
Fix — Guarantee queries:
_STATUTE_GUARANTEE_QUERIES = {"work_injury_benefits_act_cap236.txt": ["no action employee employer tort damages personal injury Kenya"]}
Whenever WIBA is applicable, this query fires using vocabulary close to the actual statutory text.
The judge then decides whether s.16 is relevant to this specific case. The guarantee query ensures s.16 is always a candidate — it does not force inclusion.
Why not just lower the threshold to 0.30? You'd include every vaguely employment-related chunk. The guarantee query solves a specific semantic gap without degrading quality across all searches.
| Decision | What the naive approach gets wrong | Why this solution is better |
|---|---|---|
| Sentence-aware chunking | Fixed-size splits cut statutory sentences mid-phrase → broken embeddings, incomplete reasoning | No chunk ever breaks a sentence. 100-char overlap prevents missing provisions at boundaries. |
| Five-filter quality gate | TOC pages and publisher headers embed as "legal" — strategy agent reads a contents page instead of statute text | Only substantive provisions enter the index. Two-condition interpretation filter avoids false positives. |
| Delete-before-upsert | Re-running ingestion without deleting creates duplicate vectors that grow unboundedly | Idempotent — run 50 times, always end up with exactly one copy per chunk. |
| Query expansion (7 queries) | One embedding of 500 words is mediocre for every specific legal issue in a multi-issue case | 7 focused queries each target a distinct legal question → up to 35 sharp candidates vs. 5 diluted ones. |
| Statute metadata filtering | Generic searches pull wrong-branch provisions (Penal Code in a civil case) scoring well on shared vocabulary | Only statute-specific keyword phrases are mapped — generic terms like "negligence" span Acts legitimately and are not filtered. |
| Dual score thresholds (0.60 / 0.45) | Applying 0.60 to source-filtered searches drops genuinely relevant chunks (smaller corpus = naturally lower scores) | 0.45 for filtered searches was set empirically by testing WIBA retrieval — prevents false negatives on targeted lookups. |
| Guarantee queries | Exclusion-language provisions (WIBA s.16 "no action shall lie") embed far from compensation queries — missed by all 7 expanded queries | Hard-coded queries using statutory vocabulary ensure these provisions are always candidates for the judge, without lowering global thresholds. |
| Fuzzy dedup at 0.85 / 90% | Lower thresholds collapse distinct provisions that share legal boilerplate ("employer", "court", "person") | 0.85 / 90% means genuinely near-identical text, not just provisions from the same area of law. |
| Explicit judge negatives | Without explicit 0–4 examples, judge approved definition sections, wrong-branch provisions, and surface keyword matches | Explicit negative categories (each from a real test-case failure) teach the judge what "wrong" looks like, not just what "right" looks like. |
| Compression fallback = full chunk | Empty string from compressor was treated as "no relevant content → drop chunk" — lost judge-approved provisions silently | If compressor returns "", fall back to full chunk. Judge is gatekeeper for relevance; compressor only trims. |