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:
81
lib/server/financials/ratios.test.ts
Normal file
81
lib/server/financials/ratios.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import type {
|
||||
FinancialCadence,
|
||||
FinancialStatementPeriod,
|
||||
StandardizedFinancialRow
|
||||
} from '@/lib/types';
|
||||
import { buildRatioRows } from './ratios';
|
||||
|
||||
function createPeriod(id: string, filingId: number, filingDate: string, periodEnd: string): FinancialStatementPeriod {
|
||||
return {
|
||||
id,
|
||||
filingId,
|
||||
accessionNumber: `0000-${filingId}`,
|
||||
filingDate,
|
||||
periodStart: '2025-01-01',
|
||||
periodEnd,
|
||||
filingType: '10-Q',
|
||||
periodLabel: id
|
||||
};
|
||||
}
|
||||
|
||||
function createRow(key: string, values: Record<string, number | null>): StandardizedFinancialRow {
|
||||
return {
|
||||
key,
|
||||
label: key,
|
||||
category: 'test',
|
||||
order: 10,
|
||||
unit: 'currency',
|
||||
values,
|
||||
sourceConcepts: [`us-gaap:${key}`],
|
||||
sourceRowKeys: [key],
|
||||
sourceFactIds: [1],
|
||||
formulaKey: null,
|
||||
hasDimensions: false,
|
||||
resolvedSourceRowKeys: Object.fromEntries(Object.keys(values).map((periodId) => [periodId, key]))
|
||||
};
|
||||
}
|
||||
|
||||
describe('ratio engine', () => {
|
||||
it('nulls valuation ratios when price data is unavailable', () => {
|
||||
const periods = [createPeriod('2025-q4', 1, '2026-01-31', '2025-12-31')];
|
||||
const rows = {
|
||||
income: [createRow('revenue', { '2025-q4': 100 }), createRow('diluted_eps', { '2025-q4': 2 }), createRow('ebitda', { '2025-q4': 20 }), createRow('net_income', { '2025-q4': 10 })],
|
||||
balance: [createRow('total_equity', { '2025-q4': 50 }), createRow('total_debt', { '2025-q4': 30 }), createRow('cash_and_equivalents', { '2025-q4': 5 }), createRow('short_term_investments', { '2025-q4': 5 }), createRow('diluted_shares', { '2025-q4': 10 })],
|
||||
cashFlow: [createRow('free_cash_flow', { '2025-q4': 12 })]
|
||||
};
|
||||
|
||||
const ratioRows = buildRatioRows({
|
||||
periods,
|
||||
cadence: 'quarterly' satisfies FinancialCadence,
|
||||
rows,
|
||||
pricesByPeriodId: { '2025-q4': null }
|
||||
});
|
||||
|
||||
expect(ratioRows.find((row) => row.key === 'market_cap')?.values['2025-q4']).toBeNull();
|
||||
expect(ratioRows.find((row) => row.key === 'price_to_earnings')?.values['2025-q4']).toBeNull();
|
||||
expect(ratioRows.find((row) => row.key === 'ev_to_sales')?.values['2025-q4']).toBeNull();
|
||||
});
|
||||
|
||||
it('nulls ratios on zero denominators', () => {
|
||||
const periods = [
|
||||
createPeriod('2024-q4', 1, '2025-01-31', '2024-12-31'),
|
||||
createPeriod('2025-q4', 2, '2026-01-31', '2025-12-31')
|
||||
];
|
||||
const rows = {
|
||||
income: [createRow('net_income', { '2024-q4': 5, '2025-q4': 10 }), createRow('revenue', { '2024-q4': 100, '2025-q4': 120 }), createRow('diluted_eps', { '2024-q4': 1, '2025-q4': 2 }), createRow('ebitda', { '2024-q4': 10, '2025-q4': 12 })],
|
||||
balance: [createRow('total_equity', { '2024-q4': 0, '2025-q4': 0 }), createRow('total_assets', { '2024-q4': 50, '2025-q4': 60 }), createRow('total_debt', { '2024-q4': 20, '2025-q4': 25 }), createRow('cash_and_equivalents', { '2024-q4': 2, '2025-q4': 3 }), createRow('short_term_investments', { '2024-q4': 1, '2025-q4': 1 }), createRow('current_assets', { '2024-q4': 10, '2025-q4': 12 }), createRow('current_liabilities', { '2024-q4': 0, '2025-q4': 0 }), createRow('diluted_shares', { '2024-q4': 10, '2025-q4': 10 })],
|
||||
cashFlow: [createRow('free_cash_flow', { '2024-q4': 6, '2025-q4': 7 })]
|
||||
};
|
||||
|
||||
const ratioRows = buildRatioRows({
|
||||
periods,
|
||||
cadence: 'quarterly',
|
||||
rows,
|
||||
pricesByPeriodId: { '2024-q4': 10, '2025-q4': 12 }
|
||||
});
|
||||
|
||||
expect(ratioRows.find((row) => row.key === 'debt_to_equity')?.values['2025-q4']).toBeNull();
|
||||
expect(ratioRows.find((row) => row.key === 'current_ratio')?.values['2025-q4']).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user