At this state, it produces a nice web-based viewer of the loan files including the payment history and future amortization.

This commit is contained in:
JohnKent
2019-07-07 14:59:33 -04:00
parent 27cf64aced
commit a304869708
2 changed files with 403 additions and 0 deletions

159
templates/main.html Normal file
View File

@@ -0,0 +1,159 @@
<html>
<head>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() { $( "#tabs" ).tabs(); } );
</script>
<title>Loan Management</title>
</head>
<body>
<p align="center">Web Mortgage Manager</p>
<form>
<table><tr>
<td>Loan:</td>
<td>
<select name="loan">
{% for loan in loans %}
{% if loan.filename==filename %}
<option value="{{loan.filename}}" selected>{{loan.name}}</option>
{% else %}
<option value="{{loan.filename}}">{{loan.name}}</option>
{% endif %}
{% endfor %}
</select>
<button>Select</button>
</td>
</table>
</form>
</p>
<div id="tabs">
<ul>
<li><a href="#loan_information">Loan Information</a></li>
<li><a href="#loan_history">Loan History</a></li>
<li><a href="#loan_amortization">Future Amortization</a></li>
<li><a href="#email">Email Statement</a></li>
<li><a href="#add_payment">Add Payment</a></li>
</ul>
<div id="loan_information">
<table align="center" border="1px">
<thead><tr><th colspan='2' width='60%' align='center'>Loan Information</th></tr></thead>
<tbody>
<tr>
<td><b>Lender:</b></td>
<td>{{ model.lender.name }}<br/>{{ model.lender.address }}<br/>
{{ model.lender.city }} {{model.lender.state }} {{ model.lender.zip }}<br/>
{{ model.lender.phone }}
</td></tr>
<tr><td><b>Borrower:</b></td><td>{{ model.borrower.name }}&nbsp;<br/>{{ model.borrower.address }}&nbsp;
<br/>{{ model.borrower.city }}, {{model.borrower.state }} {{ model.borrower.zip }}
</td></tr>
<tr><td><b>Account Number:</b></td><td>{{ model.parameters.account_number }}</td></tr>
<tr><td><b>Origination Date:</b></td><td> {{ model.parameters.start_date }}</td></tr>
<tr><td><b>Original Principal:</b></td><td>{{ "$%.2f"|format(model.parameters.principal) }}</td></tr>
<tr><td><b>Rate:</b></td><td>{{model.parameters.interest_rate }}% </td></tr>
<tr><td><b>Term: </b></td><td>{{model.parameters.periods }} months </td></tr>
<tr><td><b>Next Payment Due Date:</b></td><td> {{model.parameters.next_due_date}} </td></tr>
<tr><td><b>Payment Due:</b></td><td> {{ "$%.2f"|format(model.parameters.next_payment_amt) }} </td></tr>
</tbody>
</table>
<p/>
</div>
<div id="loan_history">
<table border="1px" align="center">
<thead>
<tr>
<th colspan="8">Loan History</th>
</tr>
<tr>
<th width='5%'>#</td>
<th width='10%'>Due Date</th>
<th width='10%'>Date Paid</th>
<th width='10%'>Days Interest</th>
<th width='15%' align='right'>Payment Amt</th>
<th width='15%' align='right'>Principal Pmt</th>
<th width='15%' align='right'>Interest Pmt</th>
<th width='20%' align='right'>New Balance</th>
</tr>
</thead>
<tbody>
{% for item in model.past_payments %}
<tr>
<td align='center'> {{ item.payment_number }} </td>
<td align='center'> {{ item.bill_date }} </td>
<td align='center'> {{ item.payment_date }} </td>
<td align='center'> {{ item.days_of_interest }} </td>
<td align='right'> {{ "$%.2f"|format(item.payment_amount) }} </td>
<td align='right'> {{ "$%.2f"|format(item.principal_payment) }} </td>
<td align='right'> {{ "$%.2f"|format(item.interest_payment) }} </td>
<td align='right'> {{ "$%.2f"|format(item.new_balance) }} </td>
</tr>
{% if item.month == 12 or loop.last %}
<tr><td colspan='8'"> Total interest paid in {{item.year}} is {{ "$%.2f"|format(item.annual_interest_to_date) }}.</td></tr>
{% endif %}
{% endfor %}
<tr><td colspan='8'"> Total interest paid to date is {{ "$%.2f"|format(model.total_interest_paid_to_date) }}.</td></tr>
</tbody>
</table>
</div>
<div id="loan_amortization">
<table border="1px">
<thead>
<tr>
<th colspan="7">Remaining Amortization</th>
</tr>
<tr>
<th width='8%'>#</th>
<th width='15%'>Due Date</th>
<th width='8%'>Days Interest</th>
<th width='15%' align='right'>Payment Amt</th>
<th width='15%' align='right'>Principal Pmt</th>
<th width='15%' align='right'>Interest Pmt</th>
<th width='20%' align='right'>Principal Balance</th>
</tr>
</thead>
<tbody>
{% for item in model.future_payments %}
<tr><td align='center'> {{ item.payment_number }} </td>
<td align='center'> {{ item.payment_date }} </td>
<td align='center'> {{ item.days_of_interest }} </td>
<td align='right'> {{ "$%.2f"|format(item.payment_amount) }} </td>
<td align='right'> {{ "$%.2f"|format(item.principal_payment) }} </td>
<td align='right'> {{ "$%.2f"|format(item.interest_payment) }} </td>
<td align='right'> {{ "$%.2f"|format(item.new_balance) }} </td>
</tr>
{% endfor %}
<tr colspan="7">Balloon Payment Due: {{ "$%.2f"|format(model.balloon_payment) }}</tr>
</tbody>
</table>
</div>
<div id="email">
<form name="Send Statement" method="post" action="/send_statement">
<table>
<tr><td colspan="2">Generate and Send Statement</td></tr>
<tr><td>Send From:</td><td>{{model.email.from_address}}</td></tr>
<tr><td>Send To:</td><td>{{model.email.to_address}}</td></tr>
<tr><td>Topic:</td><td><input type="text" name="title" value="{{model.email.subject}}"></td></tr>
<tr><td>Message Body:</td><td><input type="textarea" name="message" value="{{model.email.body}}"></td></tr>
<tr><td>Send Statement As:</td><td><select name="style">
<option value="attach">Attachment</option>
<option value="embed">Embedded</option>
</select></td></tr>
<tr><td>Include Future Amortization</td>
<td><select name="amort">
<option value="yes">Yes</option>
<option value="no">No</option>
</select></td></tr>
<tr><td><button>Send Statement</button></td></tr>
</table>
<input hidden="loan" name='loan' value="{{filename}}" />
</form>
</div>
<div id="add_payment">
<p>Add Loan Payment Tab</p>
</div>
</div>
</body>
</html>

