import { computeDcf, DcfInputs, DcfParams } from "../lib/dcf"; interface Scenario { name: string; inputs: DcfInputs; params: DcfParams; expected: { intrinsicValuePerShare: number; enterpriseValue: number; equityValue: number; fcfPvSum: number; terminalValuePv: number; }; } // Independent reference implementation of the DCF math used by both backend // (data_service._run_dcf) and frontend (lib/dcf.ts:computeDcf). function expectedDcf( baseFcf: number, growthRate: number, wacc: number, terminalGrowth: number, projectionYears: number, equityBridge: number, sharesOutstanding: number ) { const projected: number[] = []; for (let i = 1; i <= projectionYears; i++) { projected.push(baseFcf * (1 + growthRate) ** i); } const fcfPvSum = projected.reduce( (sum, fcf, i) => sum + fcf / (1 + wacc) ** (i + 0.5), 0 ); const terminalFcf = projected[projected.length - 1] * (1 + terminalGrowth); const terminalValue = terminalFcf / (wacc - terminalGrowth); const terminalValuePv = terminalValue / (1 + wacc) ** (projectionYears - 0.5); const enterpriseValue = fcfPvSum + terminalValuePv; const equityValue = enterpriseValue - equityBridge; const intrinsicValuePerShare = equityValue / sharesOutstanding; return { intrinsicValuePerShare, enterpriseValue, equityValue, fcfPvSum, terminalValuePv, }; } function makeScenario( name: string, baseFcf: number, growthRate: number, wacc: number, terminalGrowth: number, projectionYears: number, sharesOutstanding: number, netDebt: number, preferredEquity = 0, minorityInterest = 0 ): Scenario { const equityBridge = netDebt + preferredEquity + minorityInterest; return { name, inputs: { baseFcf, equityBridge, sharesOutstanding }, params: { wacc, terminalGrowth, projectionYears, growthRate }, expected: expectedDcf( baseFcf, growthRate, wacc, terminalGrowth, projectionYears, equityBridge, sharesOutstanding ), }; } const scenarios: Scenario[] = [ makeScenario("1-year with equity bridge", 105, 0.05, 0.1, 0.03, 1, 10, 15, 2, 3), makeScenario("3-year no claims", 105, 0.05, 0.1, 0.03, 3, 10, 0), makeScenario("5-year default horizon", 105, 0.05, 0.1, 0.03, 5, 10, 0), makeScenario("zero growth flat FCF", 100, 0.0, 0.1, 0.02, 5, 10, 0), ]; function closeEnough(actual: number, expected: number): boolean { const tol = 1e-9 + 1e-6 * Math.abs(expected); return Math.abs(actual - expected) <= tol; } let failed = 0; for (const scenario of scenarios) { const computed = computeDcf(scenario.inputs, scenario.params); if ("error" in computed) { console.error(`FAIL ${scenario.name}: returned error "${computed.error}"`); failed++; continue; } const keys = Object.keys(scenario.expected) as Array; const mismatches = keys.filter( (key) => !closeEnough(computed[key], scenario.expected[key]) ); if (mismatches.length === 0) { console.log(`PASS ${scenario.name}`); } else { console.error(`FAIL ${scenario.name}`); for (const key of mismatches) { console.error( ` ${key}: got ${computed[key]}, expected ${scenario.expected[key]}` ); } failed++; } } if (failed === 0) { console.log("\nAll frontend DCF scenarios passed."); process.exit(0); } else { console.error(`\n${failed} frontend DCF scenario(s) failed.`); process.exit(1); }