From 53327a9e092a7b40350e04408431098752450154 Mon Sep 17 00:00:00 2001 From: JohnKent Date: Wed, 19 Dec 2018 23:09:04 -0500 Subject: [PATCH] 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) }}
+

+

Payment History

+ + + + + + + + + + + +{% for item in past_payments %} + + + + + + + + + + {% if item.print_interest_total %} + + {% endif %} +{% endfor %} + +
# + Due DateDate PaidDays InterestPayment AmtPrincipal PmtInterest PmtNew Balance
{{ 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) }}
{{ item.interest_total_message }}
+

Total interest paid to date is {{ "$%.2f"|format(total_interest_paid_to_date) }}.

+

+ +

Remaining Amortization

+ + + + + + + + + + + +{% for item in future_payments %} + + + + + + + + +{% endfor %} + +
#Due DateDays InterestPayment AmtPrincipal PmtInterest PmtPrincipal Balance
{{ 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) }}
+

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 }} +