Implementiert Task 5 mit TDD-Ansatz: projection.py mit Projection-Klasse und project()-Funktion für Cashflow-Simulation; scenario.py mit PlainRecurring, PlainPlanned, PlainModifier Dataclasses und build_cashflows() für Szenarien-Zahlungsströme mit Modifier-Support (remove, percent, absolute). Alle 15 Tests grün. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
40 lines
1.3 KiB
Python
40 lines
1.3 KiB
Python
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)
|