feat(taxonomy): add rust sidecar compact surface pipeline

This commit is contained in:
2026-03-12 15:23:10 -04:00
parent f2c25fb9c6
commit 58061af006
84 changed files with 19350 additions and 265 deletions

View File

@@ -1,12 +1,16 @@
import type {
CompanyFinancialStatementsResponse,
DetailFinancialRow,
FinancialCadence,
FinancialDisplayMode,
FinancialStatementKind,
FinancialStatementPeriod,
FinancialSurfaceKind,
NormalizationMetadata,
StandardizedFinancialRow,
StructuredKpiRow,
SurfaceDetailMap,
SurfaceFinancialRow,
TaxonomyFactRow,
TaxonomyStatementRow
} from '@/lib/types';
@@ -59,9 +63,11 @@ type GetCompanyFinancialsInput = {
};
type StandardizedStatementBundlePayload = {
rows: StandardizedFinancialRow[];
rows: SurfaceFinancialRow[];
detailRows: SurfaceDetailMap;
trendSeries: CompanyFinancialStatementsResponse['trendSeries'];
categories: CompanyFinancialStatementsResponse['categories'];
normalization: NormalizationMetadata;
};
type FilingDocumentRef = {
@@ -204,6 +210,354 @@ 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 {
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<string, number | null>) {
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<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 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<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 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<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
});
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<string>;
}) {
const rowMap = new Map<string, StructuredKpiRow>();
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;
@@ -230,6 +584,7 @@ function buildEmptyResponse(input: {
statementRows: isStatementSurface(input.surfaceKind)
? { faithful: [], standardized: [] }
: null,
statementDetails: null,
ratioRows: input.surfaceKind === 'ratios' ? [] : null,
kpiRows: input.surfaceKind === 'segments_kpis' ? [] : null,
trendSeries: [],
@@ -255,6 +610,7 @@ function buildEmptyResponse(input: {
queuedSync: input.queuedSync
},
metrics: input.metrics,
normalization: emptyNormalizationMetadata(),
dimensionBreakdown: null
} satisfies CompanyFinancialStatementsResponse;
}
@@ -262,7 +618,9 @@ function buildEmptyResponse(input: {
async function buildStatementSurfaceBundle(input: {
surfaceKind: Extract<FinancialSurfaceKind, 'income_statement' | 'balance_sheet' | 'cash_flow_statement'>;
cadence: FinancialCadence;
periods: FinancialStatementPeriod[];
sourcePeriods: FinancialStatementPeriod[];
targetPeriods: FinancialStatementPeriod[];
selectedPeriodIds: Set<string>;
faithfulRows: TaxonomyStatementRow[];
facts: TaxonomyFactRow[];
snapshots: FilingTaxonomySnapshotRecord[];
@@ -274,7 +632,11 @@ async function buildStatementSurfaceBundle(input: {
snapshots: input.snapshots
});
if (cached) {
if (
cached
&& Array.isArray((cached as Partial<StandardizedStatementBundlePayload>).rows)
&& typeof (cached as Partial<StandardizedStatementBundlePayload>).detailRows === 'object'
) {
return cached as StandardizedStatementBundlePayload;
}
@@ -282,25 +644,48 @@ async function buildStatementSurfaceBundle(input: {
if (!statement || (statement !== 'income' && statement !== 'balance' && statement !== 'cash_flow')) {
return {
rows: [],
detailRows: {},
trendSeries: [],
categories: []
categories: [],
normalization: buildNormalizationMetadata(input.snapshots)
} satisfies StandardizedStatementBundlePayload;
}
const standardizedRows = buildStandardizedRows({
rows: input.faithfulRows,
const quarterlyRows = buildQuarterlyStatementSurfaceRows({
statement,
periods: input.periods,
facts: input.facts
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: standardizedRows,
rows,
detailRows,
trendSeries: buildTrendSeries({
surfaceKind: input.surfaceKind,
statementRows: standardizedRows
statementRows: rows
}),
categories: buildFinancialCategories(standardizedRows, input.surfaceKind)
categories: buildFinancialCategories(rows, input.surfaceKind),
normalization
} satisfies StandardizedStatementBundlePayload;
await writeFinancialBundle({
@@ -386,12 +771,19 @@ async function buildKpiSurfaceBundle(input: {
return cached as Pick<CompanyFinancialStatementsResponse, 'kpiRows' | 'trendSeries' | 'categories'>;
}
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: [],
trendSeries: [],
categories: []
kpiRows: persistedRows,
trendSeries: buildTrendSeries({
surfaceKind: 'segments_kpis',
kpiRows: persistedRows
}),
categories: buildFinancialCategories(persistedRows, 'segments_kpis')
};
}
@@ -408,27 +800,11 @@ async function buildKpiSurfaceBundle(input: {
definitions: resolved.definitions
});
const rowsByKey = new Map<string, StructuredKpiRow>();
for (const row of [...taxonomyRows, ...noteRows]) {
const existing = rowsByKey.get(row.key);
if (existing) {
existing.values = {
...existing.values,
...row.values
};
continue;
}
rowsByKey.set(row.key, row);
}
const kpiRows = [...rowsByKey.values()].sort((left, right) => {
if (left.order !== right.order) {
return left.order - right.order;
}
return left.label.localeCompare(right.label);
});
const kpiRows = mergeStructuredKpiRowsByPriority([
persistedRows,
taxonomyRows,
noteRows
]);
const payload = {
kpiRows,
@@ -515,7 +891,8 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
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)
};
}
@@ -539,48 +916,39 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
const periods = input.cadence === 'ltm'
? buildLtmPeriods(selection.periods)
: selection.periods;
const baseFaithfulRows = buildRows(selection.snapshots, statement, selection.selectedPeriodIds);
const faithfulRows = input.cadence === 'ltm'
? buildLtmFaithfulRows(
buildRows(selection.snapshots, statement, selection.selectedPeriodIds),
baseFaithfulRows,
selection.periods,
periods,
statement
)
: buildRows(selection.snapshots, statement, selection.selectedPeriodIds);
: 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,
periods,
faithfulRows,
sourcePeriods: selection.periods,
targetPeriods: periods,
selectedPeriodIds: selection.selectedPeriodIds,
faithfulRows: baseFaithfulRows,
facts: factsForStandardization,
snapshots: selection.snapshots
});
const standardizedRows = input.cadence === 'ltm'
? buildLtmStandardizedRows(
buildStandardizedRows({
rows: buildRows(selection.snapshots, statement, selection.selectedPeriodIds),
statement: statement as Extract<FinancialStatementKind, 'income' | 'balance' | 'cash_flow'>,
periods: selection.periods,
facts: factsForStandardization
}),
selection.periods,
periods,
statement as Extract<FinancialStatementKind, 'income' | 'balance' | 'cash_flow'>
)
: standardizedPayload.rows;
const standardizedRows = standardizedPayload.rows;
const rawFacts = input.includeFacts
? await listTaxonomyFactsByTicker({
ticker,
window: 'all',
filingTypes: [...filingTypes],
statement,
cursor: input.factsCursor,
limit: input.factsLimit
statement,
cursor: input.factsCursor,
limit: input.factsLimit
})
: { facts: [], nextCursor: null };
@@ -603,12 +971,10 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
faithful: faithfulRows,
standardized: standardizedRows
},
statementDetails: standardizedPayload.detailRows,
ratioRows: null,
kpiRows: null,
trendSeries: buildTrendSeries({
surfaceKind: input.surfaceKind,
statementRows: standardizedRows
}),
trendSeries: standardizedPayload.trendSeries,
categories: standardizedPayload.categories,
availability: {
adjusted: false,
@@ -636,6 +1002,7 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
queuedSync: input.queuedSync
},
metrics,
normalization: standardizedPayload.normalization,
dimensionBreakdown
};
}
@@ -654,23 +1021,29 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
? buildLtmPeriods(incomeSelection.periods)
: incomeSelection.periods;
const incomeQuarterlyRows = buildStandardizedRows({
rows: buildRows(incomeSelection.snapshots, 'income', incomeSelection.selectedPeriodIds),
const incomeQuarterlyRows = buildQuarterlyStatementSurfaceRows({
statement: 'income',
periods: incomeSelection.periods,
facts: allFacts.facts
sourcePeriods: incomeSelection.periods,
selectedPeriodIds: incomeSelection.selectedPeriodIds,
faithfulRows: buildRows(incomeSelection.snapshots, 'income', incomeSelection.selectedPeriodIds),
facts: allFacts.facts,
snapshots: incomeSelection.snapshots
});
const balanceQuarterlyRows = rekeyRowsByFilingId(buildStandardizedRows({
rows: buildRows(balanceSelection.snapshots, 'balance', balanceSelection.selectedPeriodIds),
const balanceQuarterlyRows = rekeyRowsByFilingId(buildQuarterlyStatementSurfaceRows({
statement: 'balance',
periods: balanceSelection.periods,
facts: allFacts.facts
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(buildStandardizedRows({
rows: buildRows(cashFlowSelection.snapshots, 'cash_flow', cashFlowSelection.selectedPeriodIds),
const cashFlowQuarterlyRows = rekeyRowsByFilingId(buildQuarterlyStatementSurfaceRows({
statement: 'cash_flow',
periods: cashFlowSelection.periods,
facts: allFacts.facts
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'
@@ -706,6 +1079,7 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
defaultDisplayMode: 'standardized',
periods: basePeriods,
statementRows: null,
statementDetails: null,
ratioRows: ratioBundle.ratioRows,
kpiRows: null,
trendSeries: ratioBundle.trendSeries,
@@ -731,6 +1105,7 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
queuedSync: input.queuedSync
},
metrics,
normalization: buildNormalizationMetadata(incomeSelection.snapshots),
dimensionBreakdown: null
};
}
@@ -770,6 +1145,7 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
defaultDisplayMode: 'standardized',
periods: basePeriods,
statementRows: null,
statementDetails: null,
ratioRows: null,
kpiRows: kpiBundle.kpiRows,
trendSeries: kpiBundle.trendSeries,
@@ -795,6 +1171,7 @@ export async function getCompanyFinancials(input: GetCompanyFinancialsInput): Pr
queuedSync: input.queuedSync
},
metrics,
normalization: buildNormalizationMetadata(incomeSelection.snapshots),
dimensionBreakdown: mergeDimensionBreakdownMaps(kpiBreakdown)
};
}
@@ -807,6 +1184,9 @@ export const __financialTaxonomyInternals = {
buildRows,
buildStandardizedRows,
buildDimensionBreakdown,
buildNormalizationMetadata,
aggregateSurfaceRows,
mergeStructuredKpiRowsByPriority,
periodSorter,
selectPrimaryPeriodsByCadence,
buildLtmPeriods,