from __future__ import annotations from datetime import date from enum import Enum from typing import Literal from pydantic import BaseModel, Field class InstallmentType(str, Enum): equal = "equal" decreasing = "decreasing" class OverpaymentEffect(str, Enum): shorten = "shorten" lower_payment = "lower_payment" class GraceType(str, Enum): none = "none" interest_only = "interest_only" full = "full" class RateChange(BaseModel): month: int = Field(ge=1, description="Miesiac od startu kredytu") annual_rate: float = Field(ge=0, le=30, description="Roczne oprocentowanie procentowo") class Overpayment(BaseModel): month: int = Field(ge=1) amount: float = Field(gt=0) repeat: Literal["once", "monthly", "yearly"] = "once" until_month: int | None = Field(default=None, ge=1) commission_percent: float = Field(default=0, ge=0, le=20, description="Prowizja od nadplaty w procentach") commission_until_month: int | None = Field(default=None, ge=1, description="Ostatni miesiac naliczania prowizji od nadplaty") class HistoricalMonth(BaseModel): month: int = Field(ge=1) annual_rate: float | None = Field(default=None, ge=0, le=30) grace_type: GraceType = GraceType.none overpayment: float = Field(default=0, ge=0) overpayment_commission_percent: float = Field(default=0, ge=0, le=20) note: str = "" class SimulationRequest(BaseModel): principal: float = Field(gt=0) years: int = Field(default=25, ge=1, le=50) term_months: int | None = Field(default=None, ge=1, le=600, description="Okres kredytu w miesiacach") margin: float = Field(ge=0, le=20, default=2.0) base_rate: float = Field(ge=0, le=30, default=5.75) installment_type: InstallmentType = InstallmentType.equal overpayment_effect: OverpaymentEffect = OverpaymentEffect.shorten loan_start_date: date = Field(default_factory=date.today) due_day: int = Field(default=5, ge=1, le=28, description="Dzien splaty raty") move_due_date_to_business_day: bool = True overpayment_protection_months: int | None = Field(default=None, ge=1, description="Okres ochronny prowizji od nadplat w miesiacach") overpayment_protection_commission_percent: float = Field(default=0, ge=0, le=20, description="Domyslna prowizja od nadplat w okresie ochronnym") rate_changes: list[RateChange] = Field(default_factory=list) overpayments: list[Overpayment] = Field(default_factory=list) historical_months: list[HistoricalMonth] = Field(default_factory=list) class ScheduleRow(BaseModel): month: int due_date: str days: int rate: float payment: float principal_part: float interest_part: float overpayment: float overpayment_fee: float remaining: float grace_type: GraceType = GraceType.none cumulative_interest: float cumulative_cost: float cumulative_overpayment: float class Summary(BaseModel): months: int total_paid: float total_interest: float total_overpayment: float total_overpayment_fees: float interest_saved: float months_saved: int baseline_interest: float baseline_months: int average_payment: float max_payment: float payoff_date: str | None = None class SimulationResponse(BaseModel): schedule: list[ScheduleRow] summary: Summary