import type { CompanyFinancialStatementsResponse, DetailFinancialRow, FinancialCadence, FinancialDisplayMode, FinancialStatementKind, FinancialStatementPeriod, FinancialSurfaceKind, NormalizationMetadata, StandardizedFinancialRow, StructuredKpiRow, SurfaceDetailMap, SurfaceFinancialRow, TaxonomyFactRow, TaxonomyStatementRow } from '@/lib/types'; import { listFilingsRecords } from '@/lib/server/repos/filings'; import { countFilingTaxonomySnapshotStatuses, listFilingTaxonomySnapshotsByTicker, listTaxonomyFactsByTicker, type FilingTaxonomySnapshotRecord } from '@/lib/server/repos/filing-taxonomy'; import { buildLtmFaithfulRows, buildLtmPeriods, buildRows, isStatementSurface, periodSorter, selectPrimaryPeriodsByCadence, surfaceToStatementKind } from '@/lib/server/financials/cadence'; import { readCachedFinancialBundle, writeFinancialBundle } from '@/lib/server/financials/bundles'; import { buildDimensionBreakdown, buildLtmStandardizedRows, buildStandardizedRows } from '@/lib/server/financials/standardize'; import { buildRatioRows } from '@/lib/server/financials/ratios'; import { buildFinancialCategories, buildTrendSeries } from '@/lib/server/financials/trend-series'; import { getHistoricalClosingPrices } from '@/lib/server/prices'; import { resolveKpiDefinitions } from '@/lib/server/financials/kpi-registry'; import { extractStructuredKpisFromDimensions } from '@/lib/server/financials/kpi-dimensions'; import { extractStructuredKpisFromNotes } from '@/lib/server/financials/kpi-notes'; type DimensionBreakdownMap = Record[string]>; type GetCompanyFinancialsInput = { ticker: string; surfaceKind: FinancialSurfaceKind; cadence: FinancialCadence; includeDimensions: boolean; includeFacts: boolean; factsCursor?: string | null; factsLimit?: number; cursor?: string | null; limit?: number; queuedSync: boolean; v3Enabled: boolean; }; type StandardizedStatementBundlePayload = { rows: SurfaceFinancialRow[]; detailRows: SurfaceDetailMap; trendSeries: CompanyFinancialStatementsResponse['trendSeries']; categories: CompanyFinancialStatementsResponse['categories']; normalization: NormalizationMetadata; }; type FilingDocumentRef = { filingId: number; cik: string; accessionNumber: string; filingUrl: string | null; primaryDocument: string | null; }; function safeTicker(input: string) { return input.trim().toUpperCase(); } function isFinancialForm(type: string): type is '10-K' | '10-Q' { return type === '10-K' || type === '10-Q'; } function cadenceFilingTypes(cadence: FinancialCadence) { return cadence === 'annual' ? ['10-K'] as Array<'10-K' | '10-Q'> : ['10-Q'] as Array<'10-K' | '10-Q'>; } function latestMetrics(snapshots: FilingTaxonomySnapshotRecord[]) { for (const snapshot of snapshots) { if (snapshot.derived_metrics) { return { taxonomy: snapshot.derived_metrics, validation: snapshot.validation_result }; } } return { taxonomy: null, validation: null }; } function defaultDisplayModes(surfaceKind: FinancialSurfaceKind): FinancialDisplayMode[] { return isStatementSurface(surfaceKind) ? ['standardized', 'faithful'] : ['standardized']; } function rekeyRowsByFilingId; resolvedSourceRowKeys?: Record; }>(rows: T[], sourcePeriods: FinancialStatementPeriod[], targetPeriods: FinancialStatementPeriod[]) { const targetPeriodByFilingId = new Map(targetPeriods.map((period) => [period.filingId, period])); return rows.map((row) => { const nextValues: Record = {}; const nextResolvedSourceRowKeys: Record = {}; for (const sourcePeriod of sourcePeriods) { const targetPeriod = targetPeriodByFilingId.get(sourcePeriod.filingId); if (!targetPeriod) { continue; } nextValues[targetPeriod.id] = row.values[sourcePeriod.id] ?? null; if (row.resolvedSourceRowKeys) { nextResolvedSourceRowKeys[targetPeriod.id] = row.resolvedSourceRowKeys[sourcePeriod.id] ?? null; } } return { ...row, values: nextValues, ...(row.resolvedSourceRowKeys ? { resolvedSourceRowKeys: nextResolvedSourceRowKeys } : {}) }; }); } function mergeDimensionBreakdownMaps(...maps: Array) { const merged = new Map[string]>(); for (const map of maps) { if (!map) { continue; } for (const [key, rows] of Object.entries(map)) { const existing = merged.get(key); if (existing) { existing.push(...rows); } else { merged.set(key, [...rows]); } } } return merged.size > 0 ? Object.fromEntries(merged.entries()) : null; } function buildKpiDimensionBreakdown(input: { rows: StructuredKpiRow[]; periods: FinancialStatementPeriod[]; facts: TaxonomyFactRow[]; }) { const map = new Map[string]>(); for (const row of input.rows) { if (row.provenanceType !== 'taxonomy') { continue; } const matchedFacts = input.facts.filter((fact) => row.sourceFactIds.includes(fact.id)); if (matchedFacts.length === 0) { continue; } map.set(row.key, matchedFacts.flatMap((fact) => { const matchedPeriod = input.periods.find((period) => period.filingId === fact.filingId); if (!matchedPeriod) { return []; } return fact.dimensions.map((dimension) => ({ rowKey: row.key, concept: fact.qname, sourceRowKey: fact.conceptKey, sourceLabel: row.label, periodId: matchedPeriod.id, axis: dimension.axis, member: dimension.member, value: fact.value, unit: fact.unit, provenanceType: 'taxonomy' as const })); })); } return map.size > 0 ? Object.fromEntries(map.entries()) : null; } function latestPeriodDate(period: FinancialStatementPeriod) { return period.periodEnd ?? period.filingDate; } function cloneStructuredKpiRow(row: StructuredKpiRow): StructuredKpiRow { return { ...row, values: { ...row.values }, sourceConcepts: [...row.sourceConcepts], sourceFactIds: [...row.sourceFactIds] }; } function mergeStructuredKpiRowsByPriority(groups: StructuredKpiRow[][]) { const rowsByKey = new Map(); for (const group of groups) { for (const row of group) { const existing = rowsByKey.get(row.key); if (!existing) { rowsByKey.set(row.key, cloneStructuredKpiRow(row)); continue; } for (const [periodId, value] of Object.entries(row.values)) { const hasExistingValue = Object.prototype.hasOwnProperty.call(existing.values, periodId) && existing.values[periodId] !== null; if (!hasExistingValue) { existing.values[periodId] = value; } } existing.sourceConcepts = [...new Set([...existing.sourceConcepts, ...row.sourceConcepts])] .sort((left, right) => left.localeCompare(right)); existing.sourceFactIds = [...new Set([...existing.sourceFactIds, ...row.sourceFactIds])] .sort((left, right) => left - right); existing.hasDimensions = existing.hasDimensions || row.hasDimensions; existing.segment ??= row.segment; existing.axis ??= row.axis; existing.member ??= row.member; } } return [...rowsByKey.values()].sort((left, right) => { if (left.order !== right.order) { return left.order - right.order; } return left.label.localeCompare(right.label); }); } function emptyNormalizationMetadata(): NormalizationMetadata { return { regime: 'unknown', fiscalPack: null, parserVersion: '0.0.0', unmappedRowCount: 0, materialUnmappedRowCount: 0 }; } function buildNormalizationMetadata( snapshots: FilingTaxonomySnapshotRecord[] ): NormalizationMetadata { const latestSnapshot = snapshots[snapshots.length - 1]; if (!latestSnapshot) { return emptyNormalizationMetadata(); } return { regime: latestSnapshot.taxonomy_regime, fiscalPack: latestSnapshot.fiscal_pack, parserVersion: latestSnapshot.parser_version, unmappedRowCount: snapshots.reduce( (sum, snapshot) => sum + (snapshot.normalization_summary?.unmappedRowCount ?? 0), 0 ), materialUnmappedRowCount: snapshots.reduce( (sum, snapshot) => sum + (snapshot.normalization_summary?.materialUnmappedRowCount ?? 0), 0 ) }; } function rowHasValues(values: Record) { return Object.values(values).some((value) => value !== null); } const PINNED_INCOME_SURFACE_ROWS = new Set([ 'revenue', 'gross_profit', 'operating_expenses', 'selling_general_and_administrative', 'research_and_development', 'other_operating_expense', 'operating_income', 'income_tax_expense', 'net_income' ]); function shouldRetainSurfaceRow( statement: FinancialStatementKind, row: SurfaceFinancialRow, values: Record ) { if (rowHasValues(values)) { return true; } return statement === 'income' && PINNED_INCOME_SURFACE_ROWS.has(row.key); } function aggregateSurfaceRows(input: { snapshots: FilingTaxonomySnapshotRecord[]; statement: FinancialStatementKind; selectedPeriodIds: Set; }) { const rowMap = new Map(); for (const snapshot of input.snapshots) { const rows = snapshot.surface_rows?.[input.statement] ?? []; for (const row of rows) { const filteredValues = Object.fromEntries( Object.entries(row.values).filter(([periodId]) => input.selectedPeriodIds.has(periodId)) ); const filteredResolvedSourceRowKeys = Object.fromEntries( Object.entries(row.resolvedSourceRowKeys ?? {}).filter(([periodId]) => input.selectedPeriodIds.has(periodId)) ); if (!shouldRetainSurfaceRow(input.statement, row, filteredValues)) { continue; } const existing = rowMap.get(row.key); if (!existing) { rowMap.set(row.key, { ...row, values: filteredValues, resolvedSourceRowKeys: filteredResolvedSourceRowKeys, sourceConcepts: [...row.sourceConcepts], sourceRowKeys: [...row.sourceRowKeys], sourceFactIds: [...row.sourceFactIds], warningCodes: row.warningCodes ? [...row.warningCodes] : undefined }); continue; } for (const [periodId, value] of Object.entries(filteredValues)) { if (!(periodId in existing.values)) { existing.values[periodId] = value; } } for (const [periodId, sourceRowKey] of Object.entries(filteredResolvedSourceRowKeys)) { if (!(periodId in existing.resolvedSourceRowKeys)) { existing.resolvedSourceRowKeys[periodId] = sourceRowKey; } } existing.sourceConcepts = [...new Set([...existing.sourceConcepts, ...row.sourceConcepts])].sort((left, right) => left.localeCompare(right)); existing.sourceRowKeys = [...new Set([...existing.sourceRowKeys, ...row.sourceRowKeys])].sort((left, right) => left.localeCompare(right)); existing.sourceFactIds = [...new Set([...existing.sourceFactIds, ...row.sourceFactIds])].sort((left, right) => left - right); existing.hasDimensions = existing.hasDimensions || row.hasDimensions; existing.order = Math.min(existing.order, row.order); existing.detailCount = Math.max(existing.detailCount ?? 0, row.detailCount ?? 0); existing.formulaKey = existing.formulaKey ?? row.formulaKey; existing.statement = existing.statement ?? row.statement; existing.resolutionMethod = existing.resolutionMethod ?? row.resolutionMethod; existing.confidence = existing.confidence ?? row.confidence; existing.warningCodes = [...new Set([...(existing.warningCodes ?? []), ...(row.warningCodes ?? [])])] .sort((left, right) => left.localeCompare(right)); } } return [...rowMap.values()].sort((left, right) => { if (left.order !== right.order) { return left.order - right.order; } return left.label.localeCompare(right.label); }); } function aggregateDetailRows(input: { snapshots: FilingTaxonomySnapshotRecord[]; statement: FinancialStatementKind; selectedPeriodIds: Set; }) { const detailBuckets = new Map>(); for (const snapshot of input.snapshots) { const groups = snapshot.detail_rows?.[input.statement] ?? {}; for (const [surfaceKey, rows] of Object.entries(groups)) { let bucket = detailBuckets.get(surfaceKey); if (!bucket) { bucket = new Map(); detailBuckets.set(surfaceKey, bucket); } for (const row of rows) { const filteredValues = Object.fromEntries( Object.entries(row.values).filter(([periodId]) => input.selectedPeriodIds.has(periodId)) ); if (!rowHasValues(filteredValues)) { continue; } const existing = bucket.get(row.key); if (!existing) { bucket.set(row.key, { ...row, values: filteredValues, sourceFactIds: [...row.sourceFactIds], dimensionsSummary: [...row.dimensionsSummary] }); continue; } for (const [periodId, value] of Object.entries(filteredValues)) { if (!(periodId in existing.values)) { existing.values[periodId] = value; } } existing.sourceFactIds = [...new Set([...existing.sourceFactIds, ...row.sourceFactIds])].sort((left, right) => left - right); existing.dimensionsSummary = [...new Set([...existing.dimensionsSummary, ...row.dimensionsSummary])].sort((left, right) => left.localeCompare(right)); existing.isExtension = existing.isExtension || row.isExtension; existing.residualFlag = existing.residualFlag || row.residualFlag; } } } return Object.fromEntries( [...detailBuckets.entries()].map(([surfaceKey, bucket]) => [ surfaceKey, [...bucket.values()].sort((left, right) => left.label.localeCompare(right.label)) ]) ) satisfies SurfaceDetailMap; } function buildLtmDetailRows(input: { detailRows: SurfaceDetailMap; quarterlyPeriods: FinancialStatementPeriod[]; ltmPeriods: FinancialStatementPeriod[]; statement: Extract; }) { const sortedQuarterlyPeriods = [...input.quarterlyPeriods].sort(periodSorter); return Object.fromEntries( Object.entries(input.detailRows).map(([surfaceKey, rows]) => { const ltmRows = rows .map((row) => { const values: Record = {}; for (const ltmPeriod of input.ltmPeriods) { const anchorIndex = sortedQuarterlyPeriods.findIndex((period) => `ltm:${period.id}` === ltmPeriod.id); if (anchorIndex < 3) { continue; } const slice = sortedQuarterlyPeriods.slice(anchorIndex - 3, anchorIndex + 1); const sourceValues = slice.map((period) => row.values[period.id] ?? null); values[ltmPeriod.id] = input.statement === 'balance' ? sourceValues[sourceValues.length - 1] ?? null : sourceValues.some((value) => value === null) ? null : sourceValues.reduce((sum, value) => sum + (value ?? 0), 0); } return { ...row, values }; }) .filter((row) => rowHasValues(row.values)); return [surfaceKey, ltmRows]; }) ) satisfies SurfaceDetailMap; } function buildQuarterlyStatementSurfaceRows(input: { statement: Extract; sourcePeriods: FinancialStatementPeriod[]; selectedPeriodIds: Set; faithfulRows: TaxonomyStatementRow[]; facts: TaxonomyFactRow[]; snapshots: FilingTaxonomySnapshotRecord[]; }) { const aggregatedRows = aggregateSurfaceRows({ snapshots: input.snapshots, statement: input.statement, selectedPeriodIds: input.selectedPeriodIds }); if (aggregatedRows.length > 0) { return aggregatedRows; } return buildStandardizedRows({ rows: input.faithfulRows, statement: input.statement, periods: input.sourcePeriods, facts: input.facts }) as SurfaceFinancialRow[]; } function aggregatePersistedKpiRows(input: { snapshots: FilingTaxonomySnapshotRecord[]; selectedPeriodIds: Set; }) { const rowMap = new Map(); for (const snapshot of input.snapshots) { for (const row of snapshot.kpi_rows ?? []) { const filteredValues = Object.fromEntries( Object.entries(row.values).filter(([periodId]) => input.selectedPeriodIds.has(periodId)) ); if (!rowHasValues(filteredValues)) { continue; } const existing = rowMap.get(row.key); if (!existing) { rowMap.set(row.key, { ...row, values: filteredValues, sourceConcepts: [...row.sourceConcepts], sourceFactIds: [...row.sourceFactIds] }); continue; } existing.values = { ...existing.values, ...filteredValues }; existing.sourceConcepts = [...new Set([...existing.sourceConcepts, ...row.sourceConcepts])].sort((left, right) => left.localeCompare(right)); existing.sourceFactIds = [...new Set([...existing.sourceFactIds, ...row.sourceFactIds])].sort((left, right) => left - right); existing.hasDimensions = existing.hasDimensions || row.hasDimensions; } } return [...rowMap.values()].sort((left, right) => { if (left.order !== right.order) { return left.order - right.order; } return left.label.localeCompare(right.label); }); } function buildEmptyResponse(input: { ticker: string; companyName: string; cik: string | null; surfaceKind: FinancialSurfaceKind; cadence: FinancialCadence; queuedSync: boolean; enabled: boolean; metrics: CompanyFinancialStatementsResponse['metrics']; nextCursor: string | null; coverageFacts: number; }) { return { company: { ticker: input.ticker, companyName: input.companyName, cik: input.cik }, surfaceKind: input.surfaceKind, cadence: input.cadence, displayModes: defaultDisplayModes(input.surfaceKind), defaultDisplayMode: 'standardized', periods: [], statementRows: isStatementSurface(input.surfaceKind) ? { faithful: [], standardized: [] } : null, statementDetails: null, ratioRows: input.surfaceKind === 'ratios' ? [] : null, kpiRows: input.surfaceKind === 'segments_kpis' ? [] : null, trendSeries: [], categories: [], availability: { adjusted: false, customMetrics: false }, nextCursor: input.nextCursor, facts: null, coverage: { filings: 0, rows: 0, dimensions: 0, facts: input.coverageFacts }, dataSourceStatus: { enabled: input.enabled, hydratedFilings: 0, partialFilings: 0, failedFilings: 0, pendingFilings: 0, queuedSync: input.queuedSync }, metrics: input.metrics, normalization: emptyNormalizationMetadata(), dimensionBreakdown: null } satisfies CompanyFinancialStatementsResponse; } async function buildStatementSurfaceBundle(input: { surfaceKind: Extract; cadence: FinancialCadence; sourcePeriods: FinancialStatementPeriod[]; targetPeriods: FinancialStatementPeriod[]; selectedPeriodIds: Set; faithfulRows: TaxonomyStatementRow[]; facts: TaxonomyFactRow[]; snapshots: FilingTaxonomySnapshotRecord[]; }) { const cached = await readCachedFinancialBundle({ ticker: input.snapshots[0]?.ticker ?? '', surfaceKind: input.surfaceKind, cadence: input.cadence, snapshots: input.snapshots }); if ( cached && Array.isArray((cached as Partial).rows) && typeof (cached as Partial).detailRows === 'object' ) { return cached as StandardizedStatementBundlePayload; } const statement = surfaceToStatementKind(input.surfaceKind); if (!statement || (statement !== 'income' && statement !== 'balance' && statement !== 'cash_flow')) { return { rows: [], detailRows: {}, trendSeries: [], categories: [], normalization: buildNormalizationMetadata(input.snapshots) } satisfies StandardizedStatementBundlePayload; } const quarterlyRows = buildQuarterlyStatementSurfaceRows({ statement, sourcePeriods: input.sourcePeriods, selectedPeriodIds: input.selectedPeriodIds, faithfulRows: input.faithfulRows, facts: input.facts, snapshots: input.snapshots }); const quarterlyDetailRows = aggregateDetailRows({ snapshots: input.snapshots, statement, selectedPeriodIds: input.selectedPeriodIds }); const rows = input.cadence === 'ltm' ? buildLtmStandardizedRows(quarterlyRows, input.sourcePeriods, input.targetPeriods, statement) as SurfaceFinancialRow[] : quarterlyRows; const detailRows = input.cadence === 'ltm' ? buildLtmDetailRows({ detailRows: quarterlyDetailRows, quarterlyPeriods: input.sourcePeriods, ltmPeriods: input.targetPeriods, statement }) : quarterlyDetailRows; const normalization = buildNormalizationMetadata(input.snapshots); const payload = { rows, detailRows, trendSeries: buildTrendSeries({ surfaceKind: input.surfaceKind, statementRows: rows }), categories: buildFinancialCategories(rows, input.surfaceKind), normalization } satisfies StandardizedStatementBundlePayload; await writeFinancialBundle({ ticker: input.snapshots[0]?.ticker ?? '', surfaceKind: input.surfaceKind, cadence: input.cadence, snapshots: input.snapshots, payload: payload as unknown as Record }); return payload; } async function buildRatioSurfaceBundle(input: { ticker: string; cadence: FinancialCadence; periods: FinancialStatementPeriod[]; snapshots: FilingTaxonomySnapshotRecord[]; incomeRows: StandardizedFinancialRow[]; balanceRows: StandardizedFinancialRow[]; cashFlowRows: StandardizedFinancialRow[]; }) { const cached = await readCachedFinancialBundle({ ticker: input.ticker, surfaceKind: 'ratios', cadence: input.cadence, snapshots: input.snapshots }); if (cached) { return cached as Pick; } const pricesByDate = await getHistoricalClosingPrices(input.ticker, input.periods.map((period) => latestPeriodDate(period))); const pricesByPeriodId = Object.fromEntries(input.periods.map((period) => [period.id, pricesByDate[latestPeriodDate(period)] ?? null])); const ratioRows = buildRatioRows({ periods: input.periods, cadence: input.cadence, rows: { income: input.incomeRows, balance: input.balanceRows, cashFlow: input.cashFlowRows }, pricesByPeriodId }); const payload = { ratioRows, trendSeries: buildTrendSeries({ surfaceKind: 'ratios', ratioRows }), categories: buildFinancialCategories(ratioRows, 'ratios') } satisfies Pick; await writeFinancialBundle({ ticker: input.ticker, surfaceKind: 'ratios', cadence: input.cadence, snapshots: input.snapshots, payload: payload as unknown as Record }); return payload; } async function buildKpiSurfaceBundle(input: { ticker: string; cadence: FinancialCadence; periods: FinancialStatementPeriod[]; snapshots: FilingTaxonomySnapshotRecord[]; facts: TaxonomyFactRow[]; filings: FilingDocumentRef[]; }) { const cached = await readCachedFinancialBundle({ ticker: input.ticker, surfaceKind: 'segments_kpis', cadence: input.cadence, snapshots: input.snapshots }); if (cached) { return cached as Pick; } const persistedRows = aggregatePersistedKpiRows({ snapshots: input.snapshots, selectedPeriodIds: new Set(input.periods.map((period) => period.id)) }); const resolved = resolveKpiDefinitions(input.ticker); if (!resolved.template) { return { kpiRows: persistedRows, trendSeries: buildTrendSeries({ surfaceKind: 'segments_kpis', kpiRows: persistedRows }), categories: buildFinancialCategories(persistedRows, 'segments_kpis') }; } const taxonomyRows = extractStructuredKpisFromDimensions({ facts: input.facts, periods: input.periods, definitions: resolved.definitions }); const noteRows = await extractStructuredKpisFromNotes({ ticker: input.ticker, periods: input.periods, filings: input.filings, definitions: resolved.definitions }); const kpiRows = mergeStructuredKpiRowsByPriority([ persistedRows, taxonomyRows, noteRows ]); const payload = { kpiRows, trendSeries: buildTrendSeries({ surfaceKind: 'segments_kpis', kpiRows }), categories: buildFinancialCategories(kpiRows, 'segments_kpis') } satisfies Pick; await writeFinancialBundle({ ticker: input.ticker, surfaceKind: 'segments_kpis', cadence: input.cadence, snapshots: input.snapshots, payload: payload as unknown as Record }); return payload; } export function defaultFinancialSyncLimit() { return 60; } export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Promise { const ticker = safeTicker(input.ticker); const filingTypes = cadenceFilingTypes(input.cadence); const safeLimit = Math.min(Math.max(Math.trunc(input.limit ?? 12), 1), 40); const snapshotLimit = input.cadence === 'ltm' ? safeLimit + 3 : safeLimit; const [snapshotResult, statuses, filings] = await Promise.all([ listFilingTaxonomySnapshotsByTicker({ ticker, window: 'all', filingTypes: [...filingTypes], limit: snapshotLimit, cursor: input.cursor }), countFilingTaxonomySnapshotStatuses(ticker), listFilingsRecords({ ticker, limit: 250 }) ]); const latestFiling = filings[0] ?? null; const financialFilings = filings.filter((filing) => isFinancialForm(filing.filing_type)); const metrics = latestMetrics(snapshotResult.snapshots); if (snapshotResult.snapshots.length === 0) { return buildEmptyResponse({ ticker, companyName: latestFiling?.company_name ?? ticker, cik: latestFiling?.cik ?? null, surfaceKind: input.surfaceKind, cadence: input.cadence, queuedSync: input.queuedSync, enabled: input.v3Enabled, metrics, nextCursor: snapshotResult.nextCursor, coverageFacts: 0 }); } if (input.surfaceKind === 'adjusted' || input.surfaceKind === 'custom_metrics') { return { ...buildEmptyResponse({ ticker, companyName: latestFiling?.company_name ?? ticker, cik: latestFiling?.cik ?? null, surfaceKind: input.surfaceKind, cadence: input.cadence, queuedSync: input.queuedSync, enabled: input.v3Enabled, metrics, nextCursor: snapshotResult.nextCursor, coverageFacts: 0 }), dataSourceStatus: { enabled: input.v3Enabled, hydratedFilings: statuses.ready, partialFilings: statuses.partial, failedFilings: statuses.failed, pendingFilings: Math.max(0, financialFilings.filter((filing) => filingTypes.includes(filing.filing_type as '10-K' | '10-Q')).length - statuses.ready - statuses.partial - statuses.failed), queuedSync: input.queuedSync }, normalization: buildNormalizationMetadata(snapshotResult.snapshots) }; } const allFacts = await listTaxonomyFactsByTicker({ ticker, window: 'all', filingTypes: [...filingTypes], limit: 10000 }); if (isStatementSurface(input.surfaceKind)) { const statement = surfaceToStatementKind(input.surfaceKind); if (!statement) { throw new Error(`Unsupported statement surface ${input.surfaceKind}`); } const selection = input.cadence === 'ltm' ? selectPrimaryPeriodsByCadence(snapshotResult.snapshots, statement, 'quarterly') : selectPrimaryPeriodsByCadence(snapshotResult.snapshots, statement, input.cadence); const periods = input.cadence === 'ltm' ? buildLtmPeriods(selection.periods) : selection.periods; const baseFaithfulRows = buildRows(selection.snapshots, statement, selection.selectedPeriodIds); const faithfulRows = input.cadence === 'ltm' ? buildLtmFaithfulRows( baseFaithfulRows, selection.periods, periods, statement ) : baseFaithfulRows; const factsForStatement = allFacts.facts.filter((fact) => fact.statement === statement); const factsForStandardization = allFacts.facts; const standardizedPayload = await buildStatementSurfaceBundle({ surfaceKind: input.surfaceKind as Extract, cadence: input.cadence, sourcePeriods: selection.periods, targetPeriods: periods, selectedPeriodIds: selection.selectedPeriodIds, faithfulRows: baseFaithfulRows, facts: factsForStandardization, snapshots: selection.snapshots }); const standardizedRows = standardizedPayload.rows; const rawFacts = input.includeFacts ? await listTaxonomyFactsByTicker({ ticker, window: 'all', filingTypes: [...filingTypes], statement, cursor: input.factsCursor, limit: input.factsLimit }) : { facts: [], nextCursor: null }; const dimensionBreakdown = input.includeDimensions ? buildDimensionBreakdown(factsForStandardization, periods, faithfulRows, standardizedRows) : null; return { company: { ticker, companyName: latestFiling?.company_name ?? ticker, cik: latestFiling?.cik ?? null }, surfaceKind: input.surfaceKind, cadence: input.cadence, displayModes: defaultDisplayModes(input.surfaceKind), defaultDisplayMode: 'standardized', periods, statementRows: { faithful: faithfulRows, standardized: standardizedRows }, statementDetails: standardizedPayload.detailRows, ratioRows: null, kpiRows: null, trendSeries: standardizedPayload.trendSeries, categories: standardizedPayload.categories, availability: { adjusted: false, customMetrics: false }, nextCursor: snapshotResult.nextCursor, facts: input.includeFacts ? { rows: rawFacts.facts, nextCursor: rawFacts.nextCursor } : null, coverage: { filings: periods.length, rows: standardizedRows.length, dimensions: dimensionBreakdown ? Object.values(dimensionBreakdown).reduce((sum, rows) => sum + rows.length, 0) : 0, facts: input.includeFacts ? rawFacts.facts.length : allFacts.facts.length }, dataSourceStatus: { enabled: input.v3Enabled, hydratedFilings: statuses.ready, partialFilings: statuses.partial, failedFilings: statuses.failed, pendingFilings: Math.max(0, financialFilings.filter((filing) => filingTypes.includes(filing.filing_type as '10-K' | '10-Q')).length - statuses.ready - statuses.partial - statuses.failed), queuedSync: input.queuedSync }, metrics, normalization: standardizedPayload.normalization, dimensionBreakdown }; } const incomeSelection = input.cadence === 'ltm' ? selectPrimaryPeriodsByCadence(snapshotResult.snapshots, 'income', 'quarterly') : selectPrimaryPeriodsByCadence(snapshotResult.snapshots, 'income', input.cadence); const balanceSelection = input.cadence === 'ltm' ? selectPrimaryPeriodsByCadence(snapshotResult.snapshots, 'balance', 'quarterly') : selectPrimaryPeriodsByCadence(snapshotResult.snapshots, 'balance', input.cadence); const cashFlowSelection = input.cadence === 'ltm' ? selectPrimaryPeriodsByCadence(snapshotResult.snapshots, 'cash_flow', 'quarterly') : selectPrimaryPeriodsByCadence(snapshotResult.snapshots, 'cash_flow', input.cadence); const basePeriods = input.cadence === 'ltm' ? buildLtmPeriods(incomeSelection.periods) : incomeSelection.periods; const incomeQuarterlyRows = buildQuarterlyStatementSurfaceRows({ statement: 'income', sourcePeriods: incomeSelection.periods, selectedPeriodIds: incomeSelection.selectedPeriodIds, faithfulRows: buildRows(incomeSelection.snapshots, 'income', incomeSelection.selectedPeriodIds), facts: allFacts.facts, snapshots: incomeSelection.snapshots }); const balanceQuarterlyRows = rekeyRowsByFilingId(buildQuarterlyStatementSurfaceRows({ statement: 'balance', sourcePeriods: balanceSelection.periods, selectedPeriodIds: balanceSelection.selectedPeriodIds, faithfulRows: buildRows(balanceSelection.snapshots, 'balance', balanceSelection.selectedPeriodIds), facts: allFacts.facts, snapshots: balanceSelection.snapshots }), balanceSelection.periods, incomeSelection.periods); const cashFlowQuarterlyRows = rekeyRowsByFilingId(buildQuarterlyStatementSurfaceRows({ statement: 'cash_flow', sourcePeriods: cashFlowSelection.periods, selectedPeriodIds: cashFlowSelection.selectedPeriodIds, faithfulRows: buildRows(cashFlowSelection.snapshots, 'cash_flow', cashFlowSelection.selectedPeriodIds), facts: allFacts.facts, snapshots: cashFlowSelection.snapshots }), cashFlowSelection.periods, incomeSelection.periods); const incomeRows = input.cadence === 'ltm' ? buildLtmStandardizedRows(incomeQuarterlyRows, incomeSelection.periods, basePeriods, 'income') : incomeQuarterlyRows; const balanceRows = input.cadence === 'ltm' ? buildLtmStandardizedRows(balanceQuarterlyRows, incomeSelection.periods, basePeriods, 'balance') : balanceQuarterlyRows; const cashFlowRows = input.cadence === 'ltm' ? buildLtmStandardizedRows(cashFlowQuarterlyRows, incomeSelection.periods, basePeriods, 'cash_flow') : cashFlowQuarterlyRows; if (input.surfaceKind === 'ratios') { const ratioBundle = await buildRatioSurfaceBundle({ ticker, cadence: input.cadence, periods: basePeriods, snapshots: incomeSelection.snapshots, incomeRows, balanceRows, cashFlowRows }); return { company: { ticker, companyName: latestFiling?.company_name ?? ticker, cik: latestFiling?.cik ?? null }, surfaceKind: input.surfaceKind, cadence: input.cadence, displayModes: defaultDisplayModes(input.surfaceKind), defaultDisplayMode: 'standardized', periods: basePeriods, statementRows: null, statementDetails: null, ratioRows: ratioBundle.ratioRows, kpiRows: null, trendSeries: ratioBundle.trendSeries, categories: ratioBundle.categories, availability: { adjusted: false, customMetrics: false }, nextCursor: snapshotResult.nextCursor, facts: null, coverage: { filings: basePeriods.length, rows: ratioBundle.ratioRows?.length ?? 0, dimensions: 0, facts: allFacts.facts.length }, dataSourceStatus: { enabled: input.v3Enabled, hydratedFilings: statuses.ready, partialFilings: statuses.partial, failedFilings: statuses.failed, pendingFilings: Math.max(0, financialFilings.filter((filing) => filingTypes.includes(filing.filing_type as '10-K' | '10-Q')).length - statuses.ready - statuses.partial - statuses.failed), queuedSync: input.queuedSync }, metrics, normalization: buildNormalizationMetadata(incomeSelection.snapshots), dimensionBreakdown: null }; } const filingRefs: FilingDocumentRef[] = filings.map((filing) => ({ filingId: filing.id, cik: filing.cik, accessionNumber: filing.accession_number, filingUrl: filing.filing_url ?? null, primaryDocument: filing.primary_document ?? null })); const kpiBundle = await buildKpiSurfaceBundle({ ticker, cadence: input.cadence, periods: basePeriods, snapshots: incomeSelection.snapshots, facts: allFacts.facts, filings: filingRefs }); const kpiBreakdown = input.includeDimensions ? buildKpiDimensionBreakdown({ rows: kpiBundle.kpiRows ?? [], periods: basePeriods, facts: allFacts.facts }) : null; return { company: { ticker, companyName: latestFiling?.company_name ?? ticker, cik: latestFiling?.cik ?? null }, surfaceKind: input.surfaceKind, cadence: input.cadence, displayModes: defaultDisplayModes(input.surfaceKind), defaultDisplayMode: 'standardized', periods: basePeriods, statementRows: null, statementDetails: null, ratioRows: null, kpiRows: kpiBundle.kpiRows, trendSeries: kpiBundle.trendSeries, categories: kpiBundle.categories, availability: { adjusted: false, customMetrics: false }, nextCursor: snapshotResult.nextCursor, facts: null, coverage: { filings: basePeriods.length, rows: kpiBundle.kpiRows?.length ?? 0, dimensions: kpiBreakdown ? Object.values(kpiBreakdown).reduce((sum, rows) => sum + rows.length, 0) : 0, facts: allFacts.facts.length }, dataSourceStatus: { enabled: input.v3Enabled, hydratedFilings: statuses.ready, partialFilings: statuses.partial, failedFilings: statuses.failed, pendingFilings: Math.max(0, financialFilings.filter((filing) => filingTypes.includes(filing.filing_type as '10-K' | '10-Q')).length - statuses.ready - statuses.partial - statuses.failed), queuedSync: input.queuedSync }, metrics, normalization: buildNormalizationMetadata(incomeSelection.snapshots), dimensionBreakdown: mergeDimensionBreakdownMaps(kpiBreakdown) }; } export async function getCompanyFinancialTaxonomy(input: GetCompanyFinancialsInput) { return await getCompanyFinancials(input); } export const __financialTaxonomyInternals = { buildRows, buildStandardizedRows, buildDimensionBreakdown, buildNormalizationMetadata, aggregateSurfaceRows, mergeStructuredKpiRowsByPriority, periodSorter, selectPrimaryPeriodsByCadence, buildLtmPeriods, buildLtmFaithfulRows, buildLtmStandardizedRows };