/**
* Free SEC-only verification — no Anthropic, no agent layer.
*
* Calls the four SEC EDGAR functions directly against the live API for
* the six major U.S. banks in the demo and prints the actual FY-2024
* 10-K values for the revenue + operating-expense XBRL concepts.
*
* Use this to validate the SEC data path (User-Agent, rate limiting,
* CIK resolution, XBRL fact filtering) without spending a dollar on
* Anthropic. The full agent run (`npm run demo`) drives the same four
* tools through claude-sonnet-4-5 tool use.
*
* npm run sec:verify
*/
import {
secEdgarCompanies,
secFinancials,
secSubmissions,
type SecCompanyMatch,
} from '../src/tools/retrieval/connectors/sec-edgar.js';
interface FactRow {
readonly val: number;
readonly fy?: number;
readonly fp?: string;
readonly form?: string;
readonly end?: string;
readonly start?: string;
readonly accn?: string;
readonly frame?: string;
}
const BANKS: readonly string[] = [
'JPMorgan Chase',
'Bank of America',
'Goldman Sachs',
'Morgan Stanley',
'Wells Fargo',
'Citigroup',
];
/** Concepts banks commonly use to report top-line revenue. */
const REVENUE_CONCEPTS = [
'Revenues',
'RevenuesNetOfInterestExpense',
'InterestAndDividendIncomeOperating',
];
/** Concepts banks commonly use to report total operating expenses. */
const OPEX_CONCEPTS = [
'NoninterestExpense',
'OperatingExpenses',
'CostsAndExpenses',
];
const ALL_CONCEPTS = [...REVENUE_CONCEPTS, ...OPEX_CONCEPTS];
function pickFy2024Annual(rows: readonly FactRow[]): FactRow | null {
/* Prefer an annual 10-K row whose period covers FY2024
* (start≤2024-01-31 and end≥2024-12-01) to avoid quarterly rows. */
const annual = rows.filter(
r =>
r.form === '10-K' &&
r.fp === 'FY' &&
(r.fy === 2024 || (r.end ?? '').startsWith('2024')) &&
(r.end ?? '') >= '2024-12-01' &&
(r.start ?? '') <= '2024-01-31',
);
if (annual.length > 0) {
return annual.sort((a, b) => (b.accn ?? '').localeCompare(a.accn ?? ''))[0]!;
}
/* Fall back to any 10-K fy=2024 row. */
const any = rows.filter(r => r.form === '10-K' && r.fy === 2024);
return any.length > 0 ? any.sort((a, b) => (b.end ?? '').localeCompare(a.end ?? ''))[0]! : null;
}
function fmtUsd(v: number): string {
if (Math.abs(v) >= 1e9) return `$${(v / 1e9).toFixed(2)}B`;
if (Math.abs(v) >= 1e6) return `$${(v / 1e6).toFixed(1)}M`;
return `$${v.toLocaleString()}`;
}
async function processBank(searchTerm: string): Promise<void> {
console.log(`\n──── ${searchTerm} ────`);
const matches = await secEdgarCompanies(searchTerm);
if (matches.length === 0) {
console.log(` (no CIK match)`);
return;
}
const m = pickBest(matches, searchTerm);
console.log(` CIK: ${m.cik}`);
console.log(` Ticker: ${m.ticker}`);
console.log(` Name: ${m.name}`);
const subs = await secSubmissions(m.cik, '10-K');
const latestAnnual = subs.filings.find(
f => f.reportDate.startsWith('2024') || f.filingDate.startsWith('2025'),
);
if (latestAnnual) {
console.log(` Latest 10-K: ${latestAnnual.accessionNumber} filed=${latestAnnual.filingDate} reportDate=${latestAnnual.reportDate}`);
}
const facts = await secFinancials(m.cik, ALL_CONCEPTS.join(','));
const gaap = ((facts.facts ?? {})['us-gaap'] ?? {}) as Record<
string,
{ units?: Record<string, FactRow[]> } | undefined
>;
console.log(` Revenue candidates:`);
for (const concept of REVENUE_CONCEPTS) {
const usd = gaap[concept]?.units?.USD ?? [];
const hit = pickFy2024Annual(usd);
if (hit) {
console.log(` ${concept.padEnd(40)} ${fmtUsd(hit.val).padStart(10)} (period ${hit.start ?? '?'}→${hit.end ?? '?'}, accn ${hit.accn ?? '?'})`);
} else if (usd.length > 0) {
console.log(` ${concept.padEnd(40)} (no FY2024 annual row; ${usd.length} other rows)`);
} else {
console.log(` ${concept.padEnd(40)} (not reported)`);
}
}
console.log(` Operating-expense candidates:`);
for (const concept of OPEX_CONCEPTS) {
const usd = gaap[concept]?.units?.USD ?? [];
const hit = pickFy2024Annual(usd);
if (hit) {
console.log(` ${concept.padEnd(40)} ${fmtUsd(hit.val).padStart(10)} (period ${hit.start ?? '?'}→${hit.end ?? '?'}, accn ${hit.accn ?? '?'})`);
} else if (usd.length > 0) {
console.log(` ${concept.padEnd(40)} (no FY2024 annual row; ${usd.length} other rows)`);
} else {
console.log(` ${concept.padEnd(40)} (not reported)`);
}
}
}
function pickBest(matches: readonly SecCompanyMatch[], term: string): SecCompanyMatch {
const upper = term.toUpperCase();
const exact = matches.find(m => m.ticker === upper || m.name.toUpperCase() === upper);
if (exact) return exact;
const tickerByFirstWord: Record<string, string> = {
'JPMORGAN CHASE': 'JPM',
'BANK OF AMERICA': 'BAC',
'GOLDMAN SACHS': 'GS',
'MORGAN STANLEY': 'MS',
'WELLS FARGO': 'WFC',
'CITIGROUP': 'C',
};
const wantTicker = tickerByFirstWord[upper];
if (wantTicker) {
const hit = matches.find(m => m.ticker === wantTicker);
if (hit) return hit;
}
return matches[0]!;
}
async function main(): Promise<void> {
console.log('SEC EDGAR direct verification — six U.S. banks, FY-2024 annual (10-K).');
console.log('User-Agent: MR mitchell.roy@sia-partners.com');
console.log('Concepts:', ALL_CONCEPTS.join(', '));
const startedAt = Date.now();
for (const bank of BANKS) {
try {
await processBank(bank);
} catch (err) {
console.log(` ERROR: ${err instanceof Error ? err.message : String(err)}`);
}
}
const elapsedMs = Date.now() - startedAt;
console.log(`\nDone. Elapsed: ${(elapsedMs / 1000).toFixed(1)}s`);
}
main().catch(err => {
console.error('Unhandled error:', err);
process.exit(1);
});