refactor: Modifikator-Helfer öffentlich, load_scenario_inputs aus run_projection (A13-T1)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, timedelta
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
@@ -18,36 +19,61 @@ from app.models.tables import (Loan, PlannedItem, ProjectionPoint,
|
||||
from app.services.balances import total_balance
|
||||
|
||||
|
||||
def run_projection(session: Session, scenario: Scenario, horizon_days: int,
|
||||
start_date: date) -> ProjectionResult:
|
||||
end = start_date + timedelta(days=horizon_days)
|
||||
@dataclass(frozen=True)
|
||||
class ScenarioInputs:
|
||||
"""Eingaben einer Szenario-Rechnung (Projektion wie Aufteilung)."""
|
||||
recurring: list[PlainRecurring]
|
||||
planned: list[PlainPlanned]
|
||||
loans: list[Loan]
|
||||
modifiers: list[PlainModifier]
|
||||
|
||||
|
||||
def load_scenario_inputs(session: Session, scenario: Scenario | None) -> ScenarioInputs:
|
||||
"""Lädt die Eingaben eines Szenarios. scenario=None ist die reine
|
||||
Planung („System-Plan"): alle Fixposten, alle globalen Einmalposten,
|
||||
alle Kredite, keine Modifikatoren, keine Szenario-Einmalposten."""
|
||||
recurring = []
|
||||
if scenario.include_recurring:
|
||||
if scenario is None or scenario.include_recurring:
|
||||
recurring = [PlainRecurring(r.id, r.name, Decimal(r.amount), r.rhythm,
|
||||
r.due_day, r.start_date, r.end_date, r.category_id)
|
||||
for r in session.execute(select(RecurringItem)).scalars()]
|
||||
for r in session.execute(
|
||||
select(RecurringItem).order_by(RecurringItem.id)).scalars()]
|
||||
planned = []
|
||||
if scenario.include_planned:
|
||||
if scenario is None or scenario.include_planned:
|
||||
planned = [PlainPlanned(p.name, Decimal(p.amount), p.due, p.category_id)
|
||||
for p in session.execute(select(PlannedItem)).scalars()]
|
||||
for p in session.execute(
|
||||
select(PlannedItem).order_by(PlannedItem.id)).scalars()]
|
||||
if scenario is None:
|
||||
loans = list(session.execute(select(Loan).order_by(Loan.id)).scalars())
|
||||
return ScenarioInputs(recurring, planned, loans, [])
|
||||
# Szenario-eigene Einmalzahlungen zaehlen IMMER fuer dieses Szenario -
|
||||
# include_planned steuert nur die globalen PlannedItems.
|
||||
planned += [PlainPlanned(p.name, Decimal(p.amount), p.due, None)
|
||||
for p in session.execute(select(ScenarioPlannedItem).where(
|
||||
ScenarioPlannedItem.scenario_id == scenario.id)).scalars()]
|
||||
loans = session.execute(
|
||||
ScenarioPlannedItem.scenario_id == scenario.id)
|
||||
.order_by(ScenarioPlannedItem.id)).scalars()]
|
||||
loans = list(session.execute(
|
||||
select(Loan).join(ScenarioLoan, ScenarioLoan.loan_id == Loan.id)
|
||||
.where(ScenarioLoan.scenario_id == scenario.id)).scalars().all()
|
||||
schedules = [loan_schedule(Decimal(l.principal), Decimal(l.annual_rate_pct),
|
||||
l.term_months, l.payout_date, l.repayment_type)
|
||||
for l in loans]
|
||||
payouts = [(l.payout_date, Decimal(l.principal)) for l in loans]
|
||||
.where(ScenarioLoan.scenario_id == scenario.id)
|
||||
.order_by(Loan.id)).scalars())
|
||||
modifiers = [PlainModifier(m.target_type, m.target_id, m.kind,
|
||||
Decimal(m.value), m.end_date)
|
||||
for m in session.execute(select(ScenarioModifier).where(
|
||||
ScenarioModifier.scenario_id == scenario.id)).scalars()]
|
||||
flows = build_cashflows(recurring, planned, schedules, payouts, modifiers,
|
||||
start_date, end)
|
||||
ScenarioModifier.scenario_id == scenario.id)
|
||||
.order_by(ScenarioModifier.id)).scalars()]
|
||||
return ScenarioInputs(recurring, planned, loans, modifiers)
|
||||
|
||||
|
||||
def run_projection(session: Session, scenario: Scenario, horizon_days: int,
|
||||
start_date: date) -> ProjectionResult:
|
||||
end = start_date + timedelta(days=horizon_days)
|
||||
inputs = load_scenario_inputs(session, scenario)
|
||||
schedules = [loan_schedule(Decimal(l.principal), Decimal(l.annual_rate_pct),
|
||||
l.term_months, l.payout_date, l.repayment_type)
|
||||
for l in inputs.loans]
|
||||
payouts = [(l.payout_date, Decimal(l.principal)) for l in inputs.loans]
|
||||
flows = build_cashflows(inputs.recurring, inputs.planned, schedules, payouts,
|
||||
inputs.modifiers, start_date, end)
|
||||
proj = project(total_balance(session), start_date, flows, horizon_days,
|
||||
threshold=get_settings().warn_threshold)
|
||||
session.execute(delete(ProjectionPoint).where(
|
||||
|
||||
Reference in New Issue
Block a user