Files
mortgage/mortgage_template.py

322 lines
12 KiB
Python

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
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
def transformTemplate(template_fileName, loanModel):
# template_filename = "statement.txt.jinja"
# setup jinja for creating the statement
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template(template_fileName)
print loanModel
report = template.render(model=loanModel)
return report
def generatePDFAttachment():
template_filename = "statement.pdf.jinja"
pass
def generateEmail(from_address, to_address, subject, body, pdfAttachment, txtAttachment):
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_address
msg['To'] = to_address
msg.attach(MIMEText(body))
if (pdfAttachment != None):
part = MIMEBase("application", "octet-stream")
part.set_payload(pdfAttachment)
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="statement.pdf"')
msg.attach(part)
if (txtAttachment != None):
part = MIMEBase("text", "html")
part.set_payload(txtAttachment)
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="statement.html"')
msg.attach(part)
return msg
def sendEmail(msg, from_address, to_address, passwd):
try:
server = smtplib.SMTP('smtp.gmail.com', 587)
server.ehlo()
server.starttls()
server.login(from_address, passwd)
server.sendmail(from_address, to_address, msg.as_string())
server.close()
except:
print "Couldn't send email."
def createPDF(report):
# create pdf
class MyFPDF(FPDF, HTMLMixin):
pass
pdf = MyFPDF()
pdf.set_font(family='Arial', size=12)
pdf.add_page()
pdf.write_html(report)
# pdf.output(name='test.pdf', dest='F')
return pdf.output(dest='S')
def selectTemplate(loan):
templateKey = loan["datastore"]["format"] + "Template"
if templateKey in loan:
template = loan[templateKey]
else:
template = 'statement.pdf.jinja'
return template
def main():
test_flag = True
# this program reads the json file describing a mortgage and calculate the actual and projected amortization
# the first payment in the file should be the interest paid at closing
# the interest at closing covers interest that would be incurred from the date of closing until the first day
# that will be covered by the first loan payment
# i.e., the first loan payment is expected a month and a half after closing, the first two weeks interest is due
# at closing. The first payment will incur interest from one month before the bill is due.
# read in the file
test_flag = False
test_address = 'jkent3rd@yahoo.com'
#filename = "./testloan.json"
filename = "./9Kloan.json"
#filename = "./dadmortgage.json"
#filename = "./brendamortgage.json"
#filename = "./greenfield_mortgage.json"
template_filename = "statement.pdf.jinja"
loan = loadLoanInformation(filename)
amortizeLoan(loan)
report = transformTemplate(selectTemplate(loan), loan)
if loan["email"]["send_pdf"] == "true":
pdfAttachment = createPDF(report)
# send email
emailParameters = loan["email"]
msg = generateEmail(emailParameters["from_address"], emailParameters["to_address"], emailParameters["subject"],
emailParameters["body"], pdfAttachment, report)
if test_flag == False:
sendEmail(msg, emailParameters["from_address"], emailParameters["to_address"], emailParameters['password'])
else:
sendEmail(msg, emailParameters["from_address"], test_address, emailParameters['password'])
if __name__ == '__main__':
main()