feat(financials-v2): hydrate filing statements and aggregate history

This commit is contained in:
2026-03-02 09:33:58 -05:00
parent bcf4c69c92
commit 3f3182310b
4 changed files with 1532 additions and 3 deletions

View File

@@ -10,9 +10,14 @@ import { buildPortfolioSummary } from '@/lib/server/portfolio';
import { getQuote } from '@/lib/server/prices';
import {
getFilingByAccession,
listFilingsRecords,
saveFilingAnalysis,
upsertFilingsRecords
} from '@/lib/server/repos/filings';
import {
getFilingStatementSnapshotByFilingId,
upsertFilingStatementSnapshot
} from '@/lib/server/repos/filing-statements';
import {
applyRefreshedPrices,
listHoldingsForPriceRefresh,
@@ -22,7 +27,8 @@ import { createPortfolioInsight } from '@/lib/server/repos/insights';
import {
fetchFilingMetricsForFilings,
fetchPrimaryFilingText,
fetchRecentFilings
fetchRecentFilings,
hydrateFilingStatementSnapshot
} from '@/lib/server/sec';
const EXTRACTION_REQUIRED_KEYS = [
@@ -40,6 +46,8 @@ const EXTRACTION_REQUIRED_KEYS = [
const EXTRACTION_MAX_ITEMS = 6;
const EXTRACTION_ITEM_MAX_LENGTH = 280;
const EXTRACTION_SUMMARY_MAX_LENGTH = 900;
const STATEMENT_HYDRATION_DELAY_MS = 120;
const STATEMENT_HYDRATION_MAX_FILINGS = 80;
const SEGMENT_PATTERNS = [
/\boperating segment\b/i,
/\bsegment revenue\b/i,
@@ -556,11 +564,67 @@ async function processSyncFilings(task: Task) {
}))
);
let statementSnapshotsHydrated = 0;
let statementSnapshotsFailed = 0;
const hydrateCandidates = (await listFilingsRecords({
ticker,
limit: Math.min(Math.max(limit * 3, 40), STATEMENT_HYDRATION_MAX_FILINGS)
}))
.filter((filing): filing is Filing & { filing_type: '10-K' | '10-Q' } => {
return filing.filing_type === '10-K' || filing.filing_type === '10-Q';
});
for (const filing of hydrateCandidates) {
const existingSnapshot = await getFilingStatementSnapshotByFilingId(filing.id);
const shouldRefresh = !existingSnapshot
|| Date.parse(existingSnapshot.updated_at) < Date.parse(filing.updated_at);
if (!shouldRefresh) {
continue;
}
try {
const snapshot = await hydrateFilingStatementSnapshot({
filingId: filing.id,
ticker: filing.ticker,
cik: filing.cik,
accessionNumber: filing.accession_number,
filingDate: filing.filing_date,
filingType: filing.filing_type,
filingUrl: filing.filing_url,
primaryDocument: filing.primary_document ?? null,
metrics: filing.metrics
});
await upsertFilingStatementSnapshot(snapshot);
statementSnapshotsHydrated += 1;
} catch (error) {
await upsertFilingStatementSnapshot({
filing_id: filing.id,
ticker: filing.ticker,
filing_date: filing.filing_date,
filing_type: filing.filing_type,
period_end: filing.filing_date,
statement_bundle: null,
standardized_bundle: null,
dimension_bundle: null,
parse_status: 'failed',
parse_error: error instanceof Error ? error.message : 'Statement hydration failed',
source: 'companyfacts_fallback'
});
statementSnapshotsFailed += 1;
}
await Bun.sleep(STATEMENT_HYDRATION_DELAY_MS);
}
return {
ticker,
fetched: filings.length,
inserted: saveResult.inserted,
updated: saveResult.updated
updated: saveResult.updated,
statementSnapshotsHydrated,
statementSnapshotsFailed
};
}