Files
Neon-Desk/lib/server/financial-taxonomy.ts
francy51 a7f7be50b4 Remove legacy TypeScript financial surface mapping, make Rust JSON single source of truth
- Delete standard-template.ts, surface.ts, materialize.ts (dead code)
- Delete financial-taxonomy.test.ts (relied on removed code)
- Add missing income statement surfaces to core.surface.json
- Add cost_of_revenue mapping to core.income-bridge.json
- Refactor standardize.ts to remove template dependency
- Simplify financial-taxonomy.ts to use only DB snapshots
- Add architecture documentation
2026-03-15 14:38:48 -04:00

1307 lines
42 KiB
TypeScript

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
} 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, NonNullable<CompanyFinancialStatementsResponse['dimensionBreakdown']>[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 isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function hasRequiredDerivedRowArrays(row: unknown) {
return isRecord(row)
&& Array.isArray(row.sourceConcepts)
&& Array.isArray(row.sourceRowKeys)
&& Array.isArray(row.sourceFactIds);
}
function hasRequiredDetailRowArrays(row: unknown) {
return isRecord(row)
&& Array.isArray(row.sourceFactIds)
&& Array.isArray(row.dimensionsSummary);
}
function isValidStatementBundlePayload(value: unknown): value is StandardizedStatementBundlePayload {
if (!isRecord(value) || !Array.isArray(value.rows) || !isRecord(value.detailRows)) {
return false;
}
if (!value.rows.every((row) => hasRequiredDerivedRowArrays(row))) {
return false;
}
return Object.values(value.detailRows).every((rows) => (
Array.isArray(rows) && rows.every((row) => hasRequiredDetailRowArrays(row))
));
}
function isValidRatioBundlePayload(value: unknown): value is Pick<CompanyFinancialStatementsResponse, 'ratioRows' | 'trendSeries' | 'categories'> {
return isRecord(value)
&& Array.isArray(value.ratioRows)
&& value.ratioRows.every((row) => hasRequiredDerivedRowArrays(row));
}
function isValidKpiBundlePayload(value: unknown): value is Pick<CompanyFinancialStatementsResponse, 'kpiRows' | 'trendSeries' | 'categories'> {
return isRecord(value)
&& Array.isArray(value.kpiRows)
&& value.kpiRows.every((row) => isRecord(row) && Array.isArray(row.sourceConcepts) && Array.isArray(row.sourceFactIds));
}
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<T extends {
values: Record<string, number | null>;
resolvedSourceRowKeys?: Record<string, string | null>;
}>(rows: T[], sourcePeriods: FinancialStatementPeriod[], targetPeriods: FinancialStatementPeriod[]) {
const targetPeriodByFilingId = new Map(targetPeriods.map((period) => [period.filingId, period]));
return rows.map((row) => {
const nextValues: Record<string, number | null> = {};
const nextResolvedSourceRowKeys: Record<string, string | null> = {};
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<DimensionBreakdownMap | null>) {
const merged = new Map<string, NonNullable<CompanyFinancialStatementsResponse['dimensionBreakdown']>[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, NonNullable<CompanyFinancialStatementsResponse['dimensionBreakdown']>[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<string, StructuredKpiRow>();
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 {
parserEngine: 'unknown',
regime: 'unknown',
fiscalPack: null,
parserVersion: '0.0.0',
surfaceRowCount: 0,
detailRowCount: 0,
kpiRowCount: 0,
unmappedRowCount: 0,
materialUnmappedRowCount: 0,
warnings: []
};
}
function buildNormalizationMetadata(
snapshots: FilingTaxonomySnapshotRecord[]
): NormalizationMetadata {
const latestSnapshot = snapshots[snapshots.length - 1];
if (!latestSnapshot) {
return emptyNormalizationMetadata();
}
return {
parserEngine: latestSnapshot.parser_engine,
regime: latestSnapshot.taxonomy_regime,
fiscalPack: latestSnapshot.fiscal_pack,
parserVersion: latestSnapshot.parser_version,
surfaceRowCount: snapshots.reduce(
(sum, snapshot) => sum + (snapshot.normalization_summary?.surfaceRowCount ?? 0),
0
),
detailRowCount: snapshots.reduce(
(sum, snapshot) => sum + (snapshot.normalization_summary?.detailRowCount ?? 0),
0
),
kpiRowCount: snapshots.reduce(
(sum, snapshot) => sum + (snapshot.normalization_summary?.kpiRowCount ?? 0),
0
),
unmappedRowCount: snapshots.reduce(
(sum, snapshot) => sum + (snapshot.normalization_summary?.unmappedRowCount ?? 0),
0
),
materialUnmappedRowCount: snapshots.reduce(
(sum, snapshot) => sum + (snapshot.normalization_summary?.materialUnmappedRowCount ?? 0),
0
),
warnings: [...new Set(snapshots.flatMap((snapshot) => snapshot.normalization_summary?.warnings ?? []))]
.sort((left, right) => left.localeCompare(right))
};
}
function rowHasValues(values: Record<string, number | null>) {
return Object.values(values).some((value) => value !== null);
}
function detailConceptIdentity(row: DetailFinancialRow) {
const localName = row.localName.trim().toLowerCase();
if (localName.length > 0) {
if (row.isExtension) {
return `extension:${localName}`;
}
if (row.namespaceUri.includes('us-gaap')) {
return `us-gaap:${localName}`;
}
if (row.namespaceUri.includes('ifrs')) {
return `ifrs:${localName}`;
}
const prefix = row.qname.split(':')[0]?.trim().toLowerCase();
if (prefix) {
return `${prefix}:${localName}`;
}
}
const normalizedQName = row.qname.trim().toLowerCase();
if (normalizedQName.length > 0) {
return normalizedQName;
}
const normalizedConceptKey = row.conceptKey.trim().toLowerCase();
if (normalizedConceptKey.length > 0) {
return normalizedConceptKey;
}
return row.key.trim().toLowerCase();
}
function detailMergeKey(row: DetailFinancialRow) {
const dimensionsKey = [...(row.dimensionsSummary ?? [])]
.map((value) => value.trim().toLowerCase())
.filter((value) => value.length > 0)
.sort((left, right) => left.localeCompare(right))
.join('|') || 'no-dimensions';
return [
detailConceptIdentity(row),
row.unit ?? 'no-unit',
dimensionsKey
].join('::');
}
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<string, number | null>
) {
if (rowHasValues(values)) {
return true;
}
return statement === 'income' && PINNED_INCOME_SURFACE_ROWS.has(row.key);
}
function aggregateSurfaceRows(input: {
snapshots: FilingTaxonomySnapshotRecord[];
statement: FinancialStatementKind;
selectedPeriodIds: Set<string>;
}) {
const rowMap = new Map<string, SurfaceFinancialRow>();
for (const snapshot of input.snapshots) {
const rows = snapshot.surface_rows?.[input.statement] ?? [];
for (const row of rows) {
const sourceConcepts = row.sourceConcepts ?? [];
const sourceRowKeys = row.sourceRowKeys ?? [];
const sourceFactIds = row.sourceFactIds ?? [];
const rowValues = row.values ?? {};
const filteredValues = Object.fromEntries(
Object.entries(rowValues).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: [...sourceConcepts],
sourceRowKeys: [...sourceRowKeys],
sourceFactIds: [...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, ...sourceConcepts])].sort((left, right) => left.localeCompare(right));
existing.sourceRowKeys = [...new Set([...existing.sourceRowKeys, ...sourceRowKeys])].sort((left, right) => left.localeCompare(right));
existing.sourceFactIds = [...new Set([...existing.sourceFactIds, ...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<string>;
}) {
const detailBuckets = new Map<string, Map<string, DetailFinancialRow>>();
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<string, DetailFinancialRow>();
detailBuckets.set(surfaceKey, bucket);
}
for (const row of rows) {
const sourceFactIds = row.sourceFactIds ?? [];
const dimensionsSummary = row.dimensionsSummary ?? [];
const rowValues = row.values ?? {};
const filteredValues = Object.fromEntries(
Object.entries(rowValues).filter(([periodId]) => input.selectedPeriodIds.has(periodId))
);
if (!rowHasValues(filteredValues)) {
continue;
}
const mergeKey = detailMergeKey(row);
const existing = bucket.get(mergeKey);
if (!existing) {
bucket.set(mergeKey, {
...row,
values: filteredValues,
sourceFactIds: [...sourceFactIds],
dimensionsSummary: [...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, ...sourceFactIds])].sort((left, right) => left - right);
existing.dimensionsSummary = [...new Set([...existing.dimensionsSummary, ...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<FinancialStatementKind, 'income' | 'balance' | 'cash_flow'>;
}) {
const sortedQuarterlyPeriods = [...input.quarterlyPeriods].sort(periodSorter);
return Object.fromEntries(
Object.entries(input.detailRows).map(([surfaceKey, rows]) => {
const ltmRows = rows
.map((row) => {
const values: Record<string, number | null> = {};
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<number>((sum, value) => sum + (value ?? 0), 0);
}
return {
...row,
values
};
})
.filter((row) => rowHasValues(row.values));
return [surfaceKey, ltmRows];
})
) satisfies SurfaceDetailMap;
}
function buildQuarterlyStatementSurfaceRows(input: {
statement: Extract<FinancialStatementKind, 'income' | 'balance' | 'cash_flow'>;
sourcePeriods: FinancialStatementPeriod[];
selectedPeriodIds: Set<string>;
faithfulRows: TaxonomyStatementRow[];
facts: TaxonomyFactRow[];
snapshots: FilingTaxonomySnapshotRecord[];
}) {
const aggregatedRows = aggregateSurfaceRows({
snapshots: input.snapshots,
statement: input.statement,
selectedPeriodIds: input.selectedPeriodIds
});
return aggregatedRows;
}
function aggregatePersistedKpiRows(input: {
snapshots: FilingTaxonomySnapshotRecord[];
selectedPeriodIds: Set<string>;
}) {
const rowMap = new Map<string, StructuredKpiRow>();
for (const snapshot of input.snapshots) {
for (const row of snapshot.kpi_rows ?? []) {
const sourceConcepts = row.sourceConcepts ?? [];
const sourceFactIds = row.sourceFactIds ?? [];
const rowValues = row.values ?? {};
const filteredValues = Object.fromEntries(
Object.entries(rowValues).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: [...sourceConcepts],
sourceFactIds: [...sourceFactIds]
});
continue;
}
existing.values = {
...existing.values,
...filteredValues
};
existing.sourceConcepts = [...new Set([...existing.sourceConcepts, ...sourceConcepts])].sort((left, right) => left.localeCompare(right));
existing.sourceFactIds = [...new Set([...existing.sourceFactIds, ...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<FinancialSurfaceKind, 'income_statement' | 'balance_sheet' | 'cash_flow_statement'>;
cadence: FinancialCadence;
sourcePeriods: FinancialStatementPeriod[];
targetPeriods: FinancialStatementPeriod[];
selectedPeriodIds: Set<string>;
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
&& isValidStatementBundlePayload(cached)
) {
return cached;
}
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<string, unknown>
});
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 && isValidRatioBundlePayload(cached)) {
return cached;
}
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<CompanyFinancialStatementsResponse, 'ratioRows' | 'trendSeries' | 'categories'>;
await writeFinancialBundle({
ticker: input.ticker,
surfaceKind: 'ratios',
cadence: input.cadence,
snapshots: input.snapshots,
payload: payload as unknown as Record<string, unknown>
});
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 && isValidKpiBundlePayload(cached)) {
return cached;
}
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<CompanyFinancialStatementsResponse, 'kpiRows' | 'trendSeries' | 'categories'>;
await writeFinancialBundle({
ticker: input.ticker,
surfaceKind: 'segments_kpis',
cadence: input.cadence,
snapshots: input.snapshots,
payload: payload as unknown as Record<string, unknown>
});
return payload;
}
export function defaultFinancialSyncLimit() {
return 60;
}
export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Promise<CompanyFinancialStatementsResponse> {
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<FinancialSurfaceKind, 'income_statement' | 'balance_sheet' | 'cash_flow_statement'>,
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,
buildDimensionBreakdown,
buildNormalizationMetadata,
aggregateSurfaceRows,
aggregateDetailRows,
mergeStructuredKpiRowsByPriority,
periodSorter,
selectPrimaryPeriodsByCadence,
buildLtmPeriods,
buildLtmFaithfulRows,
buildLtmStandardizedRows
};