test(financials-v2): cover statement parsing and aggregation internals

This commit is contained in:
2026-03-02 09:34:51 -05:00
parent 3424a8e598
commit cf6f26fa15
2 changed files with 271 additions and 0 deletions

View File

@@ -1,6 +1,8 @@
import { describe, expect, it, mock } from 'bun:test';
import {
__statementInternals,
fetchFilingMetricsForFilings,
hydrateFilingStatementSnapshot,
fetchPrimaryFilingText,
normalizeSecDocumentText,
resolvePrimaryFilingUrl,
@@ -190,3 +192,133 @@ describe('sec filing text helpers', () => {
}
});
});
describe('statement snapshot parsing', () => {
it('parses FilingSummary reports and statement rows with order/depth/subtotals', () => {
const reports = __statementInternals.parseFilingSummaryReports(`
<FilingSummary>
<Report>
<ShortName>Statements of Operations</ShortName>
<LongName>Consolidated Statements of Operations</LongName>
<HtmlFileName>income.htm</HtmlFileName>
</Report>
</FilingSummary>
`);
expect(reports.length).toBe(1);
expect(reports[0]?.htmlFileName).toBe('income.htm');
const rows = __statementInternals.parseStatementRowsFromReport(`
<html>
<table>
<tr>
<td style="padding-left: 0px"><a id="defref_us-gaap_Revenues">Revenue</a></td>
<td>$120,000</td>
</tr>
<tr>
<td style="padding-left: 24px"><a id="defref_us-gaap_CostOfRevenue">Cost of Revenue</a></td>
<td>(50,000)</td>
</tr>
<tr>
<td style="padding-left: 0px">Total Net Income</td>
<td>25,000</td>
</tr>
</table>
</html>
`);
expect(rows.length).toBe(3);
expect(rows[0]?.label).toBe('Revenue');
expect(rows[0]?.order).toBe(1);
expect(rows[1]?.depth).toBe(2);
expect(rows[1]?.value).toBe(-50_000);
expect(rows[2]?.isSubtotal).toBe(true);
});
it('extracts dimensional facts from inline XBRL contexts', () => {
const dimensions = __statementInternals.parseDimensionFacts(`
<xbrli:context id="ctx_seg">
<xbrli:period><xbrli:endDate>2025-12-31</xbrli:endDate></xbrli:period>
<xbrli:scenario>
<xbrldi:explicitMember dimension="srt:ProductOrServiceAxis">us-gaap:ProductMember</xbrldi:explicitMember>
</xbrli:scenario>
</xbrli:context>
<ix:nonFraction name="us-gaap:Revenues" contextRef="ctx_seg" unitRef="USD">50000</ix:nonFraction>
`, 'fallback-period');
expect(dimensions.income.length).toBe(1);
expect(dimensions.income[0]?.axis).toContain('ProductOrServiceAxis');
expect(dimensions.income[0]?.member).toContain('ProductMember');
expect(dimensions.income[0]?.periodId).toBe('2025-12-31');
});
it('hydrates a filing snapshot with partial status when only one statement is found', async () => {
const fetchImpl = mock(async (input: RequestInfo | URL, _init?: RequestInit) => {
const url = String(input);
if (url.endsWith('FilingSummary.xml')) {
return new Response(`
<FilingSummary>
<Report>
<ShortName>Statements of Operations</ShortName>
<LongName>Consolidated Statements of Operations</LongName>
<HtmlFileName>income.htm</HtmlFileName>
</Report>
</FilingSummary>
`, { status: 200 });
}
if (url.endsWith('income.htm')) {
return new Response(`
<html>
<table>
<tr>
<td><a id="defref_us-gaap_Revenues">Revenue</a></td>
<td>120000</td>
</tr>
<tr>
<td><a id="defref_us-gaap_NetIncomeLoss">Net Income</a></td>
<td>24000</td>
</tr>
</table>
</html>
`, { status: 200 });
}
return new Response(`
<xbrli:context id="ctx_seg">
<xbrli:period><xbrli:endDate>2025-12-31</xbrli:endDate></xbrli:period>
<xbrli:scenario>
<xbrldi:explicitMember dimension="srt:StatementBusinessSegmentsAxis">acme:EnterpriseMember</xbrldi:explicitMember>
</xbrli:scenario>
</xbrli:context>
<ix:nonFraction name="us-gaap:Revenues" contextRef="ctx_seg" unitRef="USD">120000</ix:nonFraction>
`, { status: 200 });
}) as unknown as typeof fetch;
const snapshot = await hydrateFilingStatementSnapshot({
filingId: 99,
ticker: 'MSFT',
cik: '0000789019',
accessionNumber: '0000789019-25-000001',
filingDate: '2025-12-31',
filingType: '10-K',
filingUrl: 'https://www.sec.gov/Archives/edgar/data/789019/000078901925000001/msft10k.htm',
primaryDocument: 'msft10k.htm',
metrics: {
revenue: 120_000,
netIncome: 24_000,
totalAssets: 450_000,
cash: 90_000,
debt: 110_000
}
}, {
fetchImpl
});
expect(snapshot.parse_status).toBe('partial');
expect(snapshot.statement_bundle?.statements.income.length).toBeGreaterThan(0);
expect(snapshot.standardized_bundle?.statements.income.find((row) => row.key === 'revenue')?.values).toBeDefined();
expect(snapshot.dimension_bundle?.statements.income.length).toBeGreaterThan(0);
});
});