From e56af031a3476d0dcb9c964e6f10a541c9cc18a7 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 5 May 2017 13:36:50 -0400 Subject: [PATCH 01/10] Add empty list checker in min_non_list(), for DSCR calculate --- bpfin/lib/other.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpfin/lib/other.py b/bpfin/lib/other.py index 7f4e6b8..7c080a7 100644 --- a/bpfin/lib/other.py +++ b/bpfin/lib/other.py @@ -28,7 +28,7 @@ def min_none_list(list_1): for element in list_1: if element is not None: new_list.append(element) - return min(new_list) + return min(new_list, default=0) def add_list(obj_list, number): -- GitLab From a4c3e16d85c6e7fac1fbb8f7d9a77b81d8443022 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 5 May 2017 14:40:20 -0400 Subject: [PATCH 02/10] fix month+1 --- bpfin/utilbills/bill_lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpfin/utilbills/bill_lib.py b/bpfin/utilbills/bill_lib.py index 693e539..51b1d52 100644 --- a/bpfin/utilbills/bill_lib.py +++ b/bpfin/utilbills/bill_lib.py @@ -2,7 +2,7 @@ import datetime import calendar import pandas as pd import copy - +from bpfin.lib.other import month_shift def add_list(obj_list, number): """Add a number to each value in a list. @@ -220,8 +220,8 @@ def form_inflated_item(base_list, target_terms, inflation_coeff_dict, present_da """ pre_year_date = datetime.date( present_date.year - 1, - present_date.month + 1, - cal_last_day(present_date.year - 1, present_date.month + 1)) + month_shift(present_date.month), + cal_last_day(present_date.year - 1, month_shift(present_date.month))) base_terms = form_date_calendar(pre_year_date, present_date)[1] base_list_copy = copy.deepcopy(base_list) for term in base_terms: -- GitLab From 337efcf53f68c7401a3bbd06e32e622ca213bfe6 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 5 May 2017 14:53:30 -0400 Subject: [PATCH 03/10] try fix loan --- bpfin/financials/scenario.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index 5f3f992..af009ca 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -146,7 +146,7 @@ class Scenario(): def get_dscr(self): noi_dict = copy.deepcopy(self.post_income_statement_table.get_noi_dict()) cash_dict = copy.deepcopy(self.post_balance_sheet_table.get_cash_dict()) - debt_dict = self.get_annual_debt_service() + debt_dict = copy.deepcopy(self.get_annual_debt_service()) savings_dict = {} utility_type_list = ['electricity', 'gas', 'oil', 'water'] -- GitLab From 119ba43eebac2c6a06eee279af6be68f44804b7d Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 5 May 2017 14:54:36 -0400 Subject: [PATCH 04/10] Try fix loan --- bpfin/financials/scenario.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index af009ca..264741f 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -152,7 +152,7 @@ class Scenario(): utility_type_list = ['electricity', 'gas', 'oil', 'water'] for utility_type in utility_type_list: savings_dict = add_year_dictionary( - savings_dict, self.saving_overview.get_utility_annual_saving_charge(utility_type)) + savings_dict, copy.deepcopy(self.saving_overview.get_utility_annual_saving_charge(utility_type))) dscr_dict = {} dscr_dict['noi_dscr'] = divide_dscr_dict(noi_dict, debt_dict) -- GitLab From d9c722a7238b6c3f60eb23599f6f4f20a0cc0d86 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 5 May 2017 14:56:18 -0400 Subject: [PATCH 05/10] Try fix again --- bpfin/financials/scenario.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index 264741f..d579318 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -129,10 +129,9 @@ class Scenario(): } """ graph_dict = {} - proforma_bill = self.get_post_energy_bill() - annual_debt_service = self.get_annual_debt_service() - prior_annual_bill = self.prior_income_statement_table.get_total_energy_dict( - ) + proforma_bill = copy.deepcopy(self.get_post_energy_bill()) + annual_debt_service = copy.deepcopy(self.get_annual_debt_service()) + prior_annual_bill = copy.deepcopy(self.prior_income_statement_table.get_total_energy_dict()) net_saving_dict = {} for year in prior_annual_bill: net_saving_dict[year] = prior_annual_bill[year] - proforma_bill[year] - annual_debt_service[year] -- GitLab From bd87979e04f9eda152db8c7e0f8e0181ee80ba15 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Fri, 5 May 2017 14:57:44 -0400 Subject: [PATCH 06/10] keep trying --- bpfin/financials/scenario.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index d579318..f2cbe75 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -109,9 +109,8 @@ class Scenario(): for year in proforma_year: annual_debt_service_dict[year] = 0 - for current_loan in self.scheduled_loan_list: - current_loan_annual = annualizing_projection( - current_loan.terms, current_loan.debt_service_due) + for current_loan in copy.deepcopy(self.scheduled_loan_list): + current_loan_annual = annualizing_projection(current_loan.terms, current_loan.debt_service_due) for year in proforma_year: if year in current_loan_annual: annual_debt_service_dict[year] += current_loan_annual[year] -- GitLab From 59802dd13b896221f539d28081507e0435a29f14 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 8 May 2017 11:04:34 -0400 Subject: [PATCH 07/10] Write func description for Scenario --- bpfin/financials/financial_lib.py | 2 +- bpfin/financials/saving.py | 2 +- bpfin/financials/scenario.py | 43 ++++++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/bpfin/financials/financial_lib.py b/bpfin/financials/financial_lib.py index dabbf2b..97cde72 100644 --- a/bpfin/financials/financial_lib.py +++ b/bpfin/financials/financial_lib.py @@ -10,7 +10,7 @@ def organize_bill_overview(bill_overview, analysis_date): """take bill_overview as inputs, fill in the blank annual bill with average numbers Args: - bill_overview (dictionary): bill_overview from UI, with blank cells, for 4 utility_types + bill_overview (dictionary): original annual bill input, with blank cells, for 4 utility_types analysis_date (dictionary): proforma's starting date and the years of proforma Returns: dictionary: bill_overview_organized, annual bills for 4 utility types, with blank cells filled diff --git a/bpfin/financials/saving.py b/bpfin/financials/saving.py index 0acaff9..d124e18 100644 --- a/bpfin/financials/saving.py +++ b/bpfin/financials/saving.py @@ -227,7 +227,7 @@ class Saving_Overview(): Generate Saving objects, pro-forma date period Args: - bill_overview (dictionary): bill_overview from UI, with blank cells, for 4 utility_types + bill_overview (dictionary): original annual bill input, with blank cells, for 4 utility_types prior_annual_bill_table (dictionary): dict of dict of annual charge, for 4 utility_types analysis_date (dictionary): proforma's starting date and the years of proforma commissioning_date (date): the date that construction work finished, saving starts at NEXT month diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index f2cbe75..37a6558 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -10,12 +10,53 @@ from bpfin.tests.testdata import sample_data as db class Scenario(): + """ + Calculate scenario based saving schedule, loan amounts and schedules, and feasibility + One Scenario is one package of retrofit measures, incorpoarting certain saving estimation, and one cost + + Attributes: + analysis_date (dictionary): proforma's starting date and the years of proforma + commission_date (date): the date that construction work finished, saving starts at NEXT month + construction_cost (float): project cost, estimated or quoted + + prior_annual_bill_table (dictionary): annual bill, for 4 utility_types, proir to saving + other_debt_service (dict): dictionary of debt service values per year + + prior_income_statement_table (object): complete Income_Statement_Table, with prior_saving projection + prior_balance_sheet_table (object): complete Balance_Sheet_Table, with prior_saving projection + post_income_statement_table (object): complete Income_Statement_Table, with post_saving projection + post_balance_sheet_table (object): complete Balance_Sheet_Table, with post_saving projection + + loan_list (object): Loan_List object, used to store loan options, and result of financing planning + saving_overview (object): Saving_Overview object, calculate and store scenario based saving results + scheduled_loan_list (list): list of Loan objects. Store financing plans for each loan option + """ def __init__(self, analysis_date, commission_date, construction_cost, bill_overview, prior_annual_bill_table, other_debt_service, prior_income_statement_table, prior_balance_sheet_table, loan_input_list): """ - other_debt_service (dict): dictionary of debt service values per year + Initiate Scenario object. + Pass in historical bill, income statement and balance sheet data + Pass in loan options and generate Loan_List object + Pass in analysis date, commission date + Pass in and generate Store scenario based saving estimation, and cost estimation + + Args: + analysis_date (dictionary): proforma's starting date and the years of proforma + commission_date (date): the date that construction work finished, saving starts at NEXT month + construction_cost (float): project cost, estimated or quoted + bill_overview (dictionary): original annual bill input, with blank cells, for 4 utility_types + prior_annual_bill_table (dictionary): annual bill, for 4 utility_types, proir to saving + other_debt_service (dict): dictionary of debt service values per year + prior_income_statement_table (object): complete Income_Statement_Table, with prior_saving projection + prior_balance_sheet_table (object): complete Balance_Sheet_Table, with prior_saving projection + loan_list (object): Loan_List object, used to store loan options, and result of financing planning + + Description: + bill_overview (dictionary): dict of a list, + list[0] = dict of annual bill, allow some year missing, for 4 utility_types + list[1] = boolean value, False == from manual input, True == from scraper """ self.analysis_date = analysis_date self.commission_date = copy.deepcopy(commission_date) -- GitLab From 5013c3a6b848e185db06b5c7f0e06b886542889b Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 8 May 2017 14:49:58 -0400 Subject: [PATCH 08/10] Add description for Scenario class --- bpfin/financials/scenario.py | 127 ++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 41 deletions(-) diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index 37a6558..979a927 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -4,7 +4,6 @@ from bpfin.financials.loan import Loan_List from bpfin.lib.other import add_year_dictionary, divide_dscr_dict, min_none_list from bpfin.utilbills.bill_lib import form_bill_year, annualizing_projection # may delete from here after sample built -from bpfin.financials.cash_balance import cash_balance from bpfin.financials.financial_lib import Income_Statement_Table, Balance_Sheet_Table from bpfin.tests.testdata import sample_data as db @@ -51,7 +50,7 @@ class Scenario(): other_debt_service (dict): dictionary of debt service values per year prior_income_statement_table (object): complete Income_Statement_Table, with prior_saving projection prior_balance_sheet_table (object): complete Balance_Sheet_Table, with prior_saving projection - loan_list (object): Loan_List object, used to store loan options, and result of financing planning + loan_input_list (list): list of dictionary of loan basic terms. Description: bill_overview (dictionary): dict of a list, @@ -82,9 +81,36 @@ class Scenario(): full_saving_dict, growth_rate_flag, req_dscr, customer_preference): """ - growth_rate_flag (float): indicating assumed growth rate, -2.0 == cagr, -1.0 == historical average + Conduct preliminary analysis for given scenario. + Project savings schedule + Generate post_saving income statement + Generate post_saving balance sheet + Allocate loan and generate loan schedules - To Do: clarify commission date and loan start date + Args: + prior_month_bill (dictionary): dictionary of lists of prior monthly bill (dates, use, price, charge) + percent_saving_dict (dictionary): dict of float, percentage saving for 4 utility types + full_saving_dict (dictionary): dict of undefined saving, from engineering saving analysis. can be None + growth_rate_flag (float): indicating assumed growth rate, -2.0 == cagr, -1.0 == historical average + req_dscr (dictionary): dict of required debt service coverage ratios + customer_preference (dictionary): dict of customer preference + + Description: + prior_month_bill (dictionary) = { + 'date_from': prior_bill_start, 'date_to': prior_bill_end, + 'usage': prior_monthly_usage, 'charge': prior_monthly_charge, + 'price': prior_monthly_price} + req_dscr (dictionary) = { + 'req_noi_dscr': 1.15, + 'req_cash_dscr': 1.15, + 'req_saving_dscr': 1.10} + customer_preference (dictionary) = { + 'downpayment_max': float, upper limit of self finance amount, + 'expected_payback': int, months that customer expect the project can payback, + 'cust_saving_dscr': float, customer required lower limit of (saving / debt service) ratio} + To Do: + clarify commission date and loan start date + balance sheet is not considering in energy loan. Which is should. And it changes get_graph_dict """ # calculate saving, by generating saving_overview object self.saving_overview.put_saving( @@ -129,6 +155,13 @@ class Scenario(): self.scheduled_loan_list = scheduled_loan_list def get_post_energy_bill(self): + """ + Get annual energy bill for pro-forma period, considering commission date. + Bill before commission date is without saving, after commission date is with sving + + Return: + dictionary: dict of total annual dollar charge {year: float} + """ proforma_year = form_bill_year(self.analysis_date['proforma_start'], self.analysis_date['proforma_duration']) post_annual_bill_table = copy.deepcopy(self.saving_overview.get_post_annual_bill_table()) @@ -138,12 +171,16 @@ class Scenario(): for year in proforma_year: current_total_energy_bill = 0 for utility_type in utility_type_list: - current_total_energy_bill += post_annual_bill_table[ - utility_type][year] + current_total_energy_bill += post_annual_bill_table[utility_type][year] post_energy_bill[year] = current_total_energy_bill return post_energy_bill def get_annual_debt_service(self): + """ + Get annual energy debt service. + Return: + dictionary: dict of total annual energy debt service {year: float} + """ proforma_year = form_bill_year(self.analysis_date['proforma_start'], self.analysis_date['proforma_duration']) annual_debt_service_dict = {} @@ -183,6 +220,14 @@ class Scenario(): return graph_dict def get_dscr(self): + """ + Get dscr (debt service coverage raios) + Return: + dictionary: dict of dict of {year: float}, showcase 3 types of dscr for all years + Description: + dscr_dict = {'noi_dscr': noi_dscr, 'cash_dscr': cash_dscr, 'saving_dscr': saving_dscr} + noi_dscr = {2017: 1.17, 2018: 1.98, ...} + """ noi_dict = copy.deepcopy(self.post_income_statement_table.get_noi_dict()) cash_dict = copy.deepcopy(self.post_balance_sheet_table.get_cash_dict()) debt_dict = copy.deepcopy(self.get_annual_debt_service()) @@ -201,6 +246,8 @@ class Scenario(): def get_economics(self): """ + Get project economics, some highlighted items for showcase and final analysis + Return: dictionary: { 'estimated_cost': float, @@ -210,8 +257,6 @@ class Scenario(): 'min_saving_dscr': float, in x multiple, 'min_noi_dscr': float, in x multiple, 'min_cash_dscr': float, in x multiple} - - To Do: generate dscr for every year """ dscr_dict = self.get_dscr() economics_overview = {} @@ -228,38 +273,38 @@ class Scenario(): # # ***** ugly test that can be a guide for front end dev ***** # # liability_dictionary = cash_balance('dict of tuples, date: (float, boolean)') -# growth_toggle = 0.01 -# prior_IS_table = Income_Statement_Table( -# db.raw_income_input, -# db.bill_overview_organized) -# prior_IS_table.project(growth_toggle, db.analysis_date, db.bill_overview_organized) -# prior_BS_table = Balance_Sheet_Table(db.raw_balance_sheet) -# prior_BS_table.project_balance_sheet( -# db.analysis_date, -# db.liability_dictionary, -# prior_IS_table.get_noi_dict()) - -# scenario = Scenario( -# analysis_date=db.analysis_date, -# commission_date=db.commission_date, -# construction_cost=60000, -# bill_overview=db.bill_overview, -# prior_annual_bill_table=db.bill_overview_organized, -# other_debt_service=db.liability_dictionary, -# prior_income_statement_table=prior_IS_table, -# prior_balance_sheet_table=prior_BS_table, -# loan_input_list=db.loan_input_list -# ) - -# scenario.prelim_anlaysis( -# prior_month_bill=db.prior_month_bill, -# percent_saving_dict=db.percent_saving_dict, -# full_saving_dict=db.full_saving_dict, -# growth_rate_flag=growth_toggle, -# req_dscr=db.req_dscr, -# customer_preference=db.customer_preference) - -# print(scenario.get_annual_debt_service()) -# print(scenario.get_graph_dict()) +growth_toggle = 0.01 +prior_IS_table = Income_Statement_Table( + db.raw_income_input, + db.bill_overview_organized) +prior_IS_table.project(growth_toggle, db.analysis_date, db.bill_overview_organized) +prior_BS_table = Balance_Sheet_Table(db.raw_balance_sheet) +prior_BS_table.project_balance_sheet( + db.analysis_date, + db.liability_dictionary, + prior_IS_table.get_noi_dict()) + +scenario = Scenario( + analysis_date=db.analysis_date, + commission_date=db.commission_date, + construction_cost=60000, + bill_overview=db.bill_overview, + prior_annual_bill_table=db.bill_overview_organized, + other_debt_service=db.liability_dictionary, + prior_income_statement_table=prior_IS_table, + prior_balance_sheet_table=prior_BS_table, + loan_input_list=db.loan_input_list +) + +scenario.prelim_anlaysis( + prior_month_bill=db.prior_month_bill, + percent_saving_dict=db.percent_saving_dict, + full_saving_dict=db.full_saving_dict, + growth_rate_flag=growth_toggle, + req_dscr=db.req_dscr, + customer_preference=db.customer_preference) + +print(scenario.get_annual_debt_service()) +print(scenario.get_graph_dict()) # print(scenario.get_dscr()) # print(scenario.get_economics()) -- GitLab From 58e0df8091226c7478e85ae5444372a890957c92 Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 8 May 2017 14:50:40 -0400 Subject: [PATCH 09/10] Fix description in Saving --- bpfin/financials/saving.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpfin/financials/saving.py b/bpfin/financials/saving.py index d124e18..0a90322 100644 --- a/bpfin/financials/saving.py +++ b/bpfin/financials/saving.py @@ -357,7 +357,7 @@ class Saving_Overview(): """ Get the proforma annual saving on charge for a utility_type Args: - utility_type: string. 'electricity', 'gas', 'oil', 'water' + utility_type (string): 'electricity', 'gas', 'oil', 'water' Return: dictionary: {year: saving on charge} """ -- GitLab From ebdc47a70bebd5f56b5c3ff4b0401b3edebb9efd Mon Sep 17 00:00:00 2001 From: chenzheng06 Date: Mon, 8 May 2017 14:52:20 -0400 Subject: [PATCH 10/10] Finalize Scenario fix-two --- bpfin/financials/scenario.py | 70 ++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index 979a927..bdce9e1 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -4,8 +4,8 @@ from bpfin.financials.loan import Loan_List from bpfin.lib.other import add_year_dictionary, divide_dscr_dict, min_none_list from bpfin.utilbills.bill_lib import form_bill_year, annualizing_projection # may delete from here after sample built -from bpfin.financials.financial_lib import Income_Statement_Table, Balance_Sheet_Table -from bpfin.tests.testdata import sample_data as db +# from bpfin.financials.financial_lib import Income_Statement_Table, Balance_Sheet_Table +# from bpfin.tests.testdata import sample_data as db class Scenario(): @@ -273,38 +273,38 @@ class Scenario(): # # ***** ugly test that can be a guide for front end dev ***** # # liability_dictionary = cash_balance('dict of tuples, date: (float, boolean)') -growth_toggle = 0.01 -prior_IS_table = Income_Statement_Table( - db.raw_income_input, - db.bill_overview_organized) -prior_IS_table.project(growth_toggle, db.analysis_date, db.bill_overview_organized) -prior_BS_table = Balance_Sheet_Table(db.raw_balance_sheet) -prior_BS_table.project_balance_sheet( - db.analysis_date, - db.liability_dictionary, - prior_IS_table.get_noi_dict()) - -scenario = Scenario( - analysis_date=db.analysis_date, - commission_date=db.commission_date, - construction_cost=60000, - bill_overview=db.bill_overview, - prior_annual_bill_table=db.bill_overview_organized, - other_debt_service=db.liability_dictionary, - prior_income_statement_table=prior_IS_table, - prior_balance_sheet_table=prior_BS_table, - loan_input_list=db.loan_input_list -) - -scenario.prelim_anlaysis( - prior_month_bill=db.prior_month_bill, - percent_saving_dict=db.percent_saving_dict, - full_saving_dict=db.full_saving_dict, - growth_rate_flag=growth_toggle, - req_dscr=db.req_dscr, - customer_preference=db.customer_preference) - -print(scenario.get_annual_debt_service()) -print(scenario.get_graph_dict()) +# growth_toggle = 0.01 +# prior_IS_table = Income_Statement_Table( +# db.raw_income_input, +# db.bill_overview_organized) +# prior_IS_table.project(growth_toggle, db.analysis_date, db.bill_overview_organized) +# prior_BS_table = Balance_Sheet_Table(db.raw_balance_sheet) +# prior_BS_table.project_balance_sheet( +# db.analysis_date, +# db.liability_dictionary, +# prior_IS_table.get_noi_dict()) + +# scenario = Scenario( +# analysis_date=db.analysis_date, +# commission_date=db.commission_date, +# construction_cost=60000, +# bill_overview=db.bill_overview, +# prior_annual_bill_table=db.bill_overview_organized, +# other_debt_service=db.liability_dictionary, +# prior_income_statement_table=prior_IS_table, +# prior_balance_sheet_table=prior_BS_table, +# loan_input_list=db.loan_input_list +# ) + +# scenario.prelim_anlaysis( +# prior_month_bill=db.prior_month_bill, +# percent_saving_dict=db.percent_saving_dict, +# full_saving_dict=db.full_saving_dict, +# growth_rate_flag=growth_toggle, +# req_dscr=db.req_dscr, +# customer_preference=db.customer_preference) + +# print(scenario.get_annual_debt_service()) +# print(scenario.get_graph_dict()) # print(scenario.get_dscr()) # print(scenario.get_economics()) -- GitLab