From 53327a9e092a7b40350e04408431098752450154 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Wed, 19 Dec 2018 23:09:04 -0500
Subject: [PATCH 01/38] Initial commit. Things dont work yet. -- user:
JohnKent branch 'default' added greenfield_mortgage.txt added mortgage.py
added mortgage_template.py added statement.pdf.jinja added
statement.txt.jinja
---
greenfield_mortgage.txt | 25 ++++
mortgage.py | 130 ++++++++++++++++++
mortgage_template.py | 290 ++++++++++++++++++++++++++++++++++++++++
statement.pdf.jinja | 82 ++++++++++++
statement.txt.jinja | 49 +++++++
5 files changed, 576 insertions(+)
create mode 100644 greenfield_mortgage.txt
create mode 100644 mortgage.py
create mode 100644 mortgage_template.py
create mode 100644 statement.pdf.jinja
create mode 100644 statement.txt.jinja
diff --git a/greenfield_mortgage.txt b/greenfield_mortgage.txt
new file mode 100644
index 0000000..91bc430
--- /dev/null
+++ b/greenfield_mortgage.txt
@@ -0,0 +1,25 @@
+{
+"principal": 97750.00,
+"interest rate": 5.5,
+"periods per year": 12,
+"periods": 182,
+"start date": "2017-11-07",
+"start_interest_date": "2017-11-11",
+"first payment month": "2017-12-15",
+"monthly_payment": 803.00,
+"payment day of month": "15",
+"payments": [
+ ["2017-12-11", "803.00"],
+ ["2018-01-23", "803.00"],
+ ["2018-03-23", "803.00"],
+ ["2018-04-18", "803.00"],
+ ["2018-04-26", "803.00"],
+ ["2018-05-15", "0.00"],
+ ["2018-06-15", "0.00"],
+ ["2018-07-12", "803.00"],
+ ["2018-08-07", "803.00"],
+ ["2018-09-06", "803.00"],
+ ["2018-10-11", "803.00"],
+ ["2018-11-13", "803.00"]
+ ]
+}
diff --git a/mortgage.py b/mortgage.py
new file mode 100644
index 0000000..18a582e
--- /dev/null
+++ b/mortgage.py
@@ -0,0 +1,130 @@
+import json
+from decimal import *
+from datetime import *
+
+# 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
+#filename = "./10Kloan.txt"
+filename = "./dadmortgage.txt"
+#filename = "./brendamortgage.txt"
+#filename = "./greenfield_mortgage.txt"
+
+if filename:
+ with open(filename, 'r') as f:
+ datastore = json.load(f)
+
+#read in the loan profile information
+annual_rate = Decimal(datastore['interest rate'])/100
+daily_interest_rate = annual_rate/360
+principal = Decimal(datastore["principal"]).quantize(Decimal("1.00"))
+periods_per_year = Decimal(datastore["periods per year"])
+total_periods = Decimal(datastore["periods"])
+payment_day_of_month = int(datastore['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"))
+
+
+
+print "Principal: ", principal
+print "Interest Rate: ", annual_rate
+print "Payments Per Year: ", periods_per_year
+print "Loan Term: ", total_periods
+print "Monthly Payment", monthly_payment
+print "Payments due day: ", payment_day_of_month
+
+print
+
+#loop over the payments and calculate the actual amortization
+actual_payments = datastore["payments"]
+
+remaining_principal = principal
+annual_interest = 0
+total_interest = 0
+
+interest_paid_through_date = datetime.strptime(datastore["start_interest_date"], "%Y-%m-%d").date()
+next_payment_date = datetime.strptime(datastore["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
+
+print "Payment History:"
+print "\tBill\t\t\tPayment\tDays of\tPayment\t\tRemaining\t\tPrincipal\tInterest\t\t\tNew"
+print "#\tDate\t\t\tDate\tInterst\tAmount\t\tPrincipal\t\tPayment\t\tPayment\t\t\tBalance"
+for payment in actual_payments:
+ payment_date = datetime.strptime((payment[0]), '%Y-%m-%d').date()
+ payment_amount = Decimal(payment[1]).quantize(Decimal("1.00"))
+ #print next_bill_date, "\t", next_payment_date, "\t",
+ days_since_last_payment = (payment_date-interest_paid_through_date).days
+ #print days_since_last_payment
+ new_interest = (days_since_last_payment * remaining_principal * daily_interest_rate).quantize(Decimal("0.00"))
+# new_principal = monthly_payment - new_interest
+ new_principal = payment_amount - new_interest
+
+ print payment_number, " ", next_bill_date, "\t\t", payment_date, "\t", days_since_last_payment, "\t", payment_amount, "\t\t\t", remaining_principal, "\t\t", new_principal,\
+ "\t\t", 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
+ print "\t\t\t", remaining_principal
+ 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)
+ print "Total interest paid to date for", old_bill_date.year, " is", annual_interest
+ else:
+ next_bill_date = date(year=old_bill_date.year+1, month = 1, day=payment_day_of_month)
+ print "Total interest for ", old_bill_date.year, " was ", annual_interest
+ annual_interest = 0
+
+print "Total interest paid to date on this loan is", total_interest
+
+#loop over remaining scheduled payments and present estimated amortization
+print "\nEstimated future amortization"
+
+while (payment_number <= total_periods) and (remaining_principal > 0):
+ print payment_number, "\t",
+ payment_number = payment_number+1
+
+ print next_bill_date,
+
+ days_since_last_payment = (next_bill_date-interest_paid_through_date).days
+ print days_since_last_payment, "\t",
+ 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
+
+ print monthly_payment, "\t\t\t", remaining_principal, "\t\t", new_principal,\
+ "\t\t", new_interest,
+
+ remaining_principal = remaining_principal - new_principal
+ print "\t", remaining_principal
+
+ interest_paid_through_date = next_bill_date
+ 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)
+
+if remaining_principal > 0:
+ print 'Balloon payment due:', remaining_principal
diff --git a/mortgage_template.py b/mortgage_template.py
new file mode 100644
index 0000000..29ea278
--- /dev/null
+++ b/mortgage_template.py
@@ -0,0 +1,290 @@
+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 loadLoanInformation(filename):
+
+ datastore = getDataStore(filename)
+
+ loanModel['loan'] = getLoan(dataStore)
+ loanModel['lender'] = getLender(datastore)
+ loanModel['borrower'] = getBorrower(datastore)
+ loanModel['paymentHistory'] = getPaymentHistory(datastore, loan, asOfDate)
+ loanModel['futureAmortization'] = getAmortization(datastore, loan, paymentHistory)
+ return loanModel
+
+def getLoan(datastore):
+ # read in the loan profile information
+
+ 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):
+ #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):
+ #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=principal, future_payments=future_payments, \
+ past_payments=past_payments, balloon_payment=remaining_principal, \
+ total_interest_paid_to_date=total_interest, statement=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 = loadModel.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 = principal
+ 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)
+ else:
+ annual_interest_record['year'] = old_bill_date.year
+ annual_interest_record['interest'] = annual_interest
+ past_interest.append(annual_interest_record)
+ next_bill_date = date(year=old_bill_date.year + 1, month=1, day=payment_day_of_month)
+ payment_record['print_interest_total'] = True
+ payment_record[
+ 'interest_total_message'] = "Total interest for " + old_bill_date.year.__str__() + " was $" + annual_interest.__str__()
+ annual_interest = 0
+
+ if old_bill_date.month < 12:
+ payment_record['print_interest_total'] = True
+ payment_record[
+ 'interest_total_message'] = "Total interest to date for " + old_bill_date.year.__str__() + " is $" + annual_interest.__str__()
+
+ # 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()
diff --git a/statement.pdf.jinja b/statement.pdf.jinja
new file mode 100644
index 0000000..b1a43cd
--- /dev/null
+++ b/statement.pdf.jinja
@@ -0,0 +1,82 @@
+
+
+{{ statement.title }}
+{{ statement.lender.name }}
+
+{{ statement.lender.phone }} - {{ statement.lender.address }} -
+{{ statement.lender.city }} {{statement.lender.state }} {{ statement.lender.zip }}
+Statement Date: {{ statement.date }}
+
+
+
+Loan Information
+
+ Borrower: {{ statement.borrower.name }} Account Number: {{ statement.loan.account_number }}
+ {{ statement.borrower.address }} Origination Date: {{ statement.loan.origination_date }}
+ {{ statement.borrower.city }}, {{statement.borrower.state }} {{ statement.borrower.zip }}
+ Original Principal: {{ "$%.2f"|format(statement.loan.principal) }}
+ Rate: {{statement.loan.rate }} Term: {{statement.loan.term }} months
+ Next Payment Due Date: {{statement.loan.next_due_date}} Payment Due: {{ "$%.2f"|format(statement.loan.next_payment_amt) }}
+
+
+
+
+
+
+ #
+ Due Date
+ Date Paid
+ Days Interest
+ Payment Amt
+ Principal Pmt
+ Interest Pmt
+ New Balance
+
+
+{% for item in past_payments %}
+ {{ item.payment_number }}
+ {{ item.bill_date }}
+ {{ item.payment_date }}
+ {{ item.days_of_interest }}
+ {{ "$%.2f"|format(item.payment_amount) }}
+ {{ "$%.2f"|format(item.principal_payment) }}
+ {{ "$%.2f"|format(item.interest_payment) }}
+ {{ "$%.2f"|format(item.new_balance) }}
+
+ {% if item.print_interest_total %}
+ {{ item.interest_total_message }}
+ {% endif %}
+{% endfor %}
+
+
+Total interest paid to date is {{ "$%.2f"|format(total_interest_paid_to_date) }}.
+
+
+
+
+
+ #
+ Due Date
+ Days Interest
+ Payment Amt
+ Principal Pmt
+ Interest Pmt
+ Principal Balance
+
+
+{% for item in future_payments %}
+ {{ item.payment_number }}
+ {{ item.payment_date }}
+ {{ item.days_of_interest }}
+ {{ "$%.2f"|format(item.payment_amount) }}
+ {{ "$%.2f"|format(item.principal_payment) }}
+ {{ "$%.2f"|format(item.interest_payment) }}
+ {{ "$%.2f"|format(item.new_balance) }}
+
+{% endfor %}
+
+
+Balloon Payment Due: {{ "$%.2f"|format(balloon_payment) }}
+
+
+
\ No newline at end of file
diff --git a/statement.txt.jinja b/statement.txt.jinja
new file mode 100644
index 0000000..d7fbef2
--- /dev/null
+++ b/statement.txt.jinja
@@ -0,0 +1,49 @@
+Original Principal Balance: {{ original_principal_balance }}
+
+Payment History
+ #\tBill Date
+ Payment Date
+ Days of Interest
+ Payment Amount
+ Principal Payment
+ Interest Payment
+ New Balance
+
+{% for item in past_payments %}
+{{ item.payment_number }}
+{{ item.bill_date }}
+{{ item.payment_date }}
+{{ item.days_of_interest }}
+{{ item.payment_amount }}
+{{ item.principal_payment }}
+{{ item.interest_payment }}
+{{ item.new_balance }}
+
+ {% if item.print_interest_total %}
+ {{ item.interest_total_message }}
+ {% endif %}
+{% endfor %}
+Total interest paid to date is {{ total_interest_paid_to_date }}.
+
+
+Remaining Amortization
+ #
+ Days of Interest
+ Due Date
+ Payment Amount
+ Scheduled Principal Payment
+ Scheduled Interest Payment
+ Expected New Balance
+
+{% for item in future_payments %}
+{{ item.payment_number }}
+{{ item.days_of_interest }}
+{{ item.payment_date }}
+{{ item.payment_amount }}
+{{ item.principal_payment }}
+{{ item.interest_payment }}
+{{ item.new_balance }}
+{% endfor %}
+
+Balloon Payment Due: {{ balloon_payment }}
+
From e274b96672598a0e35084598cbd74fcb9f89eb53 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sun, 23 Dec 2018 01:05:36 -0500
Subject: [PATCH 02/38] Fixing references.
---
mortgage_template.py | 67 +++++++++++++++++++++++++-------------------
1 file changed, 38 insertions(+), 29 deletions(-)
diff --git a/mortgage_template.py b/mortgage_template.py
index 29ea278..38e67a9 100644
--- a/mortgage_template.py
+++ b/mortgage_template.py
@@ -9,19 +9,30 @@ 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 = {}
- datastore = getDataStore(filename)
-
- loanModel['loan'] = getLoan(dataStore)
+ loanModel['loan'] = getLoan(datastore)
loanModel['lender'] = getLender(datastore)
loanModel['borrower'] = getBorrower(datastore)
loanModel['paymentHistory'] = getPaymentHistory(datastore, loan, asOfDate)
- loanModel['futureAmortization'] = getAmortization(datastore, loan, paymentHistory)
+ 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
@@ -49,8 +60,10 @@ def getLoan(datastore):
loan['next_payment_date'] = '12/12/12'
return loan
+
def getLender(datastore):
- #to be replaced with real loading code
+ 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'
@@ -59,8 +72,10 @@ def getLender(datastore):
lender['zip'] = '20011'
return getLender
+
def getBorrower(datastore):
- #to be replaced with real loading code
+ borrower = {}
+ # to be replaced with real loading code
borrower['name'] = 'Bear Houses, LLC'
borrower['address'] = '123 Any Street'
borrower['city'] = 'Alltown'
@@ -68,7 +83,8 @@ def getBorrower(datastore):
borrower['zip'] = '11111'
return borrower
-def getDataStore(filename=None):
+
+def getDatastore(filename=None):
try:
if filename:
with open(filename, 'r') as f:
@@ -84,31 +100,34 @@ def getDataStore(filename=None):
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=principal, future_payments=future_payments, \
- past_payments=past_payments, balloon_payment=remaining_principal, \
- total_interest_paid_to_date=total_interest, statement=statement)
+ 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
@@ -126,6 +145,7 @@ def generateEmail(from_address, to_address, subject, body, attachment):
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
@@ -156,7 +176,7 @@ def main():
statement, lender, borrower, loan = {}, {}, {}, {}
lender = loanModel.lender
borrower = loanModel.borrower
- past_payments = loadModel.past_payments
+ past_payments = loanModel.past_payments
future_payments = loanModel.future_payments
statement['title'] = "Mortgage Statement - 185 James River Rd"
@@ -168,7 +188,10 @@ def main():
# loop over the payments and calculate the actual amortization
actual_payments = loanModel["payments"]
- remaining_principal = principal
+ 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
@@ -209,20 +232,6 @@ def main():
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_record['year'] = old_bill_date.year
- annual_interest_record['interest'] = annual_interest
- past_interest.append(annual_interest_record)
- next_bill_date = date(year=old_bill_date.year + 1, month=1, day=payment_day_of_month)
- payment_record['print_interest_total'] = True
- payment_record[
- 'interest_total_message'] = "Total interest for " + old_bill_date.year.__str__() + " was $" + annual_interest.__str__()
- annual_interest = 0
-
- if old_bill_date.month < 12:
- payment_record['print_interest_total'] = True
- payment_record[
- 'interest_total_message'] = "Total interest to date for " + old_bill_date.year.__str__() + " is $" + annual_interest.__str__()
# loop over remaining scheduled payments and present estimated amortization
while (payment_number <= total_periods) and (remaining_principal > 0):
@@ -257,7 +266,7 @@ def main():
else:
next_bill_date = date(year=old_bill_date.year + 1, month=1, day=payment_day_of_month)
- report = transformTemplate('',loanModel)
+ report = transformTemplate('', loanModel)
# create pdf
class MyFPDF(FPDF, HTMLMixin):
From 45d6e32149ab613310ea1b3d8e6b5f53f8765eb0 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sun, 6 Jan 2019 16:39:54 -0500
Subject: [PATCH 03/38] Updated all of the payment files to reflect new format.
Updated the PDF generation template. Updated the email sending code. Fixed
a few off by one errors when the last payment also is the last payment of the
year.
---
10Kloan.json | 63 ++++++++
brendamortgage.json | 56 +++++++
dadmortgage.json | 56 +++++++
greenfield_mortgage.json | 63 ++++++++
greenfield_mortgage.txt | 25 ----
mortgage_template.py | 307 ++++++++++++++++++++-------------------
statement.pdf.jinja | 30 ++--
7 files changed, 409 insertions(+), 191 deletions(-)
create mode 100644 10Kloan.json
create mode 100644 brendamortgage.json
create mode 100644 dadmortgage.json
create mode 100644 greenfield_mortgage.json
delete mode 100644 greenfield_mortgage.txt
diff --git a/10Kloan.json b/10Kloan.json
new file mode 100644
index 0000000..b9cd72d
--- /dev/null
+++ b/10Kloan.json
@@ -0,0 +1,63 @@
+{
+"htmlTemplate": "statement.pdf.jinja",
+"txtTemplate": "statement.txt.jinja",
+"format": "html",
+"email": {
+ "from_address": "jkent3rd@gmail.com",
+ "to_address": "grady@gradystreet.com",
+ "server": "smtp.gmail.com",
+ "password": "pvyrbcnzrjoizprn",
+ "template": "./template.txt",
+ "send_pdf": "true",
+ "send_text": "true",
+ "subject": "Your loan statement...",
+ "body": "Your loan statement is attached.",
+ "text": "Please see your most recent account statement."
+ },
+"parameters": {
+ "account_number": "100100",
+ "principal": 10020.00,
+ "interest_rate": 6.5,
+ "periods_per_year": 12,
+ "periods": 18,
+ "start_date": "2017-12-05",
+ "start_interest_date": "2017-12-05",
+ "first_payment_month": "2018-01-15",
+ "monthly_payment": 589.00,
+ "payment_day_of_month": "15"
+ },
+"payments": [
+ ["2018-01-16", "589.00"],
+ ["2018-03-30", "589.00"],
+ ["2018-04-06", "589.00"],
+ ["2018-04-12", "589.00"],
+ ["2018-05-28", "589.00"],
+ ["2018-06-12", "589.00"],
+ ["2018-07-13", "589.00"],
+ ["2018-08-14", "589.00"],
+ ["2018-09-26", "589.00"],
+ ["2018-10-15", "0"],
+ ["2018-11-29", "589.00"],
+ ["2018-12-31", "589.00"]
+ ],
+"borrower": {
+ "name": "Bear Houses, LLC",
+ "address": "301 N Beauregard St Apt 203",
+ "city": "Alexandria",
+ "state": "VA",
+ "zip": "22312"
+ },
+"lender": {
+ "name": "John Kent",
+ "phone": "703.343.0782",
+ "address": "743 Madison St NW",
+ "city": "Washington",
+ "state": "DC",
+ "zip": "20011"
+ },
+"header": {
+ "title": "Installment Loan Statement",
+ "date": "Today"
+ }
+}
+
diff --git a/brendamortgage.json b/brendamortgage.json
new file mode 100644
index 0000000..d13476f
--- /dev/null
+++ b/brendamortgage.json
@@ -0,0 +1,56 @@
+{
+"htmlTemplate": "statement.pdf.jinja",
+"txtTemplate": "statement.txt.jinja",
+"format": "html",
+"email": {
+ "from_address": "jkent3rd@gmail.com",
+ "to_address": "bck@virginia.edu",
+ "server": "smtp.gmail.com",
+ "password": "pvyrbcnzrjoizprn",
+ "template": "./template.txt",
+ "send_pdf": "true",
+ "send_text": "true",
+ "subject": "Your loan statement...",
+ "body": "Your loan statement is attached.",
+ "text": "Please see your most recent account statement."
+ },
+"parameters": {
+ "account_number": "100001",
+ "principal": 35000.00,
+ "interest_rate": 5.0,
+ "periods_per_year": 12,
+ "periods": 180,
+ "start_date": "2018-06-25",
+ "start_interest_date": "2018-07-01",
+ "first_payment_month": "2018-08-01",
+ "monthly_payment": 278.15,
+ "payment_day_of_month": "01"
+ },
+"borrower": {
+ "name": "Grandma Tina's Properties, LLC",
+ "address": "743 Madison St NW",
+ "city": "Washington",
+ "state": "DC",
+ "zip": "20011"
+ },
+"lender": {
+ "name": "John Kent",
+ "phone": "434-510-7272",
+ "address": "109 Shores Rd",
+ "city": "Palmyra",
+ "state": "VA",
+ "zip": "22963"
+ },
+"header": {
+ "title": "Mortgage Loan Statement - 185 James River Rd, Scottsville VA",
+ "date": "Today"
+ },
+"payments": [
+ ["2018-08-01", "278.15"],
+ ["2018-09-01", "278.15"],
+ ["2018-10-01", "278.15"],
+ ["2018-11-01", "278.15"],
+ ["2018-12-01", "278.15"],
+ ["2019-01-01", "278.15"]
+ ]
+}
diff --git a/dadmortgage.json b/dadmortgage.json
new file mode 100644
index 0000000..9ff0acb
--- /dev/null
+++ b/dadmortgage.json
@@ -0,0 +1,56 @@
+{
+"htmlTemplate": "statement.pdf.jinja",
+"txtTemplate": "statement.txt.jinja",
+"format": "html",
+"email": {
+ "from_address": "jkent3rd@gmail.com",
+ "to_address": "johnkent49@gmail.com",
+ "server": "smtp.gmail.com",
+ "password": "pvyrbcnzrjoizprn",
+ "template": "./template.txt",
+ "send_pdf": "true",
+ "send_text": "true",
+ "subject": "Your loan statement...",
+ "body": "Your loan statement is attached.",
+ "text": "Please see your most recent account statement."
+ },
+"parameters": {
+ "account_number": "100001",
+ "principal": 35000.00,
+ "interest_rate": 5.0,
+ "periods_per_year": 12,
+ "periods": 180,
+ "start_date": "2018-06-25",
+ "start_interest_date": "2018-07-01",
+ "first_payment_month": "2018-08-01",
+ "monthly_payment": 278.15,
+ "payment_day_of_month": "01"
+ },
+"borrower": {
+ "name": "Grandma Tina's Properties, LLC",
+ "address": "743 Madison St NW",
+ "city": "Washington",
+ "state": "DC",
+ "zip": "20011"
+ },
+"lender": {
+ "name": "John Kent",
+ "phone": "434-510-7272",
+ "address": "109 Shores Rd",
+ "city": "Palmyra",
+ "state": "VA",
+ "zip": "22963"
+ },
+"header": {
+ "title": "Mortgage Loan Statement - 185 James River Rd, Scottsville VA",
+ "date": "Today"
+ },
+"payments": [
+ ["2018-08-01", "278.15"],
+ ["2018-09-01", "278.15"],
+ ["2018-10-01", "278.15"],
+ ["2018-11-01", "278.15"],
+ ["2018-12-01", "278.15"],
+ ["2019-01-01", "278.15"]
+ ]
+}
diff --git a/greenfield_mortgage.json b/greenfield_mortgage.json
new file mode 100644
index 0000000..65d2127
--- /dev/null
+++ b/greenfield_mortgage.json
@@ -0,0 +1,63 @@
+{
+"htmlTemplate": "statement.pdf.jinja",
+"txtTemplate": "statement.txt.jinja",
+"format": "html",
+"email": {
+ "from_address": "jkent3rd@gmail.com",
+ "to_address": "grady@gradystreet.com",
+ "server": "smtp.gmail.com",
+ "password": "pvyrbcnzrjoizprn",
+ "template": "./template.txt",
+ "send_pdf": "true",
+ "send_text": "true",
+ "subject": "Your loan statement...",
+ "body": "Your loan statement is attached.",
+ "text": "Please see your most recent account statement."
+ },
+"parameters": {
+ "account_number": "100001",
+ "principal": 97750.00,
+ "interest_rate": 5.5,
+ "periods_per_year": 12,
+ "periods": 182,
+ "start_date": "2017-11-07",
+ "start_interest_date": "2017-11-11",
+ "first_payment_month": "2017-12-15",
+ "monthly_payment": 803.00,
+ "payment_day_of_month": "15"
+ },
+"payments": [
+ ["2017-12-11", "803.00"],
+ ["2018-01-23", "803.00"],
+ ["2018-03-23", "803.00"],
+ ["2018-04-18", "803.00"],
+ ["2018-04-26", "803.00"],
+ ["2018-05-15", "0.00"],
+ ["2018-06-15", "0.00"],
+ ["2018-07-12", "803.00"],
+ ["2018-08-07", "803.00"],
+ ["2018-09-06", "803.00"],
+ ["2018-10-11", "803.00"],
+ ["2018-11-13", "803.00"],
+ ["2018-12-13", "803.00"]
+ ],
+"borrower": {
+ "name": "Bear Houses, LLC",
+ "address": "301 N Beauregard St Apt 203",
+ "city": "Alexandria",
+ "state": "VA",
+ "zip": "22312"
+ },
+"lender": {
+ "name": "Rivanna Graphite Investments, LLC",
+ "phone": "703.343.0782",
+ "address": "743 Madison St NW",
+ "city": "Washington",
+ "state": "DC",
+ "zip": "20011"
+ },
+"header": {
+ "title": "Mortgage Loan Statement - 195 Greenfield Lane, Pearl MS",
+ "date": "Today"
+ }
+}
diff --git a/greenfield_mortgage.txt b/greenfield_mortgage.txt
deleted file mode 100644
index 91bc430..0000000
--- a/greenfield_mortgage.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-"principal": 97750.00,
-"interest rate": 5.5,
-"periods per year": 12,
-"periods": 182,
-"start date": "2017-11-07",
-"start_interest_date": "2017-11-11",
-"first payment month": "2017-12-15",
-"monthly_payment": 803.00,
-"payment day of month": "15",
-"payments": [
- ["2017-12-11", "803.00"],
- ["2018-01-23", "803.00"],
- ["2018-03-23", "803.00"],
- ["2018-04-18", "803.00"],
- ["2018-04-26", "803.00"],
- ["2018-05-15", "0.00"],
- ["2018-06-15", "0.00"],
- ["2018-07-12", "803.00"],
- ["2018-08-07", "803.00"],
- ["2018-09-06", "803.00"],
- ["2018-10-11", "803.00"],
- ["2018-11-13", "803.00"]
- ]
-}
diff --git a/mortgage_template.py b/mortgage_template.py
index 38e67a9..df1cb4b 100644
--- a/mortgage_template.py
+++ b/mortgage_template.py
@@ -4,45 +4,47 @@ 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 getStatementHeader(datastore):
+ return datastore['header']
-def getAmortization(datastore, loan, paymentHistory):
- pass
+def getEmailInformation(datastore):
+ return datastore['email']
def loadLoanInformation(filename):
datastore = getDatastore(filename)
- loanModel = {}
- loanModel['loan'] = getLoan(datastore)
+ loanModel = {}
+ loanModel['datastore'] = datastore
+ loanModel['email'] = getEmailInformation(datastore)
+ loanModel['parameters'] = getLoanParameters(datastore)
loanModel['lender'] = getLender(datastore)
loanModel['borrower'] = getBorrower(datastore)
- loanModel['paymentHistory'] = getPaymentHistory(datastore, loan, asOfDate)
- loanModel['futureAmortization'] = getAmortization(datastore, loan, loanModel.paymentHistory)
+ loanModel['header'] = getStatementHeader(datastore)
return loanModel
-def getLoan(datastore):
+def getLoanParameters(datastore):
# read in the loan profile information
- loan = {}
+ loan = datastore['parameters']
- annual_rate = Decimal(datastore['loan.interest rate']) / 100
+ annual_rate = Decimal(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'])
+ 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 datastore:
- monthly_payment = Decimal(datastore["monthly_payment"]).quantize(Decimal("1.00"))
+ 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
@@ -50,38 +52,24 @@ def getLoan(datastore):
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['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):
- 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
+ return datastore['lender']
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
+ return datastore['borrower']
def getDatastore(filename=None):
@@ -91,7 +79,7 @@ def getDatastore(filename=None):
datastore = json.load(f)
except Exception as e:
- print "An error occurred opening your loan file '#s'. " % filename
+ print "An error occurred opening your loan file '%s'. " % filename
print "The Exception:"
print e.__repr__()
quit()
@@ -99,109 +87,29 @@ def getDatastore(filename=None):
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
-
+def amortizeLoan(loan):
# loop over the payments and calculate the actual amortization
- actual_payments = loanModel["payments"]
+ monthly_payment = loan["parameters"]["monthly_payment"]
- 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
+ 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
-
- 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
+ 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"))
@@ -232,6 +140,16 @@ def main():
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["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):
@@ -254,7 +172,7 @@ def main():
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['principal_payment'] = new_principal
future_payment_record['interest_payment'] = new_interest
future_payment_record['new_balance'] = remaining_principal
future_payments.append(future_payment_record)
@@ -266,8 +184,68 @@ def main():
else:
next_bill_date = date(year=old_bill_date.year + 1, month=1, day=payment_day_of_month)
- report = transformTemplate('', loanModel)
+ 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
@@ -277,22 +255,49 @@ def main():
pdf.add_page()
pdf.write_html(report)
# pdf.output(name='test.pdf', dest='F')
- attachment = pdf.output(dest='S')
+ 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():
+ # 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
+ # filename = "./10Kloan.txt"
+ #filename = "./10Kloan.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
- 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()
+ emailParameters = loan["email"]
+ msg = generateEmail(emailParameters["from_address"], emailParameters["to_address"], emailParameters["subject"],
+ emailParameters["body"], pdfAttachment, report)
+ sendEmail(msg, emailParameters["from_address"], emailParameters["to_address"], emailParameters['password'])
if __name__ == '__main__':
diff --git a/statement.pdf.jinja b/statement.pdf.jinja
index b1a43cd..1e2370c 100644
--- a/statement.pdf.jinja
+++ b/statement.pdf.jinja
@@ -1,22 +1,22 @@
-{{ statement.title }}
-{{ statement.lender.name }}
+{{ model.header.title }}
+{{ model.lender.name }}
-{{ statement.lender.phone }} - {{ statement.lender.address }} -
-{{ statement.lender.city }} {{statement.lender.state }} {{ statement.lender.zip }}
-Statement Date: {{ statement.date }}
+{{ model.lender.phone }} - {{ model.lender.address }} -
+{{ model.lender.city }} {{model.lender.state }} {{ model.lender.zip }}
+Statement Date: {{ model.header.date }}
Loan Information
- Borrower: {{ statement.borrower.name }} Account Number: {{ statement.loan.account_number }}
- {{ statement.borrower.address }} Origination Date: {{ statement.loan.origination_date }}
- {{ statement.borrower.city }}, {{statement.borrower.state }} {{ statement.borrower.zip }}
- Original Principal: {{ "$%.2f"|format(statement.loan.principal) }}
- Rate: {{statement.loan.rate }} Term: {{statement.loan.term }} months
- Next Payment Due Date: {{statement.loan.next_due_date}} Payment Due: {{ "$%.2f"|format(statement.loan.next_payment_amt) }}
+ Borrower: {{ model.borrower.name }} Account Number: {{ model.parameters.account_number }}
+ {{ model.borrower.address }} Origination Date: {{ model.parameters.start_date }}
+ {{ model.borrower.city }}, {{model.borrower.state }} {{ model.borrower.zip }}
+ Original Principal: {{ "$%.2f"|format(model.parameters.principal) }}
+ Rate: {{model.parameters.interest_rate }}% Term: {{model.parameters.periods }} months
+ Next Payment Due Date: {{model.parameters.next_due_date}} Payment Due: {{ "$%.2f"|format(model.parameters.next_payment_amt) }}
@@ -33,7 +33,7 @@
New Balance
-{% for item in past_payments %}
+{% for item in model.past_payments %}
{{ item.payment_number }}
{{ item.bill_date }}
{{ item.payment_date }}
@@ -49,7 +49,7 @@
{% endfor %}
-Total interest paid to date is {{ "$%.2f"|format(total_interest_paid_to_date) }}.
+Total interest paid to date is {{ "$%.2f"|format(model.total_interest_paid_to_date) }}.
@@ -64,7 +64,7 @@
Principal Balance
-{% for item in future_payments %}
+{% for item in model.future_payments %}
{{ item.payment_number }}
{{ item.payment_date }}
{{ item.days_of_interest }}
@@ -76,7 +76,7 @@
{% endfor %}
-Balloon Payment Due: {{ "$%.2f"|format(balloon_payment) }}
+Balloon Payment Due: {{ "$%.2f"|format(model.balloon_payment) }}
\ No newline at end of file
From 7612f854d7d5f2575560955383ddc4f4ce3ddda3 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sun, 13 Jan 2019 14:53:53 -0500
Subject: [PATCH 04/38] Fixed the program to print the annual interest paid.
---
greenfield_mortgage.json | 3 ++-
mortgage_template.py | 8 +++++---
statement.pdf.jinja | 6 +++---
3 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/greenfield_mortgage.json b/greenfield_mortgage.json
index 65d2127..c42064f 100644
--- a/greenfield_mortgage.json
+++ b/greenfield_mortgage.json
@@ -39,7 +39,8 @@
["2018-09-06", "803.00"],
["2018-10-11", "803.00"],
["2018-11-13", "803.00"],
- ["2018-12-13", "803.00"]
+ ["2018-12-13", "803.00"],
+ ["2018-01-11", "803.00"]
],
"borrower": {
"name": "Bear Houses, LLC",
diff --git a/mortgage_template.py b/mortgage_template.py
index df1cb4b..69459c2 100644
--- a/mortgage_template.py
+++ b/mortgage_template.py
@@ -123,6 +123,8 @@ def amortizeLoan(loan):
# 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
@@ -276,11 +278,11 @@ def main():
# at closing. The first payment will incur interest from one month before the bill is due.
# read in the file
- # filename = "./10Kloan.txt"
- #filename = "./10Kloan.json"
+ filename = "./testloan.json"
+ # filename = "./10Kloan.json"
# filename = "./dadmortgage.json"
# filename = "./brendamortgage.json"
- filename = "./greenfield_mortgage.json"
+ #filename = "./greenfield_mortgage.json"
template_filename = "statement.pdf.jinja"
loan = loadLoanInformation(filename)
diff --git a/statement.pdf.jinja b/statement.pdf.jinja
index 1e2370c..288e119 100644
--- a/statement.pdf.jinja
+++ b/statement.pdf.jinja
@@ -43,13 +43,13 @@
{{ "$%.2f"|format(item.interest_payment) }}
{{ "$%.2f"|format(item.new_balance) }}
- {% if item.print_interest_total %}
- {{ item.interest_total_message }}
+ {% if item.month == '12' or loop.last %}
+ Total interest paid in {{item.year}} is {{ "$%.2f"|format(item.annual_interest_to_date) }}.
{% endif %}
{% endfor %}
+ Total interest paid to date is {{ "$%.2f"|format(model.total_interest_paid_to_date) }}.
-Total interest paid to date is {{ "$%.2f"|format(model.total_interest_paid_to_date) }}.
From f5c78ca7dc4742f25fb40427d0a09fef51211455 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sun, 13 Jan 2019 14:54:30 -0500
Subject: [PATCH 05/38] Added this file for testing of email function.
---
testloan.json | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 testloan.json
diff --git a/testloan.json b/testloan.json
new file mode 100644
index 0000000..f0cc95c
--- /dev/null
+++ b/testloan.json
@@ -0,0 +1,63 @@
+{
+"htmlTemplate": "statement.pdf.jinja",
+"txtTemplate": "statement.txt.jinja",
+"format": "html",
+"email": {
+ "from_address": "jkent3rd@gmail.com",
+ "to_address": "jkent3rd@yahoo.com",
+ "server": "smtp.gmail.com",
+ "password": "pvyrbcnzrjoizprn",
+ "template": "./template.txt",
+ "send_pdf": "true",
+ "send_text": "true",
+ "subject": "Your loan statement...",
+ "body": "Your loan statement is attached.",
+ "text": "Please see your most recent account statement."
+ },
+"parameters": {
+ "account_number": "100100",
+ "principal": 10020.00,
+ "interest_rate": 6.5,
+ "periods_per_year": 12,
+ "periods": 18,
+ "start_date": "2017-12-05",
+ "start_interest_date": "2017-12-05",
+ "first_payment_month": "2018-01-15",
+ "monthly_payment": 589.00,
+ "payment_day_of_month": "15"
+ },
+"payments": [
+ ["2018-01-16", "589.00"],
+ ["2018-03-30", "589.00"],
+ ["2018-04-06", "589.00"],
+ ["2018-04-12", "589.00"],
+ ["2018-05-28", "589.00"],
+ ["2018-06-12", "589.00"],
+ ["2018-07-13", "589.00"],
+ ["2018-08-14", "589.00"],
+ ["2018-09-26", "589.00"],
+ ["2018-10-15", "0"],
+ ["2018-11-29", "589.00"],
+ ["2018-12-31", "589.00"]
+ ],
+"borrower": {
+ "name": "Bear Houses, LLC",
+ "address": "301 N Beauregard St Apt 203",
+ "city": "Alexandria",
+ "state": "VA",
+ "zip": "22312"
+ },
+"lender": {
+ "name": "John Kent",
+ "phone": "703.343.0782",
+ "address": "743 Madison St NW",
+ "city": "Washington",
+ "state": "DC",
+ "zip": "20011"
+ },
+"header": {
+ "title": "Installment Loan Statement",
+ "date": "Today"
+ }
+}
+
From 4dae3256c532cb5b8d1a545959d04e0f48d213fa Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Tue, 5 Mar 2019 00:09:56 -0500
Subject: [PATCH 06/38] Updated the template to print the correct interest
information. Fixed logic in script to send correct interest information to
the template. Added ability to do a test run to a different email address
with a real data file (the debug flag replaces the email address with a hard
coded one). Added an aborting checking that the payments are listed in
order.
---
mortgage_template.py | 25 ++++++++++++++++++++-----
statement.pdf.jinja | 2 +-
testloan.json | 6 ++++--
3 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/mortgage_template.py b/mortgage_template.py
index 69459c2..8565c46 100644
--- a/mortgage_template.py
+++ b/mortgage_template.py
@@ -114,6 +114,13 @@ def amortizeLoan(loan):
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
@@ -121,6 +128,7 @@ def amortizeLoan(loan):
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
@@ -143,6 +151,7 @@ def amortizeLoan(loan):
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
@@ -278,10 +287,14 @@ def main():
# at closing. The first payment will incur interest from one month before the bill is due.
# read in the file
+ test_flag = True
+ #test_flag = False
+ test_address = 'jkent3rd@yahoo.com'
+
filename = "./testloan.json"
- # filename = "./10Kloan.json"
- # filename = "./dadmortgage.json"
- # filename = "./brendamortgage.json"
+ #filename = "./10Kloan.json"
+ #filename = "./dadmortgage.json"
+ #filename = "./brendamortgage.json"
#filename = "./greenfield_mortgage.json"
template_filename = "statement.pdf.jinja"
@@ -299,8 +312,10 @@ def main():
emailParameters = loan["email"]
msg = generateEmail(emailParameters["from_address"], emailParameters["to_address"], emailParameters["subject"],
emailParameters["body"], pdfAttachment, report)
- sendEmail(msg, emailParameters["from_address"], emailParameters["to_address"], emailParameters['password'])
-
+ 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()
diff --git a/statement.pdf.jinja b/statement.pdf.jinja
index 288e119..acd59c5 100644
--- a/statement.pdf.jinja
+++ b/statement.pdf.jinja
@@ -43,7 +43,7 @@
{{ "$%.2f"|format(item.interest_payment) }}
{{ "$%.2f"|format(item.new_balance) }}
- {% if item.month == '12' or loop.last %}
+ {% if item.month == 12 or loop.last %}
Total interest paid in {{item.year}} is {{ "$%.2f"|format(item.annual_interest_to_date) }}.
{% endif %}
{% endfor %}
diff --git a/testloan.json b/testloan.json
index f0cc95c..8a7331b 100644
--- a/testloan.json
+++ b/testloan.json
@@ -4,7 +4,7 @@
"format": "html",
"email": {
"from_address": "jkent3rd@gmail.com",
- "to_address": "jkent3rd@yahoo.com",
+ "to_address": "jkent3rd@gmail.com",
"server": "smtp.gmail.com",
"password": "pvyrbcnzrjoizprn",
"template": "./template.txt",
@@ -38,7 +38,9 @@
["2018-09-26", "589.00"],
["2018-10-15", "0"],
["2018-11-29", "589.00"],
- ["2018-12-31", "589.00"]
+ ["2018-12-31", "589.00"],
+ ["2019-01-28", "589.00"],
+ ["2019-03-15", "589.00"]
],
"borrower": {
"name": "Bear Houses, LLC",
From 42232903c53dca1fdf518f73d9bf268464ed61f7 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Tue, 5 Mar 2019 00:10:22 -0500
Subject: [PATCH 07/38] Updated the payment history.
---
10Kloan.json | 6 ++++--
brendamortgage.json | 6 ++++--
dadmortgage.json | 4 +++-
greenfield_mortgage.json | 3 ++-
4 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/10Kloan.json b/10Kloan.json
index b9cd72d..a731fea 100644
--- a/10Kloan.json
+++ b/10Kloan.json
@@ -38,8 +38,10 @@
["2018-09-26", "589.00"],
["2018-10-15", "0"],
["2018-11-29", "589.00"],
- ["2018-12-31", "589.00"]
- ],
+ ["2018-12-31", "589.00"],
+ ["2019-01-15", "0.00"],
+ ["2019-02-13", "589.00"]
+],
"borrower": {
"name": "Bear Houses, LLC",
"address": "301 N Beauregard St Apt 203",
diff --git a/brendamortgage.json b/brendamortgage.json
index d13476f..b119644 100644
--- a/brendamortgage.json
+++ b/brendamortgage.json
@@ -4,7 +4,7 @@
"format": "html",
"email": {
"from_address": "jkent3rd@gmail.com",
- "to_address": "bck@virginia.edu",
+ "to_address": "jkent3rd@gmail.com",
"server": "smtp.gmail.com",
"password": "pvyrbcnzrjoizprn",
"template": "./template.txt",
@@ -51,6 +51,8 @@
["2018-10-01", "278.15"],
["2018-11-01", "278.15"],
["2018-12-01", "278.15"],
- ["2019-01-01", "278.15"]
+ ["2019-01-01", "278.15"],
+ ["2019-02-01", "278.15"],
+ ["2019-03-01", "278.15"]
]
}
diff --git a/dadmortgage.json b/dadmortgage.json
index 9ff0acb..60d3f41 100644
--- a/dadmortgage.json
+++ b/dadmortgage.json
@@ -51,6 +51,8 @@
["2018-10-01", "278.15"],
["2018-11-01", "278.15"],
["2018-12-01", "278.15"],
- ["2019-01-01", "278.15"]
+ ["2019-01-01", "278.15"],
+ ["2019-02-01", "278.15"],
+ ["2019-03-01", "278.15"]
]
}
diff --git a/greenfield_mortgage.json b/greenfield_mortgage.json
index c42064f..e2147da 100644
--- a/greenfield_mortgage.json
+++ b/greenfield_mortgage.json
@@ -40,7 +40,8 @@
["2018-10-11", "803.00"],
["2018-11-13", "803.00"],
["2018-12-13", "803.00"],
- ["2018-01-11", "803.00"]
+ ["2019-01-14", "803.00"],
+ ["2019-02-05", "803.00"]
],
"borrower": {
"name": "Bear Houses, LLC",
From 8cb4be32486f71ba327e8b4942ffb43441700c26 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Wed, 3 Apr 2019 14:06:02 -0400
Subject: [PATCH 08/38] Updated the payment history for April statements.
---
10Kloan.json | 3 ++-
brendamortgage.json | 3 ++-
dadmortgage.json | 3 ++-
greenfield_mortgage.json | 3 ++-
mortgage_template.py | 8 ++++----
5 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/10Kloan.json b/10Kloan.json
index a731fea..e7bb2f1 100644
--- a/10Kloan.json
+++ b/10Kloan.json
@@ -40,7 +40,8 @@
["2018-11-29", "589.00"],
["2018-12-31", "589.00"],
["2019-01-15", "0.00"],
- ["2019-02-13", "589.00"]
+ ["2019-02-13", "589.00"],
+ ["2019-03-15", "0"]
],
"borrower": {
"name": "Bear Houses, LLC",
diff --git a/brendamortgage.json b/brendamortgage.json
index b119644..8176c6c 100644
--- a/brendamortgage.json
+++ b/brendamortgage.json
@@ -53,6 +53,7 @@
["2018-12-01", "278.15"],
["2019-01-01", "278.15"],
["2019-02-01", "278.15"],
- ["2019-03-01", "278.15"]
+ ["2019-03-01", "278.15"],
+ ["2019-04-01", "278.15"]
]
}
diff --git a/dadmortgage.json b/dadmortgage.json
index 60d3f41..c4c35f0 100644
--- a/dadmortgage.json
+++ b/dadmortgage.json
@@ -53,6 +53,7 @@
["2018-12-01", "278.15"],
["2019-01-01", "278.15"],
["2019-02-01", "278.15"],
- ["2019-03-01", "278.15"]
+ ["2019-03-01", "278.15"],
+ ["2019-04-01", "278.15"]
]
}
diff --git a/greenfield_mortgage.json b/greenfield_mortgage.json
index e2147da..d835dcd 100644
--- a/greenfield_mortgage.json
+++ b/greenfield_mortgage.json
@@ -41,7 +41,8 @@
["2018-11-13", "803.00"],
["2018-12-13", "803.00"],
["2019-01-14", "803.00"],
- ["2019-02-05", "803.00"]
+ ["2019-02-05", "803.00"],
+ ["2019-03-05", "803.00"]
],
"borrower": {
"name": "Bear Houses, LLC",
diff --git a/mortgage_template.py b/mortgage_template.py
index 8565c46..6d6d81f 100644
--- a/mortgage_template.py
+++ b/mortgage_template.py
@@ -279,6 +279,7 @@ def selectTemplate(loan):
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
@@ -287,14 +288,13 @@ def main():
# at closing. The first payment will incur interest from one month before the bill is due.
# read in the file
- test_flag = True
- #test_flag = False
+ test_flag = False
test_address = 'jkent3rd@yahoo.com'
- filename = "./testloan.json"
+ #filename = "./testloan.json"
#filename = "./10Kloan.json"
#filename = "./dadmortgage.json"
- #filename = "./brendamortgage.json"
+ filename = "./brendamortgage.json"
#filename = "./greenfield_mortgage.json"
template_filename = "statement.pdf.jinja"
From 56c0f333f6574dd212436f4ef7570c51ad848fe0 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sun, 2 Jun 2019 18:35:16 -0400
Subject: [PATCH 09/38] Updated the payment history.
---
brendamortgage.json | 6 ++++--
dadmortgage.json | 4 +++-
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/brendamortgage.json b/brendamortgage.json
index 8176c6c..e79d4d0 100644
--- a/brendamortgage.json
+++ b/brendamortgage.json
@@ -4,7 +4,7 @@
"format": "html",
"email": {
"from_address": "jkent3rd@gmail.com",
- "to_address": "jkent3rd@gmail.com",
+ "to_address": "bck@virginia.edu",
"server": "smtp.gmail.com",
"password": "pvyrbcnzrjoizprn",
"template": "./template.txt",
@@ -54,6 +54,8 @@
["2019-01-01", "278.15"],
["2019-02-01", "278.15"],
["2019-03-01", "278.15"],
- ["2019-04-01", "278.15"]
+ ["2019-04-01", "278.15"],
+ ["2019-05-01", "278.15"],
+ ["2019-05-31", "278.15"]
]
}
diff --git a/dadmortgage.json b/dadmortgage.json
index c4c35f0..107f7fa 100644
--- a/dadmortgage.json
+++ b/dadmortgage.json
@@ -54,6 +54,8 @@
["2019-01-01", "278.15"],
["2019-02-01", "278.15"],
["2019-03-01", "278.15"],
- ["2019-04-01", "278.15"]
+ ["2019-04-01", "278.15"],
+ ["2019-05-01", "278.15"],
+ ["2019-05-31", "278.15"]
]
}
From 8b2af86db972b8c7fcdc8c5c18dc853047ea19b7 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sat, 6 Jul 2019 14:50:59 -0400
Subject: [PATCH 10/38] Updated the payment history.
---
10Kloan.json | 5 ++++-
brendamortgage.json | 3 ++-
dadmortgage.json | 3 ++-
greenfield_mortgage.json | 7 +++++--
mortgage_template.py | 4 ++--
5 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/10Kloan.json b/10Kloan.json
index e7bb2f1..3bac0b5 100644
--- a/10Kloan.json
+++ b/10Kloan.json
@@ -41,7 +41,10 @@
["2018-12-31", "589.00"],
["2019-01-15", "0.00"],
["2019-02-13", "589.00"],
- ["2019-03-15", "0"]
+ ["2019-03-15", "0"],
+ ["2019-04-15", "0"],
+ ["2019-05-15", "589.00"],
+ ["2019-06-21", "2985.00"]
],
"borrower": {
"name": "Bear Houses, LLC",
diff --git a/brendamortgage.json b/brendamortgage.json
index e79d4d0..5b38944 100644
--- a/brendamortgage.json
+++ b/brendamortgage.json
@@ -56,6 +56,7 @@
["2019-03-01", "278.15"],
["2019-04-01", "278.15"],
["2019-05-01", "278.15"],
- ["2019-05-31", "278.15"]
+ ["2019-05-31", "278.15"],
+ ["2019-07-01", "278.15"]
]
}
diff --git a/dadmortgage.json b/dadmortgage.json
index 107f7fa..f7ec1a1 100644
--- a/dadmortgage.json
+++ b/dadmortgage.json
@@ -56,6 +56,7 @@
["2019-03-01", "278.15"],
["2019-04-01", "278.15"],
["2019-05-01", "278.15"],
- ["2019-05-31", "278.15"]
+ ["2019-05-31", "278.15"],
+ ["2019-07-01", "278.15"]
]
}
diff --git a/greenfield_mortgage.json b/greenfield_mortgage.json
index d835dcd..89e1a59 100644
--- a/greenfield_mortgage.json
+++ b/greenfield_mortgage.json
@@ -19,7 +19,7 @@
"principal": 97750.00,
"interest_rate": 5.5,
"periods_per_year": 12,
- "periods": 182,
+ "periods": 185,
"start_date": "2017-11-07",
"start_interest_date": "2017-11-11",
"first_payment_month": "2017-12-15",
@@ -42,7 +42,10 @@
["2018-12-13", "803.00"],
["2019-01-14", "803.00"],
["2019-02-05", "803.00"],
- ["2019-03-05", "803.00"]
+ ["2019-03-05", "803.00"],
+ ["2019-04-15", "803.00"],
+ ["2019-05-15", "0.00"],
+ ["2019-06-13", "803.00"]
],
"borrower": {
"name": "Bear Houses, LLC",
diff --git a/mortgage_template.py b/mortgage_template.py
index 6d6d81f..26bd2a8 100644
--- a/mortgage_template.py
+++ b/mortgage_template.py
@@ -292,9 +292,9 @@ def main():
test_address = 'jkent3rd@yahoo.com'
#filename = "./testloan.json"
- #filename = "./10Kloan.json"
+ filename = "./10Kloan.json"
#filename = "./dadmortgage.json"
- filename = "./brendamortgage.json"
+ #filename = "./brendamortgage.json"
#filename = "./greenfield_mortgage.json"
template_filename = "statement.pdf.jinja"
From 6462ae562818b22150cd14f31229a1170c355ffc Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sat, 6 Jul 2019 14:58:43 -0400
Subject: [PATCH 11/38] Added options for the new 9K loan from Rivanna Graphite
Investments, LLC. Removed the personal 10K loan I made to Bear Houses, LLC
---
9Kloan.json | 51 ++++++++++++++++++++++++++++++++++++++++++++
mortgage_template.py | 2 +-
2 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 9Kloan.json
diff --git a/9Kloan.json b/9Kloan.json
new file mode 100644
index 0000000..266d42a
--- /dev/null
+++ b/9Kloan.json
@@ -0,0 +1,51 @@
+{
+"htmlTemplate": "statement.pdf.jinja",
+"txtTemplate": "statement.txt.jinja",
+"format": "html",
+"email": {
+ "from_address": "jkent3rd@gmail.com",
+ "to_address": "grady@gradystreet.com",
+ "server": "smtp.gmail.com",
+ "password": "pvyrbcnzrjoizprn",
+ "template": "./template.txt",
+ "send_pdf": "true",
+ "send_text": "true",
+ "subject": "Your loan statement...",
+ "body": "Your loan statement is attached.",
+ "text": "Please see your most recent account statement."
+ },
+"parameters": {
+ "account_number": "100002",
+ "principal": 9000.00,
+ "interest_rate": 6.5,
+ "periods_per_year": 12,
+ "periods": 20,
+ "start_date": "2019-06-15",
+ "start_interest_date": "2019-06-17",
+ "first_payment_month": "2019-07-15",
+ "monthly_payment": 475.00,
+ "payment_day_of_month": "15"
+ },
+"payments": [
+],
+"borrower": {
+ "name": "Bear Houses, LLC",
+ "address": "301 N Beauregard St Apt 203",
+ "city": "Alexandria",
+ "state": "VA",
+ "zip": "22312"
+ },
+"lender": {
+ "name": "Rivanna Graphite Investments, LLC",
+ "phone": "703.343.0782",
+ "address": "743 Madison St NW",
+ "city": "Washington",
+ "state": "DC",
+ "zip": "20011"
+ },
+"header": {
+ "title": "Installment Loan Statement",
+ "date": "Today"
+ }
+}
+
diff --git a/mortgage_template.py b/mortgage_template.py
index 26bd2a8..f31ae5d 100644
--- a/mortgage_template.py
+++ b/mortgage_template.py
@@ -292,7 +292,7 @@ def main():
test_address = 'jkent3rd@yahoo.com'
#filename = "./testloan.json"
- filename = "./10Kloan.json"
+ filename = "./9Kloan.json"
#filename = "./dadmortgage.json"
#filename = "./brendamortgage.json"
#filename = "./greenfield_mortgage.json"
From 27cf64aceda6db709bdd05c9f5ba8fef7c02f0ed Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sun, 7 Jul 2019 14:58:20 -0400
Subject: [PATCH 12/38] Updated Brenda's contact information which has been
wrong.
---
brendamortgage.json | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/brendamortgage.json b/brendamortgage.json
index 5b38944..2a9d7c7 100644
--- a/brendamortgage.json
+++ b/brendamortgage.json
@@ -34,12 +34,12 @@
"zip": "20011"
},
"lender": {
- "name": "John Kent",
- "phone": "434-510-7272",
- "address": "109 Shores Rd",
- "city": "Palmyra",
+ "name": "Brenda Kelly",
+ "phone": "434-286-2110",
+ "address": "150 Confederate Street",
+ "city": "Scottsville",
"state": "VA",
- "zip": "22963"
+ "zip": "24590"
},
"header": {
"title": "Mortgage Loan Statement - 185 James River Rd, Scottsville VA",
From a30486970889feabe9716f80d91fd6fd0daa6abb Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Sun, 7 Jul 2019 14:59:33 -0400
Subject: [PATCH 13/38] At this state, it produces a nice web-based viewer of
the loan files including the payment history and future amortization.
---
templates/main.html | 159 +++++++++++++++++++++++++++++
web.py | 244 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 403 insertions(+)
create mode 100644 templates/main.html
create mode 100644 web.py
diff --git a/templates/main.html b/templates/main.html
new file mode 100644
index 0000000..674a873
--- /dev/null
+++ b/templates/main.html
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+ Loan Management
+
+
+Web Mortgage Manager
+
+
+
+
+
+
+
+
+
+ Loan History
+
+
+ #
+ Due Date
+ Date Paid
+ Days Interest
+ Payment Amt
+ Principal Pmt
+ Interest Pmt
+ New Balance
+
+
+
+ {% for item in model.past_payments %}
+
+ {{ item.payment_number }}
+ {{ item.bill_date }}
+ {{ item.payment_date }}
+ {{ item.days_of_interest }}
+ {{ "$%.2f"|format(item.payment_amount) }}
+ {{ "$%.2f"|format(item.principal_payment) }}
+ {{ "$%.2f"|format(item.interest_payment) }}
+ {{ "$%.2f"|format(item.new_balance) }}
+
+ {% if item.month == 12 or loop.last %}
+ Total interest paid in {{item.year}} is {{ "$%.2f"|format(item.annual_interest_to_date) }}.
+ {% endif %}
+ {% endfor %}
+ Total interest paid to date is {{ "$%.2f"|format(model.total_interest_paid_to_date) }}.
+
+
+
+
+
+
+
+ Remaining Amortization
+
+
+ #
+ Due Date
+ Days Interest
+ Payment Amt
+ Principal Pmt
+ Interest Pmt
+ Principal Balance
+
+
+
+ {% for item in model.future_payments %}
+ {{ item.payment_number }}
+ {{ item.payment_date }}
+ {{ item.days_of_interest }}
+ {{ "$%.2f"|format(item.payment_amount) }}
+ {{ "$%.2f"|format(item.principal_payment) }}
+ {{ "$%.2f"|format(item.interest_payment) }}
+ {{ "$%.2f"|format(item.new_balance) }}
+
+ {% endfor %}
+ Balloon Payment Due: {{ "$%.2f"|format(model.balloon_payment) }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web.py b/web.py
new file mode 100644
index 0000000..672499d
--- /dev/null
+++ b/web.py
@@ -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()
\ No newline at end of file
From e9fefb1a1abd51871a984ae7994dc36243995475 Mon Sep 17 00:00:00 2001
From: JohnKent
Date: Mon, 8 Jul 2019 00:27:38 -0400
Subject: [PATCH 14/38] Web version updated to allow sending emails.
---
templates/add.html | 18 +++
templates/email.html | 19 +++
templates/main.html | 35 ++---
templates/statement.html.jinja | 84 +++++++++++
.../statement.pdf.jinja | 6 +-
.../statement.text.jinja | 0
web.py | 134 +++++++++++++++++-
7 files changed, 273 insertions(+), 23 deletions(-)
create mode 100644 templates/add.html
create mode 100644 templates/email.html
create mode 100644 templates/statement.html.jinja
rename statement.pdf.jinja => templates/statement.pdf.jinja (99%)
rename statement.txt.jinja => templates/statement.text.jinja (100%)
diff --git a/templates/add.html b/templates/add.html
new file mode 100644
index 0000000..7e68337
--- /dev/null
+++ b/templates/add.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+ Loan Management
+
+
+
+
+
+Return to Main Screen .
+
+
\ No newline at end of file
diff --git a/templates/email.html b/templates/email.html
new file mode 100644
index 0000000..530e273
--- /dev/null
+++ b/templates/email.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ Loan Management
+
+
+
+
+ The email has been sent.
+Return to Main Screen .
+
+
+
\ No newline at end of file
diff --git a/templates/main.html b/templates/main.html
index 674a873..b311656 100644
--- a/templates/main.html
+++ b/templates/main.html
@@ -10,7 +10,7 @@
Loan Management
-Web Mortgage Manager
+