Expand financials surfaces with ratios, KPIs, and cadence support
- Add bundled financial modeling pipeline (ratios, KPI dimensions/notes, trend series, standardization) - Introduce company financial bundles storage (Drizzle migration + repo wiring) - Refactor financials page/API/query flow to use surfaceKind + cadence and new response shapes
This commit is contained in:
@@ -4,8 +4,9 @@ import type {
|
||||
CoveragePriority,
|
||||
CoverageStatus,
|
||||
Filing,
|
||||
FinancialHistoryWindow,
|
||||
FinancialCadence,
|
||||
FinancialStatementKind,
|
||||
FinancialSurfaceKind,
|
||||
ResearchJournalEntryType,
|
||||
TaskStatus
|
||||
} from '@/lib/types';
|
||||
@@ -15,7 +16,7 @@ import { asErrorMessage, jsonError } from '@/lib/server/http';
|
||||
import { buildPortfolioSummary } from '@/lib/server/portfolio';
|
||||
import {
|
||||
defaultFinancialSyncLimit,
|
||||
getCompanyFinancialTaxonomy
|
||||
getCompanyFinancials
|
||||
} from '@/lib/server/financial-taxonomy';
|
||||
import { redactInternalFilingAnalysisFields } from '@/lib/server/api/filing-redaction';
|
||||
import {
|
||||
@@ -68,7 +69,16 @@ const FINANCIAL_STATEMENT_KINDS: FinancialStatementKind[] = [
|
||||
'equity',
|
||||
'comprehensive_income'
|
||||
];
|
||||
const FINANCIAL_HISTORY_WINDOWS: FinancialHistoryWindow[] = ['10y', 'all'];
|
||||
const FINANCIAL_CADENCES: FinancialCadence[] = ['annual', 'quarterly', 'ltm'];
|
||||
const FINANCIAL_SURFACES: FinancialSurfaceKind[] = [
|
||||
'income_statement',
|
||||
'balance_sheet',
|
||||
'cash_flow_statement',
|
||||
'ratios',
|
||||
'segments_kpis',
|
||||
'adjusted',
|
||||
'custom_metrics'
|
||||
];
|
||||
const COVERAGE_STATUSES: CoverageStatus[] = ['backlog', 'active', 'watch', 'archive'];
|
||||
const COVERAGE_PRIORITIES: CoveragePriority[] = ['low', 'medium', 'high'];
|
||||
const JOURNAL_ENTRY_TYPES: ResearchJournalEntryType[] = ['note', 'filing_note', 'status_change'];
|
||||
@@ -152,10 +162,29 @@ function asStatementKind(value: unknown): FinancialStatementKind {
|
||||
: 'income';
|
||||
}
|
||||
|
||||
function asHistoryWindow(value: unknown): FinancialHistoryWindow {
|
||||
return FINANCIAL_HISTORY_WINDOWS.includes(value as FinancialHistoryWindow)
|
||||
? value as FinancialHistoryWindow
|
||||
: '10y';
|
||||
function asCadence(value: unknown): FinancialCadence {
|
||||
return FINANCIAL_CADENCES.includes(value as FinancialCadence)
|
||||
? value as FinancialCadence
|
||||
: 'annual';
|
||||
}
|
||||
|
||||
function surfaceFromLegacyStatement(statement: FinancialStatementKind): FinancialSurfaceKind {
|
||||
switch (statement) {
|
||||
case 'balance':
|
||||
return 'balance_sheet';
|
||||
case 'cash_flow':
|
||||
return 'cash_flow_statement';
|
||||
default:
|
||||
return 'income_statement';
|
||||
}
|
||||
}
|
||||
|
||||
function asSurfaceKind(surface: unknown, statement: unknown): FinancialSurfaceKind {
|
||||
if (FINANCIAL_SURFACES.includes(surface as FinancialSurfaceKind)) {
|
||||
return surface as FinancialSurfaceKind;
|
||||
}
|
||||
|
||||
return surfaceFromLegacyStatement(asStatementKind(statement));
|
||||
}
|
||||
|
||||
function asCoverageStatus(value: unknown) {
|
||||
@@ -926,8 +955,8 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
return jsonError('ticker is required');
|
||||
}
|
||||
|
||||
const statement = asStatementKind(query.statement);
|
||||
const window = asHistoryWindow(query.window);
|
||||
const surfaceKind = asSurfaceKind(query.surface, query.statement);
|
||||
const cadence = asCadence(query.cadence);
|
||||
const includeDimensions = asBoolean(query.includeDimensions, false);
|
||||
const includeFacts = asBoolean(query.includeFacts, false);
|
||||
const cursor = typeof query.cursor === 'string' && query.cursor.trim().length > 0
|
||||
@@ -943,10 +972,10 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
? Number(query.factsLimit)
|
||||
: undefined;
|
||||
|
||||
let payload = await getCompanyFinancialTaxonomy({
|
||||
let payload = await getCompanyFinancials({
|
||||
ticker,
|
||||
statement,
|
||||
window,
|
||||
surfaceKind,
|
||||
cadence,
|
||||
includeDimensions,
|
||||
includeFacts,
|
||||
factsCursor,
|
||||
@@ -961,7 +990,7 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
const shouldQueueSync = cursor === null && (
|
||||
payload.dataSourceStatus.pendingFilings > 0
|
||||
|| payload.coverage.filings === 0
|
||||
|| (window === 'all' && payload.nextCursor !== null)
|
||||
|| payload.nextCursor !== null
|
||||
);
|
||||
|
||||
if (shouldQueueSync) {
|
||||
@@ -972,7 +1001,7 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
taskType: 'sync_filings',
|
||||
payload: buildSyncFilingsPayload({
|
||||
ticker,
|
||||
limit: defaultFinancialSyncLimit(window),
|
||||
limit: defaultFinancialSyncLimit(),
|
||||
category: watchlistItem?.category,
|
||||
tags: watchlistItem?.tags
|
||||
}),
|
||||
@@ -999,6 +1028,20 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
}, {
|
||||
query: t.Object({
|
||||
ticker: t.String({ minLength: 1 }),
|
||||
surface: t.Optional(t.Union([
|
||||
t.Literal('income_statement'),
|
||||
t.Literal('balance_sheet'),
|
||||
t.Literal('cash_flow_statement'),
|
||||
t.Literal('ratios'),
|
||||
t.Literal('segments_kpis'),
|
||||
t.Literal('adjusted'),
|
||||
t.Literal('custom_metrics')
|
||||
])),
|
||||
cadence: t.Optional(t.Union([
|
||||
t.Literal('annual'),
|
||||
t.Literal('quarterly'),
|
||||
t.Literal('ltm')
|
||||
])),
|
||||
statement: t.Optional(t.Union([
|
||||
t.Literal('income'),
|
||||
t.Literal('balance'),
|
||||
@@ -1006,7 +1049,6 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
t.Literal('equity'),
|
||||
t.Literal('comprehensive_income')
|
||||
])),
|
||||
window: t.Optional(t.Union([t.Literal('10y'), t.Literal('all')])),
|
||||
includeDimensions: t.Optional(t.Union([t.String(), t.Boolean()])),
|
||||
includeFacts: t.Optional(t.Union([t.String(), t.Boolean()])),
|
||||
cursor: t.Optional(t.String()),
|
||||
|
||||
Reference in New Issue
Block a user