From 0251431d1fc5f519a9be91a0a72b8089980971f0 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 16 Jun 2017 11:44:29 -0400 Subject: [PATCH 01/19] add description in financial_budget_simulator and back_end_budget, to guide data validation work --- bpfin/back_end_call/back_end_budget.py | 4 ++ .../financials/financial_budget_simulator.py | 38 ++++++++++--------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index 81d3fd6..4036635 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -22,6 +22,10 @@ def budget_simulation( raw_loan_input_list): """ Generate budget simulation data, to draw graph and to put in table + + Inputs Validation: + If input data are empty or not valid, pass None values to applicable variables + To Do: Need to refactor when merge from saving-scenario refactoring Need to validate data, to allow a lot of empty inputs diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index 9a28e45..62dfd2d 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -23,8 +23,6 @@ def cal_max_budget( determine the maximum budget for a project and its financing plan. Run a linear programming (LP) to determine the result, which is how much to borrow from each lender Math process is as following: - **** pre-request is: - # quoted_cost <= sum_loan_max_amount + downpayment_max **** LP constrains are: 1. objective function, determine maximum budget. x_1 + x_2 + x_3 + ... + x_n = max @@ -51,16 +49,22 @@ def cal_max_budget( Args: loan_list (list): list of objectives of loans - customer_preference (dictionary): customer preference for financing plan + downpayment_max (float): max amount that customer can make downpayment (self-finance part) + expected_payback: expected pay back on customer downpayment. if customer has no preference, it is infinity req_dscr(dictionary): required dscr - # cost (float): construction cost, can be estimated or quoted - first_year_saving (float): first year saving. # maybe it can be min_annual_saving first_year_noi (float): first year noi, after commissioning date. # maybe it can be min_noi first_year_cash (float): first year cash, after commissioning date + savings (float): first year saving. # maybe it can be min_annual_saving is_loan_first (boolean): 0 == client wants to reduce loan with self-finance, 1 == use as much loan as possible - # liability: do we need this? it's already calculated in balance sheet - # commissioning_date (date): construction finish date. Saving start at NEXT month - # scenario (dictionary): package of energy conservation measures (ECMs) + + Inputs Validation: + loan_list: empty list is allowed. project budget = loan + self-finance. result shows no loan options + downpayment_max: None allowed. If None passed in, replace its default value = 0 + expected_payback: None allowed, can be passed in as None + req_dscr: None allowed. When pass in None, replace its default value = 1 + first_year_noi: None allowed, can be passed in as None + first_year_cash: None allowed, can be passed in as None + savings: None not allowed. Either 0 or a number Return: float: max budget with given conditions @@ -75,18 +79,16 @@ def cal_max_budget( Note: noi == net operating income dscr == debt service coverage ratio - - To do: calculate self_finance amount - rewrite descriptions """ - # sum_loan_max_amount = sum(list(current_loan.max_amount for current_loan in loan_list)) - # # pre-request judge: loan_list has a length - # print(len(loan_list)) + # sum_loan_max_amount = sum(list(current_loan.max_amount for current_loan in loan_list)) in fact, not needed !!!!!!! + # # pre-request judge: loan_list has a length. in fact, not needed !!!!!!! # linear programming (LP) # Set LP constrains. + # objective function: x1 + x2 + x3 +...+ xn = maximum c_formula = [-1] * (len(loan_list) + 1) + # x variables constraint, x1/payback1 + x2/payback2 +...+ xn/payback_n <= saving/dscr # x variables constraint, debt services <= required_debt_service A_matrix = [] @@ -103,12 +105,14 @@ def cal_max_budget( ) b_list.append(min( savings / req_dscr['req_saving_dscr'], - first_year_noi / req_dscr['req_noi_dscr'], - first_year_cash / req_dscr['req_cash_dscr'] - )) + first_year_noi / req_dscr['req_noi_dscr'], # !!!!!!! if first_year_noi is None, return None + first_year_cash / req_dscr['req_cash_dscr'] # !!!!!!! if req_dscr is 1, just fine + )) # !!!!!! min(for m in list if m is not None) + # x variables bounds, 0 <= x[i] <= loan[i]_max_amount bound_list = list((0, current_loan.max_amount) for current_loan in loan_list) bound_list.append((0, downpayment_max if not is_loan_first else 0)) + # print('\ndscr_allow', savings / req_dscr['req_saving_dscr'], first_year_noi / req_dscr['req_noi_dscr'], first_year_cash / req_dscr['req_cash_dscr']) # print('\nLP constraints =', A_matrix, b_list, bound_list) -- GitLab From 9e777f262482a342264630a2233d93d2b701b001 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 19 Jun 2017 15:36:11 -0400 Subject: [PATCH 02/19] Resolved a few None inputs, wrote down guide for other work --- .../financials/financial_budget_simulator.py | 134 +++++++----------- 1 file changed, 55 insertions(+), 79 deletions(-) diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index 62dfd2d..4a9c908 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -61,7 +61,7 @@ def cal_max_budget( loan_list: empty list is allowed. project budget = loan + self-finance. result shows no loan options downpayment_max: None allowed. If None passed in, replace its default value = 0 expected_payback: None allowed, can be passed in as None - req_dscr: None allowed. When pass in None, replace its default value = 1 + req_dscr: None allowed. if a dscr is None, it means no requirement on that condition first_year_noi: None allowed, can be passed in as None first_year_cash: None allowed, can be passed in as None savings: None not allowed. Either 0 or a number @@ -82,6 +82,10 @@ def cal_max_budget( """ # sum_loan_max_amount = sum(list(current_loan.max_amount for current_loan in loan_list)) in fact, not needed !!!!!!! # # pre-request judge: loan_list has a length. in fact, not needed !!!!!!! + loan_list = ([] if loan_list is None else copy.deepcopy(loan_list)) + downpayment_max = (0 if downpayment_max is None else copy.deepcopy(downpayment_max)) + # for current_dscr in req_dscr: + # req_dscr[current_dscr] = (req_dscr[current_dscr] if req_dscr[current_dscr] else 1.0) # linear programming (LP) # Set LP constrains. @@ -97,17 +101,28 @@ def cal_max_budget( a_2 = copy.deepcopy(a_0) a_1.append(1 / expected_payback * 12 if expected_payback else 0) a_2.append(0) - A_matrix.append(a_1) - A_matrix.append(a_2) + + b_0 = [] + b_0.append((savings / req_dscr['req_saving_dscr'] if req_dscr['req_saving_dscr'] else None)) + if first_year_noi is None: + b_0.append(None) + else: + b_0.append(first_year_noi / req_dscr['req_noi_dscr'] if req_dscr['req_noi_dscr'] else None) + if first_year_cash is None: + b_0.append(None) + else: + b_0.append(first_year_cash / req_dscr['req_cash_dscr'] if req_dscr['req_cash_dscr'] else None) + + b_1 = b_0[0] + b_2 = min(b_i for b_i in b_0 if b_i) # !!!!!! what if 3 of them are None? + b_list = [] - b_list.append( - savings / req_dscr['req_saving_dscr'] - ) - b_list.append(min( - savings / req_dscr['req_saving_dscr'], - first_year_noi / req_dscr['req_noi_dscr'], # !!!!!!! if first_year_noi is None, return None - first_year_cash / req_dscr['req_cash_dscr'] # !!!!!!! if req_dscr is 1, just fine - )) # !!!!!! min(for m in list if m is not None) + if b_1: + b_list.append(b_1) + A_matrix.append(a_1) + if b_2: + b_list.append(b_2) + A_matrix.append(a_2) # x variables bounds, 0 <= x[i] <= loan[i]_max_amount bound_list = list((0, current_loan.max_amount) for current_loan in loan_list) @@ -123,7 +138,10 @@ def cal_max_budget( return [0] * (len(loan_list) + 1) if is_loan_first: # print('\n saving allow =', (savings/req_dscr['req_saving_dscr'] - total_ds) * expected_payback / 12) - downpayment = min((savings/req_dscr['req_saving_dscr'] - total_ds) * expected_payback / 12, downpayment_max) + if not req_dscr['req_saving_dscr']: + downpayment = downpayment_max + else: + downpayment = min((savings/req_dscr['req_saving_dscr'] - total_ds) * expected_payback / 12, downpayment_max) # !!!! need careful calculation res.x[-1] = downpayment return list(res.x) @@ -218,76 +236,34 @@ def form_budget_simulation_result(loan_list): return budget_simulation_result -# print(form_budget( +# **** ugly test **** +req_dscr = { + 'req_noi_dscr': 1.15, + 'req_cash_dscr': 1.15, + 'req_saving_dscr': 1.10 +} + +# print(cal_max_budget( # loan_list=Loan_List(db.loan_input_list).loan_list, # downpayment_max=90000, # expected_payback=120, # req_dscr=db.req_dscr, # first_year_noi=10000, # first_year_cash=10000, -# savings=15000)) - - -# ******** the following is draft for old work, for budge calculator development ******** - -# ## LP function, return total budget and variables -# def budget_LP(Financial_list,Loan_list,saving_percentage,other_loan,exp_pb,SF_fin,is_loan_first,saving_contigency,min_DSCR): -# # loan information ************* -# other_loan_DS=other_loan -# loan_max_list=[] -# ratio_list=[] -# DSmax_list=[] -# for loan in Loan_list: -# loan_max_list.append(loan.max_amount) -# ratio=cal_loan_payback(loan.interest,loan.duration) -# ratio_list.append(ratio) -# DSmax_list.append(loan.max_amount/ratio) -# DSmax=sum(DSmax_list) - -# # client preference ************* -# ratio_SF=exp_pb #expected payback period for self finance -# # is_loan_first = does client want to reduce loan by self-finance -# # willingtopay = max amount that the client can/want to self-finance - -# # financial and savings ************* -# noi=average([Financial_list[0].noi,Financial_list[1].noi,Financial_list[2].noi]) -# savings=average([Financial_list[0].energy_opex,Financial_list[1].energy_opex,Financial_list[2].energy_opex])*saving_percentage - -# ## object formula and conditions ********************************** -# c =[-1]*(len(Loan_list)+1) - -# a_base=[] -# for ratio in ratio_list: -# a_base.append(1/ratio) -# a1=[] -# a2=[] -# for pp in a_base: -# a1.append(pp) -# a2.append(pp) -# a1.append(1/ratio_SF) -# a2.append(0) -# a3=[0]*(len(Loan_list)+1) -# a3[-1]=1 - -# A =[ -# a1, -# a2, -# a3 -# ] -# b =[ -# savings/saving_contigency, -# ((noi+savings)/min_DSCR-other_loan_DS), -# SF_fin -# ] - -# bound_list=[] -# for loan_max in loan_max_list: -# bound_list.append((0,loan_max)) -# bound_list.append((0,(max(savings-DSmax,0)*ratio_SF if is_loan_first==True else None))) -# bounds=bound_list - -# res = linprog(c,A_ub=A,b_ub=b,bounds=bounds,options={'disp':False}) -# # print('Max_Budget',-res.fun) -# # print(res.x) -# # print('\n') -# return [-res.fun,res.x] +# savings=15000, +# is_loan_first=1)) + +print(cal_max_budget( + loan_list=Loan_List(db.loan_input_list).loan_list, + downpayment_max=90000, + expected_payback=1200000, + req_dscr=req_dscr, + first_year_noi=10000, + first_year_cash=10000, + savings=15000, + is_loan_first=1)) +# Should list out all possible situations and reolve them one by one. +# Especially when Saving_dscr is 0, is None, and when expected payback on S-F is 0, is None +# dscr == 0 == None, they should be same, aka, no coverage ratio is required. req_saving_dscr = 0 -> net saving is acceptable +# expected_payback on S-F = 0 -> hope get paid back immediately, downpayment = 0, raise an warning +# expected_payback on S-F = None -> no hope for payback on SF, downpayment = downpayment_max -- GitLab From 132a911b04a0a116bd442a18746cec8272dfd0e9 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Tue, 20 Jun 2017 17:28:48 -0400 Subject: [PATCH 03/19] finalized guidence for future fixing --- .../financials/financial_budget_simulator.py | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index 4a9c908..6534a33 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -81,11 +81,11 @@ def cal_max_budget( dscr == debt service coverage ratio """ # sum_loan_max_amount = sum(list(current_loan.max_amount for current_loan in loan_list)) in fact, not needed !!!!!!! - # # pre-request judge: loan_list has a length. in fact, not needed !!!!!!! + # # pre-requet check: at least one financing source is indicated: loan_list = ([] if loan_list is None else copy.deepcopy(loan_list)) downpayment_max = (0 if downpayment_max is None else copy.deepcopy(downpayment_max)) - # for current_dscr in req_dscr: - # req_dscr[current_dscr] = (req_dscr[current_dscr] if req_dscr[current_dscr] else 1.0) + # # pre-requet check: at least one upper limit (saving, noi or cash) is indicated: + # # linear programming (LP) # Set LP constrains. @@ -262,7 +262,22 @@ print(cal_max_budget( first_year_cash=10000, savings=15000, is_loan_first=1)) -# Should list out all possible situations and reolve them one by one. + + +# at least one source input of loan or downpayment +# at least one of the saving, first_year_noi, first_year_cash is passed in +# loan_list is None: run without loan, just SF. +# downpayment_max is None, set it to 0 +# expected_payback = 0: hope get paid back immediately, 1/expected_payback = 0, downpayment = 0, raise a warning +# expected_payback on S-F = None: no hope for payback on SF, 1/expected_payback = 0, downpayment = downpayment_max +# req_dscr = 0: req_dscr = None: no coverage ratio is required. req_saving_dscr = 0 -> negtive net saving is acceptable +# first_year_noi = None: no I/S input, this condition is not considered +# first_year_cash = None: no cash or B/S input, this condition is not considered +# savings = None: no total bill as input, this condition is not considered + + +# ** uesless draft *** +# Should list out all possible situations and reolve them one by one. # Especially when Saving_dscr is 0, is None, and when expected payback on S-F is 0, is None # dscr == 0 == None, they should be same, aka, no coverage ratio is required. req_saving_dscr = 0 -> net saving is acceptable # expected_payback on S-F = 0 -> hope get paid back immediately, downpayment = 0, raise an warning -- GitLab From 89c59b35058462159415b6a900466a2d5d0fb714 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Thu, 22 Jun 2017 18:33:45 -0400 Subject: [PATCH 04/19] Upgrade budget inputs tolerance. None, 0 inputs are allowed in almost all situations. --- .../financials/financial_budget_simulator.py | 92 +++++++++++-------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index 6534a33..2786ade 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -4,11 +4,26 @@ run budget calculator and return table of results """ import copy from scipy.optimize import linprog -from bpfin.lib.other import sumproduct +from bpfin.lib.other import sumproduct, multiply_list from bpfin.tests.testdata import sample_data as db from bpfin.financials.loan import Loan_List +def validate_list_one_true(param_list): + """ + validate the list elements, at least one element is True. + If all elements are None or 0, return False + Args: + param_list(list): list that need validation + Return: + boolean: True == at least one element is True + """ + for param in param_list: + if param: + return True + return False + + def cal_max_budget( loan_list, downpayment_max, @@ -81,15 +96,18 @@ def cal_max_budget( dscr == debt service coverage ratio """ # sum_loan_max_amount = sum(list(current_loan.max_amount for current_loan in loan_list)) in fact, not needed !!!!!!! + # # pre-requet check: at least one financing source is indicated: loan_list = ([] if loan_list is None else copy.deepcopy(loan_list)) downpayment_max = (0 if downpayment_max is None else copy.deepcopy(downpayment_max)) - # # pre-requet check: at least one upper limit (saving, noi or cash) is indicated: - # + + # # pre-requet check: all dscr should not be None. If so, replace it with 0 + for dscr in req_dscr: + if not req_dscr[dscr]: + req_dscr[dscr] = 0 # linear programming (LP) # Set LP constrains. - # objective function: x1 + x2 + x3 +...+ xn = maximum c_formula = [-1] * (len(loan_list) + 1) @@ -99,50 +117,45 @@ def cal_max_budget( a_0 = list(1 / current_loan.payback for current_loan in loan_list) a_1 = copy.deepcopy(a_0) a_2 = copy.deepcopy(a_0) + a_3 = copy.deepcopy(a_0) a_1.append(1 / expected_payback * 12 if expected_payback else 0) a_2.append(0) + a_3.append(0) - b_0 = [] - b_0.append((savings / req_dscr['req_saving_dscr'] if req_dscr['req_saving_dscr'] else None)) - if first_year_noi is None: - b_0.append(None) - else: - b_0.append(first_year_noi / req_dscr['req_noi_dscr'] if req_dscr['req_noi_dscr'] else None) - if first_year_cash is None: - b_0.append(None) - else: - b_0.append(first_year_cash / req_dscr['req_cash_dscr'] if req_dscr['req_cash_dscr'] else None) + b_list = [] + b_list.append(savings if savings else 0) + b_list.append(first_year_noi if first_year_noi else 0) + b_list.append(first_year_cash if first_year_cash else 0) - b_1 = b_0[0] - b_2 = min(b_i for b_i in b_0 if b_i) # !!!!!! what if 3 of them are None? + A_matrix.append(multiply_list(a_1, (req_dscr['req_saving_dscr'] if savings is not None else 0))) + A_matrix.append(multiply_list(a_2, (req_dscr['req_noi_dscr'] if first_year_noi is not None else 0))) + A_matrix.append(multiply_list(a_2, (req_dscr['req_cash_dscr'] if first_year_cash is not None else 0))) - b_list = [] - if b_1: - b_list.append(b_1) - A_matrix.append(a_1) - if b_2: - b_list.append(b_2) - A_matrix.append(a_2) + print('\n A_matrix =', A_matrix) + print('\n b_list =', b_list) # x variables bounds, 0 <= x[i] <= loan[i]_max_amount bound_list = list((0, current_loan.max_amount) for current_loan in loan_list) bound_list.append((0, downpayment_max if not is_loan_first else 0)) - # print('\ndscr_allow', savings / req_dscr['req_saving_dscr'], first_year_noi / req_dscr['req_noi_dscr'], first_year_cash / req_dscr['req_cash_dscr']) - # print('\nLP constraints =', A_matrix, b_list, bound_list) - # LP calculation. x[i] == loan amount from loan[i], x[-1] == self-finance amount res = linprog(c_formula, A_ub=A_matrix, b_ub=b_list, bounds=bound_list, options={'disp': False}) total_ds = sumproduct(res.x, a_2) if not res.success: return [0] * (len(loan_list) + 1) + + # Calculate self-finance amount if loan_fist is True if is_loan_first: - # print('\n saving allow =', (savings/req_dscr['req_saving_dscr'] - total_ds) * expected_payback / 12) - if not req_dscr['req_saving_dscr']: - downpayment = downpayment_max + if savings is not None and req_dscr['req_saving_dscr']: + # self finance amount is capped by extra_saving, which is Savings/req_dscr - total loan burden + extra_saving = float("{:0.4f}".format((savings/req_dscr['req_saving_dscr'] - total_ds))) + downpayment = (min(max([0, extra_saving]) * expected_payback / 12, downpayment_max)) else: - downpayment = min((savings/req_dscr['req_saving_dscr'] - total_ds) * expected_payback / 12, downpayment_max) # !!!! need careful calculation + downpayment = downpayment_max res.x[-1] = downpayment + + # Return final result + print('\n total budget =', sum(list(res.x))) return list(res.x) @@ -239,7 +252,7 @@ def form_budget_simulation_result(loan_list): # **** ugly test **** req_dscr = { 'req_noi_dscr': 1.15, - 'req_cash_dscr': 1.15, + 'req_cash_dscr': 1.05, 'req_saving_dscr': 1.10 } @@ -256,24 +269,29 @@ req_dscr = { print(cal_max_budget( loan_list=Loan_List(db.loan_input_list).loan_list, downpayment_max=90000, - expected_payback=1200000, + expected_payback=120, req_dscr=req_dscr, - first_year_noi=10000, - first_year_cash=10000, - savings=15000, + first_year_noi=None, + first_year_cash=None, + savings=None, is_loan_first=1)) +# !!!! next step: write description for different possible situations +# !!!! and also, write some warning message. Learn to run warning message + # at least one source input of loan or downpayment # at least one of the saving, first_year_noi, first_year_cash is passed in # loan_list is None: run without loan, just SF. # downpayment_max is None, set it to 0 # expected_payback = 0: hope get paid back immediately, 1/expected_payback = 0, downpayment = 0, raise a warning # expected_payback on S-F = None: no hope for payback on SF, 1/expected_payback = 0, downpayment = downpayment_max -# req_dscr = 0: req_dscr = None: no coverage ratio is required. req_saving_dscr = 0 -> negtive net saving is acceptable +# req_dscr = 0: req_dscr = None: no coverage ratio is required. +# cash, noi dscr = 0 or None -> not required for financial coverage. raise a warning +# req_saving_dscr = 0 -> negtive net saving is acceptable. raise a warning # first_year_noi = None: no I/S input, this condition is not considered # first_year_cash = None: no cash or B/S input, this condition is not considered -# savings = None: no total bill as input, this condition is not considered +# savings = None: no total bill as input, savings condition is not considered, totally depend on noi/cash condition # ** uesless draft *** -- GitLab From b395c70159e84e55352e572d36706d259bfc1eff Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 23 Jun 2017 16:58:34 -0400 Subject: [PATCH 05/19] Update description in financial_budget_simulator, wrote test cases --- .../financials/financial_budget_simulator.py | 284 ++++++++++++++---- 1 file changed, 219 insertions(+), 65 deletions(-) diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index 2786ade..88ace55 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -9,21 +9,6 @@ from bpfin.tests.testdata import sample_data as db from bpfin.financials.loan import Loan_List -def validate_list_one_true(param_list): - """ - validate the list elements, at least one element is True. - If all elements are None or 0, return False - Args: - param_list(list): list that need validation - Return: - boolean: True == at least one element is True - """ - for param in param_list: - if param: - return True - return False - - def cal_max_budget( loan_list, downpayment_max, @@ -45,8 +30,10 @@ def cal_max_budget( 2. x variables constraints sum(x_i / payback_i) <= saving/dscr * sum of annual debt service and self-finance payback <= saving/dscr - sum(x_i / payback_i) - (x_n / payback_n) <= min (saving/dscr, noi/dscr, cash/dscr) - * sum of loan debt service <= financial constraints, making project financially feasibile + sum(x_i / payback_i) - (x_n / payback_n) <= noi/dscr + * sum of loan debt service <= noi constraint, making project financially feasibile + sum(x_i / payback_i) - (x_n / payback_n) <= cash/dscr + * sum of loan debt service <= cash constraint, making project financially feasibile 3. x variables bounds 0 <= x_i <= loan[i]_max_amount 0 <= x_n <= downpayment_max @@ -66,20 +53,36 @@ def cal_max_budget( loan_list (list): list of objectives of loans downpayment_max (float): max amount that customer can make downpayment (self-finance part) expected_payback: expected pay back on customer downpayment. if customer has no preference, it is infinity - req_dscr(dictionary): required dscr + req_dscr(dictionary): required dscr, kyes are: req_saving_dscr, req_noi_dscr, req_cash_dscr first_year_noi (float): first year noi, after commissioning date. # maybe it can be min_noi first_year_cash (float): first year cash, after commissioning date savings (float): first year saving. # maybe it can be min_annual_saving is_loan_first (boolean): 0 == client wants to reduce loan with self-finance, 1 == use as much loan as possible - Inputs Validation: - loan_list: empty list is allowed. project budget = loan + self-finance. result shows no loan options - downpayment_max: None allowed. If None passed in, replace its default value = 0 - expected_payback: None allowed, can be passed in as None - req_dscr: None allowed. if a dscr is None, it means no requirement on that condition - first_year_noi: None allowed, can be passed in as None - first_year_cash: None allowed, can be passed in as None - savings: None not allowed. Either 0 or a number + Inputs Validation, Cases Explain if Inputs Are None or 0: + All inputs are allowed as None or 0, converting work is done inside function. + However, certain rules apply as following: + + loan_list: empty list or None is allowed, if no Loan options are available + if loan_list is empty, project budget = self-finance amount. result also shows no loan options + downpayment_max: 0 or None is allowed, if customer has no self-finance money or info is n/a + If None passed in, replace its value with 0 (done in this function) + This also ensures that at least one financing source is indicated, even it is 0 + If loan_list is empty and downpayment_max is 0/None, warning raised + expected_payback: 0 or None is allowed, but meanings differ + If customer hopes to get investment immediately, pass in 0, result of downpayment = 0, warning raised + If customer has no expectation on payback, pass in None, result of downpayment = downpayment_max + req_dscr: 0 or None is allowed. If a condition (one of the saving, noi, or cash) is not required, pass in None + if a dscr is None, it means no requirement on that condition, replace with 0 + first_year_noi: 0 and None is allowed, but meanings differ + If financial info available but noi is 0, pass in 0. If noi_dscr required, all budget = 0, warining raised + If customer has no financial info available, pass in None. Condition of noi will not be calculated + first_year_cash: 0 and None is allowed, but meanings differ + If financial info available but cash is 0, pass in 0. If cash_dscr required, all budget = 0, warining raised + If customer has no financial info available, pass in None. Condition of cash will not be calculated + savings: 0 and None is allowed, but meanings differ + If bill info but bill is 0, pass in 0. If saving_dscr is required, all budget = 0, warining raised + If customer has no bill info available, pass in None. Condition of cash will not be calculated Return: float: max budget with given conditions @@ -131,8 +134,8 @@ def cal_max_budget( A_matrix.append(multiply_list(a_2, (req_dscr['req_noi_dscr'] if first_year_noi is not None else 0))) A_matrix.append(multiply_list(a_2, (req_dscr['req_cash_dscr'] if first_year_cash is not None else 0))) - print('\n A_matrix =', A_matrix) - print('\n b_list =', b_list) + # print('\n A_matrix =', A_matrix) + # print('\n b_list =', b_list) # x variables bounds, 0 <= x[i] <= loan[i]_max_amount bound_list = list((0, current_loan.max_amount) for current_loan in loan_list) @@ -146,7 +149,7 @@ def cal_max_budget( # Calculate self-finance amount if loan_fist is True if is_loan_first: - if savings is not None and req_dscr['req_saving_dscr']: + if savings is not None and req_dscr['req_saving_dscr'] and expected_payback is not None: # self finance amount is capped by extra_saving, which is Savings/req_dscr - total loan burden extra_saving = float("{:0.4f}".format((savings/req_dscr['req_saving_dscr'] - total_ds))) downpayment = (min(max([0, extra_saving]) * expected_payback / 12, downpayment_max)) @@ -155,7 +158,7 @@ def cal_max_budget( res.x[-1] = downpayment # Return final result - print('\n total budget =', sum(list(res.x))) + # print('\n total budget =', sum(list(res.x))) return list(res.x) @@ -249,6 +252,22 @@ def form_budget_simulation_result(loan_list): return budget_simulation_result +def validate_list_one_true(param_list): + """ + This func is not called now. Maybe it is useful in the future + validate the list elements, at least one element is True. + If all elements are None or 0, return False + Args: + param_list(list): list that need validation + Return: + boolean: True == at least one element is True + """ + for param in param_list: + if param: + return True + return False + + # **** ugly test **** req_dscr = { 'req_noi_dscr': 1.15, @@ -256,47 +275,182 @@ req_dscr = { 'req_saving_dscr': 1.10 } +# test cases +# print('\n Base Case:') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr=req_dscr, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n loan_list=None') +# print(cal_max_budget( +# loan_list=None, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr=req_dscr, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n loan_list = downpayment_max = None,') +# print(cal_max_budget( +# loan_list=None, +# downpayment_max=None, +# expected_payback=120, +# req_dscr=req_dscr, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n expected_payback = None') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=None, +# req_dscr=req_dscr, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n expected_payback = None, is_loan_first=0') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=None, +# req_dscr=req_dscr, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=0)) + +# print('\n expected_payback = 0') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=0, +# req_dscr=req_dscr, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n req_noi_dscr = None, req_cash_dscr = None') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': None, 'req_cash_dscr': None, 'req_saving_dscr': 1.10}, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n req_saving_dscr = None') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': 1.15, 'req_cash_dscr': 1.05, 'req_saving_dscr': None}, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n req_noi_dscr = None, req_cash_dscr=0, req_saving_dscr = None') # print(cal_max_budget( # loan_list=Loan_List(db.loan_input_list).loan_list, # downpayment_max=90000, # expected_payback=120, -# req_dscr=db.req_dscr, +# req_dscr={'req_noi_dscr': None, 'req_cash_dscr': 0, 'req_saving_dscr': None}, # first_year_noi=10000, # first_year_cash=10000, # savings=15000, # is_loan_first=1)) -print(cal_max_budget( - loan_list=Loan_List(db.loan_input_list).loan_list, - downpayment_max=90000, - expected_payback=120, - req_dscr=req_dscr, - first_year_noi=None, - first_year_cash=None, - savings=None, - is_loan_first=1)) - - -# !!!! next step: write description for different possible situations -# !!!! and also, write some warning message. Learn to run warning message - -# at least one source input of loan or downpayment -# at least one of the saving, first_year_noi, first_year_cash is passed in -# loan_list is None: run without loan, just SF. -# downpayment_max is None, set it to 0 -# expected_payback = 0: hope get paid back immediately, 1/expected_payback = 0, downpayment = 0, raise a warning -# expected_payback on S-F = None: no hope for payback on SF, 1/expected_payback = 0, downpayment = downpayment_max -# req_dscr = 0: req_dscr = None: no coverage ratio is required. -# cash, noi dscr = 0 or None -> not required for financial coverage. raise a warning -# req_saving_dscr = 0 -> negtive net saving is acceptable. raise a warning -# first_year_noi = None: no I/S input, this condition is not considered -# first_year_cash = None: no cash or B/S input, this condition is not considered -# savings = None: no total bill as input, savings condition is not considered, totally depend on noi/cash condition - - -# ** uesless draft *** -# Should list out all possible situations and reolve them one by one. -# Especially when Saving_dscr is 0, is None, and when expected payback on S-F is 0, is None -# dscr == 0 == None, they should be same, aka, no coverage ratio is required. req_saving_dscr = 0 -> net saving is acceptable -# expected_payback on S-F = 0 -> hope get paid back immediately, downpayment = 0, raise an warning -# expected_payback on S-F = None -> no hope for payback on SF, downpayment = downpayment_max +# print('\n first_year_noi=None') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': 1.15, 'req_cash_dscr': 1.05, 'req_saving_dscr': 1.10}, +# first_year_noi=None, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n first_year_noi=0') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': 1.15, 'req_cash_dscr': 1.05, 'req_saving_dscr': 1.10}, +# first_year_noi=0, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n first_year_noi=0, req_noi_dscr: 0') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': 0, 'req_cash_dscr': 1.05, 'req_saving_dscr': 1.10}, +# first_year_noi=0, +# first_year_cash=10000, +# savings=15000, +# is_loan_first=1)) + +# print('\n savings=None') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': 1.15, 'req_cash_dscr': 1.05, 'req_saving_dscr': 1.10}, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=None, +# is_loan_first=1)) + +# print('\n savings=0') +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': 1.15, 'req_cash_dscr': 1.05, 'req_saving_dscr': 1.10}, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=0, +# is_loan_first=1)) + +# print('\n savings=0 , req_saving_dscr =0' ) +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr={'req_noi_dscr': 1.15, 'req_cash_dscr': 1.05, 'req_saving_dscr': 0}, +# first_year_noi=10000, +# first_year_cash=10000, +# savings=0, +# is_loan_first=1)) + +# print('\n first_year_noi=None, first_year_cash=None, savings=None,' ) +# print(cal_max_budget( +# loan_list=Loan_List(db.loan_input_list).loan_list, +# downpayment_max=90000, +# expected_payback=120, +# req_dscr=req_dscr, +# first_year_noi=None, +# first_year_cash=None, +# savings=None, +# is_loan_first=1)) + + +# !!!! next step: write some warning message. Learn to run warning message -- GitLab From 68fb16922184abdb4268e8b68bacc9029bcc73c0 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 23 Jun 2017 17:15:45 -0400 Subject: [PATCH 06/19] merge from master --- bpfin/back_end_call/back_end_budget.py | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index 4036635..a664c53 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -88,20 +88,20 @@ def budget_simulation( # **** ugly test **** -# from bpfin.tests.testdata import feature_data as db -# finalresult = budget_simulation( -# db.analysis_date, -# db.commission_date, -# db.customer_preference, -# db.req_dscr, -# db.raw_bill_table, -# db.raw_annual_bill_table, -# db.raw_income_input, -# -2.0, -# db.raw_liability_input, -# db.raw_cash_balance, -# db.raw_loan_input_list -# ) +from bpfin.tests.testdata import feature_data as db +finalresult = budget_simulation( + db.analysis_date, + db.commission_date, + db.customer_preference, + db.req_dscr, + db.raw_bill_table, + db.raw_annual_bill_table, + db.raw_income_input, + -2.0, + db.raw_liability_input, + db.raw_cash_balance, + db.raw_loan_input_list + ) # import pprint -- GitLab From 57edff8ec9d9438af9f6b39c10c2fdbeb274ba46 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 23 Jun 2017 17:24:23 -0400 Subject: [PATCH 07/19] add empty check for raw_loan_input --- bpfin/back_end_call/back_end_budget.py | 6 +++--- bpfin/financials/loan.py | 1 + bpfin/tests/testdata/feature_data.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index a664c53..755e72b 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -104,6 +104,6 @@ finalresult = budget_simulation( ) -# import pprint -# pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) -# pp.pprint(finalresult) +import pprint +pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) +pp.pprint(finalresult) diff --git a/bpfin/financials/loan.py b/bpfin/financials/loan.py index 76b8fae..e8c7046 100644 --- a/bpfin/financials/loan.py +++ b/bpfin/financials/loan.py @@ -127,6 +127,7 @@ class Loan_List(): Args: loan_input_list (list): list of dictionary of loan basic terms. """ + loan_input_list = ([] if not loan_input_list else loan_input_list) self.loan_list = [] for current_loan in loan_input_list: temp_loan = None diff --git a/bpfin/tests/testdata/feature_data.py b/bpfin/tests/testdata/feature_data.py index 3896442..7392f66 100644 --- a/bpfin/tests/testdata/feature_data.py +++ b/bpfin/tests/testdata/feature_data.py @@ -266,6 +266,7 @@ loan_term_2 = {'institute': 'Joe Fund', 'max_amount': 50000, 'interest': 0.05, ' loan_term_3 = {'institute': 'Tooraj Capital', 'max_amount': 75000, 'interest': 0.07, 'duration': 114} loan_term_dict = {1: loan_term_1, 2: loan_term_2, 3: loan_term_3} raw_loan_input_list = [loan_term_1, loan_term_2, loan_term_3] +# raw_loan_input_list = None # ****** result ***** -- GitLab From cfe7a434d847e855b4587d078a086a81e3eb73a6 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 23 Jun 2017 17:51:06 -0400 Subject: [PATCH 08/19] tested out empty bill inputs --- bpfin/back_end_call/back_end_budget.py | 7 ++++++- bpfin/tests/testdata/feature_data.py | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index 755e72b..c985d21 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -24,7 +24,12 @@ def budget_simulation( Generate budget simulation data, to draw graph and to put in table Inputs Validation: - If input data are empty or not valid, pass None values to applicable variables + analysis_date is needed + commission_date is needed + customer_preference['downpayment_max'] can be None or 0. Convert None to 0 + customer_preference['expected_payback'] cane be None or 0, meanings are different + req_dscr keys can be None or 0 + if raw_bill_table is None, and raw_annual_bill_table is None, no bill is available, To Do: Need to refactor when merge from saving-scenario refactoring diff --git a/bpfin/tests/testdata/feature_data.py b/bpfin/tests/testdata/feature_data.py index 7392f66..f30f2dc 100644 --- a/bpfin/tests/testdata/feature_data.py +++ b/bpfin/tests/testdata/feature_data.py @@ -29,6 +29,12 @@ req_dscr = { 'req_saving_dscr': 1.10 } +# req_dscr = { +# 'req_noi_dscr': 1.15, +# 'req_cash_dscr': 1.15, +# 'req_saving_dscr': None +# } + # Customer Preference customer_preference = { @@ -172,6 +178,12 @@ raw_bill_table = { 'oil': raw_oil_bill_demo, 'water': None} +# raw_bill_table = { +# 'gas': None, +# 'electricity': None, +# 'oil': None, +# 'water': None} + # raw annual_bill # annual_bill_gas = {2014: 0, 2015: 1020, 2016: 1220, 2017: 1520} # annual_bill_oil = {2015: 1010, 2016: 1210, 2017: 1510} @@ -181,12 +193,19 @@ annual_bill_water = {2014: 20000, 2015: 20500, 2016: 21000} # annual_bill_oil = {2015: 1010, 2016: 1210, 2017: 1510} raw_annual_bill_table = { - 'electricity': None, # True == Mannual Input + 'electricity': None, 'gas': None, 'oil': None, 'water': annual_bill_water } +# raw_annual_bill_table = { +# 'electricity': None, +# 'gas': None, +# 'oil': None, +# 'water': None +# } + manual_input_dict = { 'electricity': False, 'gas': False, -- GitLab From 8bfb5333abc3c7475026faf104f7b4f2a46717c9 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 23 Jun 2017 18:18:34 -0400 Subject: [PATCH 09/19] halfway work on back_end_budget to test cases of empty inputs. Now we are on no energy bill case --- bpfin/back_end_call/back_end_budget.py | 10 ++++++++++ bpfin/back_end_call/back_end_inputs.py | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index c985d21..570a4e3 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -69,6 +69,14 @@ def budget_simulation( budget_simulation_result = form_budget_simulation_result(loan_list) + print('\n loan_list=', loan_list) + print('\n downpayment_max=', customer_preference['downpayment_max']) + print('\n expected_payback=', customer_preference['expected_payback']) + print('\n req_dscr=', req_dscr) + print('\n first_year_noi=', first_year_noi) + print('\n first_year_cash=', first_year_cash) + print('\n first_year_energy=', first_year_energy) + for saving_percent in saving_potential_list: financing_per_save = form_max_financing( loan_list=loan_list, @@ -108,6 +116,8 @@ finalresult = budget_simulation( db.raw_loan_input_list ) +# if energy bill is 0, first_year_noi will be lower than the case with energy bill, because no saving +# to confirm this hypothesis, need to run income number and compare noi. historical noi should be same for energy bill not 0 vs 0 import pprint pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) diff --git a/bpfin/back_end_call/back_end_inputs.py b/bpfin/back_end_call/back_end_inputs.py index e70c990..0e8ade8 100644 --- a/bpfin/back_end_call/back_end_inputs.py +++ b/bpfin/back_end_call/back_end_inputs.py @@ -4,8 +4,6 @@ from bpfin.financials.cash_balance import cash_balance from bpfin.financials.liability import final_liability_dict from bpfin.financials.financial_income import Income_Statement_Table from bpfin.financials.financial_balance import Balance_Sheet_Table -# from bpfin.tests.testdata import feature_data as db -# import pprint # Bill Overview Monthly Input @@ -202,21 +200,23 @@ def form_prior_income_table( # **** ugly test **** +import pprint +from bpfin.tests.testdata import feature_data as db # print('\nmonthly_bill =', monthly_bill(db.raw_bill_table, db.analysis_date)) # print('\nannual_bill =', annual_bill(db.raw_bill_table, db.raw_annual_bill_table, db.analysis_date)) -# print('\nprior_income_statement =', prior_income_statement_table( +# print('\nprior_income_statement =', form_prior_income_table( # db.raw_income_input, # db.raw_bill_table, # db.raw_annual_bill_table, # db.analysis_date, # -2.0)) -# result = prior_income_statement_table( +# result = form_prior_income_table( # db.raw_income_input, # db.raw_bill_table, # db.raw_annual_bill_table, # db.analysis_date, -# -2.0) +# -2.0)[0] # writein = str(result) # f = open('data_generation.py', 'w') -- GitLab From 1c79839ef0a421b1302d8999bf15abfcb782b663 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 11:29:30 -0400 Subject: [PATCH 10/19] Modify cash_balance to allow empty inputs. Halfway on test empty inputs for back_end_budget. Need income, growth_rate_flag, and raw_loan_input_list --- bpfin/back_end_call/back_end_budget.py | 51 ++++++++++++++++++++++++-- bpfin/back_end_call/back_end_inputs.py | 4 +- bpfin/financials/cash_balance.py | 20 ++++++---- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index 570a4e3..3c93338 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -26,10 +26,28 @@ def budget_simulation( Inputs Validation: analysis_date is needed commission_date is needed - customer_preference['downpayment_max'] can be None or 0. Convert None to 0 + customer_preference['downpayment_max'] can be None or 0. Convert None to 0 in inner function customer_preference['expected_payback'] cane be None or 0, meanings are different req_dscr keys can be None or 0 - if raw_bill_table is None, and raw_annual_bill_table is None, no bill is available, + raw_bill_table is allowed to be None. Convert None to its structure + raw_annual_bill_table is allowed to be None. Convert None to its structure + raw_income_input !!!!!! + growth_rate_flag is needed + raw_liability_input is allowed to be None. Convert None to empty dictionary + raw_cash_balance is allowed to be None. Convert None to empty dictionary + + Description: + raw_bill_table = { + 'gas': raw_gas_bill_demo, + 'electricity': raw_elec_bill_demo, + 'oil': raw_oil_bill_demo, + 'water': None} + + raw_annual_bill_table = { + 'electricity': None, + 'gas': None, + 'oil': None, + 'water': None} To Do: Need to refactor when merge from saving-scenario refactoring @@ -37,6 +55,31 @@ def budget_simulation( Write test file saving_interval will be read from frond-end in next version """ + # data validation and empty conversion: + if raw_bill_table is None: + raw_bill_table = { + 'electricity': None, + 'gas': None, + 'oil': None, + 'water': None + } + + if raw_annual_bill_table is None: + raw_annual_bill_table = { + 'electricity': None, + 'gas': None, + 'oil': None, + 'water': None + } + + if raw_liability_input is None: + raw_liability_input = {} + + if raw_cash_balance is None: + raw_cash_balance = {} + + + # function variable building up preference_list = ['loan_only', 'loan_first', 'sf_first', 'sf_max'] annual_bill_table = annual_bill(raw_bill_table, raw_annual_bill_table, analysis_date)[0] @@ -117,7 +160,9 @@ finalresult = budget_simulation( ) # if energy bill is 0, first_year_noi will be lower than the case with energy bill, because no saving -# to confirm this hypothesis, need to run income number and compare noi. historical noi should be same for energy bill not 0 vs 0 +# to confirm this hypothesis, ran income and compared noi for energy bill not 0 vs 0 +# reault: historical noi are same for energy bill not 0 vs 0 +# projection noi differ <5%, because bill not 0 is more accurate import pprint pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) diff --git a/bpfin/back_end_call/back_end_inputs.py b/bpfin/back_end_call/back_end_inputs.py index 0e8ade8..bdd422c 100644 --- a/bpfin/back_end_call/back_end_inputs.py +++ b/bpfin/back_end_call/back_end_inputs.py @@ -200,8 +200,8 @@ def form_prior_income_table( # **** ugly test **** -import pprint -from bpfin.tests.testdata import feature_data as db +# import pprint +# from bpfin.tests.testdata import feature_data as db # print('\nmonthly_bill =', monthly_bill(db.raw_bill_table, db.analysis_date)) # print('\nannual_bill =', annual_bill(db.raw_bill_table, db.raw_annual_bill_table, db.analysis_date)) # print('\nprior_income_statement =', form_prior_income_table( diff --git a/bpfin/financials/cash_balance.py b/bpfin/financials/cash_balance.py index 28f48e9..ed806be 100644 --- a/bpfin/financials/cash_balance.py +++ b/bpfin/financials/cash_balance.py @@ -11,8 +11,9 @@ def cash_balance(analysis_date, cash_dictionary): ValueError when more than 1 balance sheet for one year. Returns: dictionary: {datetime, cash value} + Input Validation: + cash_dictionary is allowed to be empty dictionary. If so, result is dictionary with cash values = 0 """ - analysis_years = {} start_year = analysis_date['proforma_start'].year @@ -39,17 +40,20 @@ def cash_balance(analysis_date, cash_dictionary): sum_cash = [] for year in cash_balance_dictionary: sum_cash.append(cash_balance_dictionary[year]) - cash_average = sum(sum_cash)/len(sum_cash) + cash_average = (sum(sum_cash)/len(sum_cash) if sum_cash else 0) final_dict = {} for year in analysis_years: - if year in cash_balance_dictionary: - final_dict[year] = cash_balance_dictionary[year] - if year < min(cash_balance_dictionary): - final_dict[year] = cash_average - if year > min(cash_balance_dictionary) and year < max(cash_balance_dictionary): - if year not in cash_balance_dictionary: + if cash_balance_dictionary: + if year in cash_balance_dictionary: + final_dict[year] = cash_balance_dictionary[year] + if year < min(cash_balance_dictionary): final_dict[year] = cash_average + if year > min(cash_balance_dictionary) and year < max(cash_balance_dictionary): + if year not in cash_balance_dictionary: + final_dict[year] = cash_average + else: + final_dict[year] = 0 return final_dict -- GitLab From 8647dd7730540e3b3a01fa5ddebdca5a2a806061 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 13:12:46 -0400 Subject: [PATCH 11/19] Modify financial_incpme to take empty inputs and return proper outputs for back_end_budget --- bpfin/back_end_call/back_end_budget.py | 9 +++-- bpfin/financials/financial_balance.py | 3 ++ bpfin/financials/financial_income.py | 55 +++++++++++++++++--------- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index 3c93338..63ae1ec 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -31,10 +31,11 @@ def budget_simulation( req_dscr keys can be None or 0 raw_bill_table is allowed to be None. Convert None to its structure raw_annual_bill_table is allowed to be None. Convert None to its structure - raw_income_input !!!!!! + raw_income_input is allowed to be None. growth_rate_flag is needed raw_liability_input is allowed to be None. Convert None to empty dictionary - raw_cash_balance is allowed to be None. Convert None to empty dictionary + raw_cash_balance is allowed to be None. Convert None to empty dictionary !! need further work + raw_loan_input_list is allowed to be None or empty list Description: raw_bill_table = { @@ -78,7 +79,6 @@ def budget_simulation( if raw_cash_balance is None: raw_cash_balance = {} - # function variable building up preference_list = ['loan_only', 'loan_first', 'sf_first', 'sf_max'] @@ -164,6 +164,9 @@ finalresult = budget_simulation( # reault: historical noi are same for energy bill not 0 vs 0 # projection noi differ <5%, because bill not 0 is more accurate +# if no raw_income, noi returns None, it's done. +# but if no raw_cash, cash returns 0, which is not exactly what we expect + import pprint pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) pp.pprint(finalresult) diff --git a/bpfin/financials/financial_balance.py b/bpfin/financials/financial_balance.py index a56472b..d96ecff 100644 --- a/bpfin/financials/financial_balance.py +++ b/bpfin/financials/financial_balance.py @@ -74,6 +74,9 @@ class Balance_Sheet_Table(): cash_balance_dictionary (dict): dictionary of known annual cash value items, key is year other_debt_service_dictionary (dict): dictionary of known annual liability value items, key is year net_income_dictionary (dict): dictionary of known annual net_income value items, key is year + + To Do: + data validation: if raw_cash inputs are empty, return empty tables or None, especially for first year cash """ self.hist_start_year = None diff --git a/bpfin/financials/financial_income.py b/bpfin/financials/financial_income.py index 06ab553..da0fae7 100644 --- a/bpfin/financials/financial_income.py +++ b/bpfin/financials/financial_income.py @@ -227,8 +227,8 @@ class Income_Statement_Table(): annual_bill_table years should cover proforma years """ # validate raw_income_input - if not validate_empty(raw_income_input): - raise ValueError('Income_Statement_Table-init() raw_income_input is empty') + # if not validate_empty(raw_income_input): + # raise ValueError('Income_Statement_Table-init() raw_income_input is empty') if not validate_empty(analysis_date): raise ValueError('Income_Statement_Table-init() analysis_date is empty') # validate annual_bill_table @@ -245,19 +245,21 @@ class Income_Statement_Table(): self.table = [] self.characters = {} self.analysis_date = analysis_date + self.annual_bill_table = annual_bill_table for year in raw_income_input.keys(): current_income_statement = Income_Statement() current_income_statement.put_hist(year, raw_income_input[year], annual_bill_table) self.hist_table.append(current_income_statement) - sorted_income_hist_year = sorted(raw_income_input) - self.hist_start_year = sorted_income_hist_year[0] - self.hist_end_year = sorted_income_hist_year[-1] - self.cagr = cal_cagr( - raw_income_input[self.hist_start_year]['revenue'], - raw_income_input[self.hist_end_year]['revenue'], - self.hist_end_year - self.hist_start_year) + if raw_income_input: + sorted_income_hist_year = sorted(raw_income_input) + self.hist_start_year = sorted_income_hist_year[0] + self.hist_end_year = sorted_income_hist_year[-1] + self.cagr = cal_cagr( + raw_income_input[self.hist_start_year]['revenue'], + raw_income_input[self.hist_end_year]['revenue'], + self.hist_end_year - self.hist_start_year) revenue_sum = 0 other_utility_sum = 0 @@ -267,11 +269,12 @@ class Income_Statement_Table(): other_utility_sum += current_income_statement.other_utility non_utility_expense_sum += current_income_statement.non_utility_expense - self.other_utility_percent = other_utility_sum / revenue_sum - self.non_utility_expense_percent = non_utility_expense_sum / revenue_sum + if raw_income_input: + self.other_utility_percent = other_utility_sum / revenue_sum + self.non_utility_expense_percent = non_utility_expense_sum / revenue_sum - self.revenue_average = revenue_sum / (self.hist_end_year - self.hist_start_year + 1) - self.table = copy.deepcopy(self.hist_table) + self.revenue_average = revenue_sum / (self.hist_end_year - self.hist_start_year + 1) + self.table = copy.deepcopy(self.hist_table) self.characters = { 'start_year': self.hist_start_year, @@ -293,6 +296,10 @@ class Income_Statement_Table(): Note: project() overwrites existing projection data """ + if not self.hist_table: + self.table = [] + return + if not validate_growth_rate_flag(growth_rate_flag): raise ValueError('growth_rate input is not valid') proforma_year = form_bill_year(self.analysis_date['proforma_start'], @@ -307,7 +314,7 @@ class Income_Statement_Table(): annual_bill_table) current_table.append(current_income_statement) self.table = current_table - # return current_table + return def get_hist_table(self): """ @@ -421,6 +428,7 @@ class Income_Statement_Table(): for current_income_statement in self.table: if current_income_statement.year == first_year: return current_income_statement.noi + return None def get_full_income_table(self): """ @@ -452,10 +460,21 @@ class Income_Statement_Table(): def get_total_energy_dict(self): total_energy_dict = {} - for current_income_statement in self.table: - total_energy_dict[ - current_income_statement.year] = current_income_statement.energy_opex - return total_energy_dict + if self.table: + for current_income_statement in self.table: + total_energy_dict[ + current_income_statement.year] = current_income_statement.energy_opex + return total_energy_dict + + else: + proforma_year = form_bill_year( + self.analysis_date['proforma_start'], + self.analysis_date['proforma_duration']) + for year in proforma_year: + total_energy_dict[year] = 0 + for utility in UTILITY_TYPE_LIST: + total_energy_dict[year] += self.annual_bill_table[utility][year] + return total_energy_dict # what is this??? no description, and it calls wrong attribute # def get_utility_opex_dict(self, energy_utility_opex): -- GitLab From 749e94c4b5e706d79061cfa23427522dc2ea79b1 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 14:25:55 -0400 Subject: [PATCH 12/19] Fix balance sheet taking in empty inputs, return None --- bpfin/back_end_call/back_end_budget.py | 13 ++-- bpfin/financials/cash_balance.py | 24 ++++--- bpfin/financials/financial_balance.py | 93 ++++++++++++++------------ bpfin/financials/financial_income.py | 1 - 4 files changed, 73 insertions(+), 58 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index 63ae1ec..df91b8b 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -62,16 +62,14 @@ def budget_simulation( 'electricity': None, 'gas': None, 'oil': None, - 'water': None - } + 'water': None} if raw_annual_bill_table is None: raw_annual_bill_table = { 'electricity': None, 'gas': None, 'oil': None, - 'water': None - } + 'water': None} if raw_liability_input is None: raw_liability_input = {} @@ -94,7 +92,10 @@ def budget_simulation( loan_list = Loan_List(raw_loan_input_list).get_loan_list() - first_year_energy = income_table.get_total_energy_dict()[commission_date.year + 1] + if not raw_income_input: + first_year_energy = None + else: + first_year_energy = income_table.get_total_energy_dict()[commission_date.year + 1] first_year_cash = balance_sheet.get_first_year_cash(commission_date) first_year_noi = income_table.get_first_year_noi(commission_date) @@ -128,7 +129,7 @@ def budget_simulation( req_dscr=req_dscr, first_year_noi=first_year_noi, first_year_cash=first_year_cash, - savings=first_year_energy * saving_percent + savings=(first_year_energy * saving_percent if first_year_energy else None) ) for preference in preference_list: amount_list = copy.deepcopy(financing_per_save[preference]) diff --git a/bpfin/financials/cash_balance.py b/bpfin/financials/cash_balance.py index ed806be..cf342d0 100644 --- a/bpfin/financials/cash_balance.py +++ b/bpfin/financials/cash_balance.py @@ -14,6 +14,9 @@ def cash_balance(analysis_date, cash_dictionary): Input Validation: cash_dictionary is allowed to be empty dictionary. If so, result is dictionary with cash values = 0 """ + if not cash_dictionary: + return None + analysis_years = {} start_year = analysis_date['proforma_start'].year @@ -40,20 +43,21 @@ def cash_balance(analysis_date, cash_dictionary): sum_cash = [] for year in cash_balance_dictionary: sum_cash.append(cash_balance_dictionary[year]) - cash_average = (sum(sum_cash)/len(sum_cash) if sum_cash else 0) + # cash_average = (sum(sum_cash)/len(sum_cash) if sum_cash else 0) + cash_average = sum(sum_cash)/len(sum_cash) final_dict = {} for year in analysis_years: - if cash_balance_dictionary: - if year in cash_balance_dictionary: - final_dict[year] = cash_balance_dictionary[year] - if year < min(cash_balance_dictionary): + # if cash_balance_dictionary: + if year in cash_balance_dictionary: + final_dict[year] = cash_balance_dictionary[year] + if year < min(cash_balance_dictionary): + final_dict[year] = cash_average + if year > min(cash_balance_dictionary) and year < max(cash_balance_dictionary): + if year not in cash_balance_dictionary: final_dict[year] = cash_average - if year > min(cash_balance_dictionary) and year < max(cash_balance_dictionary): - if year not in cash_balance_dictionary: - final_dict[year] = cash_average - else: - final_dict[year] = 0 + # else: + # final_dict[year] = 0 return final_dict diff --git a/bpfin/financials/financial_balance.py b/bpfin/financials/financial_balance.py index d96ecff..fa1aafc 100644 --- a/bpfin/financials/financial_balance.py +++ b/bpfin/financials/financial_balance.py @@ -84,23 +84,24 @@ class Balance_Sheet_Table(): self.hist_balance_sheet_table = [] # entire table, not just historical self.bs_table = [] - for year in sorted(cash_balance_dictionary): - current_balance_sheet = Balance_Sheet() - if year in net_income_dictionary: - net_income = net_income_dictionary[year] - else: - net_income = None - current_balance_sheet.put_hist_balance_sheet( - year, cash_balance_dictionary[year], - other_debt_service_dictionary[year], - net_income) - self.hist_balance_sheet_table.append(current_balance_sheet) - - sorted_balance_sheet_hist_year = sorted(cash_balance_dictionary) - self.hist_start_year = sorted_balance_sheet_hist_year[0] - self.hist_end_year = sorted_balance_sheet_hist_year[-1] - - self.bs_table = copy.deepcopy(self.hist_balance_sheet_table) + if cash_balance_dictionary: + for year in sorted(cash_balance_dictionary): + current_balance_sheet = Balance_Sheet() + if year in net_income_dictionary: + net_income = net_income_dictionary[year] + else: + net_income = None + current_balance_sheet.put_hist_balance_sheet( + year, cash_balance_dictionary[year], + other_debt_service_dictionary[year], + net_income) + self.hist_balance_sheet_table.append(current_balance_sheet) + + sorted_balance_sheet_hist_year = sorted(cash_balance_dictionary) + self.hist_start_year = sorted_balance_sheet_hist_year[0] + self.hist_end_year = sorted_balance_sheet_hist_year[-1] + + self.bs_table = copy.deepcopy(self.hist_balance_sheet_table) def project_balance_sheet(self, analysis_date, other_debt_service_dictionary, @@ -118,24 +119,28 @@ class Balance_Sheet_Table(): proforma_year = form_bill_year(analysis_date['proforma_start'], analysis_date['proforma_duration']) - current_table = copy.deepcopy(self.hist_balance_sheet_table) - for year in proforma_year: - last_year_cash = current_table[-1].cash - if year <= current_table[-1].year: - continue - current_other_debt_service = 0.00 - current_net_income = 0.00 - if year in other_debt_service_dictionary: - current_other_debt_service = other_debt_service_dictionary[ - year] - if year in net_income_dictionary: - current_net_income = net_income_dictionary[year] - current_balance_sheet = Balance_Sheet_Next( - year, last_year_cash, current_other_debt_service, - current_net_income) - current_table.append(current_balance_sheet) - self.bs_table = current_table - # return current_table + if self.hist_balance_sheet_table: + current_table = copy.deepcopy(self.hist_balance_sheet_table) + for year in proforma_year: + last_year_cash = current_table[-1].cash + if year <= current_table[-1].year: + continue + current_other_debt_service = 0.00 + current_net_income = 0.00 + if year in other_debt_service_dictionary: + current_other_debt_service = other_debt_service_dictionary[ + year] + if year in net_income_dictionary: + current_net_income = net_income_dictionary[year] + current_balance_sheet = Balance_Sheet_Next( + year, last_year_cash, current_other_debt_service, + current_net_income) + current_table.append(current_balance_sheet) + self.bs_table = current_table + return + else: + self.bs_table = {} + return def get_hist_balance_sheet_table(self): hist_balance_sheet_dict = {} @@ -166,9 +171,12 @@ class Balance_Sheet_Table(): def get_first_year_cash(self, commission_date): first_year = commission_date.year + 1 - for current_balance_sheet in self.bs_table: - if current_balance_sheet.year == first_year: - return current_balance_sheet.cash + if self.bs_table: + for current_balance_sheet in self.bs_table: + if current_balance_sheet.year == first_year: + return current_balance_sheet.cash + else: + return None def get_cash_dict(self): """ @@ -177,6 +185,9 @@ class Balance_Sheet_Table(): dictionary: cash for each year in balance sheet table. Key is year """ cash_dict = {} - for current_balance_sheet in self.bs_table: - cash_dict[current_balance_sheet.year] = current_balance_sheet.cash - return cash_dict + if self.bs_table: + for current_balance_sheet in self.bs_table: + cash_dict[current_balance_sheet.year] = current_balance_sheet.cash + return cash_dict + else: + return {} diff --git a/bpfin/financials/financial_income.py b/bpfin/financials/financial_income.py index da0fae7..756b2f9 100644 --- a/bpfin/financials/financial_income.py +++ b/bpfin/financials/financial_income.py @@ -465,7 +465,6 @@ class Income_Statement_Table(): total_energy_dict[ current_income_statement.year] = current_income_statement.energy_opex return total_energy_dict - else: proforma_year = form_bill_year( self.analysis_date['proforma_start'], -- GitLab From c7f9617f9ba8a763a9266ccef923d6cbee76623b Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 14:27:38 -0400 Subject: [PATCH 13/19] Clean comments for merge --- bpfin/back_end_call/back_end_budget.py | 34 +++++++++---------- .../financials/financial_budget_simulator.py | 10 +++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index df91b8b..a7da739 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -145,20 +145,20 @@ def budget_simulation( # **** ugly test **** -from bpfin.tests.testdata import feature_data as db -finalresult = budget_simulation( - db.analysis_date, - db.commission_date, - db.customer_preference, - db.req_dscr, - db.raw_bill_table, - db.raw_annual_bill_table, - db.raw_income_input, - -2.0, - db.raw_liability_input, - db.raw_cash_balance, - db.raw_loan_input_list - ) +# from bpfin.tests.testdata import feature_data as db +# finalresult = budget_simulation( +# db.analysis_date, +# db.commission_date, +# db.customer_preference, +# db.req_dscr, +# db.raw_bill_table, +# db.raw_annual_bill_table, +# db.raw_income_input, +# -2.0, +# db.raw_liability_input, +# db.raw_cash_balance, +# db.raw_loan_input_list +# ) # if energy bill is 0, first_year_noi will be lower than the case with energy bill, because no saving # to confirm this hypothesis, ran income and compared noi for energy bill not 0 vs 0 @@ -168,6 +168,6 @@ finalresult = budget_simulation( # if no raw_income, noi returns None, it's done. # but if no raw_cash, cash returns 0, which is not exactly what we expect -import pprint -pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) -pp.pprint(finalresult) +# import pprint +# pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) +# pp.pprint(finalresult) diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index 88ace55..aa2e1d8 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -269,11 +269,11 @@ def validate_list_one_true(param_list): # **** ugly test **** -req_dscr = { - 'req_noi_dscr': 1.15, - 'req_cash_dscr': 1.05, - 'req_saving_dscr': 1.10 -} +# req_dscr = { +# 'req_noi_dscr': 1.15, +# 'req_cash_dscr': 1.05, +# 'req_saving_dscr': 1.10 +# } # test cases # print('\n Base Case:') -- GitLab From 17492ccc715e3b4362a7368b71d492b719a96c35 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 14:30:38 -0400 Subject: [PATCH 14/19] put used prints in comments --- bpfin/back_end_call/back_end_budget.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index a7da739..1b2d027 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -113,13 +113,13 @@ def budget_simulation( budget_simulation_result = form_budget_simulation_result(loan_list) - print('\n loan_list=', loan_list) - print('\n downpayment_max=', customer_preference['downpayment_max']) - print('\n expected_payback=', customer_preference['expected_payback']) - print('\n req_dscr=', req_dscr) - print('\n first_year_noi=', first_year_noi) - print('\n first_year_cash=', first_year_cash) - print('\n first_year_energy=', first_year_energy) + # print('\n loan_list=', loan_list) + # print('\n downpayment_max=', customer_preference['downpayment_max']) + # print('\n expected_payback=', customer_preference['expected_payback']) + # print('\n req_dscr=', req_dscr) + # print('\n first_year_noi=', first_year_noi) + # print('\n first_year_cash=', first_year_cash) + # print('\n first_year_energy=', first_year_energy) for saving_percent in saving_potential_list: financing_per_save = form_max_financing( -- GitLab From 7cffb57a6a2930227dcaef1321d2bef624abbe60 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 18:10:44 -0400 Subject: [PATCH 15/19] Formatting updgrading --- bpfin/financials/financial_budget_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index aa2e1d8..9f03c0c 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -151,7 +151,7 @@ def cal_max_budget( if is_loan_first: if savings is not None and req_dscr['req_saving_dscr'] and expected_payback is not None: # self finance amount is capped by extra_saving, which is Savings/req_dscr - total loan burden - extra_saving = float("{:0.4f}".format((savings/req_dscr['req_saving_dscr'] - total_ds))) + extra_saving = float("{0:.4f}".format((savings/req_dscr['req_saving_dscr'] - total_ds))) downpayment = (min(max([0, extra_saving]) * expected_payback / 12, downpayment_max)) else: downpayment = downpayment_max -- GitLab From eb3e135b507223e8e24bdc9e67c2b78d01d9071b Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 18:16:36 -0400 Subject: [PATCH 16/19] indentation update --- bpfin/financials/financial_income.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpfin/financials/financial_income.py b/bpfin/financials/financial_income.py index 756b2f9..d391897 100644 --- a/bpfin/financials/financial_income.py +++ b/bpfin/financials/financial_income.py @@ -272,7 +272,6 @@ class Income_Statement_Table(): if raw_income_input: self.other_utility_percent = other_utility_sum / revenue_sum self.non_utility_expense_percent = non_utility_expense_sum / revenue_sum - self.revenue_average = revenue_sum / (self.hist_end_year - self.hist_start_year + 1) self.table = copy.deepcopy(self.hist_table) -- GitLab From fc9d768a29b17761f6e9c1a8f73cb6e959429f14 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 26 Jun 2017 18:19:02 -0400 Subject: [PATCH 17/19] Remove unneeded return --- bpfin/financials/financial_income.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpfin/financials/financial_income.py b/bpfin/financials/financial_income.py index d391897..82eaa0c 100644 --- a/bpfin/financials/financial_income.py +++ b/bpfin/financials/financial_income.py @@ -313,7 +313,6 @@ class Income_Statement_Table(): annual_bill_table) current_table.append(current_income_statement) self.table = current_table - return def get_hist_table(self): """ @@ -401,7 +400,6 @@ class Income_Statement_Table(): 'total_opex': current_income_statement.total_opex, 'noi': current_income_statement.noi } - return None def get_noi_dict(self): """ @@ -427,7 +425,6 @@ class Income_Statement_Table(): for current_income_statement in self.table: if current_income_statement.year == first_year: return current_income_statement.noi - return None def get_full_income_table(self): """ -- GitLab From eee33f75f9528409a511a9683a095a4dada8af85 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Tue, 27 Jun 2017 10:39:39 -0400 Subject: [PATCH 18/19] Updtate according to PR review --- bpfin/back_end_call/back_end_budget.py | 30 +++++++++---------- bpfin/financials/financial_balance.py | 2 -- .../financials/financial_budget_simulator.py | 28 +++++++++++++++-- bpfin/financials/financial_income.py | 8 +++-- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index 1b2d027..e2c6950 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -112,7 +112,7 @@ def budget_simulation( break budget_simulation_result = form_budget_simulation_result(loan_list) - + print('\nbudget_simulation_result = ', budget_simulation_result) # print('\n loan_list=', loan_list) # print('\n downpayment_max=', customer_preference['downpayment_max']) # print('\n expected_payback=', customer_preference['expected_payback']) @@ -145,20 +145,20 @@ def budget_simulation( # **** ugly test **** -# from bpfin.tests.testdata import feature_data as db -# finalresult = budget_simulation( -# db.analysis_date, -# db.commission_date, -# db.customer_preference, -# db.req_dscr, -# db.raw_bill_table, -# db.raw_annual_bill_table, -# db.raw_income_input, -# -2.0, -# db.raw_liability_input, -# db.raw_cash_balance, -# db.raw_loan_input_list -# ) +from bpfin.tests.testdata import feature_data as db +finalresult = budget_simulation( + db.analysis_date, + db.commission_date, + db.customer_preference, + db.req_dscr, + db.raw_bill_table, + db.raw_annual_bill_table, + db.raw_income_input, + -2.0, + db.raw_liability_input, + db.raw_cash_balance, + db.raw_loan_input_list + ) # if energy bill is 0, first_year_noi will be lower than the case with energy bill, because no saving # to confirm this hypothesis, ran income and compared noi for energy bill not 0 vs 0 diff --git a/bpfin/financials/financial_balance.py b/bpfin/financials/financial_balance.py index fa1aafc..ca3fad2 100644 --- a/bpfin/financials/financial_balance.py +++ b/bpfin/financials/financial_balance.py @@ -1,7 +1,5 @@ import copy -from bpfin.lib import other as lib from bpfin.utilbills.bill_lib import form_bill_year -from numpy import mean def convert_balance_sheet_class(balance_sheet_class): diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py index 9f03c0c..6fd3826 100644 --- a/bpfin/financials/financial_budget_simulator.py +++ b/bpfin/financials/financial_budget_simulator.py @@ -5,8 +5,6 @@ run budget calculator and return table of results import copy from scipy.optimize import linprog from bpfin.lib.other import sumproduct, multiply_list -from bpfin.tests.testdata import sample_data as db -from bpfin.financials.loan import Loan_List def cal_max_budget( @@ -239,6 +237,30 @@ def form_max_financing( def form_budget_simulation_result(loan_list): + """ + generate a data structure to store result data from budget simulation calculation + + Args: + loan_list(list): list of Loan class object + Return: + dictionary: dict of lists. keys are financing level (loan_only, loan_first, sf_first, sf_max) + values are list of list, each inner list has one element, which is financing plan line items + + Description: + budget_simulation_result = { + 'loan_only': [ + ['saving_percentage'], ['Budget'], ['NYSERDA'], ['Joe Fund'], ['Tooraj Capital'], ['Self Finance'] + ], + 'loan_first': [ + ['saving_percentage'], ['Budget'], ['NYSERDA'], ['Joe Fund'], ['Tooraj Capital'], ['Self Finance'] + ], + 'sf_first': [ + ['saving_percentage'], ['Budget'], ['NYSERDA'], ['Joe Fund'], ['Tooraj Capital'], ['Self Finance'] + ], + 'sf_max': [ + ['saving_percentage'], ['Budget'], ['NYSERDA'], ['Joe Fund'], ['Tooraj Capital'], ['Self Finance'] + ]} + """ preference_list = ['loan_only', 'loan_first', 'sf_first', 'sf_max'] budget_simulation_result = {} for preference in preference_list: @@ -269,6 +291,8 @@ def validate_list_one_true(param_list): # **** ugly test **** +# from bpfin.tests.testdata import sample_data as db +# from bpfin.financials.loan import Loan_List # req_dscr = { # 'req_noi_dscr': 1.15, # 'req_cash_dscr': 1.05, diff --git a/bpfin/financials/financial_income.py b/bpfin/financials/financial_income.py index 82eaa0c..c3dca1e 100644 --- a/bpfin/financials/financial_income.py +++ b/bpfin/financials/financial_income.py @@ -4,10 +4,9 @@ Income_Statement is incorporate with new Bill class, taking in bill_list as inpu """ import copy from bpfin.lib.other import cal_cagr -# from bpfin.lib import other as lib +from bpfin.lib.other import UTILITY_TYPE_LIST from bpfin.utilbills.bill_lib import form_bill_year from numpy import mean -from bpfin.lib.other import UTILITY_TYPE_LIST class Income_Statement(): @@ -455,6 +454,11 @@ class Income_Statement_Table(): return table_dict def get_total_energy_dict(self): + """ + Get total energy bill for each year + Return: + dictionary: keys are years in projected income statement, values are float, total energy expense(bill) + """ total_energy_dict = {} if self.table: for current_income_statement in self.table: -- GitLab From f3969312eaab723acd6ab32fbd275d2e5a77f723 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Tue, 27 Jun 2017 16:34:13 -0400 Subject: [PATCH 19/19] remove print statement --- bpfin/back_end_call/back_end_budget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index e2c6950..3feaeba 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -112,7 +112,7 @@ def budget_simulation( break budget_simulation_result = form_budget_simulation_result(loan_list) - print('\nbudget_simulation_result = ', budget_simulation_result) + # print('\nbudget_simulation_result = ', budget_simulation_result) # print('\n loan_list=', loan_list) # print('\n downpayment_max=', customer_preference['downpayment_max']) # print('\n expected_payback=', customer_preference['expected_payback']) -- GitLab