244
web.py Normal file
View File

@@ -0,0 +1,244 @@
from flask import Flask, render_template, request, redirect, url_for
import json
from decimal import *
from datetime import *
from jinja2 import Environment, FileSystemLoader
from fpdf import FPDF, HTMLMixin
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email import Encoders
app = Flask(__name__)
@app.route('/')
def hello():
loans = []
loans.append( addLoan("1 Greenfield Lane Mortgage", "greenfield_mortgage.json" ) )
loans.append( addLoan("2 Bear Houses LLC Loan", "9Kloan.json" ) )
loans.append( addLoan("3 Brenda Mortgage", "brendamortgage.json" ) )
loans.append( addLoan("4 Dad Mortgage", "dadmortgage.json") )
if 'loan' in request.args:
filename = request.args["loan"]
else:
return redirect('/?loan=' + loans[0]['filename'])
loan = loadLoanInformation('/Users/john/PycharmProjects/mortgage/' + filename)
amortizeLoan(loan)
return render_template('main.html', filename=filename, loans=loans, model=loan)
@app.route('/update_file')
def update_file():
return
@app.route('/send_statement', methods=['POST'])
def send_statement():
loan = request.form["loan"]
redirect( '/?loan=' + loan )
def addLoan(loanName, fileName):
x = {}
x['name'] = loanName
x['filename'] = fileName
return x
'''from old code'''
def getStatementHeader(datastore):
return datastore['header']
def getEmailInformation(datastore):
return datastore['email']
def loadLoanInformation(filename):
datastore = getDatastore(filename)
loanModel = {}
loanModel['datastore'] = datastore
loanModel['email'] = getEmailInformation(datastore)
loanModel['parameters'] = getLoanParameters(datastore)
loanModel['lender'] = getLender(datastore)
loanModel['borrower'] = getBorrower(datastore)
loanModel['header'] = getStatementHeader(datastore)
return loanModel
def getLoanParameters(datastore):
# read in the loan profile information
loan = datastore['parameters']
annual_rate = Decimal(loan['interest_rate']) / 100
daily_interest_rate = annual_rate / 360
principal = Decimal(loan["principal"]).quantize(Decimal("1.00"))
periods_per_year = Decimal(loan["periods_per_year"])
total_periods = Decimal(loan["periods"])
payment_day_of_month = int(loan['payment_day_of_month'])
if "monthly_payment" in loan:
monthly_payment = Decimal(loan["monthly_payment"]).quantize(Decimal("1.00"))
else:
# calculate expected monthly payment
periodic_rate = annual_rate / periods_per_year
discount_factor = (((1 + periodic_rate) ** total_periods) - 1) / (
periodic_rate * ((1 + periodic_rate) ** total_periods))
monthly_payment = (principal / discount_factor).quantize(Decimal("1.00"))
loan['principal'] = principal # standardizes the format
loan['annual_rate'] = annual_rate # standardizes the format
loan['daily_interest_rate'] = daily_interest_rate
loan['rate'] = '' + (annual_rate * 100).__str__() + '%'
loan['next_payment_amt'] = 0
loan['next_payment_date'] = '12/12/12'
loan['total_periods'] = total_periods
loan['monthly_payment'] = monthly_payment
datastore['parameters'] = loan
return loan
def getLender(datastore):
return datastore['lender']
def getBorrower(datastore):
return datastore['borrower']
def getDatastore(filename=None):
try:
if filename:
with open(filename, 'r') as f:
datastore = json.load(f)
except Exception as e:
print "An error occurred opening your loan file '%s'. " % filename
print "The Exception:"
print e.__repr__()
quit()
return datastore
def amortizeLoan(loan):
# loop over the payments and calculate the actual amortization
monthly_payment = loan["parameters"]["monthly_payment"]
actual_payments = loan["datastore"]["payments"]
remaining_principal = loan["parameters"]["principal"]
payment_day_of_month = int(loan["parameters"]["payment_day_of_month"])
daily_interest_rate = loan["parameters"]["daily_interest_rate"]
total_periods = loan["parameters"]["total_periods"]
interest_paid_through_date = datetime.strptime(loan["parameters"]["start_interest_date"], "%Y-%m-%d").date()
next_payment_date = datetime.strptime(loan["parameters"]["first_payment_month"], '%Y-%m-%d').date()
next_bill_date = date(year=next_payment_date.year, month=next_payment_date.month, day=payment_day_of_month)
payment_number = 1
annual_interest = 0
total_interest = 0
old_bill_date = next_bill_date
current_year = next_bill_date.year
past_payments = []
future_payments = []
for payment in actual_payments:
payment_date = datetime.strptime((payment[0]), '%Y-%m-%d').date()
payment_amount = Decimal(payment[1]).quantize(Decimal("1.00"))
days_since_last_payment = (payment_date - interest_paid_through_date).days
#check for out of order payments, generally a sign of a data entry problem, especially years
if days_since_last_payment < 0:
print "Payment Number %s appears out of order. The payment date '%s' is before the previous payment on '%s'." \
% (payment_number, payment_date, interest_paid_through_date)
quit()
new_interest = (days_since_last_payment * remaining_principal * daily_interest_rate).quantize(Decimal("0.00"))
new_principal = payment_amount - new_interest
interest_paid_through_date = payment_date
total_interest = total_interest + new_interest
annual_interest = annual_interest + new_interest
remaining_principal = remaining_principal - new_principal
# create the payment record for the template to render
payment_record = {}
payment_record['year']=next_bill_date.year
payment_record['month']=next_bill_date.month
payment_record['payment_number'] = payment_number
payment_record['bill_date'] = next_bill_date
payment_record['payment_date'] = payment_date
payment_record['days_of_interest'] = days_since_last_payment
payment_record['payment_amount'] = payment_amount
payment_record['principal_payment'] = payment_amount - new_interest
payment_record['interest_payment'] = new_interest
payment_record['new_balance'] = remaining_principal
payment_record['interest_to_date'] = total_interest
payment_record['annual_interest_to_date'] = annual_interest
past_payments.append(payment_record)
payment_number = payment_number + 1
old_bill_date = next_bill_date
if old_bill_date.month < 12:
next_bill_date = date(year=old_bill_date.year, month=old_bill_date.month + 1, day=payment_day_of_month)
else:
annual_interest = Decimal("0.00")
next_bill_date = date(year=old_bill_date.year + 1, month=1, day = payment_day_of_month)
loan["total_interest_paid_to_date"] = total_interest
loan["parameters"]["next_due_date"] = next_bill_date
if (remaining_principal < monthly_payment):
loan["parameters"]["next_payment_amt"] = remaining_principal
else:
loan["parameters"]["next_payment_amt"] = monthly_payment
# loop over remaining scheduled payments and present estimated amortization
while (payment_number <= total_periods) and (remaining_principal > 0):
days_since_last_payment = (next_bill_date - interest_paid_through_date).days
new_interest = (days_since_last_payment * remaining_principal * daily_interest_rate).quantize(Decimal("0.00"))
# make sure the last payment isn't too much
if new_interest + remaining_principal < monthly_payment:
monthly_payment = new_interest + remaining_principal
new_principal = monthly_payment - new_interest
remaining_principal = remaining_principal - new_principal
interest_paid_through_date = next_bill_date
# complete the future payment amortization record
future_payment_record = {}
future_payment_record['payment_number'] = payment_number
future_payment_record['payment_date'] = next_bill_date
future_payment_record['days_of_interest'] = days_since_last_payment
future_payment_record['payment_amount'] = monthly_payment
future_payment_record['principal_payment'] = new_principal
future_payment_record['interest_payment'] = new_interest
future_payment_record['new_balance'] = remaining_principal
future_payments.append(future_payment_record)
payment_number = payment_number + 1
old_bill_date = next_bill_date
if old_bill_date.month < 12:
next_bill_date = date(year=old_bill_date.year, month=old_bill_date.month + 1, day=payment_day_of_month)
else:
next_bill_date = date(year=old_bill_date.year + 1, month=1, day=payment_day_of_month)
loan["balloon_payment"] = remaining_principal
loan["past_payments"] = past_payments
loan["future_payments"] = future_payments
return
if __name__ == '__main__':
app.debug = True
app.run()