Source code for interventions.Loans

"""
Provides a class to model and calculate loan conditions for financing.

This module contains the `Loan` class, which is used to determine the terms of
a loan for purchasing a heating system. 

:Authors:
 - Ivan Digel <ivan.digel@uni-kassel.de>
"""
import math
import logging

logger = logging.getLogger("waermer.intervention.loan")

[docs] class Loan: """ Represents a loan for purchasing a heating system. This class encapsulates the logic for determining the terms of a loan. When an instance is created, it automatically calculates the loan amount, which is the lesser of the required amount (system price minus available funds) and the maximum affordable amount (based on income). It then computes the total repayment and monthly payment based on the loan term and interest rate using standard financial formulas. Parameters ---------- weekly_income : float The borrower's weekly net income in EUR. system_price : float The total price of the heating system in EUR. funds : float The amount of personal funds the borrower has available to put towards the purchase. interest : float, optional The annual interest rate as a decimal (e.g., 0.0221 for 2.21%), by default 0.0221. years : int, optional The loan term in years, by default 10. Attributes ---------- interest : float The annual interest rate. years : int The loan term in years. loan_amount : float The calculated principal amount of the loan in EUR. total_repayment : float The total amount that will be paid back over the life of the loan. monthly_payment : float The calculated monthly payment amount in EUR. """ def __init__(self, weekly_income, system_price, funds, interest=0.0221, years=10): """ Initialises a Loan instance and calculates its terms. The constructor determines the affordable loan amount based on income, calculates the required loan (price minus funds), and takes the minimum of the two as the principal. It then computes the total and monthly repayments. Parameters ---------- weekly_income : float The borrower's weekly net income in EUR. system_price : float The total price of the heating system in EUR. funds : float The amount of personal funds the borrower has available. interest : float, optional The annual interest rate, by default 0.0221. years : int, optional The loan term in years, by default 10. """ self.interest = interest # Annual interest rate self.years = years # Loan term in years # Step 1: Convert weekly net income to annual net income monthly_net_income = weekly_income * 4 # Step 2: Calculate the maximum affordable loan amount based on LTI ratio (LTV 100%) ltv_ratio = 1 #Maximum possible loan share from the price lti_multiplier = 5 #Yearly income x lti affordable_loan_amount = min((ltv_ratio*system_price), (monthly_net_income * 12 * lti_multiplier)) # Step 3: Calculate the required loan amount required_loan_amount = max(0, system_price - funds) # Step 4: Use the required loan amount if it's within the affordable limit; otherwise, use the maximum affordable amount self.loan_amount = math.ceil(min(required_loan_amount, affordable_loan_amount)) # Step 5: Calculate the monthly interest rate and total number of payments monthly_interest = interest / 12 total_payments = years * 12 # Step 6: Calculate the total repayment amount with monthly compound interest self.total_repayment = math.floor(self.loan_amount * ((1 + monthly_interest) ** total_payments)) # Step 7: Calculate monthly payment using the amortization formula payment = math.ceil(self.loan_amount * (monthly_interest * (1 + monthly_interest) ** total_payments) / ((1 + monthly_interest) ** total_payments - 1)) self.monthly_payment = math.floor(payment) if self.monthly_payment < 0: raise Exception("Negative monthly payment!")
[docs] def log_details(self): logger.info(f"Required loan amount: {self.required_loan_amount}") logger.info(f"Monthly net income: {self.monthly_net_income}") logger.info(f"Affordable loan amount: {self.affordable_loan_amount}") logger.info(f"Loan amount: {self.loan_amount}") logger.info(f"System costs: {self.system_price}") logger.info(f"Funds: {self.funds}")