Skip to content

fix(bdc): num_nonaccrual is None without itemized evidence; MCP bdc_portfolio names the borrower - #1395

Merged
dgunning merged 1 commit into
mainfrom
fix/bdc-nonaccrual-evidence-and-mcp-names
Oct 1, 2026
Merged

dgunning merged 1 commit into
mainfrom
fix/bdc-nonaccrual-evidence-and-mcp-names

Conversation

@dgunning

@dgunning dgunning commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Two BDC fixes salvaged from closed PR #1380 (beads edgartools-vblr, edgartools-rd23).

vblr: num_nonaccrual reported 0 when the filing didn't itemize

num_nonaccrual was len(investments), and only the footnote layer fills investments. A BDC that files only a portfolio-level non-accrual figure, or has no XBRL signal at all, showed 0. For example, WhiteHorse Finance's 2025 10-K files $10,561,000 of non-accrual fair value (whfcl:FinancingReceivablesRecordedInvestmentNonAccrualStatusFairValueDisclosure) with no per-investment footnote. Stellus (SCM) is the same.

  • New NonAccrualResult.evidence_level: 'investment' | 'aggregate' | 'none'. It's a property derived from the existing fields, so the constructor doesn't change.
  • num_nonaccrual is now Optional[int] and returns a count only at 'investment'. An aggregate of $0 is still not read as a zero count, because non-accrual loans are often marked to $0.
  • to_context() prints Evidence Level and says "not itemized" or "unknown" instead of 0.
  • No library code reads num_nonaccrual. The BDC guide gets a "Counting Non-Accrual Investments" section, and the funds skill YAML gets a non-accrual pattern.

rd23: MCP bdc_portfolio records named no borrower

The record builder checked hasattr(inv, 'name'), but PortfolioInvestment has company_name/identifier, so no record said whose loan it was. Records now always have the same keys: company name, identifier, type, industry, principal, cost, fair value, shares, rate, PIK, spread and % of net assets, with None for a missing figure. type is kept for existing consumers. Live check on MAIN: the first record is "MSC Adviser I, LLC", FV $255,020,000.

Verification

  • Ground truth (network, pinned by accession): WHF 0001104659-26-024735 gives aggregate, num_nonaccrual is None, and non-accrual FV $10,561,000 ±$1. MAIN 0001396440-26-000016 gives investment, 33 rows across 16 companies.
  • Offline: evidence-level tests and an MCP record test built from real MAIN rows. Each set fails against the old code.
  • Test marking: tests/bdc/test_bdc_nonaccrual.py matches no auto-mark pattern, so none of its existing unmarked tests run in test-fast. The new offline class is marked fast explicitly.
  • Existing tests: the non-accrual network tests and AFFIRMATIVE_PATTERNS fails to extract MAIN non-accruals due to footnote wording change #835's regression test pass (23).
  • test-fast: the only failure is test_dt1f1_wrapped_item_headers::test_2010_20f_…, which also fails on main at af2565d.

Out of scope, noted: MAIN's footnote rows give a 0.62% non-accrual rate, against the 1.0% stated in its 10-K text (a footnote coverage gap). MAIN's MCP records also have industry: null.

🤖 Generated with Claude Code

…ortfolio names the borrower (vblr, rd23)

vblr: NonAccrualResult.num_nonaccrual was len(self.investments), and only
the footnote layer fills investments. A filer with only a portfolio-level
figure (custom or us-gaap concept), or with no XBRL signal at all, showed
"Non-Accrual Investments: 0". WhiteHorse Finance's 2025 10-K
(0001104659-26-024735) files $10,561,000 of non-accrual fair value in
whfcl:FinancingReceivablesRecordedInvestmentNonAccrualStatusFairValueDisclosure
and no per-investment footnote, so it read as zero; so does Stellus (SCM).

New evidence_level property ('investment' | 'aggregate' | 'none'), derived
from investments and extraction_method so the frozen dataclass's
constructor is unchanged. num_nonaccrual is now Optional[int]: the count
only at 'investment'. An aggregate of zero is not treated as a zero count,
since non-accrual loans are often marked to $0. to_context() prints the
evidence level and "not itemized"/"unknown" in place of 0. No library code
read num_nonaccrual; the guide and skill YAML document the change.

rd23: _bdc_portfolio built each record with hasattr(inv, 'name'), but
PortfolioInvestment has company_name and identifier, so no record named its
borrower. _bdc_investment_record builds a fixed-key record from the real
fields (company_name, identifier, type, industry, principal, cost, fair
value, shares, rate, PIK, spread, % of net assets), None for a missing
figure. 'type' is kept for existing consumers.

Tests: offline evidence-level tests (explicitly @pytest.mark.fast: the
file matches no auto-mark pattern, so its unmarked tests never run in
test-fast); network ground truth pinned by accession for WHF (aggregate,
None, $10,561,000) and MAIN 0001396440-26-000016 (33 rows, 16 companies);
offline MCP test on real MAIN rows. Each set fails against the old code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dgunning
dgunning enabled auto-merge (squash) October 1, 2026 20:50
@dgunning
dgunning merged commit c571faa into main Oct 1, 2026
10 checks passed
@dgunning
dgunning deleted the fix/bdc-nonaccrual-evidence-and-mcp-names branch October 1, 2026 21:05
@dgunning dgunning mentioned this pull request Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant