diff --git a/bpfin/financials/financial_lib.py b/bpfin/financials/financial_lib.py index f9c22cc46649fe8485339bd4037dfb0b8e0484fd..3f8107dc7729e83cdfa7e18f0bea66255937d91a 100644 --- a/bpfin/financials/financial_lib.py +++ b/bpfin/financials/financial_lib.py @@ -20,6 +20,9 @@ def organize_bill_overview(bill_overview, analysis_date): Description: analysis_date: {'proforma_start': datetime.date, 'proforma_duration': int} + 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 """ proforma_year = form_bill_year(analysis_date['proforma_start'], analysis_date['proforma_duration']) @@ -179,9 +182,9 @@ class Income_Statement(): def convert_income_statement_class(income_statement_class): """ - Convert single year income statement objective into a dictionary format + Convert single year income statement object into a dictionary format Args: - income_statement_class (objective): single year income statement objective + income_statement_class (object): single year income statement object Return: income_statement_dict (dictionary) """ @@ -204,7 +207,7 @@ def convert_income_statement_class(income_statement_class): class Income_Statement_Next(): """ - Create single year income_statement objective, + 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): @@ -216,7 +219,7 @@ class Income_Statement_Next(): growth_rate_flag (float): indicating assumed growth rate, -2.0 == cagr, -1.0 == historical average characters (dictionary): 6 characters calculated from historical income statements annual_bill_table (dictionary): dictionary of dictionary of annual bills, for 4 utility_types - Final instance is a single year income_statement objective + Final instance is a single year income_statement object Description: characters = { @@ -258,8 +261,8 @@ class Income_Statement_Table(): """ Create a income statement table, containing multiple years, including historical and projected data Key Attributes are: - hist_table (list): list of single year income_statement objectives, containing historical data - table(list): list of single year income_statement objectives, containing historical and projected data + hist_table (list): list of single year income_statement objects, containing historical data + table(list): list of single year income_statement objects, containing historical and projected data """ def __init__(self, raw_income_input, annual_bill_table): """ @@ -272,8 +275,8 @@ class Income_Statement_Table(): cagr (float): compound annual growth rate other_utility_percent (float): percentage, (average other_utility / average revenue) non_utility_expense_percent (float): percentage, (average non_utility_expense_percent / average revenue) - hist_table (list): list of single year income_statement objectives, containing historical data - table(list): list of single year income_statement objectives, containing historical and projected data + hist_table (list): list of single year income_statement objects, containing historical data + table(list): list of single year income_statement objects, containing historical and projected data characters (dictionary): contains key characters determined from historical data. Is used to project Description: @@ -336,13 +339,13 @@ class Income_Statement_Table(): def project(self, growth_rate_flag, analysis_date, annual_bill_table): """ - Project future income statement. Append multiple single year income_statement objectives to self.table + Project future income statement. Append multiple single year income_statement objects to self.table Args: growth_rate_flag (float): indicating assumed growth rate, -2.0 == cagr, -1.0 == historical average analysis_date (dictionary): proforma's starting date and the years of proforma annual_bill_table (dictionary): dictionary of dictionary of annual bills, for 4 utility_types Return: - list: list of single year income_statement objectives, containing historical and projection + list: list of single year income_statement objects, containing historical and projection Note: project() overwrites existing projection data """ diff --git a/bpfin/financials/saving.py b/bpfin/financials/saving.py index 2c8a5df5675ffc28a1fb458fe21b4fb1d670d061..02aa18ea4d719fdbd734f0e990b21b8ffbaef660 100644 --- a/bpfin/financials/saving.py +++ b/bpfin/financials/saving.py @@ -1,84 +1,369 @@ import datetime +import copy from bpfin.utilbills.bill_lib import cal_last_day from bpfin.utilbills.bill_lib import annualizing_projection +from bpfin.utilbills.bill_lib import form_year_calendar +from bpfin.utilbills.bill_lib import form_bill_calendar +from bpfin.tests.testdata import sample_data as db +from bpfin.utilbills.bill_post_proj_rough import bill_post_proj_rough class Saving(): """ - Calculate savings and project bills with commissioning date considered. + Calculate savings and project bills with commissioning date considered. For one utility type Attributes: - commissioning_date (date): the date that construction work finished, saving starts at NEXT month - proforma_date (list): list of dates, months of pro-forma time period - prior_saving_list (list): list of float, usage or charge without (prior to) savings - post_saving_list (list): list of float, usage or charge with (post to) savings - proforma_bill (list): monthly usage or charge, for pro-forma period - Data before commissioning date == historical - Data after commissioning date == post saving data - proforma_saving (list): monthly savings on usage or charge, for pro-forma period - Data before commissioning date == historical - Data after commissioning date == post saving data - first_year_saving (float): first year saving, usage or charge + is_manual_input (boolean): lable indicating is bill input by scraper or manual. Yes == manual input + commissioning_date (date): the date that construction work finished, saving starts at NEXT month + proforma_date (list): list of dates, months of pro-forma time period + proforma_usage (list) : list of monthly usage, for pro-forma period + Data before commissioning date == historical + Data after commissioning date == post saving data + proforma_charge (list) : list of monthly charge, for pro-forma period + Data before commissioning date == historical + Data after commissioning date == post saving data + proforma_saving_usage (list) : monthly savings on usage, for pro-forma period + Data before commissioning date == historical + Data after commissioning date == post saving data + proforma_saving_charge (list) : monthly savings on usage, for pro-forma period + Data before commissioning date == historical + Data after commissioning date == post saving data + first_year_saving_usage (float) : first 12 months saving on usage. + first_year_saving_charge (float) : first 12 months saving on charge. If data is annual, saving is on NEXT year + annual_proforma_usage (dictionary) : key is year, value is float, annual usage + annual_proforma_charge (dictionary) : key is year, value is float, annual charge + first_year_prior_charge (float): first 12 months prior_saving charge. If data is annual, charge is for NEXT year + percent_saving (float): percentage saving on first year charge. 0.2 == 20% """ - def __init__(self, commissioning_date, proforma_date, prior_saving_list, post_saving_list): + def __init__(self, is_manual_input, commissioning_date, proforma_date): + """ + Initiate Saving object. + Args: + is_manual_input (boolean): lable indicating is bill input by scraper or manual. Yes == manual input + commissioning_date (date): the date that construction work finished, saving starts at NEXT month + proforma_date (list): list of dates, months of pro-forma time period + """ + self.is_manual_input = is_manual_input self.commissioning_date = commissioning_date self.proforma_date = proforma_date - self.prior_saving_list = prior_saving_list - self.post_saving_list = post_saving_list - self.proforma_bill = [] - self.proforma_saving = [] - self.first_year_saving = None - - saving_start_date = datetime.date( - self.commissioning_date.year, - self.commissioning_date.month, - cal_last_day(self.commissioning_date.year, self.commissioning_date.month)) - saving_dict = {} - bill_dict = {} - first_year_saving = 0 - first_yr_count = 0 - for date, prior, post in zip(proforma_date, prior_saving_list, post_saving_list): - if date > saving_start_date: - saving_dict[date] = prior - post - bill_dict[date] = post - if first_yr_count <= 12: - first_year_saving += prior - post - first_yr_count += 1 - else: - saving_dict[date] = 0 - bill_dict[date] = prior - self.proforma_saving = list(saving_dict.values()) - self.proforma_bill = list(bill_dict.values()) - self.first_year_saving = first_year_saving - - def get_proforma_bill(self): - """ - Get proforma_bill + self.proforma_usage = None + self.proforma_charge = None + self.proforma_saving_usage = None + self.proforma_saving_charge = None + self.first_year_saving_charge = None + self.first_year_saving_usage = None + self.annual_proforma_usage = None + self.annual_proforma_charge = None + self.first_year_prior_charge = None + self.percent_saving = 0 + + def put_monthly_proforma(self, prior_saving_usage, post_saving_usage, prior_saving_charge, post_saving_charge): + """ + Put monthly prior_saving and post_saving, generate: monthly usage and charge for pro-forma purpose. + Also generate first year savings, and annual usage and annual charge + + Args: + prior_saving_usage (list): list of float, usage without (prior to) savings + post_saving_usage (list): list of float, usage with (post to) savings + prior_saving_charge (list): list of float, charge without (prior to) savings + post_saving_charge (list): list of float, charge with (post to) savings + """ + if self.is_manual_input is False: + saving_start_date = datetime.date( + self.commissioning_date.year, + self.commissioning_date.month, + cal_last_day(self.commissioning_date.year, self.commissioning_date.month)) + + saving_usage_dict = {} + saving_charge_dict = {} + proforma_usage_dict = {} + proforma_charge_dict = {} + first_year_saving_usage = 0 + first_year_saving_charge = 0 + first_yr_counter = 0 + first_year_prior_charge = 0 + for date, prior_usage, post_usage, prior_charge, post_charge in zip( + self.proforma_date, + prior_saving_usage, + post_saving_usage, + prior_saving_charge, + post_saving_charge): + if date > saving_start_date: + saving_usage_dict[date] = prior_usage - post_usage + saving_charge_dict[date] = prior_charge - post_charge + proforma_usage_dict[date] = post_usage + proforma_charge_dict[date] = post_charge + if first_yr_counter <= 12: + first_year_saving_usage += prior_usage - post_usage + first_year_saving_charge += prior_charge - post_charge + first_year_prior_charge += prior_charge + first_yr_counter += 1 + else: + saving_usage_dict[date] = 0 + saving_charge_dict[date] = 0 + proforma_usage_dict[date] = prior_usage + proforma_charge_dict[date] = prior_charge + + self.proforma_usage = list(proforma_usage_dict.values()) + self.proforma_charge = list(proforma_charge_dict.values()) + self.proforma_saving_usage = list(saving_usage_dict.values()) + self.proforma_saving_charge = list(saving_charge_dict.values()) + self.first_year_saving_usage = first_year_saving_usage + self.first_year_saving_charge = first_year_saving_charge + self.annual_proforma_usage = annualizing_projection(self.proforma_date, self.proforma_charge) + self.annual_proforma_charge = annualizing_projection(self.proforma_date, self.proforma_charge) + self.first_year_prior_charge = first_year_prior_charge + self.percent_saving = first_year_saving_charge / first_year_prior_charge + + def put_annual_proforma(self, annual_charge, percent_saving): + """ + Put annual prior_saving charge, calculate saving on charge and overall percentage saving + + Args: + annual_charge (dictionary): dict of annual prior saving charge, {2014: 100, ...}, with future estimated + percent_saving (float): percent of saving, 0.030 == 3.0% + """ + if self.is_manual_input is True: + annual_saving_dict = {} + annual_charge_dict = {} + proforma_year = form_year_calendar(self.proforma_date[0], self.proforma_date[-1]) + for year in proforma_year: + if year < self.commissioning_date.year: + annual_saving_dict[year] = 0 + annual_charge_dict[year] = annual_charge[year] + if year > self.commissioning_date.year: + annual_saving_dict[year] = annual_charge[year] * percent_saving + annual_charge_dict[year] = annual_charge[year] * (1 - percent_saving) + if year == self.commissioning_date.year: + effective_saving = (12 - self.commissioning_date.month) / 12 * percent_saving # partial year saving + annual_saving_dict[year] = annual_charge[year] * effective_saving + annual_charge_dict[year] = annual_charge[year] * (1 - effective_saving) + + first_year = self.commissioning_date.year + 1 + self.first_year_saving_charge = annual_saving_dict[first_year] + self.annual_proforma_charge = annual_charge_dict + self.first_year_prior_charge = annual_charge[first_year] + self.percent_saving = ( + annual_saving_dict[first_year] / annual_charge[first_year] if annual_charge[first_year] != 0 else 0) + + def get_proforma_charge(self): + """ + Get proforma_charge, for monthly pro-forma, not available for annual Return: - list: proforma_bill + list: proforma_charge """ - return self.proforma_bill + return self.proforma_charge - def get_proforma_saving(self): + def get_proforma_saving_charge(self): """ - Get proforma_saving + Get proforma_saving_charge, for monthly pro-forma, not available for annual Return: - list: proforma_saving + list: proforma_saving_charge """ - return self.proforma_saving + return self.proforma_saving_charge - def get_first_year_saving(self): + def get_percent_saving(self): + """ + Get percentage saving on charge + Return: + float: overall percentage saving on charge + """ + return self.percent_saving + + def get_first_year_saving_charge(self): """ Get first year saving Return: float: sum of first 12 months of savings """ - return self.first_year_saving + return self.first_year_saving_charge + + def get_first_year_prior_charge(self): + """ + Get prior charge for first year after commissioning + Return: + float: prior charge for first year after commissioning + """ + return self.first_year_prior_charge - def get_annual_bill(self): + def get_annual_proforma_charge(self): """ - Calculate and return annual pro-forma usage or charge + Calculate and return annual pro-forma charge Return: - dictionary: dict of annual item, {year: annual usage or charge} + dictionary: dict of annual item, {year: annual charge} + """ + return self.annual_proforma_charge + + +class Saving_Overview(): + """ + Generate saving schedule, annual usages and charges for 4 utility types + Also calculate percentage saving on usages and charges. + If rate plan applies, usage % saving can be different from charge % saving + Saving_Overview should be scenario based, it is an "attribute" of a scenario + + Attributes: + manual_input_dict(dictionary): key is utility type, value is boolean. True == mannual_input + prior_annual_bill_table (dictionary): annual bill, for 4 utility_types + proforma_date (list): list of dates, months of pro-forma time period + commissioning_date (date): the date that construction work finished, saving starts at NEXT month + utility_saving_dict (dictionary): dict of Saving objects, for 4 utility types + saving_percent_charge (dictionary): key is utility type, value is float {'electricity': float} + total_first_year_saving (float): dollar saving for the first 12 month after commissioning + total_saving_percent (float): percentage saving on charge for the first 12 month after commissioning + + Description: + prior_annual_bill_table = {'electricity': electricity_bill, 'oil': oil_bill, ...} + electricity_bill = {2014: 100, 2015:200, ...} + """ + + def __init__( + self, + bill_overview, + prior_annual_bill_table, + analysis_date, + commissioning_date): """ - annual_proforma_bill = annualizing_projection(self.proforma_date, self.proforma_bill) - return annual_proforma_bill + Initiate Saving_Overview. + Take in manual_input_dict, annual and monthly bills, commissioning_date and percentage savings + Generate Saving objects, pro-forma date period + + Args: + bill_overview (dictionary): bill_overview from UI, 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 + + 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 + prior_annual_bill_table = {'electricity': electricity_bill, 'oil': oil_bill, ...} + electricity_bill = {2014: 100, 2015:200, ...} + """ + self.manual_input_dict = { + 'electricity': not bill_overview['electricity'][1], + 'gas': not bill_overview['gas'][1], + 'oil': not bill_overview['oil'][1], + 'water': not bill_overview['water'][1]} + self.prior_annual_bill_table = prior_annual_bill_table + self.proforma_date = form_bill_calendar(analysis_date['proforma_start'], analysis_date['proforma_duration'])[1] + self.commissioning_date = commissioning_date + self.utility_saving_dict = {'electricity': None, 'gas': None, 'oil': None, 'water': None} + self.saving_percent_charge = {'electricity': None, 'gas': None, 'oil': None, 'water': None} + self.total_first_year_saving = 0 # dollar + self.total_saving_percent = 0 # dollar percentage + + def put_saving(self, prior_month_bill, percent_saving_dict, full_saving_dict): + """ + Put inputs to generage saving data. + Also calculate overall saving on charge, and percentage saving + + Args: + prior_month_bill (dictionary): dict of dict of lists, projected energy bill for prior_saving + 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 + + Description: + prior_month_bill = { + 'electricity': electricity_prior_bill_rough, + 'gas': None, + 'oil': None, + 'water': None} + + electricity_prior_bill_rough = { + 'date_from': list, + 'date_to': list, + 'usage': list, + 'charge': list, + 'price': list} + """ + utility_type_list = ['electricity', 'gas', 'oil', 'water'] + + total_first_year_saving = 0 + total_first_year_prior_charge = 0 + for utility in utility_type_list: + is_manual_input = self.manual_input_dict[utility] + + utility_saving = Saving(is_manual_input, self.commissioning_date, self.proforma_date) + + if utility_saving.is_manual_input is True: # bill is from manual input, monthly bill not available + utility_saving.put_annual_proforma(self.prior_annual_bill_table[utility], percent_saving_dict[utility]) + + if utility_saving.is_manual_input is False: # bill is from scraper, monthly bill is available + if full_saving_dict[utility] is None: # engineering saving is not available, use percentage saving + post_month_bill = bill_post_proj_rough(prior_month_bill[utility], percent_saving_dict[utility]) + # else: # engineering saving is available. Project monthly_bill with regression. not finished yet + # post_month_bill = bill_proj_reg(prior_utility, full_saving_dict[utility], HDD, CDD, occupancy) + + utility_saving.put_monthly_proforma( + prior_month_bill[utility]['usage'], + post_month_bill['usage'], + prior_month_bill[utility]['charge'], + post_month_bill['charge']) + + total_first_year_saving += utility_saving.get_first_year_saving_charge() + total_first_year_prior_charge += utility_saving.get_first_year_prior_charge() + self.saving_percent_charge[utility] = utility_saving.get_percent_saving() + self.utility_saving_dict[utility] = utility_saving + + self.total_first_year_saving = total_first_year_saving + self.total_saving_percent = ( + total_first_year_saving / total_first_year_prior_charge if total_first_year_prior_charge != 0 else 0) + + def get_total_first_year_saving(self): + """ + Get first year total dollar saving + This is used for: loan allocation, simple_payback calculation + Retrun: + float: a dollar number + """ + return self.total_first_year_saving + + def get_simple_payback(self, cost): + """ + Get the simple payback for a given project cost. cost / first year dollar saving + Args: + cost (float): project cost, estimated or quoted + Return: + float: simple paback, in year + """ + return float(cost / self.total_first_year_saving if self.total_first_year_saving != 0 else 0) + + def get_total_saving_percent(self): + """ + Get the total saving percentage. First dollar saving / First year prior_saving charge + Return: + float: total dollar saving percentage + """ + return self.total_saving_percent + + def get_total_annual_saving_charge(self): + """ + """ + pass + + def get_utility_annual_saving_charge(self, utility_type): + """ + Get the proforma annual saving on charge for a utility_type + Args: + utility_type: string. 'electricity', 'gas', 'oil', 'water' + Return: + dictionary: {year: saving on charge} + """ + prior_charge_dict = copy.deepcopy(self.prior_annual_bill_table[utility_type]) + post_charge_dict = copy.deepcopy(self.utility_saving_dict[utility_type].get_annual_proforma_charge()) + saving_dict = {} + for year in prior_charge_dict: + saving_dict[year] = prior_charge_dict[year] - post_charge_dict[year] + return saving_dict + + +# **** ugly test **** +# so = Saving_Overview(db.bill_overview, db.bill_overview_organized, db.analysis_date, datetime.date(2017, 3, 14)) +# so.put_saving(db.prior_month_bill, db.percent_saving_dict, db.full_saving_dict) + +# print('\n first_year_dollar_saving =', so.get_total_first_year_saving()) +# print('\n simple payback =', so.get_simple_payback(50000)) +# print('\n total saving percent =', so.get_total_saving_percent()) +# print('\n electricity_annual_saving_charge', so.get_utility_annual_saving_charge('electricity')) + diff --git a/bpfin/tests/test_financials/test_saving.py b/bpfin/tests/test_financials/test_saving.py index cf6be7b44d4e0d332bf4737bb201be7a6fa9d3df..bc1c48ca8525fefdd2f6d8fa0d8e4d71cac245ae 100644 --- a/bpfin/tests/test_financials/test_saving.py +++ b/bpfin/tests/test_financials/test_saving.py @@ -1,8 +1,10 @@ import datetime from bpfin.financials.saving import Saving +from bpfin.financials.saving import Saving_Overview +from bpfin.tests.testdata import sample_data as db -def test_Saving(): +def test_Saving_put_monthly_proforma(): input_proforma_date = [ datetime.date(2017, 1, 31), datetime.date(2017, 2, 28), @@ -15,19 +17,124 @@ def test_Saving(): datetime.date(2017, 9, 30), datetime.date(2017, 10, 31), datetime.date(2017, 11, 30), - datetime.date(2017, 12, 31), ] + datetime.date(2017, 12, 31)] input_commissioning_date = datetime.date(2017, 3, 14) - input_prior_list = [100] * 12 - input_post_list = [100, 90, 80, 70, 60, 50, 40, 30, 40, 50, 60, 70] - + input_prior_charge = [100] * 12 + input_post_charge = [100, 90, 80, 70, 60, 50, 40, 30, 40, 50, 60, 70] + input_prior_usage = [15] * 12 + input_post_usage = [10, 9, 8, 7, 6, 5, 4, 3, 4, 5, 6, 7] output_saving = [0, 0, 0, 30, 40, 50, 60, 70, 60, 50, 40, 30] - output_bill = [100, 100, 100, 70, 60, 50, 40, 30, 40, 50, 60, 70] - elec_saving = Saving(input_commissioning_date, input_proforma_date, input_prior_list, input_post_list) - result_saving = elec_saving.get_proforma_saving() - result_bill = elec_saving.get_proforma_bill() + output_charge = [100, 100, 100, 70, 60, 50, 40, 30, 40, 50, 60, 70] + output_overall_saving = 0.4777777777777778 + + elec_saving = Saving(False, input_commissioning_date, input_proforma_date) + elec_saving.put_monthly_proforma(input_prior_usage, input_post_usage, input_prior_charge, input_post_charge) + result_saving = elec_saving.get_proforma_saving_charge() + result_charge = elec_saving.get_proforma_charge() + assert output_saving == result_saving - assert output_bill == result_bill - assert elec_saving.get_first_year_saving() == 430 - assert elec_saving.get_annual_bill() == {2017: 770} - # print('1st yr saving =', elec_saving.get_first_year_saving()) - # print('annual_bill =', elec_saving.get_annual_bill()) + assert output_charge == result_charge + assert elec_saving.get_first_year_saving_charge() == 430 + assert elec_saving.get_annual_proforma_charge() == {2017: 770} + assert elec_saving.get_percent_saving() == output_overall_saving + + +def test_Saving_put_annual_proforma(): + input_proforma_date = [ + datetime.date(2017, 1, 31), + datetime.date(2017, 2, 28), + datetime.date(2017, 3, 31), + datetime.date(2017, 4, 30), + datetime.date(2017, 5, 31), + datetime.date(2017, 6, 30), + datetime.date(2017, 7, 31), + datetime.date(2017, 8, 31), + datetime.date(2017, 9, 30), + datetime.date(2017, 10, 31), + datetime.date(2017, 11, 30), + datetime.date(2017, 12, 31), + datetime.date(2018, 1, 31), + datetime.date(2018, 2, 28), + datetime.date(2018, 3, 31), + datetime.date(2018, 4, 30), + datetime.date(2018, 5, 31), + datetime.date(2018, 6, 30), + datetime.date(2018, 7, 31), + datetime.date(2018, 8, 31), + datetime.date(2018, 9, 30), + datetime.date(2018, 10, 31), + datetime.date(2018, 11, 30), + datetime.date(2018, 12, 31)] + input_commissioning_date = datetime.date(2017, 3, 14) + input_annual_charge = {2017: 100, 2018: 100} + input_percent_saving = 0.20 + output_first_year_saving_charge = 20 + output_annual_proforma_charge = {2017: 85, 2018: 80} + output_overall_saving = 0.20 + + elec_saving = Saving(True, input_commissioning_date, input_proforma_date) + elec_saving.put_annual_proforma(input_annual_charge, input_percent_saving) + + result_first_year_saving_charge = elec_saving.get_first_year_saving_charge() + result_annual_proforma_charge = elec_saving.get_annual_proforma_charge() + result_overall_saving = elec_saving.get_percent_saving() + + assert result_first_year_saving_charge == output_first_year_saving_charge + assert result_annual_proforma_charge == output_annual_proforma_charge + assert result_overall_saving == output_overall_saving + + +def test_Saving_Overview(): + input_bill_overview = db.bill_overview + input_prior_annual_bill_table = db.bill_overview_organized + input_analysis_date = db.analysis_date + input_commissioning_date = datetime.date(2017, 3, 14) + + input_prior_month_bill = db.prior_month_bill + input_percent_saving_dict = db.percent_saving_dict + input_full_saving_dict = db.full_saving_dict + + output_first_year_saving = 10479.289108609044 + output_simple_payback = 4.771316019797902 + output_total_saving_percent = 0.2624163752635955 + output_electricity_annual_saving_charge = { + 2012: 0.0, + 2013: 0.0, + 2014: 0.0, + 2015: 0.0, + 2016: 0.0, + 2017: 6757.962319199603, + 2018: 8846.241570765102, + 2019: 9062.872690379008, + 2020: 9275.049342309605, + 2021: 9471.770503408214, + 2022: 9650.941259401636, + 2023: 9826.981304138018, + 2024: 10007.019419843535, + 2025: 10193.107315596935, + 2026: 10393.916746353145, + 2027: 10609.979647682601, + 2028: 10831.30886589543, + 2029: 11052.82469894908, + 2030: 11274.416949194128, + 2031: 11496.666332412671, + 2032: 11721.576357170983, + 2033: 11951.64472721766, + 2034: 12187.887773507784, + 2035: 12430.56386264111, + 2036: 12679.70824604718 + } + + saving_overview = Saving_Overview( + input_bill_overview, input_prior_annual_bill_table, input_analysis_date, input_commissioning_date) + saving_overview.put_saving(input_prior_month_bill, input_percent_saving_dict, input_full_saving_dict) + + result_first_year_saving = saving_overview.get_total_first_year_saving() + result_simple_payback = saving_overview.get_simple_payback(50000) + result_total_saving_percent = saving_overview.get_total_saving_percent() + result_electricity_annual_saving_charge = saving_overview.get_utility_annual_saving_charge('electricity') + + assert result_first_year_saving == output_first_year_saving + assert result_simple_payback == output_simple_payback + assert result_total_saving_percent == output_total_saving_percent + assert result_electricity_annual_saving_charge == output_electricity_annual_saving_charge diff --git a/bpfin/tests/test_utilbills/test_bill_lib.py b/bpfin/tests/test_utilbills/test_bill_lib.py index c6f14101397cca86472676bb404d564a97d339eb..d03d1d476d6459c4f62c2be07158dbfdf424d227 100644 --- a/bpfin/tests/test_utilbills/test_bill_lib.py +++ b/bpfin/tests/test_utilbills/test_bill_lib.py @@ -38,3 +38,11 @@ def test_cal_oil_price(): output_list = [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0] result_list = bl.cal_oil_price(input_oilcharge, input_oiluse) assert output_list == result_list + + +def test_form_year_calendar(): + input_date_start = date(2012, 3, 4) + input_date_end = date(2017, 5, 9) + output_list = [2012, 2013, 2014, 2015, 2016, 2017] + result_list = bl.form_year_calendar(input_date_start, input_date_end) + assert output_list == result_list diff --git a/bpfin/tests/testdata/sample_data.py b/bpfin/tests/testdata/sample_data.py index 9fd24b8525cbe3c3844b6bbbbebea77bbde61ec5..12947de9f31015d1f4173d877a7cccbc323d4184 100644 --- a/bpfin/tests/testdata/sample_data.py +++ b/bpfin/tests/testdata/sample_data.py @@ -95,7 +95,7 @@ annual_bill_oil = {2015: 1010, 2016: 1210, 2017: 1510} annual_bill_water = {2015: 0, 2016: 0, 2017: 0} bill_overview = { - 'electricity': [annual_bill_electricity, True], + 'electricity': [annual_bill_electricity, True], # True == scraper 'gas': [annual_bill_gas, False], 'oil': [annual_bill_oil, False], 'water': [annual_bill_water, False] @@ -1523,6 +1523,50 @@ post_bill_rough = { 'charge': post_proj_rough_charge, 'price': prior_proj_rough_price} +# month_saving_overview = { +# 'electricity': { +# 'prior_usage': prior_proj_rough_usage, +# 'post_usage': post_proj_rough_usage, +# 'prior_charge': prior_proj_rough_charge, +# 'post_charge': post_proj_rough_charge}, +# 'gas': { +# 'prior_usage': None, +# 'post_usage': None, +# 'prior_charge': None, +# 'post_charge': None}, +# 'oil': { +# 'prior_usage': None, +# 'post_usage': None, +# 'prior_charge': None, +# 'post_charge': None}, +# 'water': { +# 'prior_usage': None, +# 'post_usage': None, +# 'prior_charge': None, +# 'post_charge': None} +# } + +prior_month_bill = { + 'electricity': prior_bill_rough, + 'gas': None, + 'oil': None, + 'water': None, +} + +percent_saving_dict = { + 'electricity': 0.25, + 'gas': 0.10, + 'oil': 0.80, + 'water': 0.0 +} + +full_saving_dict = { + 'electricity': None, # dict of engineering analysis, {'date_from': [], 'date_to': [], 'heating': [], 'cooling': [], 'other': []} + 'gas': None, + 'oil': None, + 'water': None +} + # global variables - # inflation coefficient dictionary. this is NOT inflation rate diff --git a/bpfin/utilbills/bill_lib.py b/bpfin/utilbills/bill_lib.py index 8df3fd0c8fda15acab599aa923f4d927c99ee4bf..57001fbb52532674cdbd77a1c744e20de627b7c8 100644 --- a/bpfin/utilbills/bill_lib.py +++ b/bpfin/utilbills/bill_lib.py @@ -97,6 +97,28 @@ def form_date_calendar(date_start, date_end): return [bdstart, bdend] +def form_year_calendar(date_start, date_end): + """Return list of all calendar years. + + Args: + date_start (datetime): date xx/yy/abcd + date_end (datetime): date xx/yy/abcd + Returns: + list: all years, first year is year of date_start, last year is year of date_end + """ + year_list = [] + year = date_start.year + counter = 0 + while year <= date_end.year: + if counter > 9999: + return False + year_list.append(year) + year += 1 + counter += 1 + return year_list + + + def form_year_month(target_terms): """Return list of month,year of datetimes in a list.