from copy import deepcopy from decimal import Decimal from domain import FinancialModel, ForecastScenario from application.forecast import ForecastService class ScenarioError(Exception): pass DEFAULT_SCENARIOS = { "baseline": ForecastScenario( name="baseline", income_multiplier=Decimal("1.0"), expense_multiplier=Decimal("1.0"), growth_multiplier=Decimal("1.0"), description="Базовый сценарий без изменений", ), "optimistic": ForecastScenario( name="optimistic", income_multiplier=Decimal("1.15"), expense_multiplier=Decimal("0.95"), growth_multiplier=Decimal("1.2"), description="Оптимистичный: доход +15%, расход -5%, рост активов +20%", ), "pessimistic": ForecastScenario( name="pessimistic", income_multiplier=Decimal("0.85"), expense_multiplier=Decimal("1.1"), growth_multiplier=Decimal("0.8"), description="Пессимистичный: доход -15%, расход +10%, рост активов -20%", ), } class ScenarioService: def __init__(self, model: FinancialModel): self.model = deepcopy(model) def apply(self, scenario: ForecastScenario, months: int = 12) -> dict: model = deepcopy(self.model) for t in model.transactions: if t.amount > 0: t.amount = t.amount * scenario.income_multiplier else: t.amount = t.amount * scenario.expense_multiplier for r in model.recurring: if r.category == "income": r.amount = r.amount * scenario.income_multiplier else: r.amount = r.amount * scenario.expense_multiplier for a in model.assets: a.growth_rate = a.growth_rate * scenario.growth_multiplier service = ForecastService(model) result = service.summary(months) result["scenario"] = scenario.name result["scenario_description"] = scenario.description return result def compare(self, months: int = 12) -> dict: results = {} for name, scenario in DEFAULT_SCENARIOS.items(): results[name] = self.apply(scenario, months) return results def what_if( self, income_mult: Decimal | float = Decimal("1.0"), expense_mult: Decimal | float = Decimal("1.0"), growth_mult: Decimal | float = Decimal("1.0"), months: int = 12, ) -> dict: def to_d(v: Decimal | float) -> Decimal: return v if isinstance(v, Decimal) else Decimal(str(v)) scenario = ForecastScenario( name="what-if", income_multiplier=to_d(income_mult), expense_multiplier=to_d(expense_mult), growth_multiplier=to_d(growth_mult), description=( f"What-if: income x{income_mult}, " f"expense x{expense_mult}, growth x{growth_mult}" ), ) return self.apply(scenario, months)