300 lines
11 KiB
Python
300 lines
11 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 getPaymentHistory(datastore, loan, asOfDate):
|
|
pass
|
|
|
|
|
|
def getAmortization(datastore, loan, paymentHistory):
|
|
pass
|
|
|
|
|
|
def loadLoanInformation(filename):
|
|
datastore = getDatastore(filename)
|
|
loanModel = {}
|
|
|
|
loanModel['loan'] = getLoan(datastore)
|
|
loanModel['lender'] = getLender(datastore)
|
|
loanModel['borrower'] = getBorrower(datastore)
|
|
loanModel['paymentHistory'] = getPaymentHistory(datastore, loan, asOfDate)
|
|
loanModel['futureAmortization'] = getAmortization(datastore, loan, loanModel.paymentHistory)
|
|
return loanModel
|
|
|
|
|
|
def getLoan(datastore):
|
|
# read in the loan profile information
|
|
loan = {}
|
|
|
|
annual_rate = Decimal(datastore['loan.interest rate']) / 100
|
|
daily_interest_rate = annual_rate / 360
|
|
principal = Decimal(datastore["loan.principal"]).quantize(Decimal("1.00"))
|
|
periods_per_year = Decimal(datastore["loan.periods per year"])
|
|
total_periods = Decimal(datastore["loan.periods"])
|
|
payment_day_of_month = int(datastore['loan.payment day of month'])
|
|
|
|
if "monthly_payment" in datastore:
|
|
monthly_payment = Decimal(datastore["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['loan.account_number'] = '123456789'
|
|
loan['principal'] = principal
|
|
loan['term'] = total_periods
|
|
loan['annual_rate'] = annual_rate
|
|
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'
|
|
return loan
|
|
|
|
|
|
def getLender(datastore):
|
|
lender = {}
|
|
# to be replaced with real loading code
|
|
lender['name'] = 'Rivanna Graphite Investments, LLC'
|
|
lender['phone'] = '703.343.0782'
|
|
lender['address'] = '743 Madison St NW'
|
|
lender['city'] = 'Washington'
|
|
lender['state'] = 'DC'
|
|
lender['zip'] = '20011'
|
|
return getLender
|
|
|
|
|
|
def getBorrower(datastore):
|
|
borrower = {}
|
|
# to be replaced with real loading code
|
|
borrower['name'] = 'Bear Houses, LLC'
|
|
borrower['address'] = '123 Any Street'
|
|
borrower['city'] = 'Alltown'
|
|
borrower['state'] = 'VA'
|
|
borrower['zip'] = '11111'
|
|
return 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 calculateLoanAmortization(loanModel):
|
|
return loanModel
|
|
|
|
|
|
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)
|
|
|
|
report = template.render(original_principal_balance=loanModel.principal, future_payments=loanModel.future_payments,
|
|
past_payments=loanModel.past_payments, balloon_payment=loanModel.remaining_principal,
|
|
total_interest_paid_to_date=loanModel.total_interest, statement=loanModel.statement)
|
|
return report
|
|
|
|
|
|
def generatePDFStatement():
|
|
template_filename = "statement.pdf.jinja"
|
|
pass
|
|
|
|
|
|
def generateHTMLStatement():
|
|
pass
|
|
|
|
|
|
def generateTextStatement():
|
|
pass
|
|
|
|
|
|
def generateEmail(from_address, to_address, subject, body, attachment):
|
|
msg = MIMEMultipart()
|
|
msg['Subject'] = subject
|
|
msg['From'] = from_address
|
|
msg['To'] = to_address
|
|
|
|
msg.attach(MIMEText(body))
|
|
|
|
part = MIMEBase('application', "octet-stream")
|
|
part.set_payload(attachment)
|
|
Encoders.encode_base64(part)
|
|
|
|
part.add_header('Content-Disposition', 'attachment; filename="statement.pdf"')
|
|
|
|
msg.attach(part)
|
|
return msg
|
|
|
|
|
|
def main():
|
|
# 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.
|
|
|
|
from_address = 'jkent3rd@gmail.com'
|
|
passwd = "pvyrbcnzrjoizprn"
|
|
subject = "Mortgage or Loan Statement"
|
|
to_address = 'jkent3rd@gmail.com'
|
|
body = 'This email contains a PDF of your mortgage statement.'
|
|
|
|
# read in the file
|
|
# filename = "./10Kloan.txt"
|
|
# filename = "./10Kloan.test.txt"
|
|
# filename = "./dadmortgage.txt"
|
|
# filename = "./brendamortgage.txt"
|
|
filename = "./greenfield_mortgage.txt"
|
|
template_filename = "statement.pdf.jinja"
|
|
|
|
loanInformation = loadLoanInformation(filename)
|
|
|
|
loanModel = calculateLoanAmortization(loanInformation)
|
|
|
|
# read in the statement information
|
|
statement, lender, borrower, loan = {}, {}, {}, {}
|
|
lender = loanModel.lender
|
|
borrower = loanModel.borrower
|
|
past_payments = loanModel.past_payments
|
|
future_payments = loanModel.future_payments
|
|
|
|
statement['title'] = "Mortgage Statement - 185 James River Rd"
|
|
statement['date'] = "Today"
|
|
statement['lender'] = lender
|
|
statement['borrower'] = borrower
|
|
statement['loan'] = loan
|
|
|
|
# loop over the payments and calculate the actual amortization
|
|
actual_payments = loanModel["payments"]
|
|
|
|
remaining_principal = loanModel.principal
|
|
payment_day_of_month = loanInformation.payment_day_of_month
|
|
daily_interest_rate = loanInformation.daily_interest_rate
|
|
total_periods = loanInformation.total_periods
|
|
annual_interest = 0
|
|
total_interest = 0
|
|
|
|
interest_paid_through_date = datetime.strptime(loanModel["start_interest_date"], "%Y-%m-%d").date()
|
|
next_payment_date = datetime.strptime(loanModel["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)
|
|
old_bill_date = next_bill_date
|
|
payment_number = 1
|
|
current_year = next_bill_date.year
|
|
|
|
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
|
|
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['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)
|
|
|
|
# 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_interest
|
|
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)
|
|
|
|
report = transformTemplate('', loanModel)
|
|
|
|
# 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')
|
|
attachment = pdf.output(dest='S')
|
|
|
|
# send email
|
|
try:
|
|
server = smtplib.SMTP('smtp.gmail.com', 587)
|
|
server.ehlo()
|
|
server.starttls()
|
|
server.login(from_address, passwd)
|
|
|
|
email = generateEmail(from_address, to_address, subject, body, attachment)
|
|
server.sendmail(from_address, to_address, email.as_string())
|
|
server.close()
|
|
except:
|
|
print "Couldn't send email, dumping statement to file."
|
|
pdf.output(name='test.pdf', dest='F')
|
|
quit()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|