diff --git a/bpfin/back_end_call/back_end_budget.py b/bpfin/back_end_call/back_end_budget.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81d3fd6b92899dfbaef654c228e5aabe750ea01f 100644 --- a/bpfin/back_end_call/back_end_budget.py +++ b/bpfin/back_end_call/back_end_budget.py @@ -0,0 +1,105 @@ +import copy +from bpfin.back_end_call.back_end_inputs import annual_bill +from bpfin.financials.financial_income import Income_Statement_Table +from bpfin.financials.financial_balance import Balance_Sheet_Table +from bpfin.financials.cash_balance import cash_balance +from bpfin.financials.liability import final_liability_dict +from bpfin.financials.loan import Loan_List +from bpfin.financials.financial_budget_simulator import form_max_financing, form_budget_simulation_result + + +def budget_simulation( + analysis_date, + commission_date, + customer_preference, + req_dscr, + raw_bill_table, + raw_annual_bill_table, + raw_income_input, + growth_rate_flag, + raw_liability_input, + raw_cash_balance, + raw_loan_input_list): + """ + Generate budget simulation data, to draw graph and to put in table + To Do: + Need to refactor when merge from saving-scenario refactoring + Need to validate data, to allow a lot of empty inputs + Write test file + saving_interval will be read from frond-end in next version + """ + 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] + + income_table = Income_Statement_Table(raw_income_input, annual_bill_table, analysis_date) + income_table.project(growth_rate_flag, annual_bill_table) + + cash_dict = cash_balance(analysis_date, raw_cash_balance) + liability_dict = final_liability_dict(analysis_date, raw_liability_input) + balance_sheet = Balance_Sheet_Table(cash_dict, liability_dict, income_table.get_noi_dict()) + balance_sheet.project_balance_sheet(analysis_date, liability_dict, income_table.get_noi_dict()) + + loan_list = Loan_List(raw_loan_input_list).get_loan_list() + + 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) + + saving_interval = 0.1000 + saving_potential_list = [] + interval = saving_interval + i = 0 + while interval <= 0.5: + i += 1 + saving_potential_list.append(interval) + interval += saving_interval + if i >= 9999: + break + + budget_simulation_result = form_budget_simulation_result(loan_list) + + for saving_percent in saving_potential_list: + financing_per_save = form_max_financing( + loan_list=loan_list, + downpayment_max=customer_preference['downpayment_max'], + expected_payback=customer_preference['expected_payback'], + req_dscr=req_dscr, + first_year_noi=first_year_noi, + first_year_cash=first_year_cash, + savings=first_year_energy * saving_percent + ) + for preference in preference_list: + amount_list = copy.deepcopy(financing_per_save[preference]) + amount_list.insert(0, saving_percent) + for current_list, amount in zip(budget_simulation_result[preference], amount_list): + current_list.append(amount) + + return [ + budget_simulation_result['loan_only'], + budget_simulation_result['loan_first'], + budget_simulation_result['sf_first'], + budget_simulation_result['sf_max']] + + +# **** 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 +# ) + + +# import pprint +# pp = pprint.PrettyPrinter(width=120, indent=4, compact=True) +# pp.pprint(finalresult) diff --git a/bpfin/back_end_call/back_end_full.py b/bpfin/back_end_call/back_end_full.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b2911618ec038c1d6c6722cf1cc4f15d7cccff27 100644 --- a/bpfin/back_end_call/back_end_full.py +++ b/bpfin/back_end_call/back_end_full.py @@ -0,0 +1,3 @@ +""" +Back_end functions. Will develop when full analysis is created +""" diff --git a/bpfin/back_end_call/back_end_inputs.py b/bpfin/back_end_call/back_end_inputs.py index 0b9f1fd15c5d06255658626d2df598e062d95c1b..e70c9901180926f6df9288dc930047ecce5e2ed0 100644 --- a/bpfin/back_end_call/back_end_inputs.py +++ b/bpfin/back_end_call/back_end_inputs.py @@ -199,3 +199,26 @@ def form_prior_income_table( average_income = income_table.get_average() historical_cagr = income_table.get_cagr() return prior_income, next_year_income, average_income, historical_cagr + + +# **** ugly test **** +# 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( +# db.raw_income_input, +# db.raw_bill_table, +# db.raw_annual_bill_table, +# db.analysis_date, +# -2.0)) + +# result = prior_income_statement_table( +# db.raw_income_input, +# db.raw_bill_table, +# db.raw_annual_bill_table, +# db.analysis_date, +# -2.0) + +# writein = str(result) +# f = open('data_generation.py', 'w') +# f.write(writein) +# f.close() diff --git a/bpfin/financials/financial_budget_simulator.py b/bpfin/financials/financial_budget_simulator.py new file mode 100644 index 0000000000000000000000000000000000000000..4100973d1a5c2a460b8563f8ab9a326adfea4c9f --- /dev/null +++ b/bpfin/financials/financial_budget_simulator.py @@ -0,0 +1,289 @@ +""" +bpfin.financials.financial_budget_simulator +run budget calculator and return table of results +""" +import copy +from scipy.optimize import linprog +from bpfin.lib.other import sumproduct +from bpfin.tests.testdata import sample_data as db +from bpfin.financials.loan import Loan_List + + +def cal_max_budget( + loan_list, + downpayment_max, + expected_payback, + req_dscr, + first_year_noi, + first_year_cash, + savings, + is_loan_first): + """ + Take inputs of available financing options(loans and self-finance), and other constrains, + 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 + * x_i is loan amount, x_n is self-finance amount + 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 + 3. x variables bounds + 0 <= x_i <= loan[i]_max_amount + 0 <= x_n <= downpayment_max + 4. One of the following optional constrains applies, for 1 out of the 4 different financing levels: + 4.1 loan only, no self-finance down payment: + downpayment_max = 0 + 4.2 use loan first, leftover saving can support some self-finance amount + downpayment_max = 0 + when x_i are determined: + x_n = min(saving/dscr - sum(x_i), downpayment_max) + 4.3 use self-finance first, leftover saving can support some loan amount + no additional constrain applied + 4.4 use all available financing, self-finance's expected payback is infinite + expected_payback = None + + Args: + loan_list (list): list of objectives of loans + customer_preference (dictionary): customer preference for financing plan + 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 + 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) + + Return: + float: max budget with given conditions + list: list of loan amount that borrowed from each lender, including self-finance + + Description: + req_dscr (dictionary): dict of required debt service coverage ratios, { + 'req_noi_dscr': 1.15, + 'req_cash_dscr': 1.15, + 'req_saving_dscr': 1.10 + } + 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)) + + # 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 = [] + 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_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_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'], + first_year_cash / req_dscr['req_cash_dscr'] + )) + # 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) + 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) + res.x[-1] = downpayment + return list(res.x) + + +def form_max_financing( + loan_list, + downpayment_max, + expected_payback, + req_dscr, + first_year_noi, + first_year_cash, + savings): + """ + + Return: + dictionary: dict of tuples. each tuple represents a financing plan, with and budget, financing plan + Description: + budget = { + 'loan_only': [max_budget, x[1], x[2], x[3], ..., x[n]], + 'loan_first': [max_budget, x[1], x[2], x[3], ..., x[n]], + 'sf_first': [], + 'sf_max': [] + } + """ + budget = {} + result = cal_max_budget( + loan_list=loan_list, + downpayment_max=0, + expected_payback=expected_payback, + req_dscr=req_dscr, + first_year_noi=first_year_noi, + first_year_cash=first_year_cash, + savings=savings, + is_loan_first=False + ) + result.insert(0, sum(result)) + budget['loan_only'] = result + + result = cal_max_budget( + loan_list=loan_list, + downpayment_max=downpayment_max, + expected_payback=expected_payback, + req_dscr=req_dscr, + first_year_noi=first_year_noi, + first_year_cash=first_year_cash, + savings=savings, + is_loan_first=True + ) + result.insert(0, sum(result)) + budget['loan_first'] = result + + result = cal_max_budget( + loan_list=loan_list, + downpayment_max=downpayment_max, + expected_payback=expected_payback, + req_dscr=req_dscr, + first_year_noi=first_year_noi, + first_year_cash=first_year_cash, + savings=savings, + is_loan_first=False + ) + result.insert(0, sum(result)) + budget['sf_first'] = result + + result = cal_max_budget( + loan_list=loan_list, + downpayment_max=downpayment_max, + expected_payback=None, + req_dscr=req_dscr, + first_year_noi=first_year_noi, + first_year_cash=first_year_cash, + savings=savings, + is_loan_first=False + ) + result.insert(0, sum(result)) + budget['sf_max'] = result + + return budget + + +def form_budget_simulation_result(loan_list): + preference_list = ['loan_only', 'loan_first', 'sf_first', 'sf_max'] + budget_simulation_result = {} + for preference in preference_list: + table_lists = [] + table_lists.append(['saving_percentage']) + table_lists.append(['budget']) + for loan in loan_list: + table_lists.append([loan.get_loan_terms()['institute']]) + table_lists.append(['self_finance']) + budget_simulation_result[preference] = table_lists + return budget_simulation_result + + +# print(form_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] diff --git a/bpfin/financials/financial_income.py b/bpfin/financials/financial_income.py index 93532e4185ad38254828714d85b91ffbdcac920c..173ee3644d27741c276b6d9f4e2070fb945f0b52 100644 --- a/bpfin/financials/financial_income.py +++ b/bpfin/financials/financial_income.py @@ -4,7 +4,7 @@ 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 import other as lib from bpfin.utilbills.bill_lib import form_bill_year from numpy import mean from bpfin.lib.other import UTILITY_TYPE_LIST @@ -133,9 +133,7 @@ class Income_Statement_Next(): Create single year income_statement object, with input of last year data, income statement characters, and projected annual bill """ - - def __init__(self, year, last_revenue, growth_rate_flag, characters, - annual_bill_table): + def __init__(self, year, last_revenue, growth_rate_flag, characters, annual_bill_table): """ Calculation is done in initiation. Args: @@ -176,8 +174,7 @@ class Income_Statement_Next(): self.water_opex = annual_bill_table['water'][self.year] self.energy_opex = self.electricity_opex + self.gas_opex + self.oil_opex + self.water_opex self.other_utility = self.revenue * characters['other_utility_percent'] - self.non_utility_expense = self.revenue * characters[ - 'non_utility_expense_percent'] + self.non_utility_expense = self.revenue * characters['non_utility_expense_percent'] self.utility_expense = self.energy_opex + self.other_utility self.net_non_energy_opex = self.other_utility + self.non_utility_expense self.total_opex = self.energy_opex + self.net_non_energy_opex @@ -229,17 +226,14 @@ class Income_Statement_Table(): annual_bill_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') + 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') + raise ValueError('Income_Statement_Table-init() analysis_date is empty') # validate annual_bill_table if not validate_annual_bill_table(annual_bill_table): - raise ValueError( - 'Income_Statement_Table-init() annual_bill_table is invalid') - # validate raw_income_input + raise ValueError('Income_Statement_Table-init() annual_bill_table is invalid') self.hist_start_year = None self.hist_end_year = None @@ -252,16 +246,15 @@ class Income_Statement_Table(): self.characters = {} self.analysis_date = analysis_date - for year in sorted(raw_income_input.keys()): + 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) + 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 = lib.cal_cagr( + 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) @@ -276,10 +269,10 @@ class Income_Statement_Table(): 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.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, 'end_year': self.hist_end_year, @@ -327,32 +320,19 @@ class Income_Statement_Table(): hist_table_dict = {} for current_income_statement in self.hist_table: hist_table_dict[current_income_statement.year] = { - 'year': - current_income_statement.year, - 'revenue': - current_income_statement.revenue, - 'utility_expense': - current_income_statement.utility_expense, - 'energy_opex': - current_income_statement.energy_opex, - 'electricity_opex': - current_income_statement.electricity_opex, - 'gas_opex': - current_income_statement.gas_opex, - 'oil_opex': - current_income_statement.oil_opex, - 'water_opex': - current_income_statement.water_opex, - 'other_utility': - current_income_statement.other_utility, - 'non_utility_expense': - current_income_statement.non_utility_expense, - 'net_non_energy_opex': - current_income_statement.net_non_energy_opex, - 'total_opex': - current_income_statement.total_opex, - 'noi': - current_income_statement.noi + 'year': current_income_statement.year, + 'revenue': current_income_statement.revenue, + 'utility_expense': current_income_statement.utility_expense, + 'energy_opex': current_income_statement.energy_opex, + 'electricity_opex': current_income_statement.electricity_opex, + 'gas_opex': current_income_statement.gas_opex, + 'oil_opex': current_income_statement.oil_opex, + 'water_opex': current_income_statement.water_opex, + 'other_utility': current_income_statement.other_utility, + 'non_utility_expense': current_income_statement.non_utility_expense, + 'net_non_energy_opex': current_income_statement.net_non_energy_opex, + 'total_opex': current_income_statement.total_opex, + 'noi': current_income_statement.noi } return hist_table_dict @@ -385,8 +365,7 @@ class Income_Statement_Table(): 'water': mean(list(i_s.water_opex for i_s in self.hist_table)) } - current_income_statement.put_average(average_revenue, annual_bills, - self.characters) + current_income_statement.put_average(average_revenue, annual_bills, self.characters) return convert_income_statement_class(current_income_statement) def get_single_year(self, year): @@ -402,32 +381,19 @@ class Income_Statement_Table(): for current_income_statement in self.table: if current_income_statement.year == year: return { - 'year': - current_income_statement.year, - 'revenue': - current_income_statement.revenue, - 'utility_expense': - current_income_statement.utility_expense, - 'energy_opex': - current_income_statement.energy_opex, - 'electricity_opex': - current_income_statement.electricity_opex, - 'gas_opex': - current_income_statement.gas_opex, - 'oil_opex': - current_income_statement.oil_opex, - 'water_opex': - current_income_statement.water_opex, - 'other_utility': - current_income_statement.other_utility, - 'non_utility_expense': - current_income_statement.non_utility_expense, - 'net_non_energy_opex': - current_income_statement.net_non_energy_opex, - 'total_opex': - current_income_statement.total_opex, - 'noi': - current_income_statement.noi + 'year': current_income_statement.year, + 'revenue': current_income_statement.revenue, + 'utility_expense': current_income_statement.utility_expense, + 'energy_opex': current_income_statement.energy_opex, + 'electricity_opex': current_income_statement.electricity_opex, + 'gas_opex': current_income_statement.gas_opex, + 'oil_opex': current_income_statement.oil_opex, + 'water_opex': current_income_statement.water_opex, + 'other_utility': current_income_statement.other_utility, + 'non_utility_expense': current_income_statement.non_utility_expense, + 'net_non_energy_opex': current_income_statement.net_non_energy_opex, + 'total_opex': current_income_statement.total_opex, + 'noi': current_income_statement.noi } return None @@ -468,49 +434,37 @@ class Income_Statement_Table(): table_dict = {} for current_income_statement in self.table: table_dict[current_income_statement.year] = { - 'year': - current_income_statement.year, - 'revenue': - current_income_statement.revenue, - 'utility_expense': - current_income_statement.utility_expense, - 'energy_opex': - current_income_statement.energy_opex, - 'electricity_opex': - current_income_statement.electricity_opex, - 'gas_opex': - current_income_statement.gas_opex, - 'oil_opex': - current_income_statement.oil_opex, - 'water_opex': - current_income_statement.water_opex, - 'other_utility': - current_income_statement.other_utility, - 'non_utility_expense': - current_income_statement.non_utility_expense, - 'net_non_energy_opex': - current_income_statement.net_non_energy_opex, - 'total_opex': - current_income_statement.total_opex, - 'noi': - current_income_statement.noi + 'year': current_income_statement.year, + 'revenue': current_income_statement.revenue, + 'utility_expense': current_income_statement.utility_expense, + 'energy_opex': current_income_statement.energy_opex, + 'electricity_opex': current_income_statement.electricity_opex, + 'gas_opex': current_income_statement.gas_opex, + 'oil_opex': current_income_statement.oil_opex, + 'water_opex': current_income_statement.water_opex, + 'other_utility': current_income_statement.other_utility, + 'non_utility_expense': current_income_statement.non_utility_expense, + 'net_non_energy_opex': current_income_statement.net_non_energy_opex, + 'total_opex': current_income_statement.total_opex, + 'noi': current_income_statement.noi } return table_dict 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 + total_energy_dict[ + current_income_statement.year] = current_income_statement.energy_opex return total_energy_dict - def get_utility_opex_dict(self, energy_utility_opex): - bill_dict = {} - for current_income_statement in self.table: - bill_dict[current_income_statement. - year] = current_income_statement.energy_utility_opex + # what is this??? no description, and it calls wrong attribute + # def get_utility_opex_dict(self, energy_utility_opex): + # bill_dict = {} + # for current_income_statement in self.table: + # bill_dict[current_income_statement. + # year] = current_income_statement.energy_utility_opex - return bill_dict + # return bill_dict def validate_growth_rate_flag(growth_rate_flag): diff --git a/bpfin/financials/loan.py b/bpfin/financials/loan.py index 966ed128b2529cc00343d656a915c9632f2fbfbc..76b8faeb4c974c9d8162881b7691732239d9e8f3 100644 --- a/bpfin/financials/loan.py +++ b/bpfin/financials/loan.py @@ -196,6 +196,9 @@ class Loan_List(): current_loan.originate() return allocated_loan_list + def get_loan_list(self): + return self.loan_list + # ****** ugly tests Loan and Loan_List class ********** # loan_list = Loan_List(db.loan_input_list) diff --git a/bpfin/financials/loan_allocation.py b/bpfin/financials/loan_allocation.py index 5bcc9d140f7a5d045a454be81c754d2618387a7c..4423c34b833dbf00b2510013ff590fb4bfda558c 100644 --- a/bpfin/financials/loan_allocation.py +++ b/bpfin/financials/loan_allocation.py @@ -103,66 +103,3 @@ def loan_allocation( return res.x -# ******** 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] diff --git a/bpfin/lib/back_end_call.py b/bpfin/lib/back_end_call.py deleted file mode 100644 index e7cac024c0f05df5edf2ea8cd90eb5685c701629..0000000000000000000000000000000000000000 --- a/bpfin/lib/back_end_call.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -This file should be transfered to back_end_call folder, and then removed. -keep it for now because we need to merge from budget -""" diff --git a/bpfin/tests/test_back_end_call/test_back_end_budget.py b/bpfin/tests/test_back_end_call/test_back_end_budget.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d05d722930b2b44f7372e34ad5559b105576c46f 100644 --- a/bpfin/tests/test_back_end_call/test_back_end_budget.py +++ b/bpfin/tests/test_back_end_call/test_back_end_budget.py @@ -0,0 +1,20 @@ +from bpfin.back_end_call.back_end_budget import budget_simulation +from bpfin.tests.testdata import feature_data as db + + +def test_budget_simulation(): + output = db.budget_simulation_result + result = 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, + db.growth_rate_flag, + db.raw_liability_input, + db.raw_cash_balance, + db.raw_loan_input_list + ) + assert output == result diff --git a/bpfin/tests/test_back_end_call/test_back_end_inputs.py b/bpfin/tests/test_back_end_call/test_back_end_inputs.py index 419a62698c8216f13826801628532ba24f0f816d..8033219f5773aaba2d3d7bafcd7f0afdc40f09a1 100644 --- a/bpfin/tests/test_back_end_call/test_back_end_inputs.py +++ b/bpfin/tests/test_back_end_call/test_back_end_inputs.py @@ -1,3 +1,4 @@ +# from bpfin.lib.back_end_call import budget_simulation from bpfin.back_end_call.back_end_inputs import monthly_bill, annual_bill, form_prior_income_table from bpfin.tests.testdata import feature_data as db diff --git a/bpfin/tests/test_back_end_call/test_back_end_prelim.py b/bpfin/tests/test_back_end_call/test_back_end_prelim.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9be90b14ea6004ad89d28c0f19df8b7c77b6cc57 100644 --- a/bpfin/tests/test_back_end_call/test_back_end_prelim.py +++ b/bpfin/tests/test_back_end_call/test_back_end_prelim.py @@ -0,0 +1,3 @@ +""" +To Do we still need to write test files for back_end_call prelim_analysis +""" diff --git a/bpfin/tests/test_lib/test_back_end_call.py b/bpfin/tests/test_lib/test_back_end_call.py deleted file mode 100644 index 148f5aaf0b58101f19e7aa608156c089e6003153..0000000000000000000000000000000000000000 --- a/bpfin/tests/test_lib/test_back_end_call.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This file should get removed after budget merged -""" diff --git a/bpfin/tests/testdata/feature_data.py b/bpfin/tests/testdata/feature_data.py index 3ca96fb5aad34c05e9e9249958c9981156b2b301..8b37a7a441125d29adfdf94ec73c1c62b6c445b5 100644 --- a/bpfin/tests/testdata/feature_data.py +++ b/bpfin/tests/testdata/feature_data.py @@ -5,7 +5,7 @@ from datetime import date # from bpfin.utilbills.bill import form_bill_list_month # from bpfin.utilbills.bill import form_annual_bill_table # from bpfin.utilbills.bill import Bill_Table -import pprint +# import pprint # one big decision is, one frontend request should either call data from database, or frontend. @@ -32,7 +32,7 @@ req_dscr = { # Customer Preference customer_preference = { - 'downpayment_max': 5000.00, + 'downpayment_max': 50000.00, 'expected_payback': 120, # in months 'cust_saving_dscr': 1.15 } @@ -687,6 +687,48 @@ average_income = { } historical_cagr = 0.01242283656582921 +growth_rate_flag = -2.0 + +budget_simulation_result = [ + [['saving_percentage', 0.1, 0.2, 0.30000000000000004, 0.4, 0.5], [ + 'budget', 37723.783022506694, 68031.962268808478, 68031.962268808478, + 68031.962268808478, 68031.962268808478 + ], ['NYSERDA', 0.0, 0.0, 0.0, 0.0, 0.0], + ['Joe Fund', 37723.783022506694, 50000.0, 50000.0, 50000.0, 50000.0], [ + 'Tooraj Capital', 0.0, 18031.962268808486, 18031.962268808486, + 18031.962268808486, 18031.962268808486 + ], ['self_finance', 0.0, 0.0, 0.0, 0.0, 0.0]], + [['saving_percentage', 0.1, 0.2, 0.30000000000000004, 0.4, 0.5], [ + 'budget', 37723.783022506701, 77213.635473036207, 118031.96226880848, + 118031.96226880848, 118031.96226880848 + ], ['NYSERDA', 0.0, 0.0, 0.0, 0.0, 0.0], + ['Joe Fund', 37723.783022506694, 50000.0, 50000.0, 50000.0, 50000.0], [ + 'Tooraj Capital', 0.0, 18031.962268808486, 18031.962268808486, + 18031.962268808486, 18031.962268808486 + ], [ + 'self_finance', 9.0949470177292824e-12, 9181.6732042277363, 50000.0, + 50000.0, 50000.0 + ]], + [['saving_percentage', 0.1, 0.2, 0.30000000000000004, 0.4, 0.5], [ + 'budget', 52184.67464428641, 89908.457666793111, 118031.96226880848, + 118031.96226880848, 118031.96226880848 + ], ['NYSERDA', 0.0, 0.0, 0.0, 0.0, 0.0], [ + 'Joe Fund', 2184.6746442864069, 39908.457666793111, 50000.0, 50000.0, + 50000.0 + ], [ + 'Tooraj Capital', 0.0, 0.0, 18031.962268808486, 18031.962268808486, + 18031.962268808486 + ], ['self_finance', 50000.0, 50000.0, 50000.0, 50000.0, 50000.0]], + [['saving_percentage', 0.1, 0.2, 0.30000000000000004, 0.4, 0.5], [ + 'budget', 87723.783022506686, 118031.96226880848, 118031.96226880848, + 118031.96226880848, 118031.96226880848 + ], ['NYSERDA', 0.0, 0.0, 0.0, 0.0, + 0.0], + ['Joe Fund', 37723.783022506694, 50000.0, 50000.0, 50000.0, 50000.0], [ + 'Tooraj Capital', 0.0, 18031.962268808486, 18031.962268808486, + 18031.962268808486, 18031.962268808486 + ], ['self_finance', 50000.0, 50000.0, 50000.0, 50000.0, 50000.0]] +] post_annual_bill = { 'electricity': {