""" Tests for core.calculations — reproduces the Forrester Amazon Connect TEI totals from the published study within rounding. The PDF reports (Cash Flow Analysis, p.25): Benefits PV (RA) = $101,696,791 Costs PV (RA) = $ 22,983,076 NPV = $ 78,713,715 ROI = 342% Payback = <6 months """ from __future__ import annotations import pytest from core.calculations import ( SCENARIOS, apply_scenario, discount_factor, npv, payback_months, payback_years, present_value, present_value_series, risk_adjust_benefit, risk_adjust_cost, roi, roi_percentage, ) # ───────────────────────────────────────────── # Building blocks # ───────────────────────────────────────────── class TestDiscounting: def test_discount_factor_year_zero(self): assert discount_factor(0, 0.10) == pytest.approx(1.0) def test_discount_factor_known_value(self): # 1/(1.10)^3 assert discount_factor(3, 0.10) == pytest.approx(0.7513148, rel=1e-5) def test_negative_year_raises(self): with pytest.raises(ValueError): discount_factor(-1, 0.10) def test_present_value_year_one(self): assert present_value(110, 1, 0.10) == pytest.approx(100.0) def test_present_value_series_three_years(self): # 100 each year for 3 years at 10% → ≈ 248.685 assert present_value_series([100, 100, 100], 0.10) == pytest.approx( 248.685, rel=1e-3 ) class TestNPV: def test_zero_initial(self): assert npv([100, 100], 0.0) == pytest.approx(200.0) def test_with_initial(self): # 1000 invested up-front, 600 returned each of 2 years at 10% result = npv([600, 600], 0.10, initial=-1000) # PV of returns ≈ 545.45 + 495.87 = 1041.32, NPV ≈ 41.32 assert result == pytest.approx(41.32, abs=0.5) class TestRiskAdjustment: def test_benefit_zero_risk(self): assert risk_adjust_benefit(100, 0.0) == 100 def test_benefit_15pct(self): assert risk_adjust_benefit(100, 0.15) == pytest.approx(85.0) def test_cost_5pct_upward(self): assert risk_adjust_cost(100, 0.05) == pytest.approx(105.0) def test_clamping(self): assert risk_adjust_benefit(100, 1.5) == 0 # clamped to 1.0 assert risk_adjust_benefit(100, -0.5) == 100 # clamped to 0 class TestROI: def test_zero_costs_returns_zero(self): assert roi(100, 0) == 0.0 def test_known(self): assert roi_percentage(101_696_791, 22_983_076) == pytest.approx(342, abs=1) class TestPayback: def test_immediate(self): assert payback_years(0, [100]) == 0.0 def test_amazon_connect_under_six_months(self): # Initial $1.196M, Y1 net ~$20M → quick crossing years = payback_years(1_196_250, [19_997_953, 31_562_489, 47_443_905]) assert years is not None assert payback_months(1_196_250, [19_997_953, 31_562_489, 47_443_905]) < 6 def test_never_recovered(self): assert payback_years(1000, [100, 100, 100]) is None class TestScenarios: def test_default_scenarios_present(self): assert set(SCENARIOS) == {"conservative", "moderate", "aggressive"} def test_moderate_is_passthrough(self): items = [ { "table": "benefits", "field_key": "x", "year_values": {"1": 1000, "2": 2000}, "risk_adjustment": 0.15, } ] out = apply_scenario(items, "moderate") assert out[0]["year_values"] == {"1": 1000.0, "2": 2000.0} assert out[0]["risk_adjustment"] == pytest.approx(0.15) def test_conservative_lowers_benefits(self): items = [ { "table": "benefits", "field_key": "x", "year_values": {"1": 1000}, "risk_adjustment": 0.15, } ] out = apply_scenario(items, "conservative") assert out[0]["year_values"]["1"] == pytest.approx(800.0) # 0.15 + 0.10 = 0.25 assert out[0]["risk_adjustment"] == pytest.approx(0.25) def test_aggressive_increases_benefits(self): items = [ { "table": "benefits", "field_key": "x", "year_values": {"1": 1000}, "risk_adjustment": 0.15, } ] out = apply_scenario(items, "aggressive") assert out[0]["year_values"]["1"] == pytest.approx(1150.0) assert out[0]["risk_adjustment"] == pytest.approx(0.10) def test_unknown_scenario_raises(self): with pytest.raises(KeyError): apply_scenario([], "purple") # ───────────────────────────────────────────── # End-to-end: reproduce the PDF totals # ───────────────────────────────────────────── class TestAmazonConnectComposite: """Reproduce the Forrester Amazon Connect TEI numbers within rounding.""" DISCOUNT_RATE = 0.10 EXPECTED_BENEFITS_PV = 101_696_791 EXPECTED_COSTS_PV = 22_983_076 EXPECTED_NPV = 78_713_715 EXPECTED_ROI = 342 # percent TOLERANCE = 1_500 # dollars; PDF rounding at thousands def _benefits_pv(self, seed) -> float: total = 0.0 for b in seed.BENEFITS: rf = b["risk_adjustment"] yr = [b["year_values"][str(y)] for y in (1, 2, 3)] yr_ra = [risk_adjust_benefit(v, rf) for v in yr] total += npv(yr_ra, self.DISCOUNT_RATE) return total def _costs_pv(self, seed) -> float: total = 0.0 for c in seed.COSTS: rf = c["risk_adjustment"] init = risk_adjust_cost(c.get("initial") or 0, rf) yr = [c["year_values"][str(y)] for y in (1, 2, 3)] yr_ra = [risk_adjust_cost(v, rf) for v in yr] total += npv(yr_ra, self.DISCOUNT_RATE, initial=init) return total def test_benefits_pv(self, amazon_connect_seed): result = self._benefits_pv(amazon_connect_seed) assert result == pytest.approx(self.EXPECTED_BENEFITS_PV, abs=self.TOLERANCE) def test_costs_pv(self, amazon_connect_seed): result = self._costs_pv(amazon_connect_seed) assert result == pytest.approx(self.EXPECTED_COSTS_PV, abs=self.TOLERANCE) def test_npv(self, amazon_connect_seed): b = self._benefits_pv(amazon_connect_seed) c = self._costs_pv(amazon_connect_seed) assert (b - c) == pytest.approx(self.EXPECTED_NPV, abs=self.TOLERANCE) def test_roi(self, amazon_connect_seed): b = self._benefits_pv(amazon_connect_seed) c = self._costs_pv(amazon_connect_seed) assert roi_percentage(b, c) == pytest.approx(self.EXPECTED_ROI, abs=1)