Files
Moon-Household-Budget/mortgage.py
2026-06-06 19:12:43 -05:00

122 lines
3.8 KiB
Python

import json
import os
from datetime import date
from dateutil.relativedelta import relativedelta
import math
from storage import load_json, save_json
DATA_DIR = os.environ.get("DATA_DIR", os.path.join(os.path.dirname(__file__), "data"))
MORTGAGE_FILE = os.path.join(DATA_DIR, "mortgage.json")
def load_mortgage():
return load_json(MORTGAGE_FILE, {})
def save_mortgage(data):
save_json(MORTGAGE_FILE, data)
def monthly_payment(principal, annual_rate, term_years):
if annual_rate == 0:
return round(principal / (term_years * 12), 2)
r = (annual_rate / 100) / 12
n = term_years * 12
payment = principal * (r * (1 + r) ** n) / ((1 + r) ** n - 1)
return round(payment, 2)
def amortization_schedule(principal, annual_rate, term_years, start_date_str, extra_monthly=0):
r = (annual_rate / 100) / 12 if annual_rate > 0 else 0
n = term_years * 12
base_payment = monthly_payment(principal, annual_rate, term_years)
total_payment = base_payment + extra_monthly
try:
current = date.fromisoformat(start_date_str)
except Exception:
current = date.today().replace(day=1)
schedule = []
balance = principal
total_interest = 0
total_principal = 0
for i in range(1, n + 1):
if balance <= 0:
break
interest = round(balance * r, 2)
principal_paid = min(round(total_payment - interest, 2), balance)
if principal_paid < 0:
principal_paid = 0
balance = round(balance - principal_paid, 2)
total_interest += interest
total_principal += principal_paid
schedule.append({
"month": i,
"date": current.strftime("%b %Y"),
"payment": round(interest + principal_paid, 2),
"principal": principal_paid,
"interest": interest,
"balance": max(balance, 0),
})
if balance <= 0:
break
current = current + relativedelta(months=1)
return schedule, round(total_interest, 2), len(schedule)
def biweekly_schedule(principal, annual_rate, term_years, start_date_str):
base = monthly_payment(principal, annual_rate, term_years)
biweekly_payment = round(base / 2, 2)
r_biweekly = (annual_rate / 100) / 26 if annual_rate > 0 else 0
try:
current = date.fromisoformat(start_date_str)
except Exception:
current = date.today().replace(day=1)
balance = principal
total_interest = 0
payments = 0
max_payments = term_years * 26 + 52
while balance > 0 and payments < max_payments:
interest = round(balance * r_biweekly, 2)
principal_paid = min(round(biweekly_payment - interest, 2), balance)
if principal_paid < 0:
principal_paid = 0
balance = round(balance - principal_paid, 2)
total_interest += interest
payments += 1
if balance <= 0:
break
years = payments / 26
months_total = round(payments / 26 * 12)
return round(total_interest, 2), payments, round(years, 1), months_total
def current_balance_estimate(principal, annual_rate, term_years, start_date_str, extra_monthly=0):
schedule, _, _ = amortization_schedule(
principal, annual_rate, term_years, start_date_str, extra_monthly)
today = date.today()
for row in schedule:
try:
row_date = date.strptime(row["date"], "%b %Y") if False else None
except Exception:
pass
# Count months elapsed
try:
start = date.fromisoformat(start_date_str)
months_elapsed = (today.year - start.year) * 12 + (today.month - start.month)
months_elapsed = max(0, months_elapsed)
if months_elapsed < len(schedule):
return schedule[months_elapsed]["balance"]
return 0
except Exception:
return principal