all projects

2026

FilingsIQ — Verifiable RAG over SEC Filings

A RAG system for SEC 10-K filings where every number is cited to a source chunk, every derived number is recomputed and checked, and the system refuses rather than guesses when the corpus doesn't support an answer.

  • RAG
  • LLM Verification
  • SEC EDGAR
  • Local LLM
  • FastAPI
View source

Problem

LLMs answer financial questions fluently and sometimes wrongly — a fabricated revenue figure stated with total confidence is worse than no answer at all. Grounding every claim in a source, and actually checking derived numbers rather than trusting the model's arithmetic, is what turns a fluent guesser into something a financial analyst could rely on.

Approach

Real 10-K filings are pulled from EDGAR and parsed with table-atomic extraction, so a balance sheet round-trips into a clean markdown grid with row labels and fiscal-year columns intact rather than dissolving into unstructured text.

Retrieval is hybrid (SQLite + BM25) with hard metadata filtering applied before retrieval, not after — including fiscal-year-end edge cases, like resolving a bare 'in 2024' for NVIDIA (fiscal year ends late January) to FY2025, the label that actually covers most of calendar 2024. Generation and embeddings both run locally — Qwen2.5-3B-Instruct and BAAI/bge-small-en-v1.5 — with no API key required.

The verifier is the core of the system: every derived claim (growth rate, margin, delta) is independently recomputed in plain Python and checked against what the model claimed. A claim that fails verification gets exactly one regeneration attempt with the failure as feedback; if it still fails, the system refuses rather than looping.

Results

Verified live against real filings (Apple, Microsoft, Nvidia), not synthetic fixtures. The verifier is proven correct on four cases: a correct claim passes, a fabricated number is caught by the provenance check, a correct derived arithmetic claim (YoY growth) passes, and a hallucinated arithmetic claim is caught by recomputation. An unrecognised formula name fails closed rather than being trusted.

The company/fiscal-year metadata filter is a SQL WHERE clause, so retrieval precision holds as the corpus scales to more companies — cross-company questions without a named company are the harder case that gets worse as the pool grows, which is documented rather than glossed over.

What I learned

A verifier that recomputes numbers instead of trusting the model's arithmetic catches exactly the failure mode that matters in finance. Refusing to answer when the corpus doesn't support a claim is a feature worth designing for, not an edge case to patch over later.