fix(bdc): num_nonaccrual is None without itemized evidence; MCP bdc_portfolio names the borrower - #1395
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two BDC fixes salvaged from closed PR #1380 (beads edgartools-vblr, edgartools-rd23).
vblr:
num_nonaccrualreported 0 when the filing didn't itemizenum_nonaccrualwaslen(investments), and only the footnote layer fillsinvestments. 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.NonAccrualResult.evidence_level:'investment'|'aggregate'|'none'. It's a property derived from the existing fields, so the constructor doesn't change.num_nonaccrualis nowOptional[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()printsEvidence Leveland says "not itemized" or "unknown" instead of 0.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_portfoliorecords named no borrowerThe record builder checked
hasattr(inv, 'name'), butPortfolioInvestmenthascompany_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, withNonefor a missing figure.typeis kept for existing consumers. Live check on MAIN: the first record is "MSC Adviser I, LLC", FV $255,020,000.Verification
0001104659-26-024735givesaggregate,num_nonaccrual is None, and non-accrual FV $10,561,000 ±$1. MAIN0001396440-26-000016givesinvestment, 33 rows across 16 companies.tests/bdc/test_bdc_nonaccrual.pymatches no auto-mark pattern, so none of its existing unmarked tests run intest-fast. The new offline class is markedfastexplicitly.test-fast: the only failure istest_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