Source code for mortgagemath._summary

"""Loan summary convenience function."""

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal

from mortgagemath._payment import periodic_payment
from mortgagemath._schedule import amortization_schedule
from mortgagemath._types import LoanParams


[docs] @dataclass(frozen=True, slots=True) class LoanSummary: """High-level summary of a loan's cost and structure. Returned by :func:`loan_summary`. All monetary values are ``Decimal`` at the loan's currency-unit precision. """ periodic_payment: Decimal """Level payment per period (excludes fee_per_period).""" total_paid: Decimal """Sum of all scheduled payments over the life of the loan (includes fees). For balloon loans this does NOT include the balloon balance — see ``total_cost``.""" total_interest: Decimal """Cumulative interest paid over the life of the loan.""" total_fees: Decimal """Cumulative fees paid over the life of the loan.""" total_principal: Decimal """Principal repaid through scheduled payments. For fully amortizing loans this equals the original principal. For balloon loans this is less than the original principal by the balloon amount.""" balloon_balance: Decimal """Remaining balance at the end of the term. Zero for fully amortizing loans; the balloon amount for balloon loans.""" total_cost: Decimal """Total cash required to extinguish the debt at term: ``total_paid + balloon_balance``. For fully amortizing loans this equals ``total_paid``.""" num_payments: int """Number of payments in the schedule (excludes the initial balance row).""" def __repr__(self) -> str: """Compact repr showing payment, total interest, and total paid.""" parts = ( f"LoanSummary(payment={self.periodic_payment:,}, " f"total_interest={self.total_interest:,}, " f"total_paid={self.total_paid:,}" ) if self.balloon_balance: parts += f", balloon={self.balloon_balance:,}" parts += f", n={self.num_payments})" return parts
[docs] def loan_summary(loan: LoanParams) -> LoanSummary: """Compute a high-level summary of a loan's cost and structure. This is a convenience wrapper that calls :func:`periodic_payment` and :func:`amortization_schedule` internally and extracts the values most users need first. Args: loan: Loan parameters. Returns: A :class:`LoanSummary` with the periodic payment, total interest, total paid, total fees, balloon balance, total cost, and number of payments. Example:: >>> from mortgagemath import us_30_year_fixed, loan_summary >>> s = loan_summary(us_30_year_fixed("300000", "6.5")) >>> s.periodic_payment Decimal('1896.21') >>> s.total_interest Decimal('382628.90') >>> s.total_cost Decimal('682628.90') """ pmt = periodic_payment(loan) sched = amortization_schedule(loan) # Exclude row 0 (initial balance, no payment). payment_rows = sched[1:] n = len(payment_rows) _ZERO = Decimal("0") total_paid = sum((row.payment for row in payment_rows), _ZERO) total_interest = payment_rows[-1].total_interest if payment_rows else _ZERO total_fees = sum((row.fee for row in payment_rows), _ZERO) total_principal = sum((row.principal for row in payment_rows), _ZERO) balloon_balance = payment_rows[-1].balance if payment_rows else loan.principal total_cost = total_paid + balloon_balance return LoanSummary( periodic_payment=pmt, total_paid=total_paid, total_interest=total_interest, total_fees=total_fees, total_principal=total_principal, balloon_balance=balloon_balance, total_cost=total_cost, num_payments=n, )