SEC filings turned into clean markdown packs, cited financial queries, and evidence-linked filing diffs, one command at a time.
Public filings are the best primary source for public-company research. 10-Ks and 10-Qs carry the actual numbers, the actual risk factors, the actual management discussion.
They are also a nightmare for LLMs to parse and work with properly.
Why?
- The HTML is incredibly noisy. Presentational tags, inline styles, table gymnastics, page-break artifacts.
- XBRL (the taxonomy tagging layer) was designed for an older world. The machine-readable facts get tangled into the visible text and bloat every parse.
- Most tools that handle this well hand you an answer and hide the building blocks. You cannot diff a section, cite a specific line, or feed the cleaned prose into your own pipeline.
EdgarPack exists to compress a filing down to its substantive pieces and give them back to you as something you can actually work with. Clean section markdown. Deterministic artifacts. Every number cited to the exact accession, concept, and filing URL it came from, so an LLM or a human reviewer can always trace the claim back to primary source.
Measured against the latest NVDA, AAPL, and TSLA 10-Ks on 2026-04-20, cl100k tokens:
- Raw 10-K HTML is a median of ~595k tokens. The clean EdgarPack pack is a median of ~102k tokens. That is an 85.6% reduction, tight across the three filings (82.9% to 86.1%).
- The raw filings do not fit in any mainstream LLM context window. The clean packs fit in GPT-4 Turbo's 128k window with room for citations and instructions.
- At Claude 3.5 Sonnet input pricing ($3 / 1M tokens): raw costs about $1.79 per call, the pack costs about $0.31 per call. Same filing, ~5.8x cheaper to feed in.
- About a fifth of the win is iXBRL tag stripping. The rest comes from the semantic cleaning, markdown rendering, and polish passes together.
Full methodology, per-filing table, cost breakdown at two providers, and a section on where the win is smaller than you think in docs/BENCHMARKS.md. Committed raw artifacts (raw HTML, stripped HTML, clean markdown) under benchmarks/artifacts/ if you want to re-count anything yourself.
I built it because I do financial research daily and wanted three things the existing tools did not give me: clean section-level artifacts I could diff, deterministic output so downstream caches stay valid, and citations on every value that point back to the exact line in the exact filing. The last part is the one that really matters. If I pull an ARR figure out of a 10-K, I want the URL that took me there, not a promise that a model got it right.
If this is your first time using EdgarPack, read these in order:
docs/GETTING_STARTED.md: the first 15 minutes, from install to your first cited answer.docs/WORKFLOWS.md: practical research recipes for public companies, pre-IPO S-1 filers, cross-market comps, KPI discovery, and filing diffs.docs/QUERY.md: the metric, period, citation, JSON, and derived-value reference.docs/OBSERVATORY.md: how to use filing diffs, static HTML reports, and S-1 registration timelines.docs/learn/README.md: the code walk-through for engineers and agents once you want internals.
Hosted filing demo: https://samay58.github.io/edgarpack/.
If you installed EdgarPack from PyPI, run commands as edgarpack .... If you are working from this repo, run the same commands as uv run edgarpack .... The docs use edgarpack for readability, but the repo-local form is often what you want during development.
Run edgarpack home or bare edgarpack for the short command-line starting point.
The fastest useful first loop is:
edgarpack query NVDA revenue --period ltm
edgarpack build NVDA --form 10-K --with-chunks
edgarpack which NVDA
edgarpack comps NVDA AMD --metrics revenue,gross_margin --period ltm
edgarpack diff --ticker NVDA --form 10-K --format html --out ./reports/nvda-10k.htmlThat path shows the product's core promise: every useful number or finding should be traceable back to primary filing evidence.
Some features need optional dependency groups. These only matter when you are running from the repo with uv run or installing from source:
| Extra | Use it for |
|---|---|
china |
China Lens routing, HKEX/CNINFO support, China API helpers |
sse |
SSE / China A-share PDF building, Chinese section detection, PDF-to-markdown |
dev |
Tests and linting (pytest, ruff, mypy) |
vlm |
Anthropic/VLM fallbacks for harder extraction paths |
For normal SEC/NVDA commands, no extra is needed:
uv run edgarpack query NVDA revenue --period ltmFor XGIMI / China A-share commands, use both China extras:
uv run --extra china --extra sse edgarpack identify xgimi
uv run --extra china --extra sse edgarpack build-sse xgimi --latest-annual --with-chunksYou can also start from the 6-digit stock code directly:
uv run --extra china --extra sse edgarpack identify 688775
uv run --extra china --extra sse edgarpack build-sse 688775 --latest-annual --with-chunksFor English translation of a Chinese filing, set a DeepInfra key and use the translation flags explicitly:
export EDGARPACK_DEEPINFRA_KEY="di-..."
uv run --extra china --extra sse edgarpack translate-sse \
--pack packs/sse/688696/688696_2026-03-31 \
--model deepseek-ai/DeepSeek-V4-Flash \
--concurrency 5 \
--batch-size 25 \
--force--model is the DeepInfra model id. deepseek-ai/DeepSeek-V4-Flash is the
recommended default for full annual reports because it is faster and less
rate-limit-prone than Pro-tier models in long translation runs. --concurrency
sets how many DeepInfra requests can be in flight at once; lower it if the
provider rate-limits you. --batch-size controls how many translation units are
validated and cached before progress is printed. Smaller batches are easier to
resume; larger batches reduce overhead.
For tests, add the dev extra:
uv run --extra dev --extra china --extra sse python -m pytest -qUse this path when you want to test EdgarPack from the ground up, as if you had never run the repo on the machine before.
git clone https://github.com/samay58/edgarpack.git
cd edgarpack
# Python 3.11+ is required. Install uv if your shell does not have it yet.
python3 --version
uv --version || curl -LsSf https://astral.sh/uv/install.sh | sh
# SEC requires a real contact string for every live filing request.
export EDGARPACK_USER_AGENT="Your Name your.email@example.com"
# First CLI sanity check.
uv run edgarpack home
uv run edgarpack query NVDA revenue --period ltm --audit --show-links primary
# Build two real NVIDIA 10-K packs, then generate a readable filing diff.
uv run edgarpack build NVDA --form 10-K --last 2 --with-chunks
uv run edgarpack diff --ticker NVDA --form 10-K --format html \
--out ./reports/nvda-10k-smoke.html
# Generate the local static filing site from whatever packs you just built.
uv run edgarpack site --packs ./packs --out ./site
python3 -m http.server 8080 --directory siteIn a second terminal, confirm the local site is serving:
curl -I http://localhost:8080/For the repo quality gate, run the same wrapper agents use:
EDGARPACK_CACHE_DIR=/tmp/edgarpack-ground-up-cache scripts/symphony_quality_gate.shIf you touched web/ or are preparing a demo build, include the web gate too:
SYMPHONY_WEB=1 EDGARPACK_CACHE_DIR=/tmp/edgarpack-ground-up-cache \
scripts/symphony_quality_gate.shThe published GitHub Pages demo should also answer with HTTP/2 200:
curl -I -L https://samay58.github.io/edgarpack/A handful of commands cover most of the research loop. The commands below are the ones I reach for daily; the full user workflows are in docs/WORKFLOWS.md.
Query one metric from one company:
edgarpack query NVDA revenue,net_income --period ltm
# ticker, CIK, or company name all work:
edgarpack query "NVIDIA" revenue,net_income --period ltm
edgarpack query "apple inc" revenue --period lfyEach value carries a citation reference and a reproducible formula. Revenue for LTM is computed from the most recent 10-Q plus the last 10-K minus the prior-year 10-Q, and the output tells you which three filings it used.
Compare companies side by side:
edgarpack comps NVDA AMD INTC -m revenue,net_income,ebitda --period ltmA comps table with inline citations by default. Drop --citations off if you want the clean table for a screenshot.
Cross-market comparison (USD-normalized):
edgarpack compare NVDA BIDU BABA --metrics revenue,gross_margin --period lfycompare handles SEC + HKEX filers in one table, converts non-USD amounts via the bundled FX file, and leaves a footnote with the original reporting currency for each column.
It also emits citation markers and citation records so cross-market comparisons stay auditable instead of becoming uncited finance tables.
List the KPIs a company actually discloses:
edgarpack which FIG
edgarpack which "Figma"which walks every pack you have built for a company, pulls the qualitative metrics out of MD&A (paid customers, ARR, NRR, etc.), and shows a metric-by-period matrix so you know what you can query before asking. Run edgarpack build first for the filings you care about.
Build a filing pack:
edgarpack build NVDA --form 10-K
# CIK and company names work too:
edgarpack build 0001045810 --form 10-K
edgarpack build "NVIDIA" --form 10-KOne full-filing markdown file, one file per detected section, a manifest with hashes and offsets, optional chunk and XBRL artifacts. The output runs through a polish pass that strips TOC page-break spam, recovers bullet lists trapped in tables, normalizes heading levels, and simplifies wide financial tables into a readable blockquote format. Deterministic. Rebuild produces the same bytes.
Review what changed between filings:
edgarpack diff --ticker NVDA --form 10-K
edgarpack diff --ticker NVDA --form 10-K --format html --out ./reports/nvda-10k.htmlThe text output is fast triage. The HTML report is the thing to open when you want to actually read the changed paragraphs: old text, new text, collapsed context, section rail, SEC links, and local pack links.
Work with pre-IPO S-1 filers:
edgarpack build "Cerebras Systems" --form S-1 --last 2
edgarpack query "Cerebras Systems" revenue,gross_profit,net_income,operating_cash_flow,capex,free_cash_flow --period lfy,lfy-1
edgarpack timeline --series registration --cik 0002021728 --packs ./packs --format html --out ./reports/cerebras-s1S-1 filers usually do not have SEC companyfacts yet. EdgarPack reads the built registration packs instead, extracts selected/summary financial data when the table shape is supported, computes simple S-1-derived metrics like free cash flow, and gives you a registration-timeline redline for the filing chain. capital_expenditures works as an alias for capex.
pip install edgarpack
# or editable for local dev
uv pip install -e ".[dev]"SEC requires a User-Agent on every request in the format Name email@example.com. Set it before running anything:
export EDGARPACK_USER_AGENT="Your Name your.email@example.com"If you want LLM-assisted extraction for table shapes EdgarPack cannot parse deterministically, install the VLM extra and export an Anthropic key:
uv pip install -e ".[dev,china,sse,vlm]"
export ANTHROPIC_API_KEY="sk-ant-..."Optional cache location:
export EDGARPACK_CACHE_DIR="$HOME/.edgarpack/cache"If EDGARPACK_USER_AGENT is missing, the first network call fails with an actionable error. Requests are paced at 5 per second by default, cached on disk, and the pack builder fetches the primary filing document instead of every exhibit HTML. For shared CI runners, set EDGARPACK_SEC_RATE_LIMIT and EDGARPACK_SEC_MAX_RETRIES to tune the SEC fetch lane. If SEC returns its traffic-limit page, wait 10 minutes before retrying.
Each filing gets a title line at the top of filing.full.md (# Company Name | Form Type | Filed YYYY-MM-DD) followed by the polished markdown. Sections are split into individual files under sections/.
packs/
└── 0001045810/
└── 0001045810-25-000001/
├── filing.full.md
├── llms.txt
├── manifest.json
├── sections/
│ ├── 10k_parti_item1_business.md
│ ├── 10k_parti_item1a_risk_factors.md
│ └── ...
└── optional/
├── chunks.ndjson
└── xbrl.json
lfy,lfy-N: last fiscal year (and N years back)mrq,mrq-N: most recent quarter (standalone 3-month);mrq-Nreturns the same fiscal quarter N years backmrp: most recent reported periodltm,ltm-N: trailing twelve months (and N years back;ltm-1is the prior year's TTM window)annual:N: last N fiscal yearsquarterly:N: last N quarters
Pass a CSV to --period on query to render a metrics x periods grid:
edgarpack query NVDA revenue,net_income,gross_margin --period lfy,lfy-1,lfy-2
edgarpack query NVDA --preset perf --period ltm,ltm-1,ltm-2--preset perf expands to a curated analyst panel
(revenue, revenue_growth_yoy, revenue_cagr_3y, margins, r_and_d_intensity, sga_intensity, fcf_margin).
Columns follow the --period order exactly, so put newest first if you want
the newest period on the LEFT.
Full query model, JSON formats, derived metric catalog (including CAGR),
and citation semantics in docs/QUERY.md.
# Build & browse (all accept ticker / CIK / company name; paginate into full history)
edgarpack build NVDA --form 10-K # build latest 10-K
edgarpack build META --accession 0001326801-19-000009 # or any historical accession
edgarpack list "NVIDIA" --form 10-K --limit 10 # form-filtered, full history
edgarpack company-llms AAPL --out ./packs # llms.txt index
edgarpack site --packs ./packs --out ./site # static site generator
edgarpack which FIG # MD&A KPIs a company discloses
# Query & compare
edgarpack identify xgimi # SEC, HKEX, SSE/A-share, private, or unknown
edgarpack identify laifen # private/unknown checks do not fall into SEC
edgarpack query NVDA revenue,net_income --period ltm # single company, cited values
edgarpack comps NVDA AMD INTC -m revenue,ebitda --period ltm # SEC-to-SEC comps table
edgarpack compare NVDA BIDU BABA -m revenue --currency usd # cross-market (SEC + HKEX), USD-normalized
# Bulk harvest & search
edgarpack harvest --universe universe.toml --refresh # bulk-download from a spec file
edgarpack index --packs ./packs --incremental # build the search index
edgarpack search "export controls" --topic risk:export # full-text search across packs
# Observatory
edgarpack diff --ticker NVDA --form 10-K # compare latest two filings
edgarpack diff --ticker NVDA --form 10-K --format html \
--out ./reports/nvda-10k.html # static local HTML report
edgarpack timeline --ticker "NVIDIA" \
--section 10k_parti_item1a_risk_factors # section arc across filings
edgarpack timeline --series registration --cik 0002021728 \
--packs ./packs --format html --out ./reports/cerebras-s1 # S-1 amendment chain
# SSE / China A-share primary filings. Repo-local form:
# uv run --extra china --extra sse edgarpack build-sse xgimi --latest-annual --with-chunks
edgarpack build-sse xgimi --latest-annual --with-chunks # look up CNINFO URL/date
edgarpack build-sse --url <pdf-url> --stock-code 301536 \
--company "Unitree Robotics" --filing-date 2026-03-20 # Chinese pack
edgarpack build-sse ... --translate # writes filing.full.en.md; needs key
# Maintenance
edgarpack learned list # inspect self-heal concept mappings
edgarpack cache # cache stats or --clear
edgarpack api --port 8000 # China Lens API serverNeed the full metric surface? See docs/METRIC_DIRECTORY.md
for the generated query/comps/compare metric directory and
docs/METRIC_DIRECTORY.json for the structured version.
build --accession, list, and the downstream diff / timeline commands reach across a filer's full submission history, not just the recent window.
SEC splits high-volume filers across paginated submission files. For an active filer like META — thousands of Form 4 / 144 notices per year — the "recent" window can cap out in weeks, pushing older 10-Ks out of view. EdgarPack transparently follows the pagination chain when a match isn't in the recent window, so:
edgarpack build META --accession 0001326801-19-000009 # FY2018 10-K, filed 2019
edgarpack list META --form 10-K --limit 10 # all 10-Ks, not just recent
edgarpack timeline --ticker META \
--section 10k_parti_item1a_risk_factors # full risk-factor arc, IPO → todayall work the same way on a ten-year-old filing as on last week's. Older pagination files are immutable, so they're cached with a long TTL after first fetch — repeated historical lookups hit disk, not SEC.
The observatory answers "what actually changed?" across filings. Not byte-level diffs. Paragraph-level language diffs, with the noise stripped out so the signal is readable.
Running edgarpack diff on NVIDIA's FY2024 vs FY2025 10-K surfaces things like: the company changed its self-description from "full-stack computing infrastructure company" to "data center scale AI infrastructure company." The China export controls section was rewritten from a chronological narrative about specific chip restrictions to a blunt statement that they "are unable to create and deliver a competitive product" under current rules. The cryptocurrency risk factor was deleted entirely. None of these show up in the financial data. They show up in the prose, and the diff engine finds them.
What gets filtered out: table-of-contents links, date/fiscal-year rollovers, cross-reference sentences ("See Item 7 for discussion..."), financial statement tables, signature blocks. These all change mechanically every year and obscure the real changes. The diff engine detects and suppresses them so the output is the 15-20 sections that actually matter, not the 60+ sections that technically differ.
edgarpack diff --ticker NVDA --form 10-K --format full
edgarpack diff --ticker NVDA --form 10-K --format html --out ./reports/nvda-10k.htmlSee docs/OBSERVATORY.md for the full diff workflow, HTML report output, S-1 registration timelines, and API model.
A parallel pipeline for HKEX, CNINFO, and SSE filings. Same citation shape as the SEC path, different source formats.
When running from this repo with uv run, use --extra china --extra sse for XGIMI / China A-share commands. china supplies the China Lens support libraries; sse supplies the SSE PDF build and Chinese section tooling.
HKEX tickers (0700.HK, BIDU, BABA, 9988.HK) are first-class in query, which, and compare: when the resolver routes a company to HKEX, queries read from the pack's facts.json instead of SEC companyfacts. Query/which/compare accept --currency native|usd|both; USD-normalized output keeps the native value, FX rate, convention, and source citation visible. The extractor supports prospectus and annual-report section shapes, with regex pattern matching against financial sections and a Claude API fallback for tagged-but-unmatched metrics.
SSE / China A-share filings are built via edgarpack build-sse. Use edgarpack identify <company> first if you are not sure how the company routes, then edgarpack build-sse <company-or-code> --latest-annual to select the latest CNINFO annual report without manually hunting for the PDF URL. The builder converts the PDF via pymupdf4llm, detects CSRC annual/prospectus sections using Chinese numerals (第一节, 第二节, ...), and produces the same manifest.json + sections/*.md + llms.txt pack layout as SEC filings. Add --translate (requires EDGARPACK_DEEPINFRA_KEY) to run the zh->en translation pipeline; successful runs write sections/*.en.md, filing.full.en.md, and manifest translation metadata with provider/model, failed sections, full-filing status, and validation warning/error counts. Translation uses a provider/model-scoped cache, validates each batch before saving it, and fails closed if a model drops protected literals, table structure, or Chinese-to-English completeness.
A FastAPI workspace (edgarpack api) exposes the Evidence Explorer on top of the same data. See docs/china-lens/IMPLEMENTATION_TRACKER.md for the current status and the HKEX section of docs/QUERY.md for query-layer details.
uv pip install -e ".[dev]"
ruff check .
ruff format --check .
uv run pytest tests/The parser and pack layout are versioned (PARSER_VERSION, SCHEMA_VERSION in edgarpack/config.py) so downstream caches know when to invalidate. PARSER_VERSION was bumped to 0.2.0 with the addition of the polish pass and structural rendering fixes. Tests include a determinism check that rebuilds a pack byte-for-byte. Changes to HTML cleaning, section detection, or chunking will usually require regenerating fixtures.
Network tests that hit real SEC endpoints are gated on EDGARPACK_USER_AGENT being set. See docs/TESTING.md for the offline and live lanes.
