feat(taxonomy): add rust sidecar compact surface pipeline
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user