diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index 0bc6e05dff5e24e6e1ccad9e002c638783abeaec..eb5654bc8fa8617b135165b646f9ff996ecfec32 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -59,24 +59,24 @@ function getBillProjectionDatesForm() { headers: { 'Content-Type': 'application/json' }, - }).then(res => { - fundDropBox = document.querySelector('#id_fund-select'); - var funds = res.payload.result.funds; - for (var fundIndex = 0; fundIndex < funds.length; fundIndex++) { - fundDropBox.innerHTML += ` - - `; - if (res.payload.result.present) { - document.querySelector('#pro-forma-date-input').value = res.payload.result.pro_forma_start_date; - document.querySelector('#pro-forma-duration-input').value = res.payload.result.pro_forma_duration; - document.querySelector('#analysis-date-input').value = res.payload.result.analysis_date; - document.querySelector('#id_fund-select').value = res.payload.result.fund; - document.querySelector('#anticipated-construction-start-date-input').value = res.payload.result.anticipated_construction_start_date; - document.querySelector('#anticipated-commissioning-date-input').value = res.payload.result.anticipated_commissioning_date; - document.querySelector('#anticipated-construction-period-input').value = res.payload.result.anticipated_construction_period; - } + }).then(res => { + fundDropBox = document.querySelector('#id_fund-select'); + var funds = res.payload.result.funds; + for (var fundIndex = 0; fundIndex < funds.length; fundIndex++) { + fundDropBox.innerHTML += ` + + `; + if (res.payload.result.present) { + document.querySelector('#pro-forma-date-input').value = res.payload.result.pro_forma_start_date; + document.querySelector('#pro-forma-duration-input').value = res.payload.result.pro_forma_duration; + document.querySelector('#analysis-date-input').value = res.payload.result.analysis_date; + document.querySelector('#id_fund-select').value = res.payload.result.fund; + document.querySelector('#anticipated-construction-start-date-input').value = res.payload.result.anticipated_construction_start_date; + document.querySelector('#anticipated-commissioning-date-input').value = res.payload.result.anticipated_commissioning_date; + document.querySelector('#anticipated-construction-period-input').value = res.payload.result.anticipated_construction_period; } - }); + } + }); } /** @@ -564,7 +564,7 @@ function billsOverviewFormSubmit(form) { var columnLength = billsOverviewTable.rows[0].cells.length - 2; for (var cellIndex = 2; cellIndex < columnLength; cellIndex++) { val = 0; - for(var rowIndex = 0; rowIndex < 4; rowIndex++) { + for (var rowIndex = 0; rowIndex < 4; rowIndex++) { val += Number(billsOverviewTable.rows[rowIndex].cells[cellIndex].children[0].value); } totalCharge.push(val); @@ -1110,7 +1110,7 @@ function getIncomeStatementTable() { }, }).then(res => { loadIncomeStatementTable(); - if(res.payload.instance.present) { + if(Object.keys(res.payload.instance).length !== 0) { recordList = res.payload.instance.result; heading = document.querySelector('#income-statement-table-head'); body = document.querySelector('#income-statement-table-body'); @@ -1176,7 +1176,7 @@ function onCalculateIncomeStatement(data) { 'X-CSRFToken': Cookies.get('csrftoken') }) }).then(res => { - if (!res.payload.err) { + if (!res.err) { const REVENUE_ROW = 0; const UTILITY_EXPENSE_ROW = 1; const ENERGY_EXPENSE_ROW = 2; @@ -1190,8 +1190,8 @@ function onCalculateIncomeStatement(data) { const TOTAL_EXPENSE_ROW = 10; const NET_OPERATING_EXPENSE = 11; var incomeStatementFull = res.payload.instance; + cellIndex = 1 for (key in incomeStatementFull) { - cellIndex = Number(key)+1; updateIncomeStatementTable(ENERGY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].energy_opex); updateIncomeStatementTable(ELECTRICITY_ROW, cellIndex, incomeStatementFull[key].electricity_opex); updateIncomeStatementTable(GAS_ROW, cellIndex, incomeStatementFull[key].gas_opex); @@ -1201,6 +1201,7 @@ function onCalculateIncomeStatement(data) { updateIncomeStatementTable(NET_NON_ENERGY_EXPENSE_ROW, cellIndex, incomeStatementFull[key].net_non_energy_opex); updateIncomeStatementTable(TOTAL_EXPENSE_ROW, cellIndex, incomeStatementFull[key].total_opex); updateIncomeStatementTable(NET_OPERATING_EXPENSE, cellIndex, incomeStatementFull[key].noi); + cellIndex += 1; } var future = res.payload.future; const FUTURE_INDEX = 4; @@ -1235,15 +1236,17 @@ function onCalculateIncomeStatement(data) { updateIncomeStatementTable(NET_OPERATING_EXPENSE,AVERAGE_DICT_INDEX,averageDict.noi); var box = document.querySelector('#growth-rate option'); - box.innerHTML = `CAGR ${res.payload.CAGR}`; + box.innerHTML = `CAGR = ${res.payload.CAGR}%`; document.querySelector('#income-statement-error-msg').innerHTML = ` Saved Successfully. `; } else { - document.querySelector('#income-statement-error-msg').innerHTML = ` - ${res.payload.err} - `; + res.err.responseBody.then((error) => { + document.querySelector('#income-statement-error-msg').innerHTML = ` + ${error.error} + `; + }) } }) return false; diff --git a/blocnote/apps/financialInputs/templates/financialInputs/index.html b/blocnote/apps/financialInputs/templates/financialInputs/index.html index a208ffd677a8cd67f3633d461e4d272e2ac62027..6367e08e385b0bcb8b86da007d95e5e8ad932ca2 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/index.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/index.html @@ -52,7 +52,6 @@ {% endblock %} {% block scripts %} - {% endblock %} diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index 3d69d6be5ebe999f2e9da49a50744eebb6fef1a4..cd60eea8cf5eacefa23da9c783c670742528cbf7 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -7,9 +7,8 @@ from django.http import JsonResponse from django.db import connections from django.views import View -from bpfin.financials.financial_lib import organize_bill_overview -from bpfin.financials.financial_lib import Income_Statement_Table -from bpfin.lib.back_end_call import monthly_bill +from bpfin.lib.back_end_call import monthly_bill, prior_income_statement_table + from .models import Fund, FinancingOverview, Bills, BillsOverview, CustomerPreference, EstimationAlgorithm from .models import Liabilities, CashBalance, IncomeStatement, LoanOptions, DefaultLoan, Lender, GrowthRate @@ -61,6 +60,47 @@ def change_date_format(dates): return new_date +def get_raw_bill(building_id): + """Fetch all the bills in the database. + + Fetch the raw bill from the database and return them as a dictionary for each utility. The return value is + a dictionary with utility type as key and the bill as value. + + Args: + building_id: id of the building. + + Returns: + raw_bill: Dictionary with key as a utility type and value as the bill. The bill in turn is a dictionary + with keys as bill date from, bill date to, energy usage and charge. + """ + utility_objs = {} + UTILITIES = ['electricity', 'gas', 'oil', 'water'] + raw_bill = {} + for util in UTILITIES: + objs = Bills.objects.filter( + building_id=building_id, + utility_type=util, + ) + utility_objs[util] = objs + if objs: + date_from = [] + date_to = [] + usage = [] + charge = [] + for obj in objs: + date_from.append(obj.date_from) + date_to.append(obj.date_to) + usage.append(float(obj.usage)) + charge.append(float(obj.charge)) + raw_bill[util] = { + 'date_from': date_from, + 'date_to': date_to, + 'usage': usage, + 'charge': charge, + } + return raw_bill + + class Index(View): """Class that renders the index page.""" @@ -354,45 +394,6 @@ class BillsOverviewView(View): model_bills = Bills utility = ['electricity', 'gas', 'oil', 'water'] - def get_raw_bill(self, building_id): - """Fetch all the bills in the database. - - Fetch the raw bill from the database and return them as a dictionary for each utility. The return value is - a dictionary with utility type as key and the bill as value. - - Args: - building_id: id of the building. - - Returns: - raw_bill: Dictionary with key as a utility type and value as the bill. The bill in turn is a dictionary - with keys as bill date from, bill date to, energy usage and charge. - """ - utility_objs = {} - raw_bill = {} - for util in self.utility: - objs = Bills.objects.filter( - building_id=building_id, - utility_type=util, - ) - utility_objs[util] = objs - if objs: - date_from = [] - date_to = [] - usage = [] - charge = [] - for obj in objs: - date_from.append(obj.date_from) - date_to.append(obj.date_to) - usage.append(float(obj.usage)) - charge.append(float(obj.charge)) - raw_bill[util] = { - 'date_from': date_from, - 'date_to': date_to, - 'usage': usage, - 'charge': charge, - } - return raw_bill - def get(self, request, building_id): """Handle HTTP GET request. @@ -478,7 +479,7 @@ class BillsOverviewView(View): analysis_date = get_analysis_date(building_id) if put['Estimation Model'] == 'Rough Estimation': # Fetch all bills from the database. - raw_bill = self.get_raw_bill(building_id) + raw_bill = get_raw_bill(building_id) # Project the charge for utilities whose atleast 12 months bills are available. projected_bills = monthly_bill(raw_bill, analysis_date) for util in self.utility: @@ -819,10 +820,10 @@ class IncomeStatementTable(View): water = {} if objs: for obj in objs: - electricity[obj.year] = float(obj.electricity) - gas[obj.year] = float(obj.gas) - oil[obj.year] = float(obj.oil) - water[obj.year] = float(obj.water) + electricity[obj.year] = float(obj.electricity) if obj.electricity else None + gas[obj.year] = float(obj.gas) if obj.gas else None + oil[obj.year] = float(obj.oil) if obj.oil else None + water[obj.year] = float(obj.water) if obj.water else None bill_overview = { 'electricity': [electricity, not obj.electricity_is_user_input], 'gas': [gas, not obj.gas_is_user_input], @@ -841,7 +842,8 @@ class IncomeStatementTable(View): building_id: id of the building. """ self.model.objects.filter(building_id=building_id).delete() - for record in result: + for year in result: + record = result[year] self.model.objects.create( building_id=building_id, year=record['year'], @@ -851,6 +853,51 @@ class IncomeStatementTable(View): non_utility_operating_expense=record['non_utility_expense'] ) + def get_annual_bills(self, building_id): + """Fetch manually input annual values from the database.""" + annual_bills = {} + + electricity_objs = BillsOverview.objects.filter( + building_id=building_id, + electricity_is_user_input=True, + ) + + gas_objs = BillsOverview.objects.filter( + building_id=building_id, + gas_is_user_input=True, + ) + + oil_objs = BillsOverview.objects.filter( + building_id=building_id, + oil_is_user_input=True, + ) + + water_objs = BillsOverview.objects.filter( + building_id=building_id, + water_is_user_input=True, + ) + + if electricity_objs: + annual_bills['electricity'] = {} + for obj in electricity_objs: + annual_bills['electricity'][obj.year] = float(obj.electricity) if obj.electricity else None + + if gas_objs: + annual_bills['gas'] = {} + for obj in gas_objs: + annual_bills['gas'][obj.year] = float(obj.gas) if obj.gas else None + + if oil_objs: + annual_bills['oil'] = {} + for obj in oil_objs: + annual_bills['oil'][obj.year] = float(obj.oil) if obj.oil else None + + if water_objs: + annual_bills['water'] = {} + for obj in water_objs: + annual_bills['water'][obj.year] = float(obj.water) if obj.water else None + return annual_bills + def put(self, request, building_id): """Handle HTTP PUT request. @@ -872,13 +919,13 @@ class IncomeStatementTable(View): try: bill_overview = self.get_bills_overview_data(building_id) except: - return JsonResponse({'err': 'There are no bills projected for this building'}) + return JsonResponse({'error': 'There are no bills projected for this building'}, status=400) # Get the analysis date from the financing overview model. try: analysis_date = get_analysis_date(building_id) except: - return JsonResponse({'err': 'Please fill in the proforma information form at the top!'}) + return JsonResponse({'error': 'Please fill in the proforma information form at the top!'}, status=400) # Convert the response body to the format bpfin accepts. try: @@ -887,71 +934,65 @@ class IncomeStatementTable(View): int(analysis_date['proforma_start'].year) ) except ValueError as err: - return JsonResponse({'err': err.args[0]}) + return JsonResponse({'error': err.args[0]}, status=400) + # Update the growth rate. GrowthRate.objects.filter(building_id=building_id).delete() GrowthRate.objects.create( building_id=building_id, growth_rate=float("{0:.2f}".format(growth_rate)), ) - # organize_bill_overview takes in the bills overview and analysis date and fills in average values in places - # where there are no values. - bill_overview_organized = organize_bill_overview(bill_overview, analysis_date) - - # income statement table is an object that takes in the income statement input and bill_overview_organized. - income_statement_table = Income_Statement_Table(raw_income_statement_input, bill_overview_organized) - - # income_statement_full is the list of records with historical income statement data. - income_statement_full = income_statement_table.get_hist_table() - - # cagr is compound annual growth rate. - cagr = income_statement_table.get_cagr() - - # project function calculates the income statement data for the next few years based on the growth rate. - income_statement_table.project(growth_rate, analysis_date, bill_overview_organized) - - # result stores the historical income statement data as a list. - result = [income_statement_full[year] for year in income_statement_full] - - # result_dict has the income statement data for the next year. - result_dict = income_statement_table.get_single_year(income_statement_table.hist_end_year + 1) - - # average_dict has the income statement data for the 3 historical years and the next year projected. - average_dict = income_statement_table.get_average() - - # save function stores the data in the database. - self.save(result, building_id) - - # Change the float formatting to have only 2 decimal places. - for record in result: - record['energy_opex'] = float("{0:.2f}".format(record['energy_opex'])) - record['other_utility'] = float("{0:.2f}".format(record['other_utility'])) - record['net_non_energy_opex'] = float("{0:.2f}".format(record['net_non_energy_opex'])) - result_dict['energy_opex'] = float("{0:.2f}".format(result_dict['energy_opex'])) - result_dict['other_utility'] = float("{0:.2f}".format(result_dict['other_utility'])) - result_dict['net_non_energy_opex'] = float("{0:.2f}".format(result_dict['net_non_energy_opex'])) - result_dict['revenue'] = float("{0:.2f}".format(result_dict['revenue'])) - result_dict['utility_expense'] = float("{0:.2f}".format(result_dict['utility_expense'])) - result_dict['non_utility_expense'] = float("{0:.2f}".format(result_dict['non_utility_expense'])) - result_dict['total_opex'] = float("{0:.2f}".format(result_dict['total_opex'])) - result_dict['noi'] = float("{0:.2f}".format(result_dict['noi'])) - - average_dict['electricity_opex'] = float("{0:.2f}".format(average_dict['electricity_opex'])) - average_dict['gas_opex'] = float("{0:.2f}".format(average_dict['gas_opex'])) - average_dict['oil_opex'] = float("{0:.2f}".format(average_dict['oil_opex'])) - average_dict['water_opex'] = float("{0:.2f}".format(average_dict['water_opex'])) - average_dict['energy_opex'] = float("{0:.2f}".format(average_dict['energy_opex'])) - average_dict['other_utility'] = float("{0:.2f}".format(average_dict['other_utility'])) - average_dict['net_non_energy_opex'] = float("{0:.2f}".format(average_dict['net_non_energy_opex'])) - average_dict['revenue'] = float("{0:.2f}".format(average_dict['revenue'])) - average_dict['utility_expense'] = float("{0:.2f}".format(average_dict['utility_expense'])) - average_dict['non_utility_expense'] = float("{0:.2f}".format(average_dict['non_utility_expense'])) - average_dict['total_opex'] = float("{0:.2f}".format(average_dict['total_opex'])) - average_dict['noi'] = float("{0:.2f}".format(average_dict['noi'])) - - cagr = ('= ' + "{0:.2f}".format(cagr * 100) + '%') - return JsonResponse({'instance': result, 'future': result_dict, 'average': average_dict, 'CAGR': cagr}) + # Fetch all the raw bills for the building. + raw_bill = get_raw_bill(building_id) + + # Fetch annual bills i.e. manually input annual charge values. + annual_bills = self.get_annual_bills(building_id) + + # Call bpfin function to obtain the full, one year in future and average income statements and the CAGR value. + result = prior_income_statement_table( + raw_income_statement_input, raw_bill, + annual_bills, analysis_date, + growth_rate) + full_income_statement = result[0] + future_income_statement = result[1] + average_income_statement = result[2] + cagr = result[3] + + # Obtain only the historical income statement from the full statement. Also format it to have only 2 decimal + # places. + historical_income_statement = {} + count = 0 + for year in full_income_statement: + if count >= 3: + break + record = full_income_statement[year] + for key in record: + record[key] = float("{0:.2f}".format(record[key])) + historical_income_statement[year] = record + count += 1 + + # Save the historical values in the database. + self.save(historical_income_statement, building_id) + + # Format future income statement to have only 2 decimal places. + for key in future_income_statement: + future_income_statement[key] = float("{0:.2f}".format(future_income_statement[key])) + + # Format average income statement to have only 2 decimal places. + for key in average_income_statement: + if average_income_statement[key]: + average_income_statement[key] = float("{0:.2f}".format(average_income_statement[key])) + + # Format CAGR to be in percentage and have only 2 decimal places. + cagr = float("{0:.2f}".format(cagr * 100)) + + return JsonResponse({ + 'instance': historical_income_statement, + 'future': future_income_statement, + 'average': average_income_statement, + 'CAGR': cagr, + }) def get(self, request, building_id): """Handle HTTP GET request. @@ -969,7 +1010,6 @@ class IncomeStatementTable(View): objs = self.model.objects.filter(building_id=building_id) instance = {} if objs: - instance['present'] = True result = [] temp_result = {} for obj in objs: