From a30486970889feabe9716f80d91fd6fd0daa6abb Mon Sep 17 00:00:00 2001 From: JohnKent Date: Sun, 7 Jul 2019 14:59:33 -0400 Subject: [PATCH] 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: + + +
+
+

+
+ +
+ + + + + + + + + + + + + + + +
Loan Information
Lender:{{ model.lender.name }}
{{ model.lender.address }}
+ {{ model.lender.city }} {{model.lender.state }} {{ model.lender.zip }}
+ {{ model.lender.phone }} +
Borrower:{{ model.borrower.name }} 
{{ model.borrower.address }}  +
{{ model.borrower.city }}, {{model.borrower.state }} {{ model.borrower.zip }} +
Account Number:{{ model.parameters.account_number }}
Origination Date: {{ model.parameters.start_date }}
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) }}
+

+

+
+ + + + + + + + + + + + + + + + + {% for item in model.past_payments %} + + + + + + + + + + + {% if item.month == 12 or loop.last %} + + {% endif %} + {% endfor %} + + +
Loan History
# + 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) }}
Total interest paid in {{item.year}} is {{ "$%.2f"|format(item.annual_interest_to_date) }}.
Total interest paid to date is {{ "$%.2f"|format(model.total_interest_paid_to_date) }}.
+
+
+ + + + + + + + + + + + + + + + + {% for item in model.future_payments %} + + + + + + + + + {% endfor %} + Balloon Payment Due: {{ "$%.2f"|format(model.balloon_payment) }} + +
Remaining Amortization
#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) }}
+
+
+
+ + + + + + + + + + +
Generate and Send Statement
Send From:{{model.email.from_address}}
Send To:{{model.email.to_address}}
Topic:
Message Body:
Send Statement As:
Include Future Amortization
+ +
+
+
+

Add Loan Payment Tab

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