You're a credit analyst at a fund that reads BDC reports as public evidence about private loans. Each loan's status, such as "this borrower stopped paying interest", is a footnote mark explained pages away. BDC Footnote Tape turns any BDC's 10-K or 10-Q into a spreadsheet you can load, in seconds.
Every BDC (a stock-listed fund that lends to private companies) files its full loan list with the SEC each quarter. Today an analyst rebuilds it by hand, for every lender, every quarter. The vision: any report in, one checked row per loan out, with each status flag next to the text that explains it.
The loan list sits somewhere in a 6 to 60 MB report, often as two copies with different dates.
Each BDC prints its table differently: rows split over lines, tables continued across pages, amounts in thousands or millions.
Each loan's status is a footnote mark explained pages away from the loan. Ares Capital's 10-K has 12 marks across 1,409 holdings.
Nothing checks the extraction, so a gap reaches the data unnoticed.
| Your job | Hypothesis | Metric and target, then result | Expected impact |
|---|---|---|---|
| When a report is filed, I want the right loan list found and dated, so I start at the first loan. | If the loan list is found and dated from the report itself, you reach the first loan sooner, because no one searches a 6 to 60 MB document by hand. | Share of reports where the extracted date equals the EDGAR period: up. 4 of 4. Guardrail: previous-period leaks stay at 0 | Less time before analysis starts (unobserved) |
| When I need the loans, I want one row per holding in dollars, so I stop retyping. | If column headers are matched by keyword and units read from the page, most reports give a full spreadsheet with no hand edits, because layout differences become rules. | Share of reports within 1% of the printed total: up. 4 of 4; 1 extra report produced 0 rows | Hours per report saved (unobserved) |
| When I scan a lender's book, I want each status as a column, so I see non-accrual and PIK loans without the report. | If each mark is replaced by its meaning on the row, you find non-accrual and PIK loans without opening the report, because the status is a column, not a cross-reference. | Distinct marks explained: up. 76 of 76. Flag accuracy unmeasured | Borrowers in trouble seen sooner (unobserved) |
| When I load a file, I want to know it adds up, so I don't recheck every row. | If every report shows whether it adds up and which marks failed, your head of data loads passing files without rechecking rows, because failures are visible first. | Share of loaded reports later corrected by hand: down. Baseline unobserved. Planted errors caught: 2 of 2 | Fewer bad rows in the fund's data (unobserved) |
Demo runs on 2026-10-01, public SEC data. Matching the printed total proves the totals, not each row. No analyst or data lead has used it.
Golub Capital BDC's quarterly report: 1,812 holdings, 31 footnote marks, one spreadsheet in 12 seconds.
The weak link is belief, for both of you. For you: label non-accrual rows by hand on 10 reports, against each BDC's own list, to turn "keyword match" into a measured accuracy. For your head of data: run the demo over the 1,805 reports already downloaded and publish the pass rate per BDC. No new code.
Real reports are downloaded live from SEC EDGAR and labelled public SEC data. A made-up offline sample is included. It runs on fixed rules: no language model is called. Limits: 25 MiB per filing, 10 filings per visit.
Demo runs, 2026-10-01, empty cache, local Mac, Python 3.11. Source: demo/observed_runs.txt (output of demo/run_live.py). Public SEC data.
| Report | Holdings | Fair value vs printed total | Marks explained | Rows flagged as possible non-accrual | Time |
|---|---|---|---|---|---|
| Ares Capital 10-K, 2025-12-31, 24.5 MB | 1,409 | -0.48% (pass) | 12 of 12 | 19 | 11.5 s |
| Main Street Capital 10-Q, 2026-06-30, 13.4 MB | 619 | 0.00% (pass) | 21 of 21 | 33 | 7.7 s |
| Golub Capital BDC 10-Q, 2026-06-30, 24.6 MB | 1,812 | 0.00% (pass) | 31 of 31 | 45 | 12.0 s |
| BlackRock TCP Capital 10-Q, 2026-06-30, 18.9 MB | 319 | 0.00% (pass) | 12 of 12 | 0 | 7.0 s |
demo/test_demo.py: 11 passed, 1 skipped offline; 12 passed with the live SEC test on. The Streamlit AppTest run shows no error.output/bdc_footnotes_full_pipeline.csv (local, not in git).output/logs/qa_run_v15.log passed 25 times and failed 50 times.demo/vendor_parallax/section.py find_sections, demo/soi_parse.py choose_section.demo/vendor_parallax/tables.py find_header, classify_rows; demo/soi_parse.py. Partial: a layout with no company-name column produces 0 rows.demo/footnotes.py calling the repo's parsers/footnote_extractor.py; mark lookup in demo/pipeline.py step 6.demo/pipeline.py step 7, which compares totals with the report's own total row and lists unexplained marks.qa_reviewer/non_accrual_confirmer.py). The demo leaves it off.This build tackles 1 to 4.
| Metric | Type | Tests hypothesis | How measured |
|---|---|---|---|
| Extracted date equals EDGAR period | Early signal | 1 | Quality-check field per run, 50-report sample |
| Fair value within 1% of the printed total | Early signal | 2 | fair_value_reconciles per run |
| Distinct marks explained | Early signal | 3 | markers_resolved / markers_distinct |
| Non-accrual flag accuracy | Later result | 3 | Hand labels on 10 reports |
| Reports corrected by hand after loading | Later result | 4 | Correction log kept by the data team |
| Analyst time from report to first loan row | Later result | 1, 2 | Timed session, by hand vs demo, same 5 reports |
| In scope | Non-goal (including sub-problems not tackled) |
|---|---|
| Any BDC 10-K or 10-Q main document up to 25 MiB | Sub-problem 2, partly: layouts with no company-name column (0 rows observed on one BDC). Symbol marks (*, ^, #) are not attached to rows |
| One dated loan list per run (EDGAR period, else latest) | Sub-problem 5, confirm: tags are keyword matches. The repo's Gemini checker isn't connected, so there's no confirmed non-accrual list |
| Footnote text, keyword tags, quality checks, CSV/JSON/data dictionary | Sub-problem 6, match names: no matching of borrowers across lenders (the sibling BDC Mark Tape does this) |
| Fixed rules only, zero AI model cost | Sub-problem 7, track change: no history from one quarter to the next |
| Investment advice, currency conversion of non-dollar amounts owed, reports above 25 MiB |
| Chose | Over | Gain | Cost |
|---|---|---|---|
| fixed rules only | the repo's Gemini reviewer, paid per use | zero model cost, same output every run | non-accrual tags stay unconfirmed |
copy the Parallax table reader into demo/ | import from Parallax | one folder to deploy | two copies that can drift apart |
| repo footnote extractor as is | rewrite it on lxml | reuses existing code that has already been run and tested | about 70% of run time, a second copy of the document in memory |
| 25 MiB cap | 35 MiB | observed peak fits the Cloud memory minimum | 2.5% of reports on disk refused |
| "(A)" marks only when footnotes use upper-case letters | always | no false marks from names like "Co-Investment (E)" | a BDC mixing cases loses upper-case marks |
| 1% tolerance when matching totals | exact match | rounding in thousands doesn't fail a file | a -0.48% gap passes unexplained |
Sibling: BDC Mark Tape. It compares how different BDCs value the same borrower. How they fit: this spreadsheet shows which loans a lender flags; the Mark Tape shows where lenders disagree on a loan's value. Together they point an analyst at the borrowers to review first.
| Stage | Artifact | Repeatable with |
|---|---|---|
| PRD | WORKFLOW_DIAGRAM.md: pipeline spec with success criteria per stage | /prd-draft |
| Build | Fixed rules first, with an AI reviewer as backup; demo/ | /code-first-draft |
| Evaluation | Quality-check logs (Gemini completeness check: 25 pass, 50 fail); demo/observed_runs.txt on 4 BDCs | /feature-results |
| Showcase | This page, name audit, goals and state files | /portfolio-case-study, /name-audit, /goal-gate |
Skills are mapped from the artifact each stage produced. They show how each stage repeats with PM OS; the original working sessions were not logged.
/code-first-draft /feature-results /goal-gate /name-audit /portfolio-case-study /prd-draft