""" Net Present Value and discounting. Convention (matches the Forrester TEI methodology used in the Amazon Connect study): * The *Initial* investment (column "Initial" in TEI tables) is **not** discounted — it occurs at time zero. * Year-N cash flows are discounted at the end of the year: ``PV = CF_n / (1 + r) ** n`` for ``n = 1, 2, …``. That matches the PDF note: *"The initial investment column contains costs incurred at 'time 0' or at the beginning of Year 1 that are not discounted. All other cash flows are discounted using the discount rate at the end of the year."* """ from __future__ import annotations from collections.abc import Iterable def discount_factor(year: int, discount_rate: float) -> float: """Return ``1 / (1 + r) ** year``. Year 0 → 1.0 (no discount).""" if year < 0: raise ValueError("year must be >= 0") return 1.0 / ((1.0 + discount_rate) ** year) def present_value(amount: float, year: int, discount_rate: float) -> float: """Discount ``amount`` from end-of-year ``year`` to present.""" return amount * discount_factor(year, discount_rate) def present_value_series( cashflows: Iterable[float], discount_rate: float, start_year: int = 1, ) -> float: """ Sum the present value of a stream of year-end cashflows. Args: cashflows: iterable of year-N amounts (Y1, Y2, …). discount_rate: e.g. 0.10 for 10%. start_year: year of the first element. Default 1 (skip year-0). """ total = 0.0 for offset, cf in enumerate(cashflows): total += present_value(cf, start_year + offset, discount_rate) return total def npv( cashflows: Iterable[float], discount_rate: float, initial: float = 0.0, ) -> float: """ Net Present Value. Args: cashflows: year-end cashflows for Year 1, Year 2, … discount_rate: e.g. 0.10 initial: undiscounted year-0 cashflow (negative for a cost, positive for a one-off benefit). Returns: ``initial + Σ CF_n / (1 + r)^n`` for ``n = 1..len(cashflows)``. Example:: >>> # Amazon Connect total benefits PV ≈ $101.7M >>> benefits = [27_279_019, 40_333_658, 57_983_794] >>> round(npv(benefits, 0.10) / 1_000_000, 1) 101.7 """ return initial + present_value_series(cashflows, discount_rate, start_year=1)