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): 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(); }); });