Skip to content

Commit

Permalink
Merge pull request #134 from Josh-Voyles/bug-fixes
Browse files Browse the repository at this point in the history
Bug fixes
  • Loading branch information
Josh-Voyles authored Jul 2, 2024
2 parents e20a42a + bdc9b57 commit 87bd607
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 252 deletions.
310 changes: 172 additions & 138 deletions home-choice-pro/models/affordability_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,164 +20,198 @@
class AffordabilityCalculator:
"""Calculates the affordability based on financial parameters"""

# Class Variables
monthly_payment: float
down_payment: float
interest_rate: float
loan_term: float
home_affordability_price: int
hoa_monthly_fee: float
property_tax_percentage: float
homeowners_insurance_percentage: float
pmi_percentage: float

# Constructor
def __init__(self, monthly_payment: str = "0", down_payment: str = "0",
interest_rate: str = "0", loan_term: str = "0", hoa_monthly_fee: str = "0",
property_tax_percentage: str = "0", homeowners_insurance_percentage: str = "0",
pmi_percentage: str = "0"):
def __init__(self):
"""Initializes class variables."""
self.monthly_payment = self.convert_string_number_into_float(monthly_payment)
self.down_payment = self.convert_string_number_into_float(down_payment)
self.interest_rate = self.convert_string_number_into_float(interest_rate)
self.loan_term = self.convert_string_number_into_float(loan_term)
self.hoa_monthly_fee = self.convert_string_number_into_float(hoa_monthly_fee)
self.property_tax_percentage = self.convert_string_number_into_float(
property_tax_percentage)
self.homeowners_insurance_percentage = self.convert_string_number_into_float(
homeowners_insurance_percentage)
self.pmi_percentage = self.convert_string_number_into_float(pmi_percentage)

# Variable Checking Functions
def _user_inputs_are_valid(self) -> bool:
"""Verifies user inputted class variables are valid."""
class_variables = [
self.monthly_payment,
self.down_payment,
self.interest_rate,
self.loan_term,
self.hoa_monthly_fee,
self.property_tax_percentage,
self.homeowners_insurance_percentage,
self.pmi_percentage
]
if any(var == -1.0 for var in class_variables):
return False
return True

@staticmethod
def convert_string_number_into_float(number) -> float:
"""Converts a string representing a number into a float (if possible)."""
try:
parsed_number = float(number)
if parsed_number >= 0:
return parsed_number
return -1.0
except ValueError:
return -1.0
# to return
self._max_home_price: int = 0
self._total_loan_cost: int = 0
self._total_loan_principal: int = 0
self._total_loan_interest: int = 0

def get_max_home_price(self) -> int:
return self._max_home_price

def get_total_loan_cost(self) -> int:
return self._total_loan_cost

def get_total_loan_principal(self) -> int:
return self._total_loan_principal

def get_total_loan_interest(self) -> int:
return self._total_loan_interest

def process_affordability(
self,
monthly_payment,
down_payment,
interest_rate,
loan_term,
hoa_monthly,
property_tax,
house_insurance,
pmi,
) -> int:

# convert to needed format
interest_rate = interest_rate / 100 / 12 # convert to monthly
loan_term = loan_term * 12
property_tax = property_tax / 100 / 12
house_insurance = house_insurance / 100 / 12
pmi = pmi / 100 # monthly converter later

self._max_home_price = self._calculate_max_home_price(
monthly_payment,
down_payment,
interest_rate,
loan_term,
hoa_monthly,
property_tax,
house_insurance,
pmi,
)
self._total_loan_cost = self._calculate_total_loan_price(
self._max_home_price, down_payment, interest_rate, loan_term
)
self._total_loan_principal = self._calculate_total_loan_principal(
self._max_home_price, down_payment
)
self._total_loan_interest = self._calculate_loan_interest(
self._max_home_price, down_payment, interest_rate, loan_term
)

