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:
82
lib/server/financials/trend-series.ts
Normal file
82
lib/server/financials/trend-series.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type {
|
||||
FinancialSurfaceKind,
|
||||
RatioRow,
|
||||
StandardizedFinancialRow,
|
||||
StructuredKpiRow,
|
||||
TrendSeries
|
||||
} from '@/lib/types';
|
||||
import { KPI_CATEGORY_ORDER } from '@/lib/server/financials/kpi-registry';
|
||||
import { RATIO_CATEGORY_ORDER } from '@/lib/server/financials/ratios';
|
||||
|
||||
function toTrendSeriesRow(row: {
|
||||
key: string;
|
||||
label: string;
|
||||
category: string;
|
||||
unit: TrendSeries['unit'];
|
||||
values: Record<string, number | null>;
|
||||
}) {
|
||||
return {
|
||||
key: row.key,
|
||||
label: row.label,
|
||||
category: row.category,
|
||||
unit: row.unit,
|
||||
values: row.values
|
||||
} satisfies TrendSeries;
|
||||
}
|
||||
|
||||
export function buildFinancialCategories(rows: Array<{ category: string }>, surfaceKind: FinancialSurfaceKind) {
|
||||
const counts = new Map<string, number>();
|
||||
for (const row of rows) {
|
||||
counts.set(row.category, (counts.get(row.category) ?? 0) + 1);
|
||||
}
|
||||
|
||||
const order = surfaceKind === 'ratios'
|
||||
? [...RATIO_CATEGORY_ORDER]
|
||||
: surfaceKind === 'segments_kpis'
|
||||
? [...KPI_CATEGORY_ORDER]
|
||||
: [...counts.keys()];
|
||||
|
||||
return order
|
||||
.filter((key) => (counts.get(key) ?? 0) > 0)
|
||||
.map((key) => ({
|
||||
key,
|
||||
label: key.replace(/_/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()),
|
||||
count: counts.get(key) ?? 0
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildTrendSeries(input: {
|
||||
surfaceKind: FinancialSurfaceKind;
|
||||
statementRows?: StandardizedFinancialRow[];
|
||||
ratioRows?: RatioRow[];
|
||||
kpiRows?: StructuredKpiRow[];
|
||||
}) {
|
||||
switch (input.surfaceKind) {
|
||||
case 'income_statement':
|
||||
return (input.statementRows ?? [])
|
||||
.filter((row) => row.key === 'revenue' || row.key === 'net_income')
|
||||
.map(toTrendSeriesRow);
|
||||
case 'balance_sheet':
|
||||
return (input.statementRows ?? [])
|
||||
.filter((row) => row.key === 'total_assets' || row.key === 'cash_and_equivalents' || row.key === 'total_debt')
|
||||
.map(toTrendSeriesRow);
|
||||
case 'cash_flow_statement':
|
||||
return (input.statementRows ?? [])
|
||||
.filter((row) => row.key === 'operating_cash_flow' || row.key === 'free_cash_flow' || row.key === 'capital_expenditures')
|
||||
.map(toTrendSeriesRow);
|
||||
case 'ratios':
|
||||
return (input.ratioRows ?? [])
|
||||
.filter((row) => row.category === 'margins')
|
||||
.map(toTrendSeriesRow);
|
||||
case 'segments_kpis': {
|
||||
const rows = input.kpiRows ?? [];
|
||||
const firstCategory = buildFinancialCategories(rows, 'segments_kpis')[0]?.key ?? null;
|
||||
return rows
|
||||
.filter((row) => row.category === firstCategory)
|
||||
.slice(0, 4)
|
||||
.map(toTrendSeriesRow);
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user