Merge branch 'db_persistence' into 'master'
Db persistence See merge request Guille/hub!44
This commit is contained in:
commit
609ad59371
16
.env
Normal file
16
.env
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# production database credentials
|
||||||
|
PROD_DB_USER=postgres
|
||||||
|
PROD_DB_PASSWORD=
|
||||||
|
PROD_DB_HOST=localhost
|
||||||
|
PROD_DB_PORT=5432
|
||||||
|
|
||||||
|
# test database credentials
|
||||||
|
TEST_DB_USER=postgres
|
||||||
|
TEST_DB_PASSWORD=postgres
|
||||||
|
TEST_DB_HOST=localhost
|
||||||
|
TEST_DB_PORT=5432
|
||||||
|
|
||||||
|
#Gitlab token
|
||||||
|
HUB_TOKEN=9s_CJYh5TcWhyYL416MM
|
||||||
|
|
||||||
|
DEV_SECRET_NAME=dp.st.dev.Axvak1ILOlCOwUNGajv7fg5VPaacFR6OL1kdb3YGWHX
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,4 +5,6 @@
|
||||||
/data/energy_systems/heat_pumps/*.csv
|
/data/energy_systems/heat_pumps/*.csv
|
||||||
/data/energy_systems/heat_pumps/*.insel
|
/data/energy_systems/heat_pumps/*.insel
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
|
logs
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
|
|
66
DEPLOYMENT.md
Normal file
66
DEPLOYMENT.md
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
## Installing PostgreSQL Database Server on Linux (Ubuntu) ##
|
||||||
|
Execute the *install_postgresql_linux.sh* script to install PostgreSQL database
|
||||||
|
*NB: PostgreSQL DB Server runs on a default port of 5432.*
|
||||||
|
|
||||||
|
## Installing PostgreSQL Database Server on Windows ##
|
||||||
|
1. Download a Windows installer from [this link](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads).
|
||||||
|
2. Double click on the installer file and follow the prompts of the installation wizard
|
||||||
|
3. On the component selection page of the installation wizard make sure to select *PostgreSQL Server and Commandline tools*
|
||||||
|
4. You can optionally select pgAdmin 4 to install a graphical UI to access your database
|
||||||
|
5. On the password page when prompted, enter the default password (postgres) and confirm it
|
||||||
|
6. You can change the default password of 5432 on the port page. You should ensure that whatever port number you
|
||||||
|
provide is not used by another service.
|
||||||
|
7. Follow the installation wizard prompt to complete your installation. You can verify your installation by
|
||||||
|
searching for the *psql* tool from your start menu
|
||||||
|
|
||||||
|
## Installing PostgreSQL Database Server on Mac OS X ##
|
||||||
|
1. Download the Mac OS X installer from [this link](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads).
|
||||||
|
2. Launch the installation wizard by double-clicking it and follow the rest of steps as described above on
|
||||||
|
Installing PostgreSQL Database Server on Windows.
|
||||||
|
|
||||||
|
NB: Hub has been tested with version 15 of PostgreSQL
|
||||||
|
|
||||||
|
## Create Database and Database User ##
|
||||||
|
1. Connect to the PostgreSQL database server via psql by executing `sudo -u postgres psql`. You will be
|
||||||
|
be prompted for a password, the default password of *postgres* user is *postgres*. The above command may not work on
|
||||||
|
a Windows system using the default command line tool. You can access the psql tool from Windows start menu and follow
|
||||||
|
the rest of the instructions from step 2 below
|
||||||
|
2. Execute `create user <username> with encrypted password '<password>';` in the psql console to create a user.
|
||||||
|
3. Execute `create database <database-name>;` in the psql console to create a database.
|
||||||
|
4. Execute `grant all privileges on database <database-name> to <username>;` to grant the all privileges on the database
|
||||||
|
to the user created in step 2 above.
|
||||||
|
5. The created database by default, has on schema named public which you can use. However, if you wish to create
|
||||||
|
another schema, you can do so by following [this link](https://www.postgresqltutorial.com/postgresql-administration/postgresql-create-schema/).
|
||||||
|
|
||||||
|
**NB: You can grant selected privileges to the user on the database using commands [on this page](https://tableplus.com/blog/2018/04/postgresql-how-to-grant-access-to-users.html).*
|
||||||
|
The user should however have read and write permission to all tables in the database. You can as well create a database and user using the PgAdmin UI tool*
|
||||||
|
|
||||||
|
## Setting Up Database Connection Parameters
|
||||||
|
1. Create a .env file that contains the configuration parameters as explained in the *Database Configuration Parameters*
|
||||||
|
section in persistence/README.md file.
|
||||||
|
2. The .env file should contain the following credentials: database user, database password, database host an,d database port
|
||||||
|
3. Provide the *absolute path* to the .env file to the persistence importers and exporters whenever using them in your code
|
||||||
|
as shown below:
|
||||||
|
```python
|
||||||
|
from exports.db_factory import DBFactory
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
dotenv_path = (Path(__file__).parent / '.env').resolve()
|
||||||
|
factory = DBFactory(db_name='hub_db', app_env='PROD', dotenv_path=dotenv_path)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Create Database Tables ##
|
||||||
|
Use the *DBSetup* class in the persistence package to create the required database tables as described below
|
||||||
|
```python
|
||||||
|
from persistence import DBSetup
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
dotenv_path = (Path(__file__).parent / '.env').resolve()
|
||||||
|
DBSetup(db_name='hub_db', app_env='PROD', dotenv_path=dotenv_path)
|
||||||
|
```
|
||||||
|
The *DBSetUp* class also creates a default admin user with default credentials that can be changed.
|
||||||
|
with the import UserFactory class. The admin user (name, email, password and role) is logged into the console after it is created by the
|
||||||
|
*constructor of DBSetup*. Use can also manage users (create, read, update and delete) with user import and export factories.
|
||||||
|
|
||||||
|
**NB: Make sure to change the default admin user credentials**
|
|
@ -14,7 +14,6 @@ import pyproj
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
from pyproj import Transformer
|
from pyproj import Transformer
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from city_model_structure.building import Building
|
from city_model_structure.building import Building
|
||||||
from city_model_structure.city_object import CityObject
|
from city_model_structure.city_object import CityObject
|
||||||
from city_model_structure.city_objects_cluster import CityObjectsCluster
|
from city_model_structure.city_objects_cluster import CityObjectsCluster
|
||||||
|
@ -96,7 +95,7 @@ class City:
|
||||||
@property
|
@property
|
||||||
def country_code(self):
|
def country_code(self):
|
||||||
"""
|
"""
|
||||||
Get city country code
|
Get models country code
|
||||||
:return: str
|
:return: str
|
||||||
"""
|
"""
|
||||||
return self._get_location().country
|
return self._get_location().country
|
||||||
|
@ -290,11 +289,12 @@ class City:
|
||||||
selected_region_upper_corner = [center[0] + radius, center[1] + radius, center[2] + radius]
|
selected_region_upper_corner = [center[0] + radius, center[1] + radius, center[2] + radius]
|
||||||
selected_region_city = City(selected_region_lower_corner, selected_region_upper_corner, srs_name=self.srs_name)
|
selected_region_city = City(selected_region_lower_corner, selected_region_upper_corner, srs_name=self.srs_name)
|
||||||
selected_region_city.climate_file = self.climate_file
|
selected_region_city.climate_file = self.climate_file
|
||||||
|
# selected_region_city.climate_reference_city = self.climate_reference_city
|
||||||
for city_object in self.city_objects:
|
for city_object in self.city_objects:
|
||||||
location = city_object.centroid
|
location = city_object.centroid
|
||||||
if location is not None:
|
if location is not None:
|
||||||
distance = math.sqrt(math.pow(location[0]-center[0], 2) + math.pow(location[1]-center[1], 2)
|
distance = math.sqrt(math.pow(location[0] - center[0], 2) + math.pow(location[1] - center[1], 2)
|
||||||
+ math.pow(location[2]-center[2], 2))
|
+ math.pow(location[2] - center[2], 2))
|
||||||
if distance < radius:
|
if distance < radius:
|
||||||
selected_region_city.add_city_object(city_object)
|
selected_region_city.add_city_object(city_object)
|
||||||
return selected_region_city
|
return selected_region_city
|
||||||
|
|
|
@ -13,13 +13,12 @@ Cp: 4190
|
||||||
Rhow: 1000
|
Rhow: 1000
|
||||||
TESDiameter: 5
|
TESDiameter: 5
|
||||||
AuxHeaterEfficiency: 0.9
|
AuxHeaterEfficiency: 0.9
|
||||||
|
HPNominalCapacity: 256
|
||||||
# These come from the data model according to other student's work
|
# These come from the data model according to other student's work
|
||||||
ElecGridEF: 0.5
|
ElecGridEF: 0.5
|
||||||
ElectricityPrice: 0.073
|
ElectricityPrice: 0.073
|
||||||
|
|
||||||
# Water to Water HP constants
|
# Water to Water HP constants
|
||||||
HPNominalCapacity: 256
|
|
||||||
LowestPossibleLoadFlow: 4.73
|
LowestPossibleLoadFlow: 4.73
|
||||||
HighestPossibleLoadFlow: 9.46
|
HighestPossibleLoadFlow: 9.46
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
46
exports/db_factory.py
Normal file
46
exports/db_factory.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
"""
|
||||||
|
DBFactory performs read related operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project CoderPeter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
from persistence import CityRepo
|
||||||
|
from persistence import HeatPumpSimulationRepo
|
||||||
|
|
||||||
|
|
||||||
|
class DBFactory:
|
||||||
|
"""
|
||||||
|
DBFactory class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name, app_env, dotenv_path):
|
||||||
|
self._city_repo = CityRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
self._hp_simulation_repo = HeatPumpSimulationRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
|
||||||
|
def get_city(self, city_id):
|
||||||
|
"""
|
||||||
|
Retrieve a single city from postgres
|
||||||
|
:param city_id: the id of the city to get
|
||||||
|
"""
|
||||||
|
return self._city_repo.get_by_id(city_id)
|
||||||
|
|
||||||
|
def get_city_by_name(self, city_name):
|
||||||
|
"""
|
||||||
|
Retrieve a single city from postgres
|
||||||
|
:param city_name: the name of the city to get
|
||||||
|
"""
|
||||||
|
return self._city_repo.get_by_name(city_name)
|
||||||
|
|
||||||
|
def get_hp_simulation(self, hp_sim_id: int):
|
||||||
|
"""
|
||||||
|
Retrieve a single heat pump simulation from postgres
|
||||||
|
:param hp_sim_id: the id of the heat pump to get
|
||||||
|
"""
|
||||||
|
return self._hp_simulation_repo.get_by_id(hp_sim_id)
|
||||||
|
|
||||||
|
def get_hp_simulation_by_city(self, city_id: int):
|
||||||
|
"""
|
||||||
|
Retrieve a single city from postgres
|
||||||
|
:param city_id: the id of the city
|
||||||
|
"""
|
||||||
|
return self._hp_simulation_repo.get_by_city(city_id)
|
|
@ -6,7 +6,7 @@ Copyright © 2022 Concordia CERC group
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
"""
|
"""
|
||||||
from exports.energy_systems.heat_pump_export import HeatPumpExport
|
from exports.energy_systems.heat_pump_export import HeatPumpExport
|
||||||
from typing import List, Tuple, Union
|
from typing import List, Dict, Union
|
||||||
|
|
||||||
|
|
||||||
class AirSourceHPExport(HeatPumpExport):
|
class AirSourceHPExport(HeatPumpExport):
|
||||||
|
@ -15,17 +15,18 @@ class AirSourceHPExport(HeatPumpExport):
|
||||||
after executing insel
|
after executing insel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, base_path, city, output_path, sim_type):
|
def __init__(self, base_path, city, output_path, sim_type, demand_path=None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:param base_path: path to energy system files
|
:param base_path: path to energy system files
|
||||||
:param city: the city object
|
:param city: the city object
|
||||||
:param output_path: the file to hold insel simulation results
|
:param output_path: the file to hold insel simulation results
|
||||||
:param sim_type: the simulation type to run: 0 for series, 1 for parallel
|
:param sim_type: the simulation type to run: 0 for series, 1 for parallel
|
||||||
|
:param demand_path: path to hourly energy demand file
|
||||||
"""
|
"""
|
||||||
tmp_file = 'heat_pumps/as_series.txt' if sim_type == 0 else 'heat_pumps/as_parallel.txt'
|
tmp_file = 'heat_pumps/as_series.txt' if sim_type == 0 else 'heat_pumps/as_parallel.txt'
|
||||||
template_path = (base_path / tmp_file)
|
template_path = (base_path / tmp_file)
|
||||||
super().__init__(base_path, city, output_path, template_path)
|
super().__init__(base_path, city, output_path, template_path, demand_path)
|
||||||
|
|
||||||
def _extract_model_coff(self, hp_model: str, data_type='heat') -> Union[List, None]:
|
def _extract_model_coff(self, hp_model: str, data_type='heat') -> Union[List, None]:
|
||||||
"""
|
"""
|
||||||
|
@ -43,7 +44,7 @@ class AirSourceHPExport(HeatPumpExport):
|
||||||
return energy_system.air_source_hp.cooling_capacity_coff
|
return energy_system.air_source_hp.cooling_capacity_coff
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def execute_insel(self, user_input, hp_model, data_type):
|
def execute_insel(self, user_input, hp_model, data_type) -> Union[Dict, None]:
|
||||||
"""
|
"""
|
||||||
Runs insel and produces output files
|
Runs insel and produces output files
|
||||||
Runs insel and write the necessary files
|
Runs insel and write the necessary files
|
||||||
|
@ -54,7 +55,6 @@ class AirSourceHPExport(HeatPumpExport):
|
||||||
:param data_type: a string that indicates whether
|
:param data_type: a string that indicates whether
|
||||||
insel should run for heat or cooling performance
|
insel should run for heat or cooling performance
|
||||||
:return:
|
:return:
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
capacity_coeff = self._extract_model_coff(hp_model, data_type)
|
capacity_coeff = self._extract_model_coff(hp_model, data_type)
|
||||||
super(AirSourceHPExport, self)._run_insel(user_input, capacity_coeff, 'air_source.insel')
|
return super(AirSourceHPExport, self)._run_insel(user_input, capacity_coeff, 'air_source.insel')
|
||||||
|
|
|
@ -5,7 +5,7 @@ Copyright © 2022 Concordia CERC group
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from typing import List, Tuple, Union, Dict
|
from typing import List, Union, Dict
|
||||||
import yaml
|
import yaml
|
||||||
from string import Template
|
from string import Template
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
@ -17,18 +17,18 @@ class HeatPumpExport:
|
||||||
of some defined function
|
of some defined function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, base_path, city, output_path, template, water_temp=None):
|
def __init__(self, base_path, city, output_path, template, demand_path=None, water_temp=None):
|
||||||
self._template_path = template
|
self._template_path = template
|
||||||
self._water_temp = water_temp
|
self._water_temp = water_temp
|
||||||
self._constants_path = (base_path / 'heat_pumps/constants.yaml')
|
self._constants_path = (base_path / 'heat_pumps/constants.yaml')
|
||||||
# needed to compute max demand.
|
# needed to compute max demand.
|
||||||
self._demand_path = (base_path / 'heat_pumps/demand.txt')
|
self._demand_path = (base_path / 'heat_pumps/demand.txt') if demand_path is None else demand_path
|
||||||
self._city = city
|
self._city = city
|
||||||
self._input_data = None
|
self._input_data = None
|
||||||
self._base_path = base_path
|
self._base_path = base_path
|
||||||
self._output_path = output_path
|
self._output_path = output_path
|
||||||
|
|
||||||
def _run_insel(self, user_input: Dict, capacity_coeff: List, filename: str) -> None:
|
def _run_insel(self, user_input: Dict, capacity_coeff: List, filename: str) -> Union[Dict, None]:
|
||||||
"""
|
"""
|
||||||
Runs insel and write the necessary files
|
Runs insel and write the necessary files
|
||||||
:param user_input: a dictionary containing the user
|
:param user_input: a dictionary containing the user
|
||||||
|
@ -56,11 +56,11 @@ class HeatPumpExport:
|
||||||
insel_file_handler.write(insel_template)
|
insel_file_handler.write(insel_template)
|
||||||
# Now run insel
|
# Now run insel
|
||||||
self._delete_existing_output_files()
|
self._delete_existing_output_files()
|
||||||
os.system('insel {}'.format(insel_file))
|
os.system('/usr/local/bin/insel {}'.format(insel_file))
|
||||||
# Writer headers to csv output files generated by insel
|
# Writer headers to csv output files generated by insel
|
||||||
self._write_insel_output_headers()
|
self._write_insel_output_headers()
|
||||||
# User output
|
# User output
|
||||||
self._get_user_out_put()
|
return self._get_user_out_put()
|
||||||
except IOError as err:
|
except IOError as err:
|
||||||
print("I/O exception: {}".format(err))
|
print("I/O exception: {}".format(err))
|
||||||
finally:
|
finally:
|
||||||
|
@ -72,17 +72,33 @@ class HeatPumpExport:
|
||||||
Write headers to the various csv file generated by insel
|
Write headers to the various csv file generated by insel
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
header = [
|
||||||
|
'Year', ' Month', ' Day', 'Hour', 'Minute', 'HP Heat Output (kW)', 'Heating Demand (kW)', 'HP output flow rate',
|
||||||
|
'Building Required Flow Rate', 'TES Charging Rate (kg/s)', 'Water Flow Rate After Splitter',
|
||||||
|
'water temperature after splitter', 'TES Discharging Rate (kg/s)', 'TES discharge temperature',
|
||||||
|
'Mixer Outlet Flow Rate (kg/s)', 'Mixer outlet temperature', 'Auxiliary heater fuel flow rate',
|
||||||
|
'Auxiliary heater energy input (kW)', 'Building Inlet Flow Rate (kg/s)', 'Building inlet temperature',
|
||||||
|
'Building return temperature', 'TES Return Flow Rate (kg/s)', 'TES return temperature',
|
||||||
|
'TES Bypass Line Flow Rate (kg/s)', 'TES bypass line temperature', 'Flow Rate from TES to mixer 2 (kg/s)',
|
||||||
|
'Temperature from Tes to mixer', 'HP Inlet Flow Rate (kg/s)', 'HP Inlet temperature', 'TES Node 1 Temperature',
|
||||||
|
'TES Node 2 Temperature', 'TES Node 3 Temperature', 'TES Node 4 Temperature', 'TES Energy Content (J)',
|
||||||
|
'HP Electricity Consumption (kW)', 'HP COP', 'Ambient Temperature', 'HP Operational Cost (CAD)',
|
||||||
|
'Auxiliary Heater Operational Cost (CAD)', 'Operational CO2 Emissions of HP (g)',
|
||||||
|
'Operational CO2 Emissions of Auxiliary Heater (g)']
|
||||||
|
if 'series' in str(self._template_path):
|
||||||
|
header = [
|
||||||
|
'Year', ' Month', ' Day', 'Hour', 'Minute', 'HP Heat Output (kW)',
|
||||||
|
'HP Electricity Consumption (kW)', 'HP COP', 'TES Charging Rate (kg/s)',
|
||||||
|
'TES Discharging Rate (kg/s)', 'TES Node 1 Temperature', 'TES Node 2 Temperature',
|
||||||
|
'TES Node 3 Temperature', 'TES Node 4 Temperature', 'TES Energy Content (J)',
|
||||||
|
'TES Energy Content (kWh)', 'TES Energy Content Variation (kWh)',
|
||||||
|
'Auxiliary Heater Fuel Flow Rate (kg/s)', 'Auxiliary Heater Energy Input (kW)',
|
||||||
|
'HP Operational Cost (CAD)', 'Auxiliary Heater Operational Cost (CAD)',
|
||||||
|
'Operational CO2 Emissions of HP (g)',
|
||||||
|
'Operational CO2 Emissions of Auxiliary Heater (g)',
|
||||||
|
'Return Temperature', 'Demand (kW)']
|
||||||
header_data = {
|
header_data = {
|
||||||
self._input_data['fileOut1']: ['Year', ' Month', ' Day', 'Hour', 'Minute', 'HP Heat Output (kW)',
|
self._input_data['fileOut1']: header,
|
||||||
'HP Electricity Consumption (kW)', 'HP COP', 'TES Charging Rate (kg/s)',
|
|
||||||
'TES Discharging Rate (kg/s)', 'TES Node 1 Temperature', 'TES Node 2 Temperature',
|
|
||||||
'TES Node 3 Temperature', 'TES Node 4 Temperature', 'TES Energy Content (J)',
|
|
||||||
'TES Energy Content (kWh)', 'TES Energy Content Variation (kWh)',
|
|
||||||
'Auxiliary Heater Fuel Flow Rate (kg/s)', 'Auxiliary Heater Energy Input (kW)',
|
|
||||||
'HP Operational Cost (CAD)', 'Auxiliary Heater Operational Cost (CAD)',
|
|
||||||
'Operational CO2 Emissions of HP (g)',
|
|
||||||
'Operational CO2 Emissions of Auxiliary Heater (g)',
|
|
||||||
'Return Temperature', 'Demand (kW)'],
|
|
||||||
self._input_data['fileOut2']: ['Day', 'Operational Daily Emissions from Heat Pumps (g)',
|
self._input_data['fileOut2']: ['Day', 'Operational Daily Emissions from Heat Pumps (g)',
|
||||||
'Operational Daily Emissions from Auxiliary Heater (g)'],
|
'Operational Daily Emissions from Auxiliary Heater (g)'],
|
||||||
self._input_data['fileOut3']: ['Month', 'Monthly Operational Costs of Heat Pumps (CAD)',
|
self._input_data['fileOut3']: ['Month', 'Monthly Operational Costs of Heat Pumps (CAD)',
|
||||||
|
@ -102,7 +118,7 @@ class HeatPumpExport:
|
||||||
file_path = file_path.strip("'")
|
file_path = file_path.strip("'")
|
||||||
df = pd.read_csv(file_path, header=None, sep='\s+')
|
df = pd.read_csv(file_path, header=None, sep='\s+')
|
||||||
# ignore ambient temperature for air source series run
|
# ignore ambient temperature for air source series run
|
||||||
if df.shape[1] > 25:
|
if df.shape[1] > 25 and 'series' in str(self._template_path):
|
||||||
df.drop(columns=df.columns[-1],
|
df.drop(columns=df.columns[-1],
|
||||||
axis=1,
|
axis=1,
|
||||||
inplace=True)
|
inplace=True)
|
||||||
|
@ -161,6 +177,8 @@ class HeatPumpExport:
|
||||||
with open(self._constants_path) as file:
|
with open(self._constants_path) as file:
|
||||||
constants_dict = yaml.load(file, Loader=yaml.FullLoader)
|
constants_dict = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
for key, value in constants_dict.items():
|
for key, value in constants_dict.items():
|
||||||
|
if key in ['LowestPossibleLoadFlow', 'HighestPossibleLoadFlow'] and self._water_temp is None:
|
||||||
|
continue
|
||||||
self._input_data[key] = value
|
self._input_data[key] = value
|
||||||
# compute water to water HP specific values
|
# compute water to water HP specific values
|
||||||
if 55 <= self._input_data['HPSupTemp'] <= 60:
|
if 55 <= self._input_data['HPSupTemp'] <= 60:
|
||||||
|
@ -206,19 +224,30 @@ class HeatPumpExport:
|
||||||
self._input_data["a10"] = a_coeff[9]
|
self._input_data["a10"] = a_coeff[9]
|
||||||
self._input_data["a11"] = a_coeff[10]
|
self._input_data["a11"] = a_coeff[10]
|
||||||
|
|
||||||
def _get_user_out_put(self):
|
def _get_user_out_put(self) -> Union[Dict, None]:
|
||||||
"""
|
"""
|
||||||
Extracts monthly electricity demand and fossil fuel consumption
|
Extracts monthly electricity demand and fossil fuel consumption
|
||||||
from output files generated by insel
|
from output files generated by insel
|
||||||
:return:
|
:return: Dict for json output
|
||||||
"""
|
"""
|
||||||
|
|
||||||
electricity_df = pd.read_csv(self._input_data['fileOut8'].strip("'")).iloc[:, 2]
|
monthly_electricity_df = pd.read_csv(self._input_data['fileOut8'].strip("'")).iloc[:, 2]
|
||||||
fossil_df = pd.read_csv(self._input_data['fileOut4'].strip("'")).iloc[:, 2]
|
monthly_fossil_df = pd.read_csv(self._input_data['fileOut4'].strip("'")).iloc[:, 2]
|
||||||
|
|
||||||
data = [electricity_df, fossil_df]
|
if self._output_path is None:
|
||||||
|
return {
|
||||||
|
'hourly_electricity_demand': pd.read_csv(self._input_data['fileOut10'].strip("'")).iloc[:, 5].tolist(),
|
||||||
|
'monthly_electricity_demand': monthly_electricity_df.tolist(),
|
||||||
|
'daily_electricity_demand': pd.read_csv(self._input_data['fileOut6'].strip("'")).iloc[:, 2].tolist(),
|
||||||
|
'daily_fossil_consumption': pd.read_csv(self._input_data['fileOut9'].strip("'")).iloc[:, 2].tolist(),
|
||||||
|
'monthly_fossil_consumption': monthly_fossil_df.tolist()
|
||||||
|
}
|
||||||
|
|
||||||
|
data = [monthly_electricity_df, monthly_fossil_df]
|
||||||
df = pd.concat(data, axis=1)
|
df = pd.concat(data, axis=1)
|
||||||
df = pd.concat([df, df.agg(['sum'])])
|
df = pd.concat([df, df.agg(['sum'])])
|
||||||
s = pd.Series(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "Total"])
|
s = pd.Series(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "Total"])
|
||||||
df = df.set_index([s])
|
df = df.set_index([s])
|
||||||
df.to_csv(self._output_path)
|
df.to_csv(self._output_path)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ Copyright © 2022 Concordia CERC group
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
"""
|
"""
|
||||||
from exports.energy_systems.heat_pump_export import HeatPumpExport
|
from exports.energy_systems.heat_pump_export import HeatPumpExport
|
||||||
from typing import List, Tuple, Union
|
from typing import List, Dict, Union
|
||||||
|
|
||||||
|
|
||||||
class WaterToWaterHPExport(HeatPumpExport):
|
class WaterToWaterHPExport(HeatPumpExport):
|
||||||
|
@ -15,17 +15,19 @@ class WaterToWaterHPExport(HeatPumpExport):
|
||||||
after executing insel
|
after executing insel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, base_path, city, output_path, sim_type):
|
def __init__(self, base_path, city, output_path, sim_type, demand_path):
|
||||||
"""
|
"""
|
||||||
:param base_path: path to energy system files
|
:param base_path: path to energy system files
|
||||||
:param city: the city object
|
:param city: the city object
|
||||||
:param output_path: the file to hold insel simulation results
|
:param output_path: the file to hold insel simulation results
|
||||||
:param sim_type: the simulation type to run: 1 for series, 0 for parallel
|
:param sim_type: the simulation type to run: 1 for series, 0 for parallel
|
||||||
|
:param demand_path: path to hourly energy demand file
|
||||||
"""
|
"""
|
||||||
tmp_file = 'heat_pumps/w2w_series.txt' if sim_type == 0 else 'heat_pumps/w2w_parallel.txt'
|
tmp_file = 'heat_pumps/w2w_series.txt' if sim_type == 0 else 'heat_pumps/w2w_parallel.txt'
|
||||||
template_path = (base_path / tmp_file)
|
template_path = (base_path / tmp_file)
|
||||||
water_temp = (base_path / 'heat_pumps/wt_hourly3.txt')
|
water_temp = (base_path / 'heat_pumps/wt_hourly3.txt')
|
||||||
super().__init__(base_path, city, output_path, template_path, water_temp)
|
super().__init__(base_path=base_path, city=city, output_path=output_path, template=template_path,
|
||||||
|
demand_path=demand_path, water_temp=water_temp)
|
||||||
|
|
||||||
def _extract_model_coff(self, hp_model: str) -> Union[List, None]:
|
def _extract_model_coff(self, hp_model: str) -> Union[List, None]:
|
||||||
"""
|
"""
|
||||||
|
@ -39,7 +41,7 @@ class WaterToWaterHPExport(HeatPumpExport):
|
||||||
return energy_system.water_to_water_hp.power_demand_coff
|
return energy_system.water_to_water_hp.power_demand_coff
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def execute_insel(self, user_input, hp_model):
|
def execute_insel(self, user_input, hp_model) -> Union[Dict, None]:
|
||||||
"""
|
"""
|
||||||
Runs insel and produces output files
|
Runs insel and produces output files
|
||||||
Runs insel and write the necessary files
|
Runs insel and write the necessary files
|
||||||
|
@ -50,4 +52,4 @@ class WaterToWaterHPExport(HeatPumpExport):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
pow_demand_coeff = self._extract_model_coff(hp_model)
|
pow_demand_coeff = self._extract_model_coff(hp_model)
|
||||||
super(WaterToWaterHPExport, self)._run_insel(user_input, pow_demand_coeff, 'w2w.insel')
|
return super(WaterToWaterHPExport, self)._run_insel(user_input, pow_demand_coeff, 'w2w.insel')
|
||||||
|
|
|
@ -16,7 +16,8 @@ class EnergySystemsExportFactory:
|
||||||
Exports factory class for energy systems
|
Exports factory class for energy systems
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, city, user_input, hp_model, output_path, sim_type=0, data_type='heat', base_path=None):
|
def __init__(self, city, user_input, hp_model, output_path, sim_type=0, data_type='heat', base_path=None,
|
||||||
|
demand_path=None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:param city: the city object
|
:param city: the city object
|
||||||
|
@ -26,7 +27,9 @@ class EnergySystemsExportFactory:
|
||||||
:param sim_type: the simulation type, 0 for series 1 for parallel
|
:param sim_type: the simulation type, 0 for series 1 for parallel
|
||||||
:param data_type: indicates whether cooling or heating data is used
|
:param data_type: indicates whether cooling or heating data is used
|
||||||
:param base_path: the data directory of energy systems
|
:param base_path: the data directory of energy systems
|
||||||
|
:param demand_path: path to hourly energy dempand file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._city = city
|
self._city = city
|
||||||
if base_path is None:
|
if base_path is None:
|
||||||
base_path = Path(Path(__file__).parent.parent / 'data/energy_systems')
|
base_path = Path(Path(__file__).parent.parent / 'data/energy_systems')
|
||||||
|
@ -36,6 +39,7 @@ class EnergySystemsExportFactory:
|
||||||
self._data_type = data_type
|
self._data_type = data_type
|
||||||
self._output_path = output_path
|
self._output_path = output_path
|
||||||
self._sim_type = sim_type
|
self._sim_type = sim_type
|
||||||
|
self._demand_path = demand_path
|
||||||
|
|
||||||
def _export_heat_pump(self, source):
|
def _export_heat_pump(self, source):
|
||||||
"""
|
"""
|
||||||
|
@ -44,10 +48,10 @@ class EnergySystemsExportFactory:
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
if source == 'air':
|
if source == 'air':
|
||||||
AirSourceHPExport(self._base_path, self._city, self._output_path, self._sim_type)\
|
return AirSourceHPExport(self._base_path, self._city, self._output_path, self._sim_type, self._demand_path)\
|
||||||
.execute_insel(self._user_input, self._hp_model, self._data_type)
|
.execute_insel(self._user_input, self._hp_model, self._data_type)
|
||||||
elif source == 'water':
|
elif source == 'water':
|
||||||
WaterToWaterHPExport(self._base_path, self._city, self._output_path, self._sim_type)\
|
return WaterToWaterHPExport(self._base_path, self._city, self._output_path, self._sim_type, self._demand_path)\
|
||||||
.execute_insel(self._user_input, self._hp_model)
|
.execute_insel(self._user_input, self._hp_model)
|
||||||
|
|
||||||
def export(self, source='air'):
|
def export(self, source='air'):
|
||||||
|
|
31
exports/user_factory.py
Normal file
31
exports/user_factory.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""
|
||||||
|
User performs user related crud operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project CoderPeter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
from persistence import UserRepo
|
||||||
|
|
||||||
|
|
||||||
|
class UserFactory:
|
||||||
|
"""
|
||||||
|
UserFactory class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name, app_env, dotenv_path):
|
||||||
|
self._user_repo = UserRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
|
||||||
|
def login_user(self, email: str, password: str):
|
||||||
|
"""
|
||||||
|
Retrieve a single city from postgres
|
||||||
|
:param email: the email of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
"""
|
||||||
|
return self._user_repo.get_user_by_email_and_password(email, password)
|
||||||
|
|
||||||
|
def get_user_by_email(self, email):
|
||||||
|
"""
|
||||||
|
Retrieve a single user
|
||||||
|
:param email: the email of the user to get
|
||||||
|
"""
|
||||||
|
return self._user_repo.get_by_email(email)
|
43
helpers/auth.py
Normal file
43
helpers/auth.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import bcrypt
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class Auth(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_password(password: str) -> bool:
|
||||||
|
"""
|
||||||
|
Validates a password
|
||||||
|
:param password: the password to validate
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!#%*?&]{6,20}$"
|
||||||
|
pattern = re.compile(pattern)
|
||||||
|
if not re.search(pattern, password):
|
||||||
|
raise ValueError("Password must be between 6 to 20 characters and must have at least a number, an uppercase "
|
||||||
|
"letter, a lowercase letter, and a special character")
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hash_password(password: str) -> str:
|
||||||
|
"""
|
||||||
|
Hashes a password
|
||||||
|
:param password: the password to be hashed
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(14)).decode('utf-8')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_password(password: str, hashed_password) -> bool:
|
||||||
|
"""
|
||||||
|
Hashes a password
|
||||||
|
:param password: the password to be checked
|
||||||
|
:param hashed_password: the hashed password
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return bcrypt.checkpw(password.encode('utf-8'), hashed_password.encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
6
hub_logger/__init__.py
Normal file
6
hub_logger/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import logging as logger
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
log_file = (Path(__file__).parent.parent / 'logs/hub.log').resolve()
|
||||||
|
logger.basicConfig(filename=log_file, format="%(asctime)s:%(levelname)s:{%(pathname)s:%(funcName)s:%(lineno)d} "
|
||||||
|
"- %(message)s", level=logger.DEBUG)
|
0
imports/__init__.py
Normal file
0
imports/__init__.py
Normal file
58
imports/db_factory.py
Normal file
58
imports/db_factory.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
"""
|
||||||
|
DBFactory performs database create, delete and update operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project CoderPeter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
from persistence import CityRepo
|
||||||
|
from persistence import HeatPumpSimulationRepo
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
|
class DBFactory:
|
||||||
|
"""
|
||||||
|
DBFactory class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, city, db_name, dotenv_path, app_env):
|
||||||
|
self._city = city
|
||||||
|
self._city_repo = CityRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
|
||||||
|
self._hp_simulation_repo = HeatPumpSimulationRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
|
||||||
|
|
||||||
|
def persist_city(self):
|
||||||
|
"""
|
||||||
|
Persist city into postgres database
|
||||||
|
"""
|
||||||
|
return self._city_repo.insert(self._city)
|
||||||
|
|
||||||
|
def update_city(self, city_id, city):
|
||||||
|
"""
|
||||||
|
Update an existing city in postgres database
|
||||||
|
:param city_id: the id of the city to update
|
||||||
|
:param city: the updated city object
|
||||||
|
"""
|
||||||
|
return self._city_repo.update(city_id, city)
|
||||||
|
|
||||||
|
def delete_city(self, city_id):
|
||||||
|
"""
|
||||||
|
Deletes a single city from postgres
|
||||||
|
:param city_id: the id of the city to get
|
||||||
|
"""
|
||||||
|
self._city_repo.delete_city(city_id)
|
||||||
|
|
||||||
|
def persist_hp_simulation(self, hp_simulation_data: Dict, city_id: int):
|
||||||
|
"""
|
||||||
|
Persist heat pump simulation results
|
||||||
|
:param hp_simulation_data: the simulation results
|
||||||
|
:param city_id: the city object used in running the simulation
|
||||||
|
:return: HeatPumpSimulation object
|
||||||
|
"""
|
||||||
|
return self._hp_simulation_repo.insert(hp_simulation_data, city_id)
|
||||||
|
|
||||||
|
def delete_hp_simulation(self, hp_sim_id):
|
||||||
|
"""
|
||||||
|
Deletes a single heat pump simulation from postgres
|
||||||
|
:param hp_sim_id: the id of the heat pump simulation to get
|
||||||
|
"""
|
||||||
|
self._hp_simulation_repo.delete_hp_simulation(hp_sim_id)
|
||||||
|
|
45
imports/user_factory.py
Normal file
45
imports/user_factory.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
"""
|
||||||
|
User performs user-related crud operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project CoderPeter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
from persistence import UserRepo
|
||||||
|
from persistence import UserRoles
|
||||||
|
|
||||||
|
|
||||||
|
class UserFactory:
|
||||||
|
"""
|
||||||
|
UserFactory class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name, app_env, dotenv_path):
|
||||||
|
self._user_repo = UserRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
|
||||||
|
def create_user(self, name: str, email: str, password: str, role: UserRoles):
|
||||||
|
"""
|
||||||
|
Creates a new user
|
||||||
|
:param name: the name of the user
|
||||||
|
:param email: the email of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
:param role: the role of the user
|
||||||
|
"""
|
||||||
|
return self._user_repo.insert(name, email, password, role)
|
||||||
|
|
||||||
|
def update_user(self, user_id: int, name: str, email: str, password: str, role: UserRoles):
|
||||||
|
"""
|
||||||
|
Creates a new user
|
||||||
|
:param user_id: the id of the user
|
||||||
|
:param name: the name of the user
|
||||||
|
:param email: the email of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
:param role: the role of the user
|
||||||
|
"""
|
||||||
|
return self._user_repo.update(user_id, name, email, password, role)
|
||||||
|
|
||||||
|
def delete_user(self, user_id):
|
||||||
|
"""
|
||||||
|
Retrieve a single user
|
||||||
|
:param user_id: the id of the user to delete
|
||||||
|
"""
|
||||||
|
return self._user_repo.delete_user(user_id)
|
6
install_postgresql_linux.sh
Executable file
6
install_postgresql_linux.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||||
|
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install postgresql
|
50
persistence/README.md
Normal file
50
persistence/README.md
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
## Database Persistence ##
|
||||||
|
The persistence package includes classes to store different class objects in a Postgres database.
|
||||||
|
|
||||||
|
### models ###
|
||||||
|
This defines models for all class objects that we want to persist. It is used for Object Relation Mapping (ORM)
|
||||||
|
of the class objects to database table columns
|
||||||
|
|
||||||
|
### repositories ###
|
||||||
|
This defines repository classes that contain CRUD methods for database operations. The constructor of all repositories requires
|
||||||
|
The database name to connect to and the application environment (PROD or TEST). Tests use a different database
|
||||||
|
from the production environment, which is why this is necessary. An example is shown below
|
||||||
|
```python
|
||||||
|
from persistence import CityRepo
|
||||||
|
# instantiate city repo for hub production database
|
||||||
|
city_repo = CityRepo(db_name='hub', app_env='PROD')
|
||||||
|
```
|
||||||
|
All database operations are conducted with the production database (*PROD*) named *hub* in the example above
|
||||||
|
|
||||||
|
### config_db ##
|
||||||
|
This Python file is a configuration class that contains variables that map to configuration parameters in a .env file.
|
||||||
|
It also contains a method ``def conn_string()`` which returns the connection string to a Postgres database.
|
||||||
|
|
||||||
|
### Base ##
|
||||||
|
This class has a constructor that establishes a database connection and returns a reference for database-related CRUD operations.
|
||||||
|
|
||||||
|
### Database Configuration Parameter ###
|
||||||
|
A .env file (or environment variables) with configuration parameters described below are needed to establish a database connection:
|
||||||
|
```
|
||||||
|
# production database credentials
|
||||||
|
PROD_DB_USER=postgres-database-user
|
||||||
|
PROD_DB_PASSWORD=postgres-database-password
|
||||||
|
PROD_DB_HOST=database-host
|
||||||
|
PROD_DB_PORT=database-port
|
||||||
|
|
||||||
|
# test database credentials
|
||||||
|
TEST_DB_USER=postgres-database-user
|
||||||
|
TEST_DB_PASSWORD=postgres-database-password
|
||||||
|
TEST_DB_HOST=database-host
|
||||||
|
TEST_DB_PORT=database-port
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Related Unit Test
|
||||||
|
Unit tests that involve database operations require a Postgres database to be set up.
|
||||||
|
The tests connect to the database server using the default postgres user (*postgres*).
|
||||||
|
NB: You can provide any credentials for the test to connect to postgres, just make sure
|
||||||
|
the credentials are set in your .env file as explained above in *Database Configuration Parameters* section
|
||||||
|
|
||||||
|
When the tests are run, a **test_db** database is created and then the required tables for
|
||||||
|
the test. Before the tests run, the *test_db* is deleted to ensure that each test starts
|
||||||
|
on a clean slate
|
6
persistence/__init__.py
Normal file
6
persistence/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from .base_repo import BaseRepo
|
||||||
|
from .repositories.city_repo import CityRepo
|
||||||
|
from .repositories.heat_pump_simulation_repo import HeatPumpSimulationRepo
|
||||||
|
from .db_setup import DBSetup
|
||||||
|
from .repositories.user_repo import UserRepo
|
||||||
|
from .models.user import UserRoles
|
27
persistence/base_repo.py
Normal file
27
persistence/base_repo.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"""
|
||||||
|
Base repository class to establish db connection
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
from persistence.db_config import BaseConfiguration
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
|
class BaseRepo:
|
||||||
|
|
||||||
|
def __init__(self, db_name, dotenv_path: str, app_env='TEST'):
|
||||||
|
try:
|
||||||
|
self.config = BaseConfiguration(db_name, dotenv_path, app_env)
|
||||||
|
self.engine = create_engine(self.config.conn_string())
|
||||||
|
self.session = Session(self.engine)
|
||||||
|
except ValueError as err:
|
||||||
|
print(f'Missing value for credentials: {err}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
49
persistence/db_config.py
Normal file
49
persistence/db_config.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
"""
|
||||||
|
Persistence (Postgresql) configuration
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from hub_logger import logger
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfiguration(object):
|
||||||
|
"""
|
||||||
|
Base configuration class to hold common persistence configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env='TEST'):
|
||||||
|
"""
|
||||||
|
:param db_name: database name
|
||||||
|
:param app_env: application environment, test or production
|
||||||
|
:param dotenv_path: the absolute path to dotenv file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# load environmental variables
|
||||||
|
load_dotenv(dotenv_path=dotenv_path)
|
||||||
|
self._db_name = db_name
|
||||||
|
self._db_host = os.getenv(f'{app_env}_DB_HOST')
|
||||||
|
self._db_user = os.getenv(f'{app_env}_DB_USER')
|
||||||
|
self._db_pass = os.getenv(f'{app_env}_DB_PASSWORD')
|
||||||
|
self._db_port = os.getenv(f'{app_env}_DB_PORT')
|
||||||
|
self.hub_token = os.getenv('HUB_TOKEN')
|
||||||
|
except KeyError as err:
|
||||||
|
logger.error(f'Error with credentials: {err}')
|
||||||
|
|
||||||
|
def conn_string(self):
|
||||||
|
"""
|
||||||
|
Returns a connection string postgresql
|
||||||
|
:return: connection string
|
||||||
|
"""
|
||||||
|
if self._db_pass:
|
||||||
|
return f'postgresql://{self._db_user}:{self._db_pass}@{self._db_host}:{self._db_port}/{self._db_name}'
|
||||||
|
return f'postgresql://{self._db_user}@{self._db_host}:{self._db_port}/{self._db_name}'
|
||||||
|
|
||||||
|
def get_db_user(self):
|
||||||
|
return self._db_user
|
37
persistence/db_setup.py
Normal file
37
persistence/db_setup.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from persistence.models import City
|
||||||
|
from persistence import BaseRepo
|
||||||
|
from persistence.models import HeatPumpSimulation
|
||||||
|
from persistence.models import User
|
||||||
|
from persistence.repositories import UserRepo
|
||||||
|
from persistence.models import UserRoles
|
||||||
|
from hub_logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
class DBSetup:
|
||||||
|
|
||||||
|
def __init__(self, db_name, app_env, dotenv_path):
|
||||||
|
"""
|
||||||
|
Creates database tables and a default admin user
|
||||||
|
:param db_name:
|
||||||
|
:param app_env:
|
||||||
|
:param dotenv_path:
|
||||||
|
"""
|
||||||
|
repo = BaseRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
City.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
HeatPumpSimulation.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
User.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
self._user_repo = UserRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
self._create_admin_user(self._user_repo)
|
||||||
|
|
||||||
|
def _create_admin_user(self, user_repo):
|
||||||
|
email = 'admin@hub.com'
|
||||||
|
password = 'HubAdmin#!98'
|
||||||
|
print('Creating default admin user...')
|
||||||
|
user = user_repo.insert('Administrator', email, password, UserRoles.Admin)
|
||||||
|
if type(user) is dict:
|
||||||
|
print(user)
|
||||||
|
logger.info(user)
|
||||||
|
else:
|
||||||
|
print(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin}')
|
||||||
|
logger.info(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin}')
|
||||||
|
print('Remember to change the admin default password and email address with the UserFactory')
|
5
persistence/models/__init__.py
Normal file
5
persistence/models/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from .city import City
|
||||||
|
from .heat_pump_simulation import HeatPumpSimulation
|
||||||
|
from .heat_pump_simulation import SimulationTypes
|
||||||
|
from .heat_pump_simulation import HeatPumpTypes
|
||||||
|
from .user import User, UserRoles
|
42
persistence/models/city.py
Normal file
42
persistence/models/city.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"""
|
||||||
|
Model representation of a City
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence
|
||||||
|
from sqlalchemy import DateTime, PickleType, Float
|
||||||
|
from persistence.db_config import Base
|
||||||
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class City(Base):
|
||||||
|
"""A model representation of a city
|
||||||
|
"""
|
||||||
|
__tablename__ = "city"
|
||||||
|
id = Column(Integer, Sequence('city_id_seq'), primary_key=True)
|
||||||
|
city = Column(PickleType, nullable=False)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
srs_name = Column(String, nullable=False)
|
||||||
|
climate_reference_city = Column(String, nullable=True)
|
||||||
|
time_zone = Column(String, nullable=True)
|
||||||
|
country_code = Column(String, nullable=False)
|
||||||
|
latitude = Column(Float)
|
||||||
|
longitude = Column(Float)
|
||||||
|
lower_corner = Column(JSONB, nullable=False)
|
||||||
|
upper_corner = Column(JSONB, nullable=False)
|
||||||
|
hub_release = Column(String, nullable=False)
|
||||||
|
city_version = Column(Integer, nullable=False)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, city, name, srs_name, country_code, l_corner, u_corner):
|
||||||
|
self.city = city
|
||||||
|
self.name = name
|
||||||
|
self.srs_name = srs_name
|
||||||
|
self.country_code = country_code
|
||||||
|
self.lower_corner = l_corner.tolist()
|
||||||
|
self.upper_corner = u_corner.tolist()
|
||||||
|
|
||||||
|
|
86
persistence/models/heat_pump_simulation.py
Normal file
86
persistence/models/heat_pump_simulation.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
"""
|
||||||
|
Model representation of the results of heat pump simulation
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence
|
||||||
|
from sqlalchemy import Enum, ForeignKey, Float, DateTime
|
||||||
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
|
from persistence.db_config import Base
|
||||||
|
import enum
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationTypes(enum.Enum):
|
||||||
|
Parallel = 'PARALLEL'
|
||||||
|
Series = 'SERIES'
|
||||||
|
|
||||||
|
|
||||||
|
class HeatPumpTypes(enum.Enum):
|
||||||
|
Air = 'Air Source'
|
||||||
|
Water = 'Water to Water'
|
||||||
|
|
||||||
|
|
||||||
|
class HeatPumpSimulation(Base):
|
||||||
|
"""A model representation of a building
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
city_id, A reference to the city which was used to run this simulation.
|
||||||
|
hourly_electricity_demand, A JSON object that has hours and their electricity demand
|
||||||
|
daily_electricity_demand, A JSON object that has days and their electricity demand
|
||||||
|
monthly_electricity_demand, A JSON object that has months and their electricity demand
|
||||||
|
daily_fossil_fuel_consumption, A JSON object that has days and fossil fuel consumption
|
||||||
|
monthly_fossil_fuel_consumption, A JSON object that has months and fossil fuel consumption
|
||||||
|
heat_pump_type, Water or air heat pump
|
||||||
|
simulation_type, The type of heat pump simulation (parallel or series)
|
||||||
|
heat_pump_model, The model of the heat pump (either water to water or air source)
|
||||||
|
start year, HP simulation start year
|
||||||
|
end year, HP simulation end year
|
||||||
|
max_hp_energy_input, Maximum heat pump energy input
|
||||||
|
max_demand_storage_hour, Hours of storage at maximum demand
|
||||||
|
building_supply_temp, building supply temperature
|
||||||
|
temp_difference, Difference in HP and building supply temperatures
|
||||||
|
fuel_lhv, The lower heating value of fuel
|
||||||
|
fuel_price, The price of fuel
|
||||||
|
fuel_efficiency, the efficiency of fuel
|
||||||
|
fuel_density, the density of fuel
|
||||||
|
hp_supply_temp, supply temperature of heat pump
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
__tablename__ = "heat_pump_simulation"
|
||||||
|
id = Column(Integer, Sequence('hp_simulation_id_seq'), primary_key=True)
|
||||||
|
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
|
||||||
|
daily_electricity_demand = Column(JSONB, nullable=False)
|
||||||
|
hourly_electricity_demand = Column(JSONB, nullable=False)
|
||||||
|
daily_fossil_fuel_consumption = Column(JSONB, nullable=False)
|
||||||
|
monthly_fossil_fuel_consumption = Column(JSONB, nullable=False)
|
||||||
|
monthly_electricity_demand = Column(JSONB, nullable=False)
|
||||||
|
heat_pump_type = Column(Enum(HeatPumpTypes), nullable=False)
|
||||||
|
simulation_type = Column(Enum(SimulationTypes), nullable=False)
|
||||||
|
heat_pump_model = Column(String, nullable=False)
|
||||||
|
start_year = Column(Integer, nullable=False)
|
||||||
|
end_year = Column(Integer, nullable=False)
|
||||||
|
max_hp_energy_input = Column(Float, nullable=False)
|
||||||
|
max_demand_storage_hour = Column(Float, nullable=False)
|
||||||
|
building_supply_temp = Column(Float, nullable=False)
|
||||||
|
temp_difference = Column(Float, nullable=False)
|
||||||
|
fuel_lhv = Column(Float, nullable=False)
|
||||||
|
fuel_price = Column(Float, nullable=False)
|
||||||
|
fuel_efficiency = Column(Float, nullable=False)
|
||||||
|
fuel_density = Column(Float, nullable=False)
|
||||||
|
hp_supply_temp = Column(Float, nullable=False)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, city_id, hourly_elec_demand, daily_elec_demand, monthly_elec_demand, daily_fossil, monthly_fossil):
|
||||||
|
self.city_id = city_id
|
||||||
|
self.hourly_electricity_demand = hourly_elec_demand
|
||||||
|
self.daily_electricity_demand = daily_elec_demand
|
||||||
|
self.monthly_electricity_demand = monthly_elec_demand
|
||||||
|
self.daily_fossil_fuel_consumption = daily_fossil
|
||||||
|
self.monthly_fossil_fuel_consumption = monthly_fossil
|
||||||
|
|
||||||
|
|
||||||
|
|
45
persistence/models/user.py
Normal file
45
persistence/models/user.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
"""
|
||||||
|
Model representation of a User
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence
|
||||||
|
from sqlalchemy import DateTime, Enum
|
||||||
|
from persistence.db_config import Base
|
||||||
|
import datetime
|
||||||
|
from sqlalchemy.orm import validates
|
||||||
|
import re
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoles(enum.Enum):
|
||||||
|
Admin = 'ADMIN'
|
||||||
|
HubReader = 'HUB_READER'
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
"""A model representation of a city
|
||||||
|
"""
|
||||||
|
__tablename__ = "user"
|
||||||
|
id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
email = Column(String, nullable=False, unique=True)
|
||||||
|
password = Column(String, nullable=False)
|
||||||
|
role = Column(Enum(UserRoles), nullable=False, default=UserRoles.HubReader)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
@validates("email")
|
||||||
|
def validate_email(self, key, address):
|
||||||
|
pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
|
||||||
|
if not re.match(pattern, address):
|
||||||
|
raise ValueError("failed simple email validation")
|
||||||
|
return address
|
||||||
|
|
||||||
|
def __init__(self, name, email, password, role):
|
||||||
|
self.name = name
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
self.role = role
|
1
persistence/repositories/__init__.py
Normal file
1
persistence/repositories/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .user_repo import UserRepo
|
138
persistence/repositories/city_repo.py
Normal file
138
persistence/repositories/city_repo.py
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
"""
|
||||||
|
City repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
from city_model_structure.city import City
|
||||||
|
from persistence import BaseRepo
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy import select
|
||||||
|
from persistence.models import City as DBCity
|
||||||
|
import pickle
|
||||||
|
import requests
|
||||||
|
from urllib3.exceptions import HTTPError
|
||||||
|
from typing import Union, Dict
|
||||||
|
from hub_logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
class CityRepo(BaseRepo):
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(CityRepo, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, city: City) -> Union[City, Dict]:
|
||||||
|
db_city = DBCity(pickle.dumps(city), city.name, city.srs_name, city.country_code, city.lower_corner,
|
||||||
|
city.upper_corner)
|
||||||
|
db_city.climate_reference_city = city.climate_reference_city
|
||||||
|
db_city.longitude = city.longitude
|
||||||
|
db_city.latitude = city.latitude
|
||||||
|
db_city.time_zone = city.time_zone
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Retrieve hub project latest release
|
||||||
|
response = requests.get("https://rs-loy-gitlab.concordia.ca/api/v4/projects/2/repository/branches/master",
|
||||||
|
headers={"PRIVATE-TOKEN": self.config.hub_token})
|
||||||
|
recent_commit = response.json()["commit"]["id"]
|
||||||
|
logger.info(f'Current commit of hub is {recent_commit}')
|
||||||
|
exiting_city = self._get_by_hub_version(recent_commit, city.name)
|
||||||
|
|
||||||
|
# Do not persist the same city for the same version of Hub
|
||||||
|
if exiting_city is None:
|
||||||
|
db_city.hub_release = recent_commit
|
||||||
|
cities = self.get_by_name(city.name)
|
||||||
|
# update version for the same city but different hub versions
|
||||||
|
|
||||||
|
if len(cities) == 0:
|
||||||
|
db_city.city_version = 0
|
||||||
|
else:
|
||||||
|
db_city.city_version = cities[-1].city_version + 1
|
||||||
|
|
||||||
|
# Persist city
|
||||||
|
self.session.add(db_city)
|
||||||
|
self.session.flush()
|
||||||
|
self.session.commit()
|
||||||
|
return db_city
|
||||||
|
else:
|
||||||
|
return {'message': f'Same version of {city.name} exist'}
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while adding city: {err}')
|
||||||
|
except HTTPError as err:
|
||||||
|
logger.error(f'Error retrieving Hub latest release: {err}')
|
||||||
|
|
||||||
|
def get_by_id(self, city_id: int) -> DBCity:
|
||||||
|
"""
|
||||||
|
Fetch a City based on the id
|
||||||
|
:param city_id: the city id
|
||||||
|
:return: a city
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.session.execute(select(DBCity).where(DBCity.id == city_id)).first()[0]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching city: {err}')
|
||||||
|
|
||||||
|
def _get_by_hub_version(self, hub_commit: str, city_name: str) -> City:
|
||||||
|
"""
|
||||||
|
Fetch a City based on the name and hub project recent commit
|
||||||
|
:param hub_commit: the latest hub commit
|
||||||
|
:param city_name: the name of the city
|
||||||
|
:return: a city
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.session.execute(select(DBCity)
|
||||||
|
.where(DBCity.hub_release == hub_commit, DBCity.name == city_name)).first()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching city: {err}')
|
||||||
|
|
||||||
|
def update(self, city_id: int, city: City):
|
||||||
|
"""
|
||||||
|
Updates a city
|
||||||
|
:param city_id: the id of the city to be updated
|
||||||
|
:param city: the city object
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.session.query(DBCity).filter(DBCity.id == city_id) \
|
||||||
|
.update({
|
||||||
|
'name': city.name, 'srs_name': city.srs_name, 'country_code': city.country_code, 'longitude': city.longitude,
|
||||||
|
'latitude': city.latitude, 'time_zone': city.time_zone, 'lower_corner': city.lower_corner.tolist(),
|
||||||
|
'upper_corner': city.upper_corner.tolist(), 'climate_reference_city': city.climate_reference_city,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while updating city: {err}')
|
||||||
|
|
||||||
|
def get_by_name(self, city_name: str) -> [DBCity]:
|
||||||
|
"""
|
||||||
|
Fetch city based on the name
|
||||||
|
:param city_name: the name of the building
|
||||||
|
:return: [ModelCity] with the provided name
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result_set = self.session.execute(select(DBCity).where(DBCity.name == city_name))
|
||||||
|
return [building[0] for building in result_set]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching city by name: {err}')
|
||||||
|
|
||||||
|
def delete_city(self, city_id: int):
|
||||||
|
"""
|
||||||
|
Deletes a City with the id
|
||||||
|
:param city_id: the city id
|
||||||
|
:return: a city
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.session.query(DBCity).filter(DBCity.id == city_id).delete()
|
||||||
|
self.session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching city: {err}')
|
108
persistence/repositories/heat_pump_simulation_repo.py
Normal file
108
persistence/repositories/heat_pump_simulation_repo.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
"""
|
||||||
|
Heat pump simulation repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
from persistence import BaseRepo, CityRepo
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy import select
|
||||||
|
from persistence.models import HeatPumpSimulation
|
||||||
|
from typing import Union, Dict
|
||||||
|
from hub_logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
class HeatPumpSimulationRepo(BaseRepo):
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name, dotenv_path, app_env):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
self._city_repo = CityRepo(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(HeatPumpSimulationRepo, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, hp_sim_data: Dict, city_id: int) -> Union[HeatPumpSimulation, Dict]:
|
||||||
|
"""
|
||||||
|
Inserts the results of heat pump simulation
|
||||||
|
:param hp_sim_data: dictionary with heatpump the simulation inputs and output
|
||||||
|
:param city_id: the city that was used in running the simulation
|
||||||
|
:return: HeatPumpSimulation
|
||||||
|
"""
|
||||||
|
|
||||||
|
city = self._city_repo.get_by_id(city_id)
|
||||||
|
if city is None:
|
||||||
|
return {'message': 'city not found in database'}
|
||||||
|
|
||||||
|
try:
|
||||||
|
hp_simulation = HeatPumpSimulation(city_id, hp_sim_data["HourlyElectricityDemand"],
|
||||||
|
hp_sim_data["DailyElectricityDemand"], hp_sim_data["MonthlyElectricityDemand"],
|
||||||
|
hp_sim_data["DailyFossilFuelConsumption"],
|
||||||
|
hp_sim_data["MonthlyFossilFuelConsumption"])
|
||||||
|
hp_simulation.city_id = city_id
|
||||||
|
hp_simulation.end_year = hp_sim_data["EndYear"]
|
||||||
|
hp_simulation.start_year = hp_sim_data["StartYear"]
|
||||||
|
hp_simulation.max_demand_storage_hour = hp_sim_data["HoursOfStorageAtMaxDemand"]
|
||||||
|
hp_simulation.max_hp_energy_input = hp_sim_data["MaximumHPEnergyInput"]
|
||||||
|
hp_simulation.building_supply_temp = hp_sim_data["BuildingSuppTemp"]
|
||||||
|
hp_simulation.temp_difference = hp_sim_data["TemperatureDifference"]
|
||||||
|
hp_simulation.fuel_lhv = hp_sim_data["FuelLHV"]
|
||||||
|
hp_simulation.fuel_price = hp_sim_data["FuelPrice"]
|
||||||
|
hp_simulation.fuel_efficiency = hp_sim_data["FuelEF"]
|
||||||
|
hp_simulation.fuel_density = hp_sim_data["FuelDensity"]
|
||||||
|
hp_simulation.hp_supply_temp = hp_sim_data["HPSupTemp"]
|
||||||
|
hp_simulation.simulation_type = hp_sim_data["SimulationType"]
|
||||||
|
hp_simulation.heat_pump_model = hp_sim_data["HeatPumpModel"]
|
||||||
|
hp_simulation.heat_pump_type = hp_sim_data["HeatPumpType"]
|
||||||
|
|
||||||
|
# Persist heat pump simulation data
|
||||||
|
self.session.add(hp_simulation)
|
||||||
|
self.session.flush()
|
||||||
|
self.session.commit()
|
||||||
|
return hp_simulation
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while saving heat pump simulation data: {err}')
|
||||||
|
except KeyError as err:
|
||||||
|
logger.error(f'A required field is missing in your heat pump simulation dictionary: {err}')
|
||||||
|
|
||||||
|
def get_by_id(self, hp_simulation_id: int) -> HeatPumpSimulation:
|
||||||
|
"""
|
||||||
|
Fetches heat pump simulation data
|
||||||
|
:param hp_simulation_id: the city id
|
||||||
|
:return: a HeatPumpSimulation
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.session.execute(select(HeatPumpSimulation).where(HeatPumpSimulation.id == hp_simulation_id)).first()[
|
||||||
|
0]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching city: {err}')
|
||||||
|
|
||||||
|
def get_by_city(self, city_id: int) -> [HeatPumpSimulation]:
|
||||||
|
"""
|
||||||
|
Fetch heat pump simulation results by city
|
||||||
|
:param city_id: the name of the building
|
||||||
|
:return: [HeatPumpSimulation] with the provided name
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result_set = self.session.execute(select(HeatPumpSimulation).where(HeatPumpSimulation.city_id == city_id))
|
||||||
|
return [sim_data[0] for sim_data in result_set]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching city by name: {err}')
|
||||||
|
|
||||||
|
def delete_hp_simulation(self, hp_simulation_id: int):
|
||||||
|
"""
|
||||||
|
Deletes a heat pump simulation results
|
||||||
|
:param hp_simulation_id: the heat pump simulation results id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.session.query(HeatPumpSimulation).filter(HeatPumpSimulation.id == hp_simulation_id).delete()
|
||||||
|
self.session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching city: {err}')
|
102
persistence/repositories/user_repo.py
Normal file
102
persistence/repositories/user_repo.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
"""
|
||||||
|
City repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
from persistence import BaseRepo
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy import select
|
||||||
|
from persistence.models import User
|
||||||
|
from persistence.models import UserRoles
|
||||||
|
from helpers.auth import Auth
|
||||||
|
from typing import Union, Dict
|
||||||
|
from hub_logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
class UserRepo(BaseRepo):
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(UserRepo, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, name: str, email: str, password: str, role: UserRoles) -> Union[User, Dict]:
|
||||||
|
user = self.get_by_email(email)
|
||||||
|
if user is None:
|
||||||
|
try:
|
||||||
|
if Auth.validate_password(password):
|
||||||
|
user = User(name=name, email=email, password=Auth.hash_password(password), role=role)
|
||||||
|
self.session.add(user)
|
||||||
|
self.session.flush()
|
||||||
|
self.session.commit()
|
||||||
|
return user
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'An error occured while creating user: {err}')
|
||||||
|
else:
|
||||||
|
return {'message': f'user with {email} email already exists'}
|
||||||
|
|
||||||
|
def update(self, user_id: int, name: str, email: str, password: str, role: UserRoles):
|
||||||
|
"""
|
||||||
|
Updates a user
|
||||||
|
:param user_id: the id of the user to be updated
|
||||||
|
:param name: the name of the user
|
||||||
|
:param email: the email of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
:param role: the role of the user
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if Auth.validate_password(password):
|
||||||
|
self.session.query(User).filter(User.id == user_id) \
|
||||||
|
.update({'name': name, 'email': email, 'password': Auth.hash_password(password), 'role': role})
|
||||||
|
self.session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while updating user: {err}')
|
||||||
|
|
||||||
|
def get_by_email(self, email: str) -> [User]:
|
||||||
|
"""
|
||||||
|
Fetch user based on the email address
|
||||||
|
:param email: the email of the user
|
||||||
|
:return: [User] with the provided email
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.session.execute(select(User).where(User.email == email)).first()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching user by email: {err}')
|
||||||
|
|
||||||
|
def delete_user(self, user_id: int):
|
||||||
|
"""
|
||||||
|
Deletes a user with the id
|
||||||
|
:param user_id: the user id
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.session.query(User).filter(User.id == user_id).delete()
|
||||||
|
self.session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching user: {err}')
|
||||||
|
|
||||||
|
def get_user_by_email_and_password(self, email: str, password: str) -> [User]:
|
||||||
|
"""
|
||||||
|
Fetch user based on the email and password
|
||||||
|
:param email: the email of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
:return: [User] with the provided email and password
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = self.session.execute(select(User).where(User.email == email)).first()
|
||||||
|
if user:
|
||||||
|
if Auth.check_password(password, user[0].password):
|
||||||
|
return user
|
||||||
|
return {'message': 'user not found'}
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logger.error(f'Error while fetching user by email: {err}')
|
|
@ -16,6 +16,9 @@ rhino3dm==7.7.0
|
||||||
scipy
|
scipy
|
||||||
PyYAML
|
PyYAML
|
||||||
pyecore==0.12.2
|
pyecore==0.12.2
|
||||||
|
python-dotenv
|
||||||
|
SQLAlchemy
|
||||||
|
bcrypt==4.0.1
|
||||||
shapely
|
shapely
|
||||||
geopandas
|
geopandas
|
||||||
triangle
|
triangle
|
||||||
|
|
90
unittests/test_db_factory.py
Normal file
90
unittests/test_db_factory.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
"""
|
||||||
|
Test EnergySystemsFactory and various heatpump models
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
from unittest import TestCase
|
||||||
|
from imports.geometry_factory import GeometryFactory
|
||||||
|
from imports.db_factory import DBFactory
|
||||||
|
from exports.db_factory import DBFactory as ExportDBFactory
|
||||||
|
from persistence.base_repo import BaseRepo
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from persistence.models import City
|
||||||
|
from pickle import loads
|
||||||
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
|
||||||
|
|
||||||
|
class TestDBFactory(TestCase):
|
||||||
|
"""
|
||||||
|
TestDBFactory
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
"""
|
||||||
|
Test setup
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# Create test database
|
||||||
|
repo = BaseRepo(db_name='test_db', app_env='TEST', dotenv_path='../.env')
|
||||||
|
eng = create_engine(f'postgresql://{repo.config.get_db_user()}@/{repo.config.get_db_user()}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# delete test database if it exists
|
||||||
|
conn = eng.connect()
|
||||||
|
conn.execute('commit')
|
||||||
|
conn.execute('DROP DATABASE test_db')
|
||||||
|
conn.close()
|
||||||
|
except ProgrammingError as err:
|
||||||
|
print(f'Database does not exist. Nothing to delete')
|
||||||
|
|
||||||
|
cnn = eng.connect()
|
||||||
|
cnn.execute('commit')
|
||||||
|
cnn.execute("CREATE DATABASE test_db")
|
||||||
|
cnn.close()
|
||||||
|
|
||||||
|
City.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
|
||||||
|
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||||
|
cls.city = GeometryFactory('citygml', city_file).city
|
||||||
|
cls._db_factory = DBFactory(city=cls.city, db_name='test_db', app_env='TEST', dotenv_path='../.env')
|
||||||
|
cls._export_db_factory = ExportDBFactory(db_name='test_db', app_env='TEST', dotenv_path='../.env')
|
||||||
|
|
||||||
|
def test_save_city(self):
|
||||||
|
saved_city = self._db_factory.persist_city()
|
||||||
|
self.assertEqual(saved_city.name, 'Montréal')
|
||||||
|
pickled_city = loads(saved_city.city)
|
||||||
|
self.assertEqual(len(pickled_city.buildings), 10)
|
||||||
|
self.assertEqual(pickled_city.buildings[0].floor_area, 1990.9913970530033)
|
||||||
|
self._db_factory.delete_city(saved_city.id)
|
||||||
|
|
||||||
|
def test_save_same_city_with_same_hub_version(self):
|
||||||
|
first_city = self._db_factory.persist_city()
|
||||||
|
second_city = self._db_factory.persist_city()
|
||||||
|
self.assertEqual(second_city['message'], f'Same version of {self.city.name} exist')
|
||||||
|
self.assertEqual(first_city.name, 'Montréal')
|
||||||
|
self.assertEqual(first_city.country_code, 'ca')
|
||||||
|
self._db_factory.delete_city(first_city.id)
|
||||||
|
|
||||||
|
def test_get_city_by_name(self):
|
||||||
|
city = self._db_factory.persist_city()
|
||||||
|
retrieved_city = self._export_db_factory.get_city_by_name(city.name)
|
||||||
|
self.assertEqual(retrieved_city[0].lower_corner[0], 610610.7547462888)
|
||||||
|
self._db_factory.delete_city(city.id)
|
||||||
|
|
||||||
|
def test_get_city_by_id(self):
|
||||||
|
city = self._db_factory.persist_city()
|
||||||
|
retrieved_city = self._export_db_factory.get_city(city.id)
|
||||||
|
self.assertEqual(retrieved_city.upper_corner[0], 610818.6731258357)
|
||||||
|
self._db_factory.delete_city(city.id)
|
||||||
|
|
||||||
|
def test_get_update_city(self):
|
||||||
|
city = self._db_factory.persist_city()
|
||||||
|
self.city.longitude = 1.43589
|
||||||
|
self.city.latitude = -9.38928339
|
||||||
|
self._db_factory.update_city(city.id, self.city)
|
||||||
|
updated_city = self._export_db_factory.get_city(city.id)
|
||||||
|
self.assertEqual(updated_city.longitude, 1.43589)
|
||||||
|
self.assertEqual(updated_city.latitude, -9.38928339)
|
||||||
|
self._db_factory.delete_city(city.id)
|
|
@ -12,6 +12,21 @@ from city_model_structure.energy_systems.air_source_hp import AirSourceHP
|
||||||
from exports.energy_systems_factory import EnergySystemsExportFactory
|
from exports.energy_systems_factory import EnergySystemsExportFactory
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# User defined paramenters
|
||||||
|
user_input = {
|
||||||
|
'StartYear': 2020,
|
||||||
|
'EndYear': 2021,
|
||||||
|
'MaximumHPEnergyInput': 8000,
|
||||||
|
'HoursOfStorageAtMaxDemand': 1,
|
||||||
|
'BuildingSuppTemp': 40,
|
||||||
|
'TemperatureDifference': 15,
|
||||||
|
'FuelLHV': 47100,
|
||||||
|
'FuelPrice': 0.12,
|
||||||
|
'FuelEF': 1887,
|
||||||
|
'FuelDensity': 0.717,
|
||||||
|
'HPSupTemp': 60
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestEnergySystemsFactory(TestCase):
|
class TestEnergySystemsFactory(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -34,27 +49,20 @@ class TestEnergySystemsFactory(TestCase):
|
||||||
self.assertEqual(self._city.energy_systems[0].air_source_hp.model, '012')
|
self.assertEqual(self._city.energy_systems[0].air_source_hp.model, '012')
|
||||||
self.assertEqual(self._city.energy_systems[16].air_source_hp.model, '140')
|
self.assertEqual(self._city.energy_systems[16].air_source_hp.model, '140')
|
||||||
|
|
||||||
def test_air_source_heat_pump_export(self):
|
def test_air_source_series_heat_pump_export(self):
|
||||||
# User defined paramenters
|
EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='012',
|
||||||
user_input = {
|
output_path=self._output_path).export()
|
||||||
'StartYear': 2020,
|
|
||||||
'EndYear': 2021,
|
|
||||||
'MaximumHPEnergyInput': 8000,
|
|
||||||
'HoursOfStorageAtMaxDemand': 1,
|
|
||||||
'BuildingSuppTemp': 40,
|
|
||||||
'TemperatureDifference': 15,
|
|
||||||
'FuelLHV': 47100,
|
|
||||||
'FuelPrice': 0.12,
|
|
||||||
'FuelEF': 1887,
|
|
||||||
'FuelDensity': 0.717,
|
|
||||||
'HPSupTemp': 60
|
|
||||||
}
|
|
||||||
|
|
||||||
EnergySystemsExportFactory(self._city, user_input, '012', self._output_path).export()
|
|
||||||
df = pd.read_csv(self._output_path)
|
df = pd.read_csv(self._output_path)
|
||||||
self.assertEqual(df.shape, (13, 3))
|
self.assertEqual(df.shape, (13, 3))
|
||||||
self.assertEqual(df.iloc[0, 1], 1867715.88)
|
self.assertEqual(df.iloc[0, 1], 1867715.88)
|
||||||
|
|
||||||
|
def test_air_source_parallel_heat_pump_export(self):
|
||||||
|
output = EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='018',
|
||||||
|
output_path=None, sim_type=1).export()
|
||||||
|
self.assertEqual(output["hourly_electricity_demand"][0], 38748.5625)
|
||||||
|
self.assertIsNotNone(output["daily_fossil_consumption"])
|
||||||
|
self.assertEqual(len(output["hourly_electricity_demand"]), 8760)
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
try:
|
try:
|
||||||
os.remove(self._output_path)
|
os.remove(self._output_path)
|
||||||
|
|
|
@ -13,6 +13,21 @@ import pandas as pd
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# User defined paramenters
|
||||||
|
user_input = {
|
||||||
|
'StartYear': 2020,
|
||||||
|
'EndYear': 2021,
|
||||||
|
'MaximumHPEnergyInput': 8000,
|
||||||
|
'HoursOfStorageAtMaxDemand': 1,
|
||||||
|
'BuildingSuppTemp': 40,
|
||||||
|
'TemperatureDifference': 15,
|
||||||
|
'FuelLHV': 47100,
|
||||||
|
'FuelPrice': 0.12,
|
||||||
|
'FuelEF': 1887,
|
||||||
|
'FuelDensity': 0.717,
|
||||||
|
'HPSupTemp': 60
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestEnergySystemsFactory(TestCase):
|
class TestEnergySystemsFactory(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -62,11 +77,11 @@ class TestEnergySystemsFactory(TestCase):
|
||||||
'b11': 10
|
'b11': 10
|
||||||
}
|
}
|
||||||
|
|
||||||
EnergySystemsExportFactory(self._city, user_input, 'ClimateMaster 156 kW', self._output_path).export('water')
|
EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='ClimateMaster 256 kW',
|
||||||
|
output_path=self._output_path, sim_type=1).export('water')
|
||||||
df = pd.read_csv(self._output_path)
|
df = pd.read_csv(self._output_path)
|
||||||
print(df.shape)
|
self.assertEqual(df.shape, (13, 3))
|
||||||
#self.assertEqual(df.shape, (13, 3))
|
self.assertEqual(df.iloc[0, 1], 1031544.62)
|
||||||
#self.assertEqual(df.iloc[0, 1], 3045398.0)
|
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
try:
|
try:
|
||||||
|
|
118
unittests/test_heat_pump_simulation.py
Normal file
118
unittests/test_heat_pump_simulation.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
"""
|
||||||
|
Test EnergySystemsFactory and various heatpump models
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
from unittest import TestCase
|
||||||
|
from imports.geometry_factory import GeometryFactory
|
||||||
|
from imports.energy_systems_factory import EnergySystemsFactory
|
||||||
|
from exports.energy_systems_factory import EnergySystemsExportFactory
|
||||||
|
from imports.db_factory import DBFactory
|
||||||
|
from exports.db_factory import DBFactory as ExportDBFactory
|
||||||
|
from persistence.base_repo import BaseRepo
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from persistence.models import City
|
||||||
|
from persistence.models import SimulationTypes
|
||||||
|
from persistence.models import HeatPumpTypes
|
||||||
|
from persistence.models import HeatPumpSimulation
|
||||||
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
|
||||||
|
# User defined paramenters
|
||||||
|
hp_sim_data = {
|
||||||
|
'StartYear': 2020,
|
||||||
|
'EndYear': 2021,
|
||||||
|
'MaximumHPEnergyInput': 8000,
|
||||||
|
'HoursOfStorageAtMaxDemand': 1,
|
||||||
|
'BuildingSuppTemp': 40,
|
||||||
|
'TemperatureDifference': 15,
|
||||||
|
'FuelLHV': 47100,
|
||||||
|
'FuelPrice': 0.12,
|
||||||
|
'FuelEF': 1887,
|
||||||
|
'FuelDensity': 0.717,
|
||||||
|
'HPSupTemp': 60
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestHeatPumpSimulation(TestCase):
|
||||||
|
"""
|
||||||
|
Heat pump simulation test cases
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
"""
|
||||||
|
Test setup
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
repo = BaseRepo(db_name='test_db', app_env='TEST', dotenv_path='../.env')
|
||||||
|
eng = create_engine(f'postgresql://{repo.config.get_db_user()}@/{repo.config.get_db_user()}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = eng.connect()
|
||||||
|
conn.execute('commit')
|
||||||
|
conn.execute('DROP DATABASE test_db')
|
||||||
|
conn.close()
|
||||||
|
except ProgrammingError as err:
|
||||||
|
print(f'Database does not exist. Nothing to delete')
|
||||||
|
|
||||||
|
cnn = eng.connect()
|
||||||
|
cnn.execute('commit')
|
||||||
|
cnn.execute("CREATE DATABASE test_db")
|
||||||
|
cnn.close()
|
||||||
|
|
||||||
|
# Create test tables if they do not exit
|
||||||
|
City.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
HeatPumpSimulation.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
|
||||||
|
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||||
|
cls._city = GeometryFactory('citygml', city_file).city
|
||||||
|
EnergySystemsFactory('air source hp', cls._city).enrich()
|
||||||
|
|
||||||
|
cls._db_factory = DBFactory(city=cls._city, db_name='test_db', app_env='TEST', dotenv_path='../.env')
|
||||||
|
cls._export_db_factory = ExportDBFactory(db_name='test_db', app_env='TEST', dotenv_path='../.env')
|
||||||
|
|
||||||
|
def test_heat_pump_simulation_persistence(self):
|
||||||
|
output = EnergySystemsExportFactory(city=self._city, user_input=hp_sim_data, hp_model='018',
|
||||||
|
output_path=None, sim_type=1).export()
|
||||||
|
hp_sim_data["HeatPumpModel"] = '018'
|
||||||
|
hp_sim_data["SimulationType"] = SimulationTypes.Parallel
|
||||||
|
hp_sim_data["HeatPumpType"] = HeatPumpTypes.Air
|
||||||
|
hp_sim_data["HourlyElectricityDemand"] = output["hourly_electricity_demand"]
|
||||||
|
hp_sim_data["DailyElectricityDemand"] = output["daily_electricity_demand"]
|
||||||
|
hp_sim_data["MonthlyElectricityDemand"] = output["monthly_electricity_demand"]
|
||||||
|
hp_sim_data["DailyFossilFuelConsumption"] = output["daily_fossil_consumption"]
|
||||||
|
hp_sim_data["MonthlyFossilFuelConsumption"] = output["monthly_fossil_consumption"]
|
||||||
|
|
||||||
|
saved_city = self._db_factory.persist_city()
|
||||||
|
hp_sim = self._db_factory.persist_hp_simulation(hp_sim_data, saved_city.id)
|
||||||
|
self.assertEqual(hp_sim.heat_pump_type, HeatPumpTypes.Air)
|
||||||
|
self.assertEqual(hp_sim.simulation_type, SimulationTypes.Parallel)
|
||||||
|
self.assertEqual(hp_sim.fuel_efficiency, hp_sim_data["FuelEF"])
|
||||||
|
self.assertEqual(hp_sim.monthly_electricity_demand, output["monthly_electricity_demand"])
|
||||||
|
self._db_factory.delete_hp_simulation(hp_sim.id)
|
||||||
|
self._db_factory.delete_city(saved_city.id)
|
||||||
|
|
||||||
|
def test_get_heat_pump_simulation_by_city(self):
|
||||||
|
output = EnergySystemsExportFactory(city=self._city, user_input=hp_sim_data, hp_model='012',
|
||||||
|
output_path=None, sim_type=0).export()
|
||||||
|
hp_sim_data["HeatPumpModel"] = '012'
|
||||||
|
hp_sim_data["SimulationType"] = SimulationTypes.Series
|
||||||
|
hp_sim_data["HeatPumpType"] = HeatPumpTypes.Air
|
||||||
|
hp_sim_data["HourlyElectricityDemand"] = output["hourly_electricity_demand"]
|
||||||
|
hp_sim_data["DailyElectricityDemand"] = output["daily_electricity_demand"]
|
||||||
|
hp_sim_data["MonthlyElectricityDemand"] = output["monthly_electricity_demand"]
|
||||||
|
hp_sim_data["DailyFossilFuelConsumption"] = output["daily_fossil_consumption"]
|
||||||
|
hp_sim_data["MonthlyFossilFuelConsumption"] = output["monthly_fossil_consumption"]
|
||||||
|
|
||||||
|
saved_city = self._db_factory.persist_city()
|
||||||
|
self._db_factory.persist_hp_simulation(hp_sim_data, saved_city.id)
|
||||||
|
|
||||||
|
# retrieved saved simulation by city id
|
||||||
|
hp_sim = self._export_db_factory.get_hp_simulation_by_city(saved_city.id)
|
||||||
|
self.assertEqual(hp_sim[0].heat_pump_type, HeatPumpTypes.Air)
|
||||||
|
self.assertEqual(hp_sim[0].simulation_type, SimulationTypes.Series)
|
||||||
|
self.assertEqual(hp_sim[0].fuel_price, hp_sim_data["FuelPrice"])
|
||||||
|
self.assertEqual(hp_sim[0].hourly_electricity_demand, output["hourly_electricity_demand"])
|
||||||
|
self._db_factory.delete_hp_simulation(hp_sim[0].id)
|
||||||
|
self._db_factory.delete_city(saved_city.id)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user