# Calculation Functions
def calculate_home_affordability_price(self) -> int:
def _calculate_max_home_price(
self,
monthly_payment,
down_payment,
interest_rate,
loan_term,
hoa_monthly,
property_tax,
house_insurance,
pmi,
) -> int:
"""Calculates the maximum home price that a user can afford."""
if not self._user_inputs_are_valid():
return -1
numerator = self._calculate_numerator()
denominator = self._calculate_denominator()
loan_affordability_price = self._calculate_loan_affordability(numerator, denominator)
self.home_affordability_price = round(loan_affordability_price + self.down_payment)
self._adjust_for_property_taxes_and_insurance()
return round(self.home_affordability_price)

def calculate_total_home_loan_price(self) -> int:
numerator = self._calculate_numerator(interest_rate, loan_term)
denominator = self._calculate_denominator(interest_rate, loan_term)

monthly_payment = monthly_payment - hoa_monthly

if monthly_payment < 0:
return 0 # not a possible scenario, so zero
elif numerator == 0:
max_home_price = monthly_payment * loan_term # mythical zero interest loan
else:
max_home_price = (monthly_payment * denominator) / numerator # normal loan

max_home_price = round(max_home_price + down_payment)

max_home_price = self._adjust_for_property_taxes_and_insurance(
monthly_payment,
max_home_price,
down_payment,
interest_rate,
loan_term,
property_tax,
hoa_monthly,
house_insurance,
pmi,
)

return round(max_home_price)

def _calculate_total_loan_price(
self, max_home_price, down_payment, interest_rate, loan_term
) -> int:
"""Calculates the total cost of a home loan over the loan term."""
monthly_payment = self._calculate_monthly_mortgage_payment()
total_home_loan_price = monthly_payment * self._convert_loan_term_length_into_months()
monthly_payment = self._calculate_monthly_mortgage_payment(
max_home_price, down_payment, interest_rate, loan_term
)
total_home_loan_price = monthly_payment * loan_term
return round(total_home_loan_price)

def calculate_loan_principal(self) -> int:
def _calculate_total_loan_principal(self, max_home_price, down_payment) -> int:
"""Calculates the loan's principal."""
loan_principal = self.home_affordability_price - self.down_payment
loan_principal = max_home_price - down_payment
return round(loan_principal)

def calculate_loan_interest(self) -> int:
def _calculate_loan_interest(
self, max_home_price, down_payment, interest_rate, loan_term
) -> int:
"""Calculates the loan's interest."""
total_home_loan_price = float(self.calculate_total_home_loan_price())
loan_principal = float(self.calculate_loan_principal())
total_home_loan_price = self._calculate_total_loan_price(
max_home_price, down_payment, interest_rate, loan_term
)
loan_principal = self._calculate_total_loan_principal(
max_home_price, down_payment
)
loan_interest = total_home_loan_price - loan_principal
return round(loan_interest)

# Helper Functions
def _convert_annual_interest_rate_to_monthly_interest_rate(self) -> float:
"""Converts annual interest rate to monthly interest rate."""
return self.interest_rate / 100 / 12

def _convert_loan_term_length_into_months(self) -> float:
"""Converts loan term length from years to months."""
return self.loan_term * 12

def _calculate_numerator(self) -> float:
"""Helper function for the calculate_home_affordability_price() function."""
interest_rate = self._convert_annual_interest_rate_to_monthly_interest_rate()
loan_term = self._convert_loan_term_length_into_months()
return interest_rate * math.pow((1 + interest_rate), loan_term)

def _calculate_denominator(self) -> float:
"""Helper function for the calculate_home_affordability_price() function."""
interest_rate = self._convert_annual_interest_rate_to_monthly_interest_rate()
loan_term = self._convert_loan_term_length_into_months()
return math.pow((1 + interest_rate), loan_term) - 1

def _calculate_loan_affordability(self, numerator, denominator) -> float:
"""Helper function for the calculate_home_affordability_price() function."""
monthly_payment = self.monthly_payment - self.hoa_monthly_fee
if monthly_payment < 0: # Desired monthly payment can't be less than monthly HOA fees
return -1
loan_term = self._convert_loan_term_length_into_months()
if numerator == 0: # Numerator is 0 if interest rate inputted was 0%
return monthly_payment * loan_term
return (monthly_payment * denominator) / numerator # If interest rate > 0%, return this

