diff --git a/bpfin/financials/financial_lib.py b/bpfin/financials/financial_lib.py index dabbf2b3d6ecaf31deb12d0ee03f2100c12afa1a..97cde72600ac00c26b895100dd7b3539c6ba6a89 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 0acaff96322ac46e8279ba5ad07316aefe8fa255..0a9032229270d2fe98fbdc75bbfa3f4b1892e27e 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 @@ -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} """ diff --git a/bpfin/financials/scenario.py b/bpfin/financials/scenario.py index 5f3f992716bdc96a6d700f43548b98c02f20da4d..bdce9e1433300ed264f91569290c8d0b8ed9edb7 100644 --- a/bpfin/financials/scenario.py +++ b/bpfin/financials/scenario.py @@ -4,18 +4,58 @@ 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 +# from bpfin.financials.financial_lib import Income_Statement_Table, Balance_Sheet_Table +# 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_input_list (list): list of dictionary of loan basic terms. + + 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) @@ -41,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( @@ -88,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()) @@ -97,21 +171,24 @@ 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 = {} 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] @@ -129,10 +206,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] @@ -144,15 +220,23 @@ 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 = self.get_annual_debt_service() + debt_dict = copy.deepcopy(self.get_annual_debt_service()) savings_dict = {} 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) @@ -162,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, @@ -171,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 = {} diff --git a/bpfin/lib/other.py b/bpfin/lib/other.py index 7f4e6b8fa3a83308798584ae567e25676ea803f9..7c080a7ac50c403b0e2cdc7fbd9323a244ea790d 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): diff --git a/bpfin/utilbills/bill_lib.py b/bpfin/utilbills/bill_lib.py index 693e539e822911907517fd4a051cd03bf09d315c..51b1d5284eb9eabb8309ee000e6360ac8faee0a1 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: