from collections import defaultdict from dataclasses import dataclass from datetime import date, timedelta from decimal import Decimal @dataclass(frozen=True) class Projection: series: list[tuple[date, Decimal]] low_point: tuple[date, Decimal] first_below_zero: date | None first_below_threshold: date | None def project(start_balance: Decimal, start_date: date, cashflows: list[tuple[date, Decimal]], horizon_days: int, threshold: Decimal = Decimal("0")) -> Projection: end = start_date + timedelta(days=horizon_days) by_day: dict[date, Decimal] = defaultdict(lambda: Decimal("0")) for d, amount in cashflows: if start_date < d <= end: by_day[d] += amount series: list[tuple[date, Decimal]] = [] balance = start_balance low = (start_date, start_balance) below0: date | None = None below_t: date | None = None d = start_date + timedelta(days=1) while d <= end: balance += by_day.get(d, Decimal("0")) series.append((d, balance)) if balance < low[1]: low = (d, balance) if below0 is None and balance < 0: below0 = d if below_t is None and balance < threshold: below_t = d d += timedelta(days=1) return Projection(series, low, below0, below_t)