def _calculate_monthly_mortgage_payment(self) -> float:
"""Helper function for the calculate_total_home_loan_price() function."""
loan_amount = self.home_affordability_price - self.down_payment
interest_rate = self._convert_annual_interest_rate_to_monthly_interest_rate()
loan_term = self._convert_loan_term_length_into_months()
if interest_rate == 0:
if loan_term == 0:
monthly_payment = 0
else:
monthly_payment = loan_amount / loan_term
else:
monthly_payment = loan_amount * (
interest_rate * math.pow(1 + interest_rate, loan_term)) / (
math.pow(1 + interest_rate, loan_term) - 1)
return monthly_payment

def _adjust_for_property_taxes_and_insurance(self):
def _adjust_for_property_taxes_and_insurance(
self,
monthly_payment,
max_home_price,
down_payment,
interest_rate,
loan_term,
property_tax,
hoa_monthly,
house_insurance,
pmi,
) -> int:
"""Helper function to factor property taxes into home affordability price."""
monthly_property_tax_rate = self.property_tax_percentage / 100 / 12
monthly_insurance_rate = self.homeowners_insurance_percentage / 100 / 12
while True:
monthly_tax_payment = self.home_affordability_price * monthly_property_tax_rate
monthly_insurance_payment = self.home_affordability_price * monthly_insurance_rate
monthly_pmi_payment = self._calculate_monthly_pmi_payment()
estimated_monthly_payment = self._calculate_monthly_mortgage_payment() + \
monthly_tax_payment + \
monthly_insurance_payment + \
monthly_pmi_payment
if estimated_monthly_payment <= (self.monthly_payment - self.hoa_monthly_fee):
break
self.home_affordability_price -= 1

def _calculate_monthly_pmi_payment(self) -> float:
monthly_tax_payment = max_home_price * property_tax
monthly_insurance_payment = max_home_price * house_insurance
monthly_pmi_payment = self._calculate_monthly_pmi_payment(
max_home_price, down_payment, pmi
)

estimated_monthly_payment = (
self._calculate_monthly_mortgage_payment(
max_home_price, down_payment, interest_rate, loan_term
)
+ monthly_tax_payment
+ monthly_insurance_payment
+ monthly_pmi_payment
)
if estimated_monthly_payment <= monthly_payment:
return max_home_price
max_home_price -= 1

def _calculate_monthly_pmi_payment(
self, max_home_price, down_payment, pmi
) -> float:
"""Helper function to factor PMI percentage into home affordability price."""
loan_amount = self.calculate_loan_principal()
if loan_amount == 0 or self.home_affordability_price == 0:
loan_amount = self._calculate_total_loan_principal(max_home_price, down_payment)
if loan_amount == 0 or max_home_price == 0:
return 0.0
loan_to_value_ratio = loan_amount / self.home_affordability_price
loan_to_value_ratio = loan_amount / max_home_price
if loan_to_value_ratio <= 0.8:
return 0.0
pmi_percentage = self.pmi_percentage / 100
annual_premium = loan_amount * pmi_percentage
annual_premium = loan_amount * pmi
monthly_premium = annual_premium / 12
return monthly_premium

def _calculate_monthly_mortgage_payment(
self, max_home_price, down_payment, interest_rate, loan_term
) -> float:
"""Helper function for the _calculate_total_loan_price() function."""
loan_amount = max_home_price - down_payment
if interest_rate == 0:
monthly_payment = loan_amount / loan_term
else:
numerator = self._calculate_numerator(interest_rate, loan_term)
denominator = self._calculate_denominator(interest_rate, loan_term)
monthly_payment = loan_amount * numerator / denominator
return monthly_payment

def _calculate_numerator(self, interest_rate, loan_term) -> float:
"""Helper function for the calculate_max_home_price() function."""
return interest_rate * math.pow((1 + interest_rate), loan_term)

def _calculate_denominator(self, interest_rate, loan_term) -> float:
"""Helper function for the calculate_max_home_price() function."""
return math.pow((1 + interest_rate), loan_term) - 1
Loading

0 comments on commit 87bd607

Please sign in to comment.