From b8f3e49ff1c5b9312cc06b68a50d1f231e73935c Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Mon, 1 May 2017 09:30:53 -0400 Subject: [PATCH 01/10] Create endpoint preliminary-finance. --- blocnote/apps/preliminaryFinance/__init__.py | 0 blocnote/apps/preliminaryFinance/admin.py | 3 +++ blocnote/apps/preliminaryFinance/apps.py | 5 +++++ blocnote/apps/preliminaryFinance/migrations/__init__.py | 0 blocnote/apps/preliminaryFinance/models.py | 3 +++ blocnote/apps/preliminaryFinance/tests.py | 3 +++ blocnote/apps/preliminaryFinance/urls.py | 7 +++++++ blocnote/apps/preliminaryFinance/views.py | 9 +++++++++ blocnote/settings.py | 1 + blocnote/urls.py | 1 + 10 files changed, 32 insertions(+) create mode 100644 blocnote/apps/preliminaryFinance/__init__.py create mode 100644 blocnote/apps/preliminaryFinance/admin.py create mode 100644 blocnote/apps/preliminaryFinance/apps.py create mode 100644 blocnote/apps/preliminaryFinance/migrations/__init__.py create mode 100644 blocnote/apps/preliminaryFinance/models.py create mode 100644 blocnote/apps/preliminaryFinance/tests.py create mode 100644 blocnote/apps/preliminaryFinance/urls.py create mode 100644 blocnote/apps/preliminaryFinance/views.py diff --git a/blocnote/apps/preliminaryFinance/__init__.py b/blocnote/apps/preliminaryFinance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blocnote/apps/preliminaryFinance/admin.py b/blocnote/apps/preliminaryFinance/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/blocnote/apps/preliminaryFinance/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/blocnote/apps/preliminaryFinance/apps.py b/blocnote/apps/preliminaryFinance/apps.py new file mode 100644 index 0000000..bfb73be --- /dev/null +++ b/blocnote/apps/preliminaryFinance/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PreliminaryfinanceConfig(AppConfig): + name = 'preliminaryFinance' diff --git a/blocnote/apps/preliminaryFinance/migrations/__init__.py b/blocnote/apps/preliminaryFinance/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blocnote/apps/preliminaryFinance/models.py b/blocnote/apps/preliminaryFinance/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/blocnote/apps/preliminaryFinance/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/blocnote/apps/preliminaryFinance/tests.py b/blocnote/apps/preliminaryFinance/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/blocnote/apps/preliminaryFinance/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/blocnote/apps/preliminaryFinance/urls.py b/blocnote/apps/preliminaryFinance/urls.py new file mode 100644 index 0000000..ef315d6 --- /dev/null +++ b/blocnote/apps/preliminaryFinance/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^$', views.Index.as_view(), name='index'), +] diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py new file mode 100644 index 0000000..54c8f57 --- /dev/null +++ b/blocnote/apps/preliminaryFinance/views.py @@ -0,0 +1,9 @@ +from django.shortcuts import render +from django.views import View +from django.http import HttpResponse + + +class Index(View): + + def get(self, request, building_id): + return HttpResponse('You have reached preliminary finance') diff --git a/blocnote/settings.py b/blocnote/settings.py index cce5531..ad0f38b 100644 --- a/blocnote/settings.py +++ b/blocnote/settings.py @@ -42,6 +42,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'blocnote.apps.financialInputs', + 'blocnote.apps.preliminaryFinance', ] MIDDLEWARE = [ diff --git a/blocnote/urls.py b/blocnote/urls.py index 69e442f..de2dc38 100644 --- a/blocnote/urls.py +++ b/blocnote/urls.py @@ -19,5 +19,6 @@ from django.contrib import admin urlpatterns = [ url('', admin.site.urls), url(r'^building/(?P[0-9]+)/financial-inputs/', include('blocnote.apps.financialInputs.urls')), + url(r'^building/(?P[0-9]+)/preliminary-finance/', include('blocnote.apps.preliminaryFinance.urls')), url(r'^admin/', admin.site.urls), ] -- GitLab From 0b81b1491402b18391caaa9f8113260526a5677b Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Mon, 1 May 2017 14:12:05 -0400 Subject: [PATCH 02/10] Create index page for preliminary finance. Make scenario name an input field to take. Add tab button functionality yet to be implemented. Show cash estimation table. --- blocnote/apps/preliminaryFinance/models.py | 31 +++++++++++++- .../templates/preliminaryFinance/index.html | 24 +++++++++++ .../preliminaryFinance/scenario.html | 42 +++++++++++++++++++ blocnote/apps/preliminaryFinance/views.py | 23 +++++++++- 4 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html create mode 100644 blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html diff --git a/blocnote/apps/preliminaryFinance/models.py b/blocnote/apps/preliminaryFinance/models.py index 71a8362..c07de31 100644 --- a/blocnote/apps/preliminaryFinance/models.py +++ b/blocnote/apps/preliminaryFinance/models.py @@ -1,3 +1,32 @@ from django.db import models -# Create your models here. + +class Scenario(models.Model): + """Store scenario for preliminary finance analysis. + + Model to store scenario information for a particular building. All financial analysis will be performed based + on what is obtained from financial-inputs and following data. + """ + + building_id = models.IntegerField() + name = models.CharField(max_length=150) + analysis_level = models.CharField(max_length=11) + estimated_cost = models.DecimalField(max_digits=10, decimal_places=2) + overall_savings = models.DecimalField(max_digits=10, decimal_places=2) + first_year_savings = models.DecimalField(max_digits=10, decimal_places=2) + simple_payback = models.DecimalField(max_digits=4, decimal_places=2) + self_finance_amount = models.DecimalField(max_digits=10, decimal_places=2) + min_savings_dscr = models.DecimalField(max_digits=5, decimal_places=2) + min_net_operating_income_dscr = models.DecimalField(max_digits=5, decimal_places=2) + min_cash_dscr = models.DecimalField(max_digits=5, decimal_places=2) + + +class CostEstimation(models.Model): + """Store an item and it's cost. + + Model to store the items and their costs. This data will be used to estimate total cost in a particular scenario. + """ + + scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE, blank=True, null=True) + item = models.CharField(max_length=150) + cost = models.DecimalField(max_digits=10, decimal_places=2) diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html new file mode 100644 index 0000000..7938ec8 --- /dev/null +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block content %} +
+
+

+ Preliminary Finance +

+
+ {{ building.address }} +
+
+
+
+ {% include "preliminaryFinance/scenario.html" %} +
+{% endblock %} +{% block scripts %} + + + + +{% endblock %} + diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html new file mode 100644 index 0000000..68d689a --- /dev/null +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html @@ -0,0 +1,42 @@ +
+
+ +
+
+ +
+
+
+
+
+

+ Cost Estimation +

+ + + + + +
+ +
+
+

+ Saving Estimation +

+ + + + + +
+ +
+
+
diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py index 54c8f57..af11ff1 100644 --- a/blocnote/apps/preliminaryFinance/views.py +++ b/blocnote/apps/preliminaryFinance/views.py @@ -1,9 +1,30 @@ from django.shortcuts import render from django.views import View from django.http import HttpResponse +from django.db import connections class Index(View): + def get_building_data(self, building_id): + """Return building data given it's id. + + Connect to the building database and retrieves the building data. + + Args: + building_id: id of the building_id. + + Returns: + building: A dictionary containing all the relevant building data. + """ + cursor = connections['building_db'].cursor() + command = 'SELECT * FROM public.get_building(in_building_id := {})' + cursor.execute(command.format(building_id)) + columns = [col[0] for col in cursor.description] + row = cursor.fetchone() + building = dict(zip(columns, row)) + return building def get(self, request, building_id): - return HttpResponse('You have reached preliminary finance') + building = self.get_building_data(building_id) + context = {'building': building} + return render(request, 'preliminaryFinance/index.html', context=context) -- GitLab From 0bcb380f51c351388aebf769c47aad988d8c7bb2 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Mon, 1 May 2017 14:58:42 -0400 Subject: [PATCH 03/10] Add submit scenario button. Show savings estimation table. Implementation not done yet. --- .../templates/preliminaryFinance/scenario.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html index 68d689a..e1671b2 100644 --- a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html @@ -36,7 +36,9 @@ - + +
+
-- GitLab From a8c38e811bbbb803f49e9254d30d87e66ae31ea0 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Tue, 2 May 2017 15:54:59 -0400 Subject: [PATCH 04/10] Remove unnecessary ids. --- .../templates/preliminaryFinance/scenario.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html index e1671b2..bfff591 100644 --- a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html @@ -19,9 +19,9 @@ Cost Estimation - + - +
@@ -31,9 +31,9 @@ Saving Estimation - + - +
-- GitLab From 4937f2b72912779163d7995e9450b98e33027e62 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Tue, 2 May 2017 18:42:16 -0400 Subject: [PATCH 05/10] Front end UI to show the cost and savings estimation for a scenario. Submit scenario button gathers the scenario name, cost estimation table and savings estimation table data and makes a put request with that information. --- blocnote/apps/preliminaryFinance/models.py | 10 ++++++++++ .../templates/preliminaryFinance/scenario.html | 2 +- blocnote/apps/preliminaryFinance/urls.py | 1 + blocnote/apps/preliminaryFinance/views.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/blocnote/apps/preliminaryFinance/models.py b/blocnote/apps/preliminaryFinance/models.py index c07de31..96b6336 100644 --- a/blocnote/apps/preliminaryFinance/models.py +++ b/blocnote/apps/preliminaryFinance/models.py @@ -30,3 +30,13 @@ class CostEstimation(models.Model): scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE, blank=True, null=True) item = models.CharField(max_length=150) cost = models.DecimalField(max_digits=10, decimal_places=2) + + +class SavingsEstimation(models.Model): + """Store the utility savings estimation.""" + + scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE, blank=True, null=True) + utility_type = models.CharField(max_length=11) + estimated_savings = models.DecimalField(max_digits=4, decimal_places=4) + used_before_retrofit = models.BooleanField() + used_after_retrofit = models.BooleanField() diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html index bfff591..cfc163c 100644 --- a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html @@ -3,7 +3,7 @@ diff --git a/blocnote/apps/preliminaryFinance/urls.py b/blocnote/apps/preliminaryFinance/urls.py index ef315d6..6bd796e 100644 --- a/blocnote/apps/preliminaryFinance/urls.py +++ b/blocnote/apps/preliminaryFinance/urls.py @@ -4,4 +4,5 @@ from . import views urlpatterns = [ url(r'^$', views.Index.as_view(), name='index'), + url(r'^scenario/$', views.Scenarios.as_view(), name='scenario'), ] diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py index af11ff1..be42c17 100644 --- a/blocnote/apps/preliminaryFinance/views.py +++ b/blocnote/apps/preliminaryFinance/views.py @@ -1,8 +1,12 @@ +import json + from django.shortcuts import render from django.views import View from django.http import HttpResponse from django.db import connections +from .models import Scenario, CostEstimation, SavingsEstimation + class Index(View): def get_building_data(self, building_id): @@ -28,3 +32,9 @@ class Index(View): building = self.get_building_data(building_id) context = {'building': building} return render(request, 'preliminaryFinance/index.html', context=context) + + +class Scenarios(View): + def put(self, request, building_id): + put = json.loads(request.body.decode()) + print(put) -- GitLab From e3c4028c7025bb3884b72f754f786f90f5f3c0f9 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Wed, 3 May 2017 10:53:20 -0400 Subject: [PATCH 06/10] js file for preliminary finance endpoint. --- .../static/preliminaryFinance/scripts/app.js | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js diff --git a/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js new file mode 100644 index 0000000..1ac9f7f --- /dev/null +++ b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js @@ -0,0 +1,250 @@ +var tabCounter = 1; +costEstimationTableHead(); +costEstimationTableBody(); +savingEstimationTableHead(); +savingEstimationTableBody(); +activeTab(); + +function activeTab() { + var tab = document.querySelector("#div.tab-pane.fade.show"); + console.log(tab); +} + +function getScenarioName() { + var docs = document.querySelector("a.nav-link.active input"); + return docs.value; +} + +function addTab() { + tabCounter += 1; + var list = document.querySelector('#scenarios-tab-list'); + console.log(list); + list.innerHTML += ` + + `; + var content = document.querySelector('#tab-content'); + console.log(content); + content.innerHTML += ` +
+
+

+ Cost Estimation for Scenario-${tabCounter} +

+ + + + + +
+ +
+
+ `; + costEstimationTableHead(); + costEstimationTableBody(); + return false; +} + +function costEstimationTableHead() { + head = document.querySelector('#cost-estimation-table thead'); + head.innerHTML = ` + + Item + Estimated Cost + Option + + `; +} + +function costEstimationTableBody() { + body = document.querySelector('#cost-estimation-table tbody'); + var rowCount = body.rows.length; + var row = body.insertRow(rowCount); + var cell = row.insertCell(0); + cell.innerHTML = ``; + cell = row.insertCell(1); + cell.innerHTML = ``; + cell = row.insertCell(2); + cell.innerHTML = ``; + return false; +} + +function deleteCostEstimationRow(rowIndex) { + table = document.querySelector('#cost-estimation-table tbody'); + body.deleteRow(rowIndex-1); + return false; +} + +function savingEstimationTableHead() { + head = document.querySelector('#saving-estimation-table thead'); + head.innerHTML = ` + + Utility + Estimated Savings + Used Before Retrofit + Used After Retrofit + + `; +} + +function savingEstimationTableBody() { + body = document.querySelector('#saving-estimation-table tbody'); + var rowCount = body.rows.length; + var row = body.insertRow(rowCount); + var cell = row.insertCell(0); + cell.innerHTML = `Electricity`; + cell = row.insertCell(1); + cell.innerHTML = ` + + `; + cell = row.insertCell(2); + cell.innerHTML = ` + + `; + cell = row.insertCell(3); + cell.innerHTML = ` + + `; + + rowCount += 1; + row = body.insertRow(rowCount); + cell = row.insertCell(0); + cell.innerHTML = `Gas`; + cell = row.insertCell(1); + cell.innerHTML = ` + + `; + cell = row.insertCell(2); + cell.innerHTML = ` + + `; + cell = row.insertCell(3); + cell.innerHTML = ` + + `; + + rowCount += 1; + row = body.insertRow(rowCount); + cell = row.insertCell(0); + cell.innerHTML = `Oil`; + cell = row.insertCell(1); + cell.innerHTML = ` + + `; + cell = row.insertCell(2); + cell.innerHTML = ` + + `; + cell = row.insertCell(3); + cell.innerHTML = ` + + `; + + rowCount += 1; + row = body.insertRow(rowCount); + cell = row.insertCell(0); + cell.innerHTML = `Water`; + cell = row.insertCell(1); + cell.innerHTML = ` + + `; + cell = row.insertCell(2); + cell.innerHTML = ` + + `; + cell = row.insertCell(3); + cell.innerHTML = ` + + `; + return false; +} + +function deleteSavingEstimationRow(rowIndex) { + table = document.querySelector('#cost-estimation-table body'); + body.deleteRow(rowIndex-1); + return false; +} + +function submitScenario() { + var scenarioName = getScenarioName(); + var savingEstimationBody = document.querySelector('#saving-estimation-table tbody'); + var rowCount = savingEstimationBody.rows.length; + savings = []; + for (var rowIndex=0; rowIndex < rowCount; rowIndex++){ + beforeRetrofit = false; + afterRetrofit = false; + record = {}; + checkBoxes = savingEstimationBody.rows[rowIndex].querySelectorAll('input[type="checkbox"]:checked'); + for (index = 0; index < checkBoxes.length; index++) { + checkbox = checkBoxes[index]; + if (checkbox.name === 'Used before retrofit') { + beforeRetrofit = true; + } + if (checkbox.name === 'Used after retrofit') { + afterRetrofit = true; + } + } + + record = { + 'utility_type': savingEstimationBody.rows[rowIndex].cells[0].innerHTML, + 'savings_estimate': savingEstimationBody.rows[rowIndex].cells[1].children[0].value, + 'used_before_retrofit': beforeRetrofit, + 'used_after_retrofit': afterRetrofit, + } + savings.push(record); + } + + var costEstimationBody = document.querySelector('#cost-estimation-table tbody'); + rowCount = costEstimationBody.rows.length; + const ITEM_INDEX = 0; + const COST_INDEX = 1; + var cost = []; + for (rowIndex = 0; rowIndex < rowCount; rowIndex++) { + record = { + 'item': costEstimationBody.rows[rowIndex].cells[ITEM_INDEX].children[0].value, + 'cost': costEstimationBody.rows[rowIndex].cells[COST_INDEX].children[0].value, + }; + cost.push(record); + } + result = { + 'scenario': scenarioName, + 'savings': savings, + 'cost': cost, + } + request(`scenario/`, { + method: 'PUT', + credentials: 'same-origin', + body: JSON.stringify(result), + headers: new Headers({ + 'Content-Type': 'application/json', + 'X-CSRFToken': Cookies.get('csrftoken') + }) + }).then(res => { + console.log(res) + }) + return false; +} + +function dostuff() { + return false; +} -- GitLab From 840b9cb60c51fe6b343811b291f1a782d9afe1eb Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 4 May 2017 16:24:41 -0400 Subject: [PATCH 07/10] Fix financial-inputs frontend to clear loan options only if fund is changed. Create savings and cost estimation frontend. When save scenario is hit, generate stacked bar graph. --- .../static/financialInputs/scripts/app.js | 30 ++++-- .../static/preliminaryFinance/scripts/app.js | 100 ++++++++---------- .../templates/preliminaryFinance/index.html | 1 + .../preliminaryFinance/scenario.html | 83 ++++++++------- blocnote/apps/preliminaryFinance/views.py | 66 +++++++++++- 5 files changed, 176 insertions(+), 104 deletions(-) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index 964c36c..e5e080d 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -20,6 +20,7 @@ getLoanOptionsTable(); * This message is displayed when the mouse is over the fund select box. */ var fund = document.querySelector('#id_fund'); +var didFundChange = false; fund.onmouseenter = function() { var errorDiv = document.querySelector('#show-error'); errorDiv.innerHTML = `Changing fund will affect loan options`; @@ -30,6 +31,10 @@ fund.onmouseleave = function() { errorDiv.innerHTML = ""; } +fund.onchange = function() { + didFundChange = true; +} + /** * Handle submition of the header form. Validate commissioning date is greater * than construction start date. Create result dictionary containing the form @@ -78,17 +83,20 @@ function billProjectionDatesForm(form) { * This ensures that the loan options table is up to date with the latest fund. Clear the loan options table * and reload with new fund's default loan options. */ - request(`loan-options/?loans=delete-loan-options`, { - method: 'GET', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - }).then(res => { - var tableBody = document.querySelector('#loan-options-table tbody'); - tableBody.innerHTML = ``; - getLoanOptionsTable(); - }); + if (didFundChange) { + didFundChange = false; + request(`loan-options/?loans=delete-loan-options`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + }).then(res => { + var tableBody = document.querySelector('#loan-options-table tbody'); + tableBody.innerHTML = ``; + getLoanOptionsTable(); + }); + } }); } return false; diff --git a/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js index 1ac9f7f..b2237a6 100644 --- a/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js +++ b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js @@ -3,58 +3,18 @@ costEstimationTableHead(); costEstimationTableBody(); savingEstimationTableHead(); savingEstimationTableBody(); -activeTab(); - -function activeTab() { - var tab = document.querySelector("#div.tab-pane.fade.show"); - console.log(tab); -} function getScenarioName() { var docs = document.querySelector("a.nav-link.active input"); return docs.value; } -function addTab() { - tabCounter += 1; - var list = document.querySelector('#scenarios-tab-list'); - console.log(list); - list.innerHTML += ` - - `; - var content = document.querySelector('#tab-content'); - console.log(content); - content.innerHTML += ` -
-
-

- Cost Estimation for Scenario-${tabCounter} -

- - - - - -
- -
-
- `; - costEstimationTableHead(); - costEstimationTableBody(); - return false; -} - function costEstimationTableHead() { head = document.querySelector('#cost-estimation-table thead'); head.innerHTML = ` Item - Estimated Cost + Estimated Cost (in $) Option `; @@ -65,9 +25,9 @@ function costEstimationTableBody() { var rowCount = body.rows.length; var row = body.insertRow(rowCount); var cell = row.insertCell(0); - cell.innerHTML = ``; + cell.innerHTML = ``; cell = row.insertCell(1); - cell.innerHTML = ``; + cell.innerHTML = `$`; cell = row.insertCell(2); cell.innerHTML = ``; return false; @@ -84,7 +44,7 @@ function savingEstimationTableHead() { head.innerHTML = ` Utility - Estimated Savings + Estimated Savings (in %) Used Before Retrofit Used After Retrofit @@ -99,7 +59,7 @@ function savingEstimationTableBody() { cell.innerHTML = `Electricity`; cell = row.insertCell(1); cell.innerHTML = ` - + % `; cell = row.insertCell(2); cell.innerHTML = ` @@ -120,7 +80,7 @@ function savingEstimationTableBody() { cell.innerHTML = `Gas`; cell = row.insertCell(1); cell.innerHTML = ` - + % `; cell = row.insertCell(2); cell.innerHTML = ` @@ -141,7 +101,7 @@ function savingEstimationTableBody() { cell.innerHTML = `Oil`; cell = row.insertCell(1); cell.innerHTML = ` - + % `; cell = row.insertCell(2); cell.innerHTML = ` @@ -162,7 +122,7 @@ function savingEstimationTableBody() { cell.innerHTML = `Water`; cell = row.insertCell(1); cell.innerHTML = ` - + % `; cell = row.insertCell(2); cell.innerHTML = ` @@ -204,6 +164,10 @@ function submitScenario() { afterRetrofit = true; } } + if (Number(savingEstimationBody.rows[rowIndex].cells[1].children[0].value) > 100) { + alert(`Please enter a valid savings percentage for ${savingEstimationBody.rows[rowIndex].cells[0].innerHTML}`); + return false; + } record = { 'utility_type': savingEstimationBody.rows[rowIndex].cells[0].innerHTML, @@ -240,11 +204,41 @@ function submitScenario() { 'X-CSRFToken': Cookies.get('csrftoken') }) }).then(res => { - console.log(res) - }) - return false; -} + var config = { + type: 'bar', + data: { + labels: res.payload.year_list, + datasets: [{ + type: 'bar', + label: 'Energy Expense', + backgroundColor: "#66B2FF", + data: res.payload.energy_expense_list, + }, { + type: 'bar', + label: 'Total Loan', + backgroundColor: "#E0E0E0", + data: res.payload.total_loan_list, + }, { + type: 'bar', + label: 'Net Savings', + backgroundColor: "#99FF99", + data: res.payload.net_savings_list, + }] + }, + options: { + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + } + }; -function dostuff() { + var ctx = document.getElementById("myChart").getContext("2d"); + new Chart(ctx, config); + }) return false; } diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html index 7938ec8..787a01a 100644 --- a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/index.html @@ -19,6 +19,7 @@ + {% endblock %} diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html index cfc163c..bd84e29 100644 --- a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html @@ -1,44 +1,49 @@ -
-
- -
-
- -
-
-
-
-
-

- Cost Estimation -

- - - - - -
- +
+
+
+
-
-

- Saving Estimation -

- - - - - -
+
+
+
+
+
+
+

+ Cost Estimation +

+ + + + + +
+ +
+
+

+ Saving Estimation +

+ + + + + +
+
- + +
-
+
+
+ +
diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py index be42c17..f867898 100644 --- a/blocnote/apps/preliminaryFinance/views.py +++ b/blocnote/apps/preliminaryFinance/views.py @@ -2,8 +2,11 @@ import json from django.shortcuts import render from django.views import View -from django.http import HttpResponse +from django.http import JsonResponse from django.db import connections +import datetime + +from bpfin.financials.borrower_schedule import packaging_data from .models import Scenario, CostEstimation, SavingsEstimation @@ -38,3 +41,64 @@ class Scenarios(View): def put(self, request, building_id): put = json.loads(request.body.decode()) print(put) + total_loan = { + 2014: 0, + 2015: 0, + 2016: 0, + 2017: 21400, + 2018: 21400, + 2019: 21400, + 2020: 21400, + 2021: 21400, + 2022: 21400, + 2023: 21400, + 2024: 10700, + } + + net_savings = { + 2014: 0, + 2015: 0, + 2016: 0, + 2017: 5350, + 2018: 5350, + 2019: 5350, + 2020: 5350, + 2021: 5350, + 2022: 5350, + 2023: 5350, + 2024: 16050, + } + + energy_expense = { + 2014: 145450, + 2015: 146000, + 2016: 148320, + 2017: 150200, + 2018: 152000, + 2019: 153300, + 2020: 154400, + 2021: 155600, + 2022: 157200, + 2023: 159900, + 2024: 161200, + } + analysis_date = { + 'date': datetime.date(2014, 1, 1), + 'duration': 10, + } + result = packaging_data(analysis_date, energy_expense, total_loan, net_savings) + year_list = [] + energy_expense_list = [] + total_loan_list = [] + net_savings_list = [] + for year in result: + year_list.append(year) + energy_expense_list.append(result[year]['energy_expenses']) + total_loan_list.append(result[year]['total_loan']) + net_savings_list.append(result[year]['net_savings']) + return JsonResponse({ + 'year_list': year_list, + 'energy_expense_list': energy_expense_list, + 'total_loan_list': total_loan_list, + 'net_savings_list': net_savings_list, + }) -- GitLab From 3576e72b8fbf53c4fd29163922b8745f22271505 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 4 May 2017 16:44:57 -0400 Subject: [PATCH 08/10] Fix order of x-axis in graph. --- blocnote/apps/preliminaryFinance/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py index f867898..8882f1e 100644 --- a/blocnote/apps/preliminaryFinance/views.py +++ b/blocnote/apps/preliminaryFinance/views.py @@ -91,7 +91,7 @@ class Scenarios(View): energy_expense_list = [] total_loan_list = [] net_savings_list = [] - for year in result: + for year in sorted(result): year_list.append(year) energy_expense_list.append(result[year]['energy_expenses']) total_loan_list.append(result[year]['total_loan']) -- GitLab From 4edc395e670322aea9da550bfccf621603901ee7 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Thu, 4 May 2017 16:58:07 -0400 Subject: [PATCH 09/10] Adjust graph to show more years. --- blocnote/apps/preliminaryFinance/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py index 8882f1e..fd3fb8d 100644 --- a/blocnote/apps/preliminaryFinance/views.py +++ b/blocnote/apps/preliminaryFinance/views.py @@ -53,6 +53,7 @@ class Scenarios(View): 2022: 21400, 2023: 21400, 2024: 10700, + 2025: 0, } net_savings = { @@ -67,6 +68,7 @@ class Scenarios(View): 2022: 5350, 2023: 5350, 2024: 16050, + 2025: 26000, } energy_expense = { @@ -81,10 +83,11 @@ class Scenarios(View): 2022: 157200, 2023: 159900, 2024: 161200, + 2025: 164100, } analysis_date = { 'date': datetime.date(2014, 1, 1), - 'duration': 10, + 'duration': 12, } result = packaging_data(analysis_date, energy_expense, total_loan, net_savings) year_list = [] -- GitLab From bd86d478318def4517a44ac904825d66b5d3362d Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Tue, 9 May 2017 11:53:28 -0400 Subject: [PATCH 10/10] Add growth rate model. Store growth rate when selected. Implement Cost and Savings estimate table. Send cost and savings estimate and scenario name to backend and perform preliminary analysis to display Project Economics table and Energy Expense Savings graph. --- blocnote/apps/__init__.py | 0 .../migrations/0004_growthrate.py | 23 + blocnote/apps/financialInputs/models.py | 7 + .../static/financialInputs/scripts/app.js | 2 +- blocnote/apps/financialInputs/views.py | 8 +- .../migrations/0001_initial.py | 62 +++ .../static/preliminaryFinance/scripts/app.js | 85 +++- .../preliminaryFinance/scenario.html | 12 + blocnote/apps/preliminaryFinance/views.py | 435 +++++++++++++++--- 9 files changed, 566 insertions(+), 68 deletions(-) create mode 100644 blocnote/apps/__init__.py create mode 100644 blocnote/apps/financialInputs/migrations/0004_growthrate.py create mode 100644 blocnote/apps/preliminaryFinance/migrations/0001_initial.py diff --git a/blocnote/apps/__init__.py b/blocnote/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blocnote/apps/financialInputs/migrations/0004_growthrate.py b/blocnote/apps/financialInputs/migrations/0004_growthrate.py new file mode 100644 index 0000000..4caeb6d --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0004_growthrate.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-05-04 22:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0003_auto_20170503_2200'), + ] + + operations = [ + migrations.CreateModel( + name='GrowthRate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('growth_rate', models.DecimalField(decimal_places=2, max_digits=6)), + ], + ), + ] diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 36dbbdb..78a76d8 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -140,3 +140,10 @@ class LoanOptions(models.Model): interest_rate = models.DecimalField(max_digits=5, decimal_places=3) duration = models.DecimalField(max_digits=3, decimal_places=0) max_loan_amount = models.DecimalField(max_digits=10, decimal_places=2) + + +class GrowthRate(models.Model): + """Store growth rate for a building.""" + + building_id = models.IntegerField() + growth_rate = models.DecimalField(max_digits=6, decimal_places=2) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index e5e080d..2b2d615 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -1172,7 +1172,7 @@ function addLoanOptionsRow(lenderList, lender, interestRate, duration, maxLoanAm cell = row.insertCell(1); cell.innerHTML = `${lenderOptions(lenderList, lender)}`; cell = row.insertCell(2); - cell.innerHTML = `%`; + cell.innerHTML = `%`; cell = row.insertCell(3); cell.innerHTML = `months`; cell = row.insertCell(4); diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index 905c7b2..e7102f5 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -9,7 +9,7 @@ from bpfin.utilbills.bill_backend_call import bill_prior_proj_rough_annual from bpfin.financials.financial_lib import organize_bill_overview from bpfin.financials.financial_lib import Income_Statement_Table -from .models import Fund, FinancingOverview, Bills, BillsOverview, CustomerPreference, EstimationAlgorithm, Liabilities, CashBalance, IncomeStatement, LoanOptions, DefaultLoan, Lender +from .models import Fund, FinancingOverview, Bills, BillsOverview, CustomerPreference, EstimationAlgorithm, Liabilities, CashBalance, IncomeStatement, LoanOptions, DefaultLoan, Lender, GrowthRate from .forms import BlocNoteHeaderForm @@ -855,6 +855,11 @@ class IncomeStatementTable(View): # Convert the response body to the format bpfin accepts. raw_income_statement_input, growth_rate = self.convert_response_format(put) + GrowthRate.objects.filter(building_id=building_id).delete() + GrowthRate.objects.create( + building_id=building_id, + growth_rate=float("{0:.2f}".format(growth_rate)), + ) # Get bills overview data from the database. bill_overview = self.get_bills_overview_data(building_id) @@ -865,7 +870,6 @@ class IncomeStatementTable(View): 'proforma_start': financing_overview_obj.pro_forma_start_date, 'proforma_duration': financing_overview_obj.pro_forma_duration, } - # 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) diff --git a/blocnote/apps/preliminaryFinance/migrations/0001_initial.py b/blocnote/apps/preliminaryFinance/migrations/0001_initial.py new file mode 100644 index 0000000..54013bc --- /dev/null +++ b/blocnote/apps/preliminaryFinance/migrations/0001_initial.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-05-04 21:53 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CostEstimation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('item', models.CharField(max_length=150)), + ('cost', models.DecimalField(decimal_places=2, max_digits=10)), + ], + ), + migrations.CreateModel( + name='SavingsEstimation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('utility_type', models.CharField(max_length=11)), + ('estimated_savings', models.DecimalField(decimal_places=4, max_digits=4)), + ('used_before_retrofit', models.BooleanField()), + ('used_after_retrofit', models.BooleanField()), + ], + ), + migrations.CreateModel( + name='Scenario', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('name', models.CharField(max_length=150)), + ('analysis_level', models.CharField(max_length=11)), + ('estimated_cost', models.DecimalField(decimal_places=2, max_digits=10)), + ('overall_savings', models.DecimalField(decimal_places=2, max_digits=10)), + ('first_year_savings', models.DecimalField(decimal_places=2, max_digits=10)), + ('simple_payback', models.DecimalField(decimal_places=2, max_digits=4)), + ('self_finance_amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('min_savings_dscr', models.DecimalField(decimal_places=2, max_digits=5)), + ('min_net_operating_income_dscr', models.DecimalField(decimal_places=2, max_digits=5)), + ('min_cash_dscr', models.DecimalField(decimal_places=2, max_digits=5)), + ], + ), + migrations.AddField( + model_name='savingsestimation', + name='scenario', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='preliminaryFinance.Scenario'), + ), + migrations.AddField( + model_name='costestimation', + name='scenario', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='preliminaryFinance.Scenario'), + ), + ] diff --git a/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js index b2237a6..3028a45 100644 --- a/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js +++ b/blocnote/apps/preliminaryFinance/static/preliminaryFinance/scripts/app.js @@ -4,11 +4,17 @@ costEstimationTableBody(); savingEstimationTableHead(); savingEstimationTableBody(); +/** + * Return the Scenario name. + */ function getScenarioName() { var docs = document.querySelector("a.nav-link.active input"); return docs.value; } +/** + * Add column headings to the Cost Estimation table. + */ function costEstimationTableHead() { head = document.querySelector('#cost-estimation-table thead'); head.innerHTML = ` @@ -20,6 +26,9 @@ function costEstimationTableHead() { `; } +/** + * Load the Cost Estimation table body. + */ function costEstimationTableBody() { body = document.querySelector('#cost-estimation-table tbody'); var rowCount = body.rows.length; @@ -33,12 +42,18 @@ function costEstimationTableBody() { return false; } +/** + * Delete a row in Cost Estimation table. + */ function deleteCostEstimationRow(rowIndex) { table = document.querySelector('#cost-estimation-table tbody'); body.deleteRow(rowIndex-1); return false; } +/** + * Display column headings in Savings Estimation Table. + */ function savingEstimationTableHead() { head = document.querySelector('#saving-estimation-table thead'); head.innerHTML = ` @@ -51,6 +66,9 @@ function savingEstimationTableHead() { `; } +/** + * Load Savings Estimation Table body. + */ function savingEstimationTableBody() { body = document.querySelector('#saving-estimation-table tbody'); var rowCount = body.rows.length; @@ -139,16 +157,16 @@ function savingEstimationTableBody() { return false; } -function deleteSavingEstimationRow(rowIndex) { - table = document.querySelector('#cost-estimation-table body'); - body.deleteRow(rowIndex-1); - return false; -} - +/** + * Submit the Cost and Saving Estimation. Send information to the backend and this triggers the preliminary analysis. + * Upon getting response, display Project Economics table and Energy Expense Savings Projection graph. + */ function submitScenario() { var scenarioName = getScenarioName(); var savingEstimationBody = document.querySelector('#saving-estimation-table tbody'); var rowCount = savingEstimationBody.rows.length; + + // Package savings estimate into a list of dictionaries. savings = []; for (var rowIndex=0; rowIndex < rowCount; rowIndex++){ beforeRetrofit = false; @@ -182,6 +200,8 @@ function submitScenario() { rowCount = costEstimationBody.rows.length; const ITEM_INDEX = 0; const COST_INDEX = 1; + + // Package cost estimation into a list of dictionaries. var cost = []; for (rowIndex = 0; rowIndex < rowCount; rowIndex++) { record = { @@ -204,7 +224,47 @@ function submitScenario() { 'X-CSRFToken': Cookies.get('csrftoken') }) }).then(res => { - var config = { + loadProjectEconomicsTable(res) + loadGraph(res); + }); + return false; +} + +/** + * Take the response as argument and display the project economics table. This is the summary of the priliminary + * analysis that gives vital information about the financial health of the building. + */ +function loadProjectEconomicsTable(res) { + var doc = document.querySelector('#project-economics'); + var title = document.querySelector('#project-economics-title'); + title.innerHTML = `Project Economics` + var economicsOverview = res.payload.economics_overview; + var body = document.querySelector('#project-economics-table tbody'); + addPERow(body, economicsOverview); +} + +/** + * Add rows to the project economics table. + */ +function addPERow(body, economicsOverview) { + body.innerHTML = ``; + for (var key in economicsOverview){ + rowCount = body.rows.length; + row = body.insertRow(rowCount); + cell = row.insertCell(0); + cell.innerHTML = `${key}`; + cell = row.insertCell(1); + cell.innerHTML = `${economicsOverview[key]}`; + } +} + +/** + * Load the Energy Expense Savings Projection stacked bar graph. + */ +function loadGraph(res) { + var graph = document.querySelector('#myChart'); + graph.innerHTML = ``; + var config = { type: 'bar', data: { labels: res.payload.year_list, @@ -236,9 +296,10 @@ function submitScenario() { } } }; - - var ctx = document.getElementById("myChart").getContext("2d"); - new Chart(ctx, config); - }) - return false; + var doc = document.querySelector('#savings-schedule'); + var title = document.querySelector('#savings-schedule-title'); + title.innerHTML = 'Energy Expense Savings Projection'; + var ctx = document.getElementById("myChart").getContext("2d"); + new Chart(ctx, config); } + diff --git a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html index bd84e29..06e429c 100644 --- a/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html +++ b/blocnote/apps/preliminaryFinance/templates/preliminaryFinance/scenario.html @@ -44,6 +44,18 @@
+
+

+

+ + + + + +
+
+

+

diff --git a/blocnote/apps/preliminaryFinance/views.py b/blocnote/apps/preliminaryFinance/views.py index fd3fb8d..78560ec 100644 --- a/blocnote/apps/preliminaryFinance/views.py +++ b/blocnote/apps/preliminaryFinance/views.py @@ -6,12 +6,22 @@ from django.http import JsonResponse from django.db import connections import datetime +from bpfin.financials.financial_lib import organize_bill_overview from bpfin.financials.borrower_schedule import packaging_data +from bpfin.financials.financial_lib import Income_Statement_Table +from bpfin.financials.cash_balance import cash_balance +from bpfin.financials.liability import final_liability_dict +from bpfin.financials.financial_lib import Balance_Sheet_Table +from bpfin.financials.scenario import Scenario as ScenarioClass from .models import Scenario, CostEstimation, SavingsEstimation +from blocnote.apps.financialInputs.models import GrowthRate, IncomeStatement, BillsOverview, FinancingOverview, CashBalance, Liabilities, LoanOptions, CustomerPreference, Bills +from bpfin.utilbills.bill_backend_call import prior_proj_rough_month class Index(View): + """Render the view when you hit preliminary-finance endpoint.""" + def get_building_data(self, building_id): """Return building data given it's id. @@ -32,76 +42,395 @@ class Index(View): return building def get(self, request, building_id): + """Implement HTTP GET request. + + Fetch building data from the building database. Pass this information to the frontend. + + Args: + request: HTTP GET request. + building_id: id of the building. + + Returns: + render: Renders index.html page with building data. + """ building = self.get_building_data(building_id) context = {'building': building} return render(request, 'preliminaryFinance/index.html', context=context) class Scenarios(View): - def put(self, request, building_id): - put = json.loads(request.body.decode()) - print(put) - total_loan = { - 2014: 0, - 2015: 0, - 2016: 0, - 2017: 21400, - 2018: 21400, - 2019: 21400, - 2020: 21400, - 2021: 21400, - 2022: 21400, - 2023: 21400, - 2024: 10700, - 2025: 0, - } + """Perform preliminary analysis for a scenario. - net_savings = { - 2014: 0, - 2015: 0, - 2016: 0, - 2017: 5350, - 2018: 5350, - 2019: 5350, - 2020: 5350, - 2021: 5350, - 2022: 5350, - 2023: 5350, - 2024: 16050, - 2025: 26000, - } + Get Cost and Savings estimates from the frontend. Fetch all relevant data from the database. Make function calls + to bpfin to calculate all the outputs to be displayed and pass the information to the frontend. + """ - energy_expense = { - 2014: 145450, - 2015: 146000, - 2016: 148320, - 2017: 150200, - 2018: 152000, - 2019: 153300, - 2020: 154400, - 2021: 155600, - 2022: 157200, - 2023: 159900, - 2024: 161200, - 2025: 164100, - } + def get_liability(self, building_id, analysis_date): + """Obtain liability information. + + Fetch the records containing the liability information from the database. + + Args: + building_id: id of the building. + analysis_date: Dictionary containing: + proforma_start with value Start of projection. + proforma_duration with value Total duration of projection. + + Returns: + liability_dictionary: Dictionary containing: + keys: + debt_id- id of the debt. + value- dictionary containing: + keys: + 1. lender with value as lender for the liability/mortgage. + 2. date with value when the mortgage/liabiity statment was + requested. + 3. remaining_term with value the number of months remaining + in the mortgage/liability. + 4. liability with value as monthly service for the mortgage. + """ + liability_objs = Liabilities.objects.filter(building_id=building_id) + liabilities_input = {} + index = 1 + for obj in liability_objs: + key = 'debt'+str(index) + index += 1 + liability = float(obj.monthly_service) + lender = obj.lender + remaining_term = int(obj.remaining_term) + input_date = obj.input_date + liabilities_input[key] = (liability, lender, remaining_term, input_date) + liability_dictionary = final_liability_dict( + analysis_date['proforma_start'], + liabilities_input, + analysis_date['proforma_duration'] + ) + return liability_dictionary + + def get_cash_balance(self, building_id): + """Fetch Cash Balance data from Database. + + Args: + building_id: id of the building. + + Returns: + cash_balance: Dictionary that contains: + keys: + statement_date- date of the statement(Cash balance or bank statement). + value- Tuple containing balance amount and a boolean saying if it is + from balance sheet or not. + """ + cash_balance_objs = CashBalance.objects.filter(building_id=building_id) + cash_balance_input = {} + for obj in cash_balance_objs: + cash_balance_input[obj.statement_date] = (float(obj.balance_amount), obj.is_from_balance_sheet) + return cash_balance_input + + def get_analysis_date(self, building_id): + """Fetch analysis date for the building. + + Args: + building_id: id of the building. + + Returns: + analysis_date: Dictionary: + keys: + 1. proforma_start- Start date for projection. + 2. proforma_duration- Duration of the projection. + """ + financing_overview_obj = FinancingOverview.objects.get(building_id=building_id) analysis_date = { - 'date': datetime.date(2014, 1, 1), - 'duration': 12, + 'proforma_start': financing_overview_obj.pro_forma_start_date, + 'proforma_duration': int(financing_overview_obj.pro_forma_duration), + } + return analysis_date + + def get_bill_overview(self, building_id): + """Fetch bill overview data from database. + + Args: + building_id: id of the building_id. + + Returns: + bill_overview: Dictionary containing utility as key and value as a dictionary with key as year and value + as annual charge for that utility. + """ + objs = BillsOverview.objects.filter(building_id=building_id) + bill_overview = {} + electricity = {} + gas = {} + oil = {} + 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) + bill_overview = { + 'electricity': [electricity, not obj.electricity_is_user_input], + 'gas': [gas, not obj.gas_is_user_input], + 'oil': [oil, not obj.oil_is_user_input], + 'water': [water, not obj.water_is_user_input], } - result = packaging_data(analysis_date, energy_expense, total_loan, net_savings) - year_list = [] + return bill_overview + + def get_raw_income_statement(self, building_id): + """Fetch income statement data from database. + + Args: + building_id: id of the building. + + Returns: + raw_income_statement: Dictionary with key as year and value a dictionary with revenue, utility expense and + non-utility expense data. + """ + income_statement_objs = IncomeStatement.objects.filter(building_id=building_id) + raw_income_statment = {} + for obj in income_statement_objs: + record = {} + record['revenue'] = float(obj.revenue) + record['utility_expense'] = float(obj.utility_expense) + record['non_utility_expense'] = float(obj.non_utility_operating_expense) + raw_income_statment[int(obj.year)] = record + return raw_income_statment + + def get_growth_rate(self, building_id): + """Fetch growth rate from the database. + + Args: + building_id: id of the building. + + Returns: + growth_rate: Growth rate stored for this building. + """ + growth_rate_obj = GrowthRate.objects.filter(building_id=building_id) + return float(growth_rate_obj[0].growth_rate) + + def get_loan_input(self, building_id): + """Fetch loan options from database. + + Args: + building_id: id of the building. + + Returns: + loan_options: List of records. Each record is a dicitonary containing instititue, max loan amount, rate of + interest and duration for loan. + """ + loan_options_objs = LoanOptions.objects.filter(building_id=building_id) + loan_options = [] + for obj in loan_options_objs: + record = { + 'institute': obj.lender.name, + 'max_amount': float(obj.max_loan_amount), + 'interest': float(obj.interest_rate)/100, + 'duration': int(obj.duration), + } + loan_options.append(record) + return loan_options + + def get_commission_date(self, building_id): + """Fetch the commissioning date. + + Args: + building_id: id of the building. + + Returns: + Anticipated commissioning date. + """ + return FinancingOverview.objects.filter( + building_id=building_id + )[0].anticipated_commissioning_date + + def get_savings_percent(self, savings): + """Format savings to be in decimal value for each utility. + + Args: + savings: List of records. Each record has a utility type and savings as keys. + + Returns: + savings_percent: Dictionary with key as utility_type and value as savings as a decimal. + """ + savings_percent = {} + for record in savings: + savings_percent[str.lower(record['utility_type'])] = float(record['savings_estimate'])/100 + return savings_percent + + def get_customer_preference(self, building_id): + """Fetch customer preference data from database. + + Args: + building_id: id of the building. + + Returns: + customer_preference: Dictionary with downpayment, expected payback and customer savings DCSR. + """ + customer_preference_obj = CustomerPreference.objects.filter(building_id=building_id)[0] + customer_preference = { + 'downpayment_max': float(customer_preference_obj.downpayment), + 'expected_payback': int(customer_preference_obj.expected_payback), + 'cust_saving_dscr': float(customer_preference_obj.expected_net_noi_dscr) + } + return customer_preference + + def get_prior_month_bill(self, building_id, analysis_date): + """Get the prior bills on a monthly basis than annual. + + Args: + building_id: id of the building. + analysis_date: Dictionary with proforma_start and proforma_duration. + + Returns: + prior_month_bill: Dictionary with utility type as key value as monthly charge. + """ + utilities = [ + 'electricity', + 'gas', + 'oil', + 'water', + ] + prior_month_bill = {} + for utility_type in utilities: + bills_object = Bills.objects.filter( + building_id=building_id, + utility_type=utility_type, + ) + + if bills_object: + raw_bill = {} + raw_bill['utility_type'] = utility_type + raw_bill['date_from'] = [] + raw_bill['date_to'] = [] + raw_bill['charge'] = [] + raw_bill['usage'] = [] + + for bill in bills_object: + raw_bill['date_from'].append(bill.date_from) + raw_bill['date_to'].append(bill.date_to) + raw_bill['usage'].append(float(bill.usage)) + raw_bill['charge'].append(float(bill.charge)) + month_rough = prior_proj_rough_month(raw_bill, analysis_date) + prior_month_bill[utility_type] = month_rough + return prior_month_bill + + def put(self, request, building_id): + """Handle HTTP PUT request. + + Args: + request: HTTP PUT request. + building_id: id of the building. + + Returns: Dictionary with data to display the graph. + """ + put = json.loads(request.body.decode()) + + # Fetch all relevant data from database. + growth_rate = self.get_growth_rate(building_id) + raw_income_statement = self.get_raw_income_statement(building_id) + analysis_date = self.get_analysis_date(building_id) + bill_overview = self.get_bill_overview(building_id) + bill_overview_organized = organize_bill_overview(bill_overview, analysis_date) + cash_balance_input_dict = self.get_cash_balance(building_id) + cash_balance_input = cash_balance(analysis_date, cash_balance_input_dict) + + prior_income_statement_table = Income_Statement_Table( + raw_income_statement, + bill_overview_organized + ) + prior_income_statement_table.project( + growth_rate, + analysis_date, + bill_overview_organized + ) + + liability_dictionary = self.get_liability(building_id, analysis_date) + noi_dictionary = prior_income_statement_table.get_noi_dict() + + raw_balance_sheet = { + 'cash': cash_balance_input, + 'other_debt_service': liability_dictionary, + 'net_income': noi_dictionary, + } + prior_balance_sheet_table = Balance_Sheet_Table(raw_balance_sheet) + prior_balance_sheet_table.project_balance_sheet( + analysis_date, + liability_dictionary, + noi_dictionary, + ) + costs = [] + cost_list = put['cost'] + for record in cost_list: + costs.append(float(record['cost'])) + total_cost = sum(costs) + loan_options = self.get_loan_input(building_id) + commission_date = self.get_commission_date(building_id) + savings_percent = self.get_savings_percent(put['savings']) + customer_preference = self.get_customer_preference(building_id) + prior_month_bill = self.get_prior_month_bill(building_id, analysis_date) + full_saving_dict = { + 'electricity': None, + 'gas': None, + 'oil': None, + 'water': None, + } + req_dscr = { + 'req_noi_dscr': 1.15, + 'req_cash_dscr': 1.15, + 'req_saving_dscr': 1.10 + } + # The scenario class would store relevant data and perform preliminary analysis. + scenario = ScenarioClass( + analysis_date, + commission_date, + total_cost, + bill_overview, + bill_overview_organized, + liability_dictionary, + prior_income_statement_table, + prior_balance_sheet_table, + loan_options, + ) + + # Perform preliminary analysis + scenario.prelim_anlaysis( + prior_month_bill, + savings_percent, + full_saving_dict, + growth_rate, + req_dscr, + customer_preference + ) + + # graph_dict contains the data for the stacked bar graph. + graph_dict = scenario.get_graph_dict() + energy_expense = graph_dict['energy_expenses'] + total_loan = graph_dict['total_loan'] + net_savings = graph_dict['net_savings'] energy_expense_list = [] total_loan_list = [] net_savings_list = [] - for year in sorted(result): + year_list = [] + for year in sorted(energy_expense): year_list.append(year) - energy_expense_list.append(result[year]['energy_expenses']) - total_loan_list.append(result[year]['total_loan']) - net_savings_list.append(result[year]['net_savings']) + energy_expense_list.append(energy_expense[year]) + total_loan_list.append(total_loan[year]) + net_savings_list.append(net_savings[year]) + + # economics_overview contains the information for the summary table on frontend. + project_economics_overview = scenario.get_economics() + economics_overview = { + 'Estimated Cost': float("{0:.2f}".format(project_economics_overview['estimated_cost'])), + 'Overall Saving': float("{0:.2f}".format(project_economics_overview['overall_saving'])), + 'First Year Saving': float("{0:.2f}".format(project_economics_overview['first_year_saving'])), + 'Simple Payback': float("{0:.2f}".format(project_economics_overview['simple_payback'])), + 'Minimum Saving DSCR': float("{0:.2f}".format(project_economics_overview['min_saving_dscr'])), + 'Minimum NOI DSCR': float("{0:.2f}".format(project_economics_overview['min_noi_dscr'])), + 'Minimum Cash DSCR': float("{0:.2f}".format(project_economics_overview['min_cash_dscr'])), + } return JsonResponse({ 'year_list': year_list, 'energy_expense_list': energy_expense_list, 'total_loan_list': total_loan_list, 'net_savings_list': net_savings_list, + 'economics_overview': economics_overview, }) -- GitLab