import json from decimal import * from datetime import * from jinja2 import Environment, FileSystemLoader from fpdf import FPDF, HTMLMixin import smtplib from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText from email.MIMEBase import MIMEBase from email import Encoders def getStatementHeader(datastore): return datastore['header'] def getEmailInformation(datastore): return datastore['email'] def loadLoanInformation(filename): datastore = getDatastore(filename) loanModel = {} loanModel['datastore'] = datastore loanModel['email'] = getEmailInformation(datastore) loanModel['parameters'] = getLoanParameters(datastore) loanModel['lender'] = getLender(datastore) loanModel['borrower'] = getBorrower(datastore) loanModel['header'] = getStatementHeader(datastore) return loanModel def getLoanParameters(datastore): # read in the loan profile information loan = datastore['parameters'] annual_rate = Decimal(loan['interest_rate']) / 100 daily_interest_rate = annual_rate / 360 principal = Decimal(loan["principal"]).quantize(Decimal("1.00")) periods_per_year = Decimal(loan["periods_per_year"]) total_periods = Decimal(loan["periods"]) payment_day_of_month = int(loan['payment_day_of_month']) if "monthly_payment" in loan: monthly_payment = Decimal(loan["monthly_payment"]).quantize(Decimal("1.00")) else: # calculate expected monthly payment periodic_rate = annual_rate / periods_per_year discount_factor = (((1 + periodic_rate) ** total_periods) - 1) / ( periodic_rate * ((1 + periodic_rate) ** total_periods)) monthly_payment = (principal / discount_factor).quantize(Decimal("1.00")) loan['principal'] = principal # standardizes the format loan['annual_rate'] = annual_rate # standardizes the format loan['daily_interest_rate'] = daily_interest_rate loan['rate'] = '' + (annual_rate * 100).__str__() + '%' loan['next_payment_amt'] = 0 loan['next_payment_date'] = '12/12/12' loan['total_periods'] = total_periods loan['monthly_payment'] = monthly_payment datastore['parameters'] = loan return loan def getLender(datastore): return datastore['lender'] def getBorrower(datastore): return datastore['borrower'] def getDatastore(filename=None): try: if filename: with open(filename, 'r') as f: datastore = json.load(f) except Exception as e: print "An error occurred opening your loan file '%s'. " % filename print "The Exception:" print e.__repr__() quit() return datastore def amortizeLoan(loan): # loop over the payments and calculate the actual amortization monthly_payment = loan["parameters"]["monthly_payment"] actual_payments = loan["datastore"]["payments"] remaining_principal = loan["parameters"]["principal"] payment_day_of_month = int(loan["parameters"]["payment_day_of_month"]) daily_interest_rate = loan["parameters"]["daily_interest_rate"] total_periods = loan["parameters"]["total_periods"] interest_paid_through_date = datetime.strptime(loan["parameters"]["start_interest_date"], "%Y-%m-%d").date() next_payment_date = datetime.strptime(loan["parameters"]["first_payment_month"], '%Y-%m-%d').date() next_bill_date = date(year=next_payment_date.year, month=next_payment_date.month, day=payment_day_of_month) payment_number = 1 annual_interest = 0 total_interest = 0 old_bill_date = next_bill_date current_year = next_bill_date.year past_payments = [] future_payments = [] for payment in actual_payments: payment_date = datetime.strptime((payment[0]), '%Y-%m-%d').date() payment_amount = Decimal(payment[1]).quantize(Decimal("1.00")) days_since_last_payment = (payment_date - interest_paid_through_date).days 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: next_bill_date = date(year=old_bill_date.year + 1, month=1, day = payment_day_of_month) loan["total_interest_paid_to_date"] = total_interest loan["parameters"]["next_due_date"] = next_bill_date if (remaining_principal < monthly_payment): loan["parameters"]["next_payment_amt"] = remaining_principal else: loan["parameters"]["next_payment_amt"] = monthly_payment # loop over remaining scheduled payments and present estimated amortization while (payment_number <= total_periods) and (remaining_principal > 0): days_since_last_payment = (next_bill_date - interest_paid_through_date).days new_interest = (days_since_last_payment * remaining_principal * daily_interest_rate).quantize(Decimal("0.00")) # make sure the last payment isn't too much if new_interest + remaining_principal < monthly_payment: monthly_payment = new_interest + remaining_principal new_principal = monthly_payment - new_interest remaining_principal = remaining_principal - new_principal interest_paid_through_date = next_bill_date # complete the future payment amortization record future_payment_record = {} future_payment_record['payment_number'] = payment_number future_payment_record['payment_date'] = next_bill_date future_payment_record['days_of_interest'] = days_since_last_payment future_payment_record['payment_amount'] = monthly_payment future_payment_record['principal_payment'] = new_principal future_payment_record['interest_payment'] = new_interest future_payment_record['new_balance'] = remaining_principal future_payments.append(future_payment_record) payment_number = payment_number + 1 old_bill_date = next_bill_date if old_bill_date.month < 12: next_bill_date = date(year=old_bill_date.year, month=old_bill_date.month + 1, day=payment_day_of_month) else: next_bill_date = date(year=old_bill_date.year + 1, month=1, day=payment_day_of_month) loan["balloon_payment"] = remaining_principal loan["past_payments"] = past_payments loan["future_payments"] = future_payments return def transformTemplate(template_fileName, loanModel): # template_filename = "statement.txt.jinja" # setup jinja for creating the statement env = Environment(loader=FileSystemLoader('.')) template = env.get_template(template_fileName) print loanModel report = template.render(model=loanModel) return report def generatePDFAttachment(): template_filename = "statement.pdf.jinja" pass def generateEmail(from_address, to_address, subject, body, pdfAttachment, txtAttachment): msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = from_address msg['To'] = to_address msg.attach(MIMEText(body)) if (pdfAttachment != None): part = MIMEBase("application", "octet-stream") part.set_payload(pdfAttachment) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="statement.pdf"') msg.attach(part) if (txtAttachment != None): part = MIMEBase("text", "html") part.set_payload(txtAttachment) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="statement.html"') msg.attach(part) return msg def sendEmail(msg, from_address, to_address, passwd): try: server = smtplib.SMTP('smtp.gmail.com', 587) server.ehlo() server.starttls() server.login(from_address, passwd) server.sendmail(from_address, to_address, msg.as_string()) server.close() except: print "Couldn't send email." def createPDF(report): # create pdf class MyFPDF(FPDF, HTMLMixin): pass pdf = MyFPDF() pdf.set_font(family='Arial', size=12) pdf.add_page() pdf.write_html(report) # pdf.output(name='test.pdf', dest='F') return pdf.output(dest='S') def selectTemplate(loan): templateKey = loan["datastore"]["format"] + "Template" if templateKey in loan: template = loan[templateKey] else: template = 'statement.pdf.jinja' return template def main(): # 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 = "./testloan.json" # 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 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__': main()