diff --git a/.env b/.env new file mode 100644 index 00000000..82c1fada --- /dev/null +++ b/.env @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5cb348dd..edbb2b1c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ /data/energy_systems/heat_pumps/*.csv /data/energy_systems/heat_pumps/*.insel .DS_Store +.env +logs **/__pycache__/ diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 00000000..4613b954 --- /dev/null +++ b/DEPLOYMENT.md @@ -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 with encrypted password '';` in the psql console to create a user. +3. Execute `create database ;` in the psql console to create a database. +4. Execute `grant all privileges on database to ;` 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** diff --git a/city_model_structure/city.py b/city_model_structure/city.py index 39e9736c..e8ef59d4 100644 --- a/city_model_structure/city.py +++ b/city_model_structure/city.py @@ -14,7 +14,6 @@ import pyproj from typing import List, Union from pyproj import Transformer from pathlib import Path - from city_model_structure.building import Building from city_model_structure.city_object import CityObject from city_model_structure.city_objects_cluster import CityObjectsCluster @@ -96,7 +95,7 @@ class City: @property def country_code(self): """ - Get city country code + Get models country code :return: str """ 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_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_reference_city = self.climate_reference_city for city_object in self.city_objects: location = city_object.centroid if location is not None: - distance = math.sqrt(math.pow(location[0]-center[0], 2) + math.pow(location[1]-center[1], 2) - + math.pow(location[2]-center[2], 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)) if distance < radius: selected_region_city.add_city_object(city_object) return selected_region_city diff --git a/data/energy_systems/heat_pumps/constants.yaml b/data/energy_systems/heat_pumps/constants.yaml index a9d98713..f90c20d7 100644 --- a/data/energy_systems/heat_pumps/constants.yaml +++ b/data/energy_systems/heat_pumps/constants.yaml @@ -13,13 +13,12 @@ Cp: 4190 Rhow: 1000 TESDiameter: 5 AuxHeaterEfficiency: 0.9 - +HPNominalCapacity: 256 # These come from the data model according to other student's work ElecGridEF: 0.5 ElectricityPrice: 0.073 # Water to Water HP constants -HPNominalCapacity: 256 LowestPossibleLoadFlow: 4.73 HighestPossibleLoadFlow: 9.46 diff --git a/data/energy_systems/heat_pumps/w2w_parallel.txt b/data/energy_systems/heat_pumps/w2w_parallel.txt index 44bfb9ae..5d1e2934 100644 --- a/data/energy_systems/heat_pumps/w2w_parallel.txt +++ b/data/energy_systems/heat_pumps/w2w_parallel.txt @@ -1,981 +1,786 @@ - -B 1 MUL - 149.1 - 264.1 - -B 2 MUL - 259.1 - 190.1 - -B 3 MUL - 296.1 - 160.1 - 289.2 - 298.1 - 83.1 - -B 4 MUL +B 1 MUL + 251.1 + 123.1 + 245.2 252.1 - 79.1 - 151.1 + 64.1 -B 5 MUL - 79.1 - 205.1 +B 2 MUL + 237.1 + 120.1 -B 6 MUL - 33.1 - 178.1 - -B 7 MUL - 162.1 - 260.1 - -B 8 MUL - 171.1 - 297.1 - -B 9 MUL - 176.1 - 79.1 - 169.1 - -B 10 MUL - 176.1 - 79.1 - 170.1 - -B 11 MUL - 255.1 - 40.1 - -B 12 MUL - 176.1 - 164.1 - -B 13 MUL - 304.1 - 231.1 - 256.1 - -B 14 MUL - 245.1 - 233.1 - 299.1 - -B 15 MUL - 282.1 - 153.1 - -B 16 MUL - 79.1 - 192.1 - -B 17 MUL - 252.1 - 176.1 - 79.1 - 195.1 - -B 18 MUL - 304.1 - 13.1 - -B 19 MUL - 281.1 - 89.1 - 84.1 - 81.1 - -B 20 MUL - 202.1 - 258.1 - -B 21 MUL - 261.1 - 199.1 - -B 22 MUL - 146.1 - 263.1 - -B 23 MUL - 252.1 - 79.1 - 186.1 - -B 24 MUL - 252.1 - 189.1 - -B 25 MUL - 242.1 - 175.1 - -B 26 MUL - 283.1 - 193.1 - 148.1 - -B 27 MUL - 252.1 - 155.1 - -B 28 MUL - 289.1 - 257.1 - -B 29 MUL - 252.1 - 176.1 - 79.1 - 163.1 - -B 30 MUL - 253.1 - 88.1 - -B 31 MUL - 240.1 - 161.1 - -B 32 MUL - 286.1 - 145.1 - 144.1 - -B 33 MUL - 241.1 - 200.1 - -B 34 MUL +B 3 MUL 194.1 - 262.1 + 125.1 -B 35 MUL - 252.1 - 176.1 - 201.1 +B 4 MUL + 238.1 + 131.1 + 118.1 -B 36 MUL - 172.1 - 76.1 +B 5 MUL + 245.1 + 213.1 -B 37 MUL - 252.1 - 176.1 - 198.1 +B 6 MUL + 212.1 + 67.1 -B 38 MUL - 295.1 - 86.1 +B 7 MUL + 193.1 + 124.1 -B 39 MUL - 302.1 - 157.1 +B 8 MUL + 241.1 + 117.1 + 116.1 -B 40 MUL - 79.1 - 281.1 +B 9 MUL + 250.1 + 65.1 -B 41 MUL - 281.1 - 85.1 - 84.1 - 81.1 +B 10 MUL + 256.1 + 121.1 -B 42 MUL - 176.1 - 166.1 - -B 61 NOP - 72.1 - -B 62 NOP - 30.1 - -B 63 NOP - 80.1 - -B 64 NOP - 38.1 - -B 65 NOP - 71.1 - -B 66 NOP - 80.1 - -B 67 NOP +B 11 MUL + 139.1 68.1 -B 68 NOP - 289.2 +B 12 MUL + 258.1 + 190.1 + 214.1 -B 69 NOP - 11.1 +B 13 MUL + 258.1 + 12.1 -B 70 NOP - 279.1 +B 14 MUL + 211.1 + 138.1 + 201.1 + 137.1 -B 71 NOP - 176.1 +B 15 MUL + 211.1 + 138.1 + 150.1 -B 72 NOP - 71.1 +B 16 MUL + 211.1 + 141.1 -B 73 NOP - 287.1 +B 17 MUL + 138.1 + 146.1 -B 74 NOP - 38.1 - -B 75 SUM - 28.1 - 39.1 - -B 76 SUM - 176.1 - 229.1 - -B 77 SUM - 224.1 - 277.1 - -B 78 SUM - 197.1 - 280.2 - -B 79 SUM - 8.1 - 14.1 - 20.1 - -B 80 SUM - 75.1 - 227.1 - -B 81 SUM - 18.1 - 303.1 - -B 82 SUM - 67.1 - 226.1 - -B 83 SUM - 177.1 - 223.1 - -B 84 SUM - 294.1 - 254.1 - -B 85 SUM - 1.1 - 27.1 +B 18 MUL + 216.1 21.1 - 42.1 - 34.1 - 16.1 - 37.1 - 4.1 - 10.1 - 29.1 - 196.1 -B 86 SUM - 230.1 +B 19 MUL + 142.1 + 217.1 + +B 20 MUL + 236.1 + 156.1 + 71.1 + 70.1 + +B 21 MUL + 201.1 + 236.1 + +B 22 MUL + 236.1 + 200.1 + 71.1 + 70.1 + +B 23 MUL + 138.1 + 201.1 + 135.1 + +B 24 MUL + 211.1 + 201.1 + 151.1 + +B 25 MUL + 201.1 + 148.1 + +B 26 MUL + 218.1 + 147.1 + +B 27 MUL + 140.1 + 219.1 + +B 28 MUL + 202.1 + 161.1 + +B 29 MUL + 28.1 + 160.1 + +B 47 NOP + 57.1 + +B 48 NOP + 6.1 + +B 49 NOP + 62.1 + +B 50 NOP + 9.1 + +B 51 NOP + 56.1 + +B 52 NOP + 62.1 + +B 53 NOP + 54.1 + +B 54 NOP + 245.2 + +B 55 NOP + 18.1 + +B 56 NOP + 138.1 + +B 57 NOP + 56.1 + +B 58 NOP + 243.1 + +B 59 NOP + 9.1 + +B 60 SUM + 5.1 + 10.1 + +B 61 SUM + 132.1 + 235.2 + +B 62 SUM + 60.1 + 184.1 + +B 63 SUM + 53.1 + 183.1 + +B 64 SUM + 126.1 + 181.1 + +B 65 SUM + 186.1 + 191.1 + +B 66 SUM + 55.1 + 182.1 + +B 67 SUM + 55.1 + 185.1 + +B 68 SUM + 138.1 + 187.1 + +B 69 SUM + 19.1 + 16.1 + 26.1 + 17.1 + 27.1 + 25.1 + 15.1 + 24.1 + 23.1 + 14.1 + 149.1 + +B 70 SUM + 13.1 + 257.1 + +B 71 SUM + 253.1 + 215.1 + +B 72 SUM + 188.1 234.1 -B 87 SUM - 69.1 - 225.1 - -B 88 SUM - 69.1 - 228.1 - -B 89 SUM - 22.1 - 24.1 - 2.1 - 12.1 - 7.1 - 5.1 - 35.1 - 23.1 - 9.1 - 17.1 - 179.1 - -B 144 CONST -P 144 +B 116 CONST +P 116 $FuelEF % Constant value -B 145 CONST -P 145 +B 117 CONST +P 117 300 % Constant value -B 146 CONST -P 146 - $b1 % Constant value - -B 147 CONST -P 147 - $HPDisactivationTemperature % Constant value - -B 148 CONST -P 148 +B 118 CONST +P 118 $FuelPrice % Constant value -B 149 CONST -P 149 - $a1 % Constant value - -B 150 CONST -P 150 +B 119 CONST +P 119 $FuelLHV % Constant value -B 151 CONST +B 120 CONST +P 120 + $TemperatureDifference % Constant value + +B 121 CONST +P 121 + $BuildingSuppTemp % Constant value + +B 122 CONST +P 122 + $AuxHeaterEfficiency % Constant value + +B 123 CONST +P 123 + $Cp % Constant value + +B 124 CONST +P 124 + $ElecGridEF % Constant value + +B 125 CONST +P 125 + $ElectricityPrice % Constant value + +B 126 CONST +P 126 + $BuildingSuppTemp % Constant value + +B 127 CONST +P 127 + 40 % Constant value + +B 128 CONST +P 128 + $Cp % Constant value + +B 129 CONST +P 129 + $TemperatureDifference % Constant value + +B 130 CONST +P 130 + 12 % Constant value + +B 131 CONST +P 131 + 300 % Constant value + +B 132 CONST +P 132 + 10 % Constant value + +B 133 CONST +P 133 + 12 % Constant value + +B 134 CONST +P 134 + 5 % Constant value + +B 135 CONST +P 135 + $a9 % Constant value + +B 136 CONST +P 136 + 2 % Constant value + +B 137 CONST +P 137 + $a10 % Constant value + +B 138 CONST +P 138 + $HPSupTemp % Constant value + +B 139 CONST +P 139 + $Cp % Constant value + +B 140 CONST +P 140 + $a5 % Constant value + +B 141 CONST +P 141 + $a2 % Constant value + +B 142 CONST +P 142 + $a1 % Constant value + +B 143 CONST +P 143 + $HPDisactivationTemperature % Constant value + +B 144 CONST +P 144 + $MaximumHPEnergyInput % Constant value + +B 145 CONST +P 145 + $HPNominalCapacity % Constant value + +B 146 CONST +P 146 + $a4 % Constant value + +B 147 CONST +P 147 + $a3 % Constant value + +B 148 CONST +P 148 + $a6 % Constant value + +B 149 CONST +P 149 + $a11 % Constant value + +B 150 CONST +P 150 + $a7 % Constant value + +B 151 CONST P 151 $a8 % Constant value -B 152 CONST +B 152 CONST P 152 - $HPNominalCapacity % Constant value + $HPReactivationTemperature % Constant value -B 153 CONST +B 153 CONST P 153 - $TemperatureDifference % Constant value + 0 % Constant value -B 154 CONST +B 154 CONST P 154 2 % Constant value -B 155 CONST +B 155 CONST P 155 - $a2 % Constant value - -B 156 CONST -P 156 2 % Constant value -B 157 CONST +B 156 CONST +P 156 + $HPNominalCapacity % Constant value + +B 157 CONST P 157 - $BuildingSuppTemp % Constant value + 9 % Constant value -B 158 CONST +B 158 CONST P 158 - $AuxHeaterEfficiency % Constant value + 0 % Constant value -B 159 CONST +B 159 CONST P 159 - 5 % Constant value + $MaximumHPEnergyInput % Constant value -B 160 CONST +B 160 CONST P 160 $Cp % Constant value -B 161 CONST +B 161 CONST P 161 - $ElecGridEF % Constant value - -B 162 CONST -P 162 - $b5 % Constant value - -B 163 CONST -P 163 - $a10 % Constant value - -B 164 CONST -P 164 - $b4 % Constant value - -B 165 CONST -P 165 - $HPReactivationTemperature % Constant value - -B 166 CONST -P 166 - $a4 % Constant value - -B 167 CONST -P 167 - 2 % Constant value - -B 168 CONST -P 168 - 9 % Constant value - -B 169 CONST -P 169 - $b9 % Constant value - -B 170 CONST -P 170 - $a9 % Constant value - -B 171 CONST -P 171 - $LowestPossibleLoadFlow % Constant value - -B 172 CONST -P 172 - $Cp % Constant value - -B 173 CONST -P 173 - 2 % Constant value - -B 174 CONST -P 174 - $HPNominalCapacity % Constant value - -B 175 CONST -P 175 - $ElectricityPrice % Constant value - -B 176 CONST -P 176 - $HPSupTemp % Constant value - -B 177 CONST -P 177 - $BuildingSuppTemp % Constant value - -B 178 CONST -P 178 - $Cp % Constant value - -B 179 CONST -P 179 - $b11 % Constant value - -B 180 CONST -P 180 - 40 % Constant value - -B 181 CONST -P 181 - 0 % Constant value - -B 182 CONST -P 182 - $Cp % Constant value - -B 183 CONST -P 183 - $TemperatureDifference % Constant value - -B 184 CONST -P 184 - 2 % Constant value - -B 185 CONST -P 185 - 0 % Constant value - -B 186 CONST -P 186 - $b8 % Constant value - -B 187 CONST -P 187 - $MaximumHPEnergyInput % Constant value - -B 188 CONST -P 188 - 12 % Constant value - -B 189 CONST -P 189 - $b2 % Constant value - -B 190 CONST -P 190 - $b3 % Constant value - -B 191 CONST -P 191 - 2 % Constant value - -B 192 CONST -P 192 - $a6 % Constant value - -B 193 CONST -P 193 - 300 % Constant value - -B 194 CONST -P 194 - $a5 % Constant value - -B 195 CONST -P 195 - $b10 % Constant value - -B 196 CONST -P 196 - $a11 % Constant value - -B 197 CONST -P 197 - 10 % Constant value - -B 198 CONST -P 198 - $a7 % Constant value - -B 199 CONST -P 199 - $a3 % Constant value - -B 200 CONST -P 200 7.13 % Constant value -B 201 CONST -P 201 - $b7 % Constant value +B 162 CONST +P 162 + $HPNominalCapacity % Constant value -B 202 CONST -P 202 - $HighestPossibleLoadFlow % Constant value +B 163 CONST +P 163 + 0.18 % Constant value -B 203 CONST -P 203 - 12 % Constant value +B 164 CUMC + 255.3 + 7.1 + 8.1 -B 204 CONST -P 204 - $MaximumHPEnergyInput % Constant value - -B 205 CONST -P 205 - $b6 % Constant value - -B 206 CONST -P 206 - 8000 % Constant value - -B 207 CONST -P 207 - 0.38 % Constant value - -B 208 CUMC - 301.2 - 19.1 - -B 209 CUMC - 301.3 - 31.1 - 32.1 - -B 210 CUMC - 301.3 - 25.1 - 26.1 - -B 211 CUMC - 301.2 - 25.1 - 26.1 - -B 212 CUMC - 301.4 - 19.1 - -B 213 CUMC - 301.3 - 305.1 - -B 214 CUMC - 301.3 - 19.1 - -B 215 CUMC - 301.2 - 31.1 - 32.1 - -B 216 CUMC - 301.2 - 305.1 - -B 217 SCREEN - 249.1 -P 217 - '*' % Format - 'Total Electricity Cost in a Year (CAD)' % Headline - -B 218 SCREEN - 246.1 -P 218 - '*' % Format - 'Total CO2 Emissions from Auxiliary Heater (g)' % Headline - -B 219 SCREEN - 247.1 -P 219 - '*' % Format - 'Total Fossil Fuel consumption in a Year (m3)' % Headline - -B 220 SCREEN - 251.1 -P 220 - '*' % Format - 'Total Cost of the Auxiliary Heater Fuel in a Year (CAD)' % Headline - -B 221 SCREEN - 235.1 -P 221 - '*' % Format - 'HP Seasonal COP' % Headline - -B 222 SCREEN - 248.1 -P 222 - '*' % Format - 'Total CO2 Emissions from Electricity Grid (g)' % Headline - -B 223 CHS - 289.1 - -B 224 CHS - 243.1 - -B 225 CHS - 62.1 - -B 226 CHS - 74.1 - -B 227 CHS - 183.1 - -B 228 CHS - 234.1 - -B 229 CHS - 70.1 - -B 230 CHS - 11.1 - -B 231 GE - 287.1 - 165.1 -P 231 - 0 % Error tolerance - -B 232 GE - 287.1 - 147.1 -P 232 - 0 % Error tolerance - -B 233 GE - 245.1 - 171.1 -P 233 - 0 % Error tolerance - -B 234 DIV - 265.1 - 15.1 - -B 235 DIV - 250.1 - 250.2 - -B 236 DIV - 204.1 - 152.1 - -B 237 DIV +B 165 CUMC + 255.3 3.1 - 158.1 + 4.1 -B 238 DIV - 41.1 - 19.1 +B 166 CUMC + 255.2 + 3.1 + 4.1 -B 239 DIV - 306.1 - 36.1 +B 167 CUMC + 255.3 + 259.1 -B 240 DIV - 19.1 - 188.1 +B 168 CUMC + 255.2 + 7.1 + 8.1 -B 241 DIV - 187.1 - 174.1 +B 169 CUMC + 255.2 + 259.1 -B 242 DIV - 19.1 +B 170 CUMC + 255.2 + 242.1 + +B 171 CUMC + 255.3 + 242.1 + +B 172 CUMC + 255.4 + 242.1 + +B 181 CHS + 245.1 + +B 182 CHS + 48.1 + +B 183 CHS + 59.1 + +B 184 CHS + 129.1 + +B 185 CHS + 191.1 + +B 186 CHS + 18.1 + +B 187 CHS + 232.1 + +B 188 CHS 203.1 -B 243 DIV - 187.1 - 6.1 +B 189 GE + 243.1 + 143.1 +P 189 + 0 % Error tolerance -B 244 DIV - 237.1 - 150.1 +B 190 GE + 243.1 + 152.1 +P 190 + 0 % Error tolerance -B 245 DIV - 239.1 - 281.1 +B 191 DIV + 220.1 + 2.1 -B 246 CUM - 32.1 +B 192 DIV + 1.1 + 122.1 -B 247 CUM - 305.1 +B 193 DIV + 22.1 + 130.1 -B 248 CUM - 31.1 +B 194 DIV + 22.1 + 133.1 -B 249 CUM - 25.1 +B 195 DIV + 192.1 + 119.1 -B 250 CUM - 41.1 - 19.1 +B 196 DIV + 144.1 + 145.1 -B 251 CUM - 26.1 +B 197 DIV + 260.1 + 11.1 -B 252 HXS - 77.1 - 33.1 - 300.2 - 207.1 -P 252 +B 198 DIV + 20.1 + 22.1 + +B 199 DIV + 210.1 + 210.2 + +B 200 DIV + 156.1 + 69.1 + +B 201 DIV + 197.1 + 236.1 + +B 202 DIV + 159.1 + 162.1 + +B 203 DIV + 159.1 + 29.1 + +B 204 CUM + 8.1 + +B 205 CUM + 259.1 + +B 206 CUM + 7.1 + +B 207 CUM + 3.1 + +B 208 CUM + 4.1 + +B 209 CUM + 242.1 + +B 210 CUM + 20.1 + 22.1 + +B 211 HXS + 72.1 + 28.1 + 254.2 + 163.1 +P 211 1 % Mode 5000 % Overall heat transfer coefficient 1007 % Specific heat of side 1 fluid 1007 % Specific heat of side 2 fluid -B 253 GT - 69.1 - 234.1 - -B 254 GT - 301.2 - 168.1 - -B 255 GT - 41.1 - 185.1 - -B 256 GT - 278.1 - 181.1 - -B 257 GT - 289.1 - 157.1 - -B 258 GT - 245.1 - 202.1 - -B 259 EXPG - 176.1 - 154.1 - -B 260 EXPG - 79.1 - 156.1 - -B 261 EXPG - 176.1 - 173.1 - -B 262 EXPG - 79.1 - 184.1 - -B 263 EXPG - 252.1 +B 212 GT + 55.1 191.1 -B 264 EXPG - 252.1 - 167.1 +B 213 GT + 245.1 + 121.1 -B 265 READ -P 265 +B 214 GT + 233.1 + 158.1 + +B 215 GT + 255.2 + 157.1 + +B 216 GT + 20.1 + 153.1 + +B 217 EXPG + 211.1 + 154.1 + +B 218 EXPG + 138.1 + 136.1 + +B 219 EXPG + 201.1 + 155.1 + +B 220 READ +P 220 1 % Number of values to be read per record 0 % Number of records to be skipped on the first call $HeatingDemand % File name '*' % Fortran format -B 266 READ -P 266 +B 221 READ +P 221 1 % Number of values to be read per record 0 % Number of records to be skipped on the first call $WaterTemperature % File name '*' % Fortran format -B 267 WRITE - 208.1 - 208.2 -P 267 - 2 % Mode - $fileOut8 % File name - '*' % Fortran format - -B 268 WRITE - 210.1 - 210.2 - 210.3 -P 268 +B 222 WRITE + 165.1 + 165.2 + 165.3 +P 222 2 % Mode $fileOut7 % File name '*' % Fortran format -B 269 WRITE - 301.1 - 301.2 - 301.3 - 301.4 - 301.5 - 41.1 - 265.1 - 11.1 - 234.1 - 30.1 - 87.1 - 65.1 - 64.1 - 73.1 - 289.2 - 289.1 - 244.1 - 285.1 - 68.1 - 75.1 - 80.1 - 74.1 +B 223 WRITE + 255.1 + 255.2 + 255.3 + 255.4 + 255.5 + 20.1 + 220.1 + 18.1 + 191.1 + 6.1 66.1 - 82.1 - 63.1 + 51.1 + 50.1 + 58.1 + 245.2 + 245.1 + 195.1 + 240.1 + 54.1 + 60.1 62.1 - 287.4 - 288.2 - 288.1 - 287.1 - 287.2 - 287.3 - 287.4 - 287.5 - 19.1 - 238.1 - 280.2 - 25.1 - 26.1 - 31.1 - 32.1 -P 269 + 59.1 + 52.1 + 63.1 + 49.1 + 48.1 + 243.4 + 244.2 + 244.1 + 243.1 + 243.2 + 243.3 + 243.4 + 243.5 + 22.1 + 198.1 + 235.2 + 3.1 + 4.1 + 7.1 + 8.1 +P 223 2 % Mode $fileOut1 % File name '*' % Fortran format -B 270 WRITE - 213.1 - 213.2 -P 270 +B 224 WRITE + 167.1 + 167.2 +P 224 2 % Mode $fileOut9 % File name '*' % Fortran format -B 271 WRITE - 301.1 - 301.2 - 301.3 - 212.1 - 212.2 -P 271 - 2 % Mode - $fileOut10 % File name - '*' % Fortran format - -B 272 WRITE - 209.1 - 209.2 - 209.3 -P 272 +B 225 WRITE + 164.1 + 164.2 + 164.3 +P 225 2 % Mode $fileOut2 % File name '*' % Fortran format -B 273 WRITE - 211.1 - 211.2 - 211.3 -P 273 +B 226 WRITE + 166.1 + 166.2 + 166.3 +P 226 2 % Mode $fileOut3 % File name '*' % Fortran format -B 274 WRITE - 214.1 - 214.2 -P 274 - 2 % Mode - $fileOut6 % File name - '*' % Fortran format - -B 275 WRITE - 216.1 - 216.2 -P 275 +B 227 WRITE + 169.1 + 169.2 +P 227 2 % Mode $fileOut4 % File name '*' % Fortran format -B 276 WRITE - 215.1 - 215.2 - 215.3 -P 276 +B 228 WRITE + 168.1 + 168.2 + 168.3 +P 228 2 % Mode $fileOut5 % File name '*' % Fortran format -B 277 DELAY - 252.1 -P 277 - 10 % Initial value +B 229 WRITE + 171.1 + 171.2 +P 229 + 2 % Mode + $fileOut6 % File name + '*' % Fortran format -B 278 DELAY - 41.1 -P 278 - 0 % Initial value +B 230 WRITE + 170.1 + 170.2 +P 230 + 2 % Mode + $fileOut8 % File name + '*' % Fortran format -B 279 DELAY - 288.1 -P 279 +B 231 WRITE + 255.1 + 255.2 + 255.3 + 172.1 + 172.2 +P 231 + 2 % Mode + $fileOut10 % File name + '*' % Fortran format + +B 232 DELAY + 244.1 +P 232 25 % Initial value -B 280 GENGT2 - 308.1 - 308.3 - 308.4 - 308.5 - 308.7 - 308.8 - 301.1 - 301.2 - 301.3 - 301.4 -P 280 +B 233 DELAY + 20.1 +P 233 + 0 % Initial value + +B 234 DELAY + 211.1 +P 234 + 10 % Initial value + +B 235 GENGT2 + 262.1 + 262.3 + 262.4 + 262.5 + 262.7 + 262.8 + 255.1 + 255.2 + 255.3 + 255.4 +P 235 45.5 % Latitude -73.62 % Longitude -5 % UTC Time zone @@ -987,42 +792,47 @@ P 280 2 % Maximum allowed mean temperature deviation 100 % Maximum number of iterations -B 281 INT - 236.1 +B 236 INT + 196.1 -B 282 ATT - 182.1 -P 282 +B 237 ATT + 128.1 +P 237 1000 % Attenuation factor a -B 283 ATT - 244.1 -P 283 +B 238 ATT + 195.1 +P 238 $FuelDensity % Attenuation factor a -B 284 ATT - 244.1 -P 284 +B 239 ATT + 195.1 +P 239 $FuelDensity % Attenuation factor a -B 285 ATT - 3.1 -P 285 +B 240 ATT + 1.1 +P 240 1000 % Attenuation factor a -B 286 ATT - 244.1 -P 286 +B 241 ATT + 195.1 +P 241 $FuelDensity % Attenuation factor a -B 287 TANKST +B 242 ATT + 22.1 +P 242 + 12 % Attenuation factor a + +B 243 TANKST + 47.1 + 48.1 + 52.1 + 59.1 61.1 - 62.1 - 66.1 - 74.1 - 78.1 - 307.1 -P 287 + 261.1 +P 243 $TESCapacity % Tank volume 4 % Number of temperature nodes $TESDiameter % Tank diameter @@ -1032,60 +842,50 @@ P 287 1 % Effective heat conductivity 30 % Initial tank temperature -B 288 MIXER % Point 11 - 287.4 - 30.1 +B 244 MIXER % Point 11 + 243.4 + 6.1 + 49.1 63.1 - 82.1 -B 289 MIXER % Point 5 - 65.1 - 87.1 - 73.1 - 64.1 +B 245 MIXER % Point 5 + 51.1 + 66.1 + 58.1 + 50.1 -B 294 LT - 301.2 - 159.1 +B 250 LT + 18.1 + 191.1 -B 295 LT - 11.1 - 234.1 +B 251 LT + 18.1 + 191.1 -B 296 LT - 11.1 - 234.1 - -B 297 LT +B 252 LT 245.1 - 171.1 + 127.1 -B 298 LT - 289.1 - 180.1 +B 253 LT + 255.2 + 134.1 -B 299 LE - 245.1 - 202.1 -P 299 - 0 % Error tolerance - -B 300 UBHLOSS - 206.1 - 266.1 -P 300 - 400 % bp1 +B 254 UBHLOSS + 221.1 + 163.1 +P 254 + 95 % bp1 0.5 % bp2 0.05 % bp3 3 % bp4 4200 % bp5 - 1000 % bp6 + 981 % bp6 5 % bp7 20 % bp8 - 300 % bp9 + 60 % bp9 -B 301 CLOCK -P 301 +B 255 CLOCK +P 255 $StartYear % Start year $StartMonth % Start month $StartDay % Start day @@ -1101,35 +901,38 @@ P 301 5 % Increment 'm' % Unit -B 302 INV - 257.1 +B 256 INV + 213.1 -B 303 INV - 231.1 +B 257 INV + 190.1 -B 304 INV - 232.1 +B 258 INV + 189.1 -B 305 GAIN - 284.1 -P 305 +B 259 GAIN + 239.1 +P 259 300 % Gain factor g -B 306 GAIN - 204.1 -P 306 +B 260 GAIN + 144.1 +P 260 1000 % Gain factor g -B 307 SOY - 301.1 - 301.2 - 301.3 - 301.4 - 301.5 - 301.6 +B 261 SOY + 255.1 + 255.2 + 255.3 + 255.4 + 255.5 + 255.6 -B 308 MTM2 - 301.2 -P 308 +B 262 MTM2 + 255.2 +P 262 'Montreal' % Location +B 263 MAXX + 236.1 + diff --git a/data/energy_systems/heat_pumps/w2w_series.txt b/data/energy_systems/heat_pumps/w2w_series.txt index 1528a0ee..ca05aeed 100644 --- a/data/energy_systems/heat_pumps/w2w_series.txt +++ b/data/energy_systems/heat_pumps/w2w_series.txt @@ -1,1016 +1,762 @@ -B 65 INT - 198.1 +B 10 GAIN + 232.1 +P 10 + 1000 % Gain factor g -B 66 CUM - 242.1 - -B 67 CUM - 95.1 - -B 68 CUM - 84.1 - -B 69 CUM - 247.1 - -B 70 CUM - 96.1 - -B 71 CUM - 91.1 - -B 72 CUM - 81.1 - 112.1 - -B 73 MUL - 181.1 - 135.1 - -B 74 MUL - 191.1 - 179.1 - 194.1 - 226.1 - -B 75 MUL - 140.1 - 265.1 - -B 76 MUL - 181.1 - 196.1 - 128.1 - -B 77 MUL - 196.1 - 169.1 - -B 78 MUL - 263.1 - 159.1 - -B 79 MUL - 246.1 - 181.1 - 146.1 - -B 80 MUL - 268.1 - 148.1 - -B 81 MUL - 65.1 - 187.1 - 189.1 - 192.1 - -B 82 MUL - 145.1 - 264.1 - -B 83 MUL - 155.1 - 195.1 - -B 84 MUL - 202.1 - 157.1 - -B 85 MUL - 219.1 - 89.1 - -B 86 MUL - 196.1 - 65.1 - -B 87 MUL - 246.1 - 196.1 - 158.1 - -B 88 MUL - 246.1 - 152.1 - -B 89 MUL - 219.1 - 257.1 - 227.1 - -B 90 MUL - 172.1 - 267.1 - -B 91 MUL - 207.1 - 174.1 - -B 92 MUL - 199.1 - 256.1 - 269.1 - -B 93 MUL - 150.1 - 230.1 - -B 94 MUL - 200.1 - 183.1 - -B 95 MUL - 249.1 - 143.1 - 126.1 - -B 96 MUL - 251.1 - 177.1 - 171.1 - -B 97 MUL - 147.1 - 225.1 - -B 98 MUL - 203.1 - 137.1 - -B 99 MUL - 246.1 - 181.1 - 196.1 - 167.1 - -B 100 MUL - 149.1 - 164.1 - -B 101 MUL - 181.1 - 129.1 - -B 102 MUL - 170.1 - 266.1 - -B 103 MUL - 246.1 - 196.1 - 178.1 - -B 104 MUL - 221.1 - 127.1 - -B 105 MUL - 98.1 - 142.1 - -B 106 MUL - 196.1 - 176.1 - -B 107 MUL - 246.1 - 181.1 - 196.1 - 153.1 - -B 108 MUL - 229.1 - 86.1 - -B 109 MUL - 197.1 - 228.1 - -B 110 MUL - 181.1 - 196.1 - 154.1 - -B 111 MUL - 246.1 - 181.1 - 121.1 - -B 112 MUL - 65.1 - 185.1 - 189.1 - 192.1 - -B 113 MUL - 246.1 - 119.1 - -B 114 CHS - 278.4 - -B 115 CHS - 261.1 - -B 116 CHS - 144.1 - -B 117 CHS - 278.1 - -B 118 CHS - 206.1 - -B 119 CONST -P 119 - $b2 % Constant value - -B 120 CONST -P 120 - 2 % Constant value - -B 121 CONST -P 121 - $a7 % Constant value - -B 122 CONST -P 122 - 0.40 % Constant value - -B 123 CONST -P 123 - $HPNominalCapacity % Constant value - -B 124 CONST -P 124 - $a11 % Constant value - -B 125 CONST -P 125 - 2 % Constant value - -B 126 CONST -P 126 - $FuelEF % Constant value - -B 127 CONST -P 127 - 25 % Constant value - -B 128 CONST -P 128 - $b9 % Constant value - -B 129 CONST -P 129 - $b4 % Constant value - -B 130 CONST -P 130 - 10 % Constant value - -B 131 CONST -P 131 - $BuildingSuppTemp % Constant value - -B 132 CONST -P 132 - 12 % Constant value - -B 133 CONST -P 133 - $MaximumHPEnergyInput % Constant value - -B 134 CONST -P 134 - 2 % Constant value - -B 135 CONST -P 135 - $a4 % Constant value - -B 136 CONST -P 136 - 0 % Constant value - -B 137 CONST -P 137 - 7.13 % Constant value - -B 138 CONST -P 138 - $FuelLHV % Constant value - -B 139 CONST -P 139 - 2 % Constant value - -B 140 CONST -P 140 - $b1 % Constant value - -B 141 CONST -P 141 - 2 % Constant value - -B 142 CONST -P 142 - $Cp % Constant value - -B 143 CONST -P 143 - 300 % Constant value - -B 144 CONST -P 144 - $TemperatureDifference % Constant value - -B 145 CONST -P 145 - $b5 % Constant value - -B 146 CONST -P 146 - $b7 % Constant value - -B 147 CONST -P 147 - $LowestPossibleLoadFlow % Constant value - -B 148 CONST -P 148 - $a3 % Constant value - -B 149 CONST -P 149 - $Cp % Constant value - -B 150 CONST -P 150 - $HighestPossibleLoadFlow % Constant value - -B 151 CONST -P 151 - $MaximumHPEnergyInput % Constant value - -B 152 CONST -P 152 - $a2 % Constant value - -B 153 CONST -P 153 - $a10 % Constant value - -B 154 CONST -P 154 - $a9 % Constant value - -B 155 CONST -P 155 - $Cp % Constant value - -B 156 CONST -P 156 - $HPDisactivationTemperature % Constant value - -B 157 CONST -P 157 - $ElecGridEF % Constant value - -B 158 CONST -P 158 - $b8 % Constant value - -B 159 CONST -P 159 - $b3 % Constant value - -B 160 CONST -P 160 - 40 % Constant value - -B 161 CONST -P 161 - 5 % Constant value - -B 162 CONST -P 162 - 2 % Constant value - -B 163 CONST -P 163 - $MaximumHPEnergyInput % Constant value - -B 164 CONST -P 164 - $TemperatureDifference % Constant value - -B 165 CONST -P 165 - 40 % Constant value - -B 166 CONST -P 166 - $HPNominalCapacity % Constant value - -B 167 CONST -P 167 - $b10 % Constant value - -B 168 CONST -P 168 - 0 % Constant value - -B 169 CONST -P 169 - $a6 % Constant value - -B 170 CONST -P 170 - $a1 % Constant value - -B 171 CONST -P 171 - $FuelPrice % Constant value - -B 172 CONST -P 172 - $a5 % Constant value - -B 173 CONST -P 173 - 12 % Constant value - -B 174 CONST -P 174 - $ElectricityPrice % Constant value - -B 175 CONST -P 175 - $HPReactivationTemperature % Constant value - -B 176 CONST -P 176 - $b6 % Constant value - -B 177 CONST -P 177 - 300 % Constant value - -B 178 CONST -P 178 - $a8 % Constant value - -B 179 CONST -P 179 - $Cp % Constant value - -B 180 CONST -P 180 - 0 % Constant value - -B 181 CONST -P 181 - $HPSupTemp % Constant value - -B 182 CONST -P 182 - 9 % Constant value - -B 183 CONST -P 183 - 1 % Constant value - -B 184 CONST -P 184 - $b11 % Constant value - -B 185 SUM - 75.1 - 113.1 - 78.1 - 101.1 - 82.1 - 106.1 - 79.1 - 87.1 - 76.1 - 99.1 - 184.1 - -B 186 SUM - 278.5 - 115.1 - -B 187 SUM - 102.1 - 88.1 - 80.1 - 73.1 - 90.1 - 77.1 - 111.1 - 103.1 - 110.1 - 107.1 - 124.1 - -B 188 SUM - 118.1 - 262.1 - -B 189 SUM - 224.1 - 231.1 - -B 190 SUM +B 11 GAIN 130.1 - 277.2 +P 11 + 1000 % Gain factor g -B 191 SUM - 94.1 - 168.1 - -B 192 SUM - 85.1 - 220.1 - -B 193 SUM - 109.1 - 104.1 - -B 194 SUM - 131.1 - 117.1 - -B 195 SUM - 181.1 - 114.1 - -B 196 SUM - 97.1 - 92.1 - 93.1 - -B 197 SUM - 116.1 - 278.1 - -B 198 DIV +B 12 DIV + 137.1 151.1 + +B 13 DIV + 11.1 + 45.1 + +B 14 DIV + 137.1 + 28.1 + +B 15 DIV + 35.1 + 41.1 + +B 16 DIV + 219.1 + 133.1 + +B 17 DIV + 41.1 123.1 -B 199 DIV - 201.1 - 65.1 +B 18 DIV + 217.1 + 129.1 -B 200 DIV - 243.1 - 100.1 +B 19 DIV + 41.1 + 150.1 -B 201 DIV - 241.1 - 83.1 +B 20 DIV + 10.1 + 52.1 -B 202 DIV - 112.1 - 173.1 +B 21 DIV + 179.1 + 179.2 -B 203 DIV - 133.1 - 166.1 +B 22 DIV + 131.1 + 157.1 -B 204 DIV - 81.1 - 112.1 +B 23 DIV + 130.1 + 153.1 -B 205 DIV - 253.1 +B 24 DIV + 13.1 + 229.1 + +B 25 MUL + 230.1 + 111.1 + +B 26 MUL + 206.1 + 197.1 + 196.1 + +B 27 MUL + 230.1 + 134.1 + 136.1 + +B 28 MUL + 46.1 + 128.1 + +B 29 MUL + 24.1 + 229.1 + +B 30 MUL + 118.1 + 228.1 + +B 31 MUL + 206.1 + 26.1 + +B 32 MUL + 160.1 + 117.1 + 161.1 + 224.1 + +B 33 MUL + 195.1 + 29.1 + +B 34 MUL + 164.1 + 193.1 + +B 35 MUL + 229.1 + 131.1 + 167.1 + 163.1 + +B 36 MUL + 19.1 + 146.1 + +B 37 MUL + 230.1 + 24.1 + 113.1 + +B 38 MUL + 230.1 + 134.1 + 24.1 + 139.1 + +B 39 MUL + 132.1 + 226.1 + +B 40 MUL + 215.1 + 144.1 + 141.1 + +B 41 MUL + 229.1 + 22.1 + 167.1 + 163.1 + +B 42 MUL + 17.1 + 125.1 + +B 43 MUL + 24.1 + 114.1 + +B 44 MUL + 134.1 + 148.1 + +B 45 MUL + 149.1 + 162.1 + +B 46 MUL + 12.1 + 142.1 + +B 47 MUL + 216.1 + 108.1 + 140.1 + +B 48 MUL + 134.1 + 24.1 + 109.1 + +B 49 MUL + 205.1 138.1 -B 206 DIV - 133.1 - 105.1 +B 50 MUL + 227.1 + 120.1 -B 207 DIV - 112.1 - 132.1 +B 51 MUL + 20.1 + 154.1 -B 208 DIV - 72.1 - 72.2 +B 52 MUL + 119.1 + 106.1 -B 209 WRITE - 236.1 - 236.2 - 236.3 -P 209 - 2 % Mode - $fileOut5 % File name - '*' % Fortran format - -B 210 WRITE - 280.1 - 280.2 - 280.3 - 280.4 - 280.5 - 81.1 - 112.1 - 204.1 - 108.1 - 191.1 - 278.1 - 278.2 - 278.3 - 278.4 - 278.5 - 248.1 - 254.1 - 205.1 - 252.1 - 91.1 - 96.1 - 84.1 - 95.1 - 193.1 - 222.1 - 277.2 - 187.1 - 185.1 -P 210 - 2 % Mode - $fileOut1 % File name - '*' % Fortran format - -B 211 WRITE - 233.1 - 233.2 -P 211 +B 96 WRITE + 170.1 + 170.2 +P 96 2 % Mode $fileOut6 % File name '*' % Fortran format -B 212 WRITE - 280.1 - 280.2 - 280.3 - 239.1 - 239.2 -P 212 - 2 % Mode - $fileOut10 % File name - '*' % Fortran format - -B 213 WRITE - 235.1 - 235.2 -P 213 - 2 % Mode - $fileOut4 % File name - '*' % Fortran format - -B 214 WRITE - 232.1 - 232.2 -P 214 - 2 % Mode - $fileOut8 % File name - '*' % Fortran format - -B 215 WRITE - 237.1 - 237.2 - 237.3 -P 215 - 2 % Mode - $fileOut7 % File name - '*' % Fortran format - -B 216 WRITE - 234.1 - 234.2 - 234.3 -P 216 +B 97 WRITE + 174.1 + 174.2 + 174.3 +P 97 2 % Mode $fileOut2 % File name '*' % Fortran format -B 217 WRITE - 240.1 - 240.2 - 240.3 -P 217 +B 98 WRITE + 231.1 + 231.2 + 231.3 + 169.1 + 169.2 +P 98 + 2 % Mode + $fileOut10 % File name + '*' % Fortran format + +B 99 WRITE + 175.1 + 175.2 + 175.3 +P 99 2 % Mode $fileOut3 % File name '*' % Fortran format -B 218 WRITE - 238.1 - 238.2 -P 218 +B 100 WRITE + 173.1 + 173.2 +P 100 + 2 % Mode + $fileOut8 % File name + '*' % Fortran format + +B 101 WRITE + 171.1 + 171.2 + 171.3 +P 101 + 2 % Mode + $fileOut5 % File name + '*' % Fortran format + +B 102 WRITE + 172.1 + 172.2 +P 102 + 2 % Mode + $fileOut4 % File name + '*' % Fortran format + +B 103 WRITE + 231.1 + 231.2 + 231.3 + 231.4 + 231.5 + 35.1 + 41.1 + 15.1 + 33.1 + 160.1 + 234.1 + 234.2 + 234.3 + 234.4 + 234.5 + 213.1 + 214.1 + 18.1 + 219.1 + 42.1 + 47.1 + 36.1 + 40.1 + 159.1 + 232.1 + 222.2 +P 103 + 2 % Mode + $fileOut1 % File name + '*' % Fortran format + +B 104 WRITE + 168.1 + 168.2 +P 104 2 % Mode $fileOut9 % File name '*' % Fortran format -B 219 INV - 258.1 - -B 220 INV - 257.1 - -B 221 INV - 228.1 - -B 222 READ -P 222 - 1 % Number of values to be read per record - 0 % Number of records to be skipped on the first call - $HeatingDemand % File name +B 105 WRITE + 176.1 + 176.2 + 176.3 +P 105 + 2 % Mode + $fileOut7 % File name '*' % Fortran format -B 223 READ -P 223 - 1 % Number of values to be read per record - 0 % Number of records to be skipped on the first call - $WaterTemperature % File name - '*' % Fortran format +B 106 CONST +P 106 + $TemperatureDifference % Constant value -B 224 LT - 280.2 - 161.1 +B 107 CONST +P 107 + $HPDisactivationTemperature % Constant value -B 225 LT - 199.1 - 147.1 +B 108 CONST +P 108 + 1 % Constant value -B 226 LT - 278.1 - 165.1 +B 109 CONST +P 109 + $a9 % Constant value -B 227 GT - 260.1 - 180.1 +B 110 CONST +P 110 + 10 % Constant value -B 228 GT - 278.1 - 160.1 +B 111 CONST +P 111 + $a2 % Constant value -B 229 GT - 81.1 - 136.1 +B 112 CONST +P 112 + $TemperatureDifference % Constant value -B 230 GT - 199.1 - 150.1 +B 113 CONST +P 113 + $a8 % Constant value -B 231 GT - 280.2 - 182.1 +B 114 CONST +P 114 + $a6 % Constant value -B 232 CUMC - 280.2 - 247.1 +B 115 CONST +P 115 + 2 % Constant value -B 233 CUMC - 280.3 - 247.1 +B 116 CONST +P 116 + 40 % Constant value -B 234 CUMC - 280.3 - 84.1 - 95.1 +B 117 CONST +P 117 + $Cp % Constant value -B 235 CUMC - 280.2 - 242.1 +B 118 CONST +P 118 + $a5 % Constant value -B 236 CUMC - 280.2 - 84.1 - 95.1 +B 119 CONST +P 119 + $Cp % Constant value -B 237 CUMC - 280.3 - 91.1 - 96.1 +B 120 CONST +P 120 + $a3 % Constant value -B 238 CUMC - 280.3 - 242.1 +B 121 CONST +P 121 + 0 % Constant value -B 239 CUMC - 280.4 - 247.1 +B 122 CONST +P 122 + 2 % Constant value -B 240 CUMC - 280.2 - 91.1 - 96.1 +B 123 CONST +P 123 + 12 % Constant value -B 241 GAIN - 151.1 -P 241 - 1000 % Gain factor g +B 124 CONST +P 124 + 5 % Constant value -B 242 GAIN - 250.1 -P 242 - 300 % Gain factor g +B 125 CONST +P 125 + $ElectricityPrice % Constant value -B 243 GAIN - 222.1 -P 243 - 1000 % Gain factor g +B 126 CONST +P 126 + 40 % Constant value -B 246 HXS - 188.1 - 98.1 - 259.2 - 122.1 -P 246 - 1 % Mode - 5000 % Overall heat transfer coefficient - 1007 % Specific heat of side 1 fluid - 1007 % Specific heat of side 2 fluid +B 127 CONST +P 127 + $BuildingSuppTemp % Constant value -B 247 ATT - 112.1 -P 247 - 12 % Attenuation factor a +B 128 CONST +P 128 + $Cp % Constant value -B 248 ATT - 278.5 -P 248 - 3600000 % Attenuation factor a +B 129 CONST +P 129 + $FuelLHV % Constant value -B 249 ATT - 205.1 -P 249 - $FuelDensity % Attenuation factor a +B 130 CONST +P 130 + $MaximumHPEnergyInput % Constant value -B 250 ATT - 205.1 -P 250 - $FuelDensity % Attenuation factor a +B 131 CONST +P 131 + $HPNominalCapacity % Constant value -B 251 ATT - 205.1 -P 251 - $FuelDensity % Attenuation factor a +B 132 CONST +P 132 + $a1 % Constant value -B 252 ATT - 74.1 -P 252 - 1000 % Attenuation factor a +B 133 CONST +P 133 + 12 % Constant value -B 253 ATT - 74.1 -P 253 - $AuxHeaterEfficiency % Attenuation factor a +B 134 CONST +P 134 + $HPSupTemp % Constant value -B 254 ATT - 186.1 -P 254 - 3600000 % Attenuation factor a +B 135 CONST +P 135 + $HPReactivationTemperature % Constant value -B 255 MTM2 - 280.2 -P 255 - 'Montreal' % Location +B 136 CONST +P 136 + $a7 % Constant value -B 256 GE - 199.1 - 147.1 -P 256 - 0 % Error tolerance +B 137 CONST +P 137 + $MaximumHPEnergyInput % Constant value -B 257 GE - 278.1 - 175.1 -P 257 - 0 % Error tolerance +B 138 CONST +P 138 + 25 % Constant value -B 258 GE - 278.1 - 156.1 -P 258 - 0 % Error tolerance +B 139 CONST +P 139 + $a10 % Constant value -B 259 UBHLOSS - 163.1 +B 140 CONST +P 140 + $FuelPrice % Constant value + +B 141 CONST +P 141 + $FuelEF % Constant value + +B 142 CONST +P 142 + 7.13 % Constant value + +B 143 CONST +P 143 + 0 % Constant value + +B 144 CONST +P 144 + 1 % Constant value + +B 145 CONST +P 145 + 9 % Constant value + +B 146 CONST +P 146 + $ElecGridEF % Constant value + +B 147 CONST +P 147 + 2 % Constant value + +B 148 CONST +P 148 + $a4 % Constant value + +B 149 CONST +P 149 + $Cp % Constant value + +B 150 CONST +P 150 + 12 % Constant value + +B 151 CONST +P 151 + $HPNominalCapacity % Constant value + +B 152 CONST +P 152 + 0 % Constant value + +B 153 CONST +P 153 + $HPNominalCapacity % Constant value + +B 154 CONST +P 154 + 1 % Constant value + +B 155 CONST +P 155 + $a11 % Constant value + +B 156 CONST +P 156 + 0.18 % Constant value + +B 157 SUM + 39.1 + 25.1 + 50.1 + 44.1 + 30.1 + 43.1 + 27.1 + 37.1 + 48.1 + 38.1 + 155.1 + +B 158 SUM + 110.1 + 222.2 + +B 159 SUM + 34.1 + 49.1 + +B 160 SUM + 51.1 + 143.1 + +B 161 SUM + 127.1 + 201.1 + +B 162 SUM + 134.1 + 200.1 + +B 163 SUM + 31.1 + 207.1 + +B 164 SUM + 202.1 + 234.1 + +B 165 SUM + 204.1 + 210.1 + +B 166 SUM + 234.5 + 203.1 + +B 167 SUM 223.1 -P 259 - 300 % bp1 + 194.1 + +B 168 CUMC + 231.3 + 218.1 + +B 169 CUMC + 231.4 + 220.1 + +B 170 CUMC + 231.3 + 220.1 + +B 171 CUMC + 231.2 + 36.1 + 40.1 + +B 172 CUMC + 231.2 + 218.1 + +B 173 CUMC + 231.2 + 220.1 + +B 174 CUMC + 231.3 + 36.1 + 40.1 + +B 175 CUMC + 231.2 + 42.1 + 47.1 + +B 176 CUMC + 231.3 + 42.1 + 47.1 + +B 177 UBHLOSS + 233.1 + 156.1 +P 177 + 95 % bp1 0.5 % bp2 0.05 % bp3 3 % bp4 - 4190 % bp5 - 1000 % bp6 + 4200 % bp5 + 981 % bp6 5 % bp7 20 % bp8 - 300 % bp9 + 60 % bp9 -B 260 DELAY - 81.1 -P 260 - 0 % Initial value +B 178 CUM + 47.1 -B 261 DELAY - 278.5 -P 261 - 0 % Initial value +B 179 CUM + 35.1 + 41.1 -B 262 DELAY - 246.1 -P 262 - 10 % Initial value +B 180 CUM + 218.1 -B 263 EXPG - 181.1 - 120.1 +B 181 CUM + 36.1 -B 264 EXPG - 196.1 - 141.1 +B 182 CUM + 220.1 -B 265 EXPG - 246.1 - 125.1 +B 183 CUM + 42.1 -B 266 EXPG - 246.1 - 139.1 +B 184 CUM + 40.1 -B 267 EXPG - 196.1 - 162.1 +B 193 GT + 234.1 + 126.1 -B 268 EXPG - 181.1 - 134.1 +B 194 GT + 231.2 + 145.1 -B 269 LE - 199.1 - 150.1 -P 269 +B 195 GT + 35.1 + 121.1 + +B 196 GT + 209.1 + 152.1 + +B 197 GE + 234.1 + 135.1 +P 197 0 % Error tolerance -B 270 SCREEN - 71.1 -P 270 - '*' % Format - 'Total Electricity Cost in a Year (CAD)' % Headline +B 198 GE + 234.1 + 107.1 +P 198 + 0 % Error tolerance -B 271 SCREEN - 67.1 -P 271 - '*' % Format - 'Total CO2 Emissions from Auxiliary Heater (g)' % Headline +B 199 MAXX + 229.1 -B 272 SCREEN +B 200 CHS + 234.4 + +B 201 CHS + 234.1 + +B 202 CHS + 112.1 + +B 203 CHS 208.1 -P 272 - '*' % Format - 'HP Seasonal COP' % Headline -B 273 SCREEN - 66.1 -P 273 - '*' % Format - 'Total Fossil Fuel consumption in a Year (m3)' % Headline +B 204 CHS + 14.1 -B 274 SCREEN - 69.1 -P 274 - '*' % Format - 'Total Electricuty Demand of Heat Pumps in a year (kWh)' % Headline +B 205 INV + 193.1 -B 275 SCREEN - 68.1 -P 275 - '*' % Format - 'Total CO2 Emissions from Electricity Grid (g)' % Headline +B 206 INV + 198.1 -B 276 SCREEN - 70.1 -P 276 - '*' % Format - 'Total Cost of the Auxiliary Heater Fuel in a Year (CAD)' % Headline +B 207 INV + 197.1 -B 277 GENGT2 - 255.1 - 255.3 - 255.4 - 255.5 - 255.7 - 255.8 - 280.1 - 280.2 - 280.3 - 280.4 -P 277 +B 208 DELAY + 234.5 +P 208 + 0 % Initial value + +B 209 DELAY + 35.1 +P 209 + 0 % Initial value + +B 210 DELAY + 230.1 +P 210 + 10 % Initial value + +B 213 ATT + 234.5 +P 213 + 3600000 % Attenuation factor a + +B 214 ATT + 166.1 +P 214 + 3600000 % Attenuation factor a + +B 215 ATT + 18.1 +P 215 + $FuelDensity % Attenuation factor a + +B 216 ATT + 18.1 +P 216 + $FuelDensity % Attenuation factor a + +B 217 ATT + 16.1 +P 217 + $AuxHeaterEfficiency % Attenuation factor a + +B 218 ATT + 18.1 +P 218 + $FuelDensity % Attenuation factor a + +B 219 ATT + 32.1 +P 219 + 1000 % Attenuation factor a + +B 220 ATT + 41.1 +P 220 + 12 % Attenuation factor a + +B 221 MTM2 + 231.2 +P 221 + 'Montreal' % Location + +B 222 GENGT2 + 221.1 + 221.3 + 221.4 + 221.5 + 221.7 + 221.8 + 231.1 + 231.2 + 231.3 + 231.4 +P 222 45.5 % Latitude -73.62 % Longitude -5 % UTC Time zone @@ -1022,33 +768,50 @@ P 277 2 % Maximum allowed mean temperature deviation 100 % Maximum number of iterations -B 278 TANKST - 181.1 - 108.1 - 193.1 - 191.1 - 190.1 - 279.1 -P 278 - $TESCapacity % Tank volume - 4 % Number of temperature nodes - $TESDiameter % Tank diameter - $Cp % Specfic heat of fluid - $Rhow % Fluid density - 0 % Overall heat-loss coefficient - 1 % Effective heat conductivity - 30 % Initial tank temperature +B 223 LT + 231.2 + 124.1 -B 279 SOY - 280.1 - 280.2 - 280.3 - 280.4 - 280.5 - 280.6 +B 224 LT + 234.1 + 116.1 -B 280 CLOCK -P 280 +B 225 SOY + 231.1 + 231.2 + 231.3 + 231.4 + 231.5 + 231.6 + +B 226 EXPG + 230.1 + 122.1 + +B 227 EXPG + 134.1 + 115.1 + +B 228 EXPG + 24.1 + 147.1 + +B 229 INT + 23.1 + +B 230 HXS + 165.1 + 46.1 + 177.2 + 156.1 +P 230 + 1 % Mode + 5000 % Overall heat transfer coefficient + 1007 % Specific heat of side 1 fluid + 1007 % Specific heat of side 2 fluid + +B 231 CLOCK +P 231 $StartYear % Start year $StartMonth % Start month $StartDay % Start day @@ -1064,3 +827,34 @@ P 280 5 % Increment 'm' % Unit +B 232 READ +P 232 + 1 % Number of values to be read per record + 0 % Number of records to be skipped on the first call + $HeatingDemand % File name + '*' % Fortran format + +B 233 READ +P 233 + 1 % Number of values to be read per record + 0 % Number of records to be skipped on the first call + $WaterTemperature % File name + '*' % Fortran format + +B 234 TANKST + 134.1 + 33.1 + 159.1 + 160.1 + 158.1 + 225.1 +P 234 + $TESCapacity % Tank volume + 4 % Number of temperature nodes + $TESDiameter % Tank diameter + $Cp % Specfic heat of fluid + $Rhow % Fluid density + 0 % Overall heat-loss coefficient + 1 % Effective heat conductivity + 30 % Initial tank temperature + diff --git a/exports/db_factory.py b/exports/db_factory.py new file mode 100644 index 00000000..f9fa401e --- /dev/null +++ b/exports/db_factory.py @@ -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) diff --git a/exports/energy_systems/air_source_hp_export.py b/exports/energy_systems/air_source_hp_export.py index c825c8f5..5d8c1971 100644 --- a/exports/energy_systems/air_source_hp_export.py +++ b/exports/energy_systems/air_source_hp_export.py @@ -6,7 +6,7 @@ Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ from exports.energy_systems.heat_pump_export import HeatPumpExport -from typing import List, Tuple, Union +from typing import List, Dict, Union class AirSourceHPExport(HeatPumpExport): @@ -15,17 +15,18 @@ class AirSourceHPExport(HeatPumpExport): 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 city: the city object :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 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' 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]: """ @@ -43,7 +44,7 @@ class AirSourceHPExport(HeatPumpExport): return energy_system.air_source_hp.cooling_capacity_coff 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 write the necessary files @@ -54,7 +55,6 @@ class AirSourceHPExport(HeatPumpExport): :param data_type: a string that indicates whether insel should run for heat or cooling performance :return: - :return: """ 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') diff --git a/exports/energy_systems/heat_pump_export.py b/exports/energy_systems/heat_pump_export.py index d7bb17dd..0a6bc8f3 100644 --- a/exports/energy_systems/heat_pump_export.py +++ b/exports/energy_systems/heat_pump_export.py @@ -5,7 +5,7 @@ Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ import os -from typing import List, Tuple, Union, Dict +from typing import List, Union, Dict import yaml from string import Template import pandas as pd @@ -17,18 +17,18 @@ class HeatPumpExport: 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._water_temp = water_temp self._constants_path = (base_path / 'heat_pumps/constants.yaml') # 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._input_data = None self._base_path = base_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 :param user_input: a dictionary containing the user @@ -56,11 +56,11 @@ class HeatPumpExport: insel_file_handler.write(insel_template) # Now run insel 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 self._write_insel_output_headers() # User output - self._get_user_out_put() + return self._get_user_out_put() except IOError as err: print("I/O exception: {}".format(err)) finally: @@ -72,17 +72,33 @@ class HeatPumpExport: Write headers to the various csv file generated by insel :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 = { - self._input_data['fileOut1']: ['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)'], + self._input_data['fileOut1']: header, self._input_data['fileOut2']: ['Day', 'Operational Daily Emissions from Heat Pumps (g)', 'Operational Daily Emissions from Auxiliary Heater (g)'], self._input_data['fileOut3']: ['Month', 'Monthly Operational Costs of Heat Pumps (CAD)', @@ -102,7 +118,7 @@ class HeatPumpExport: file_path = file_path.strip("'") df = pd.read_csv(file_path, header=None, sep='\s+') # 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], axis=1, inplace=True) @@ -161,6 +177,8 @@ class HeatPumpExport: with open(self._constants_path) as file: constants_dict = yaml.load(file, Loader=yaml.FullLoader) for key, value in constants_dict.items(): + if key in ['LowestPossibleLoadFlow', 'HighestPossibleLoadFlow'] and self._water_temp is None: + continue self._input_data[key] = value # compute water to water HP specific values if 55 <= self._input_data['HPSupTemp'] <= 60: @@ -206,19 +224,30 @@ class HeatPumpExport: self._input_data["a10"] = a_coeff[9] 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 from output files generated by insel - :return: + :return: Dict for json output """ - 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_electricity_df = pd.read_csv(self._input_data['fileOut8'].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([df, df.agg(['sum'])]) s = pd.Series(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "Total"]) df = df.set_index([s]) df.to_csv(self._output_path) + + diff --git a/exports/energy_systems/water_to_water_hp_export.py b/exports/energy_systems/water_to_water_hp_export.py index a0be32d3..b2d98e05 100644 --- a/exports/energy_systems/water_to_water_hp_export.py +++ b/exports/energy_systems/water_to_water_hp_export.py @@ -6,7 +6,7 @@ Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ from exports.energy_systems.heat_pump_export import HeatPumpExport -from typing import List, Tuple, Union +from typing import List, Dict, Union class WaterToWaterHPExport(HeatPumpExport): @@ -15,17 +15,19 @@ class WaterToWaterHPExport(HeatPumpExport): 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 city: the city object :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 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' template_path = (base_path / tmp_file) 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]: """ @@ -39,7 +41,7 @@ class WaterToWaterHPExport(HeatPumpExport): return energy_system.water_to_water_hp.power_demand_coff 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 write the necessary files @@ -50,4 +52,4 @@ class WaterToWaterHPExport(HeatPumpExport): :return: """ 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') diff --git a/exports/energy_systems_factory.py b/exports/energy_systems_factory.py index 27442e1b..29a3c3c4 100644 --- a/exports/energy_systems_factory.py +++ b/exports/energy_systems_factory.py @@ -16,7 +16,8 @@ class EnergySystemsExportFactory: 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 @@ -26,7 +27,9 @@ class EnergySystemsExportFactory: :param sim_type: the simulation type, 0 for series 1 for parallel :param data_type: indicates whether cooling or heating data is used :param base_path: the data directory of energy systems + :param demand_path: path to hourly energy dempand file """ + self._city = city if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/energy_systems') @@ -36,6 +39,7 @@ class EnergySystemsExportFactory: self._data_type = data_type self._output_path = output_path self._sim_type = sim_type + self._demand_path = demand_path def _export_heat_pump(self, source): """ @@ -44,10 +48,10 @@ class EnergySystemsExportFactory: :return: None """ 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) 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) def export(self, source='air'): diff --git a/exports/user_factory.py b/exports/user_factory.py new file mode 100644 index 00000000..6ac06abc --- /dev/null +++ b/exports/user_factory.py @@ -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) diff --git a/helpers/auth.py b/helpers/auth.py new file mode 100644 index 00000000..161a3ca2 --- /dev/null +++ b/helpers/auth.py @@ -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')) + + + + + diff --git a/hub_logger/__init__.py b/hub_logger/__init__.py new file mode 100644 index 00000000..e440afbf --- /dev/null +++ b/hub_logger/__init__.py @@ -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) diff --git a/imports/__init__.py b/imports/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imports/db_factory.py b/imports/db_factory.py new file mode 100644 index 00000000..532ce270 --- /dev/null +++ b/imports/db_factory.py @@ -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) + diff --git a/imports/user_factory.py b/imports/user_factory.py new file mode 100644 index 00000000..ebfd3ee2 --- /dev/null +++ b/imports/user_factory.py @@ -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) diff --git a/install_postgresql_linux.sh b/install_postgresql_linux.sh new file mode 100755 index 00000000..70088df0 --- /dev/null +++ b/install_postgresql_linux.sh @@ -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 \ No newline at end of file diff --git a/persistence/README.md b/persistence/README.md new file mode 100644 index 00000000..157edd6d --- /dev/null +++ b/persistence/README.md @@ -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 diff --git a/persistence/__init__.py b/persistence/__init__.py new file mode 100644 index 00000000..caebc883 --- /dev/null +++ b/persistence/__init__.py @@ -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 diff --git a/persistence/base_repo.py b/persistence/base_repo.py new file mode 100644 index 00000000..266fb4bf --- /dev/null +++ b/persistence/base_repo.py @@ -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}') + + + + + + diff --git a/persistence/db_config.py b/persistence/db_config.py new file mode 100644 index 00000000..abe08db3 --- /dev/null +++ b/persistence/db_config.py @@ -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 diff --git a/persistence/db_setup.py b/persistence/db_setup.py new file mode 100644 index 00000000..69cd0464 --- /dev/null +++ b/persistence/db_setup.py @@ -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') diff --git a/persistence/models/__init__.py b/persistence/models/__init__.py new file mode 100644 index 00000000..d29ea9e3 --- /dev/null +++ b/persistence/models/__init__.py @@ -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 diff --git a/persistence/models/city.py b/persistence/models/city.py new file mode 100644 index 00000000..6c237a6e --- /dev/null +++ b/persistence/models/city.py @@ -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() + + diff --git a/persistence/models/heat_pump_simulation.py b/persistence/models/heat_pump_simulation.py new file mode 100644 index 00000000..a4c55ce4 --- /dev/null +++ b/persistence/models/heat_pump_simulation.py @@ -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 + + + diff --git a/persistence/models/user.py b/persistence/models/user.py new file mode 100644 index 00000000..c4543884 --- /dev/null +++ b/persistence/models/user.py @@ -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 diff --git a/persistence/repositories/__init__.py b/persistence/repositories/__init__.py new file mode 100644 index 00000000..febdaca3 --- /dev/null +++ b/persistence/repositories/__init__.py @@ -0,0 +1 @@ +from .user_repo import UserRepo diff --git a/persistence/repositories/city_repo.py b/persistence/repositories/city_repo.py new file mode 100644 index 00000000..cf2dda53 --- /dev/null +++ b/persistence/repositories/city_repo.py @@ -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}') diff --git a/persistence/repositories/heat_pump_simulation_repo.py b/persistence/repositories/heat_pump_simulation_repo.py new file mode 100644 index 00000000..b93f14b0 --- /dev/null +++ b/persistence/repositories/heat_pump_simulation_repo.py @@ -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}') diff --git a/persistence/repositories/user_repo.py b/persistence/repositories/user_repo.py new file mode 100644 index 00000000..a7c86580 --- /dev/null +++ b/persistence/repositories/user_repo.py @@ -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}') diff --git a/requirements.txt b/requirements.txt index 71e5f372..f4e7d9e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,9 @@ rhino3dm==7.7.0 scipy PyYAML pyecore==0.12.2 +python-dotenv +SQLAlchemy +bcrypt==4.0.1 shapely geopandas -triangle \ No newline at end of file +triangle diff --git a/unittests/test_db_factory.py b/unittests/test_db_factory.py new file mode 100644 index 00000000..d97ef906 --- /dev/null +++ b/unittests/test_db_factory.py @@ -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) diff --git a/unittests/test_energy_systems_air_source_hp.py b/unittests/test_energy_systems_air_source_hp.py index ef06a648..a35a9c97 100644 --- a/unittests/test_energy_systems_air_source_hp.py +++ b/unittests/test_energy_systems_air_source_hp.py @@ -12,6 +12,21 @@ from city_model_structure.energy_systems.air_source_hp import AirSourceHP from exports.energy_systems_factory import EnergySystemsExportFactory 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): """ @@ -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[16].air_source_hp.model, '140') - def test_air_source_heat_pump_export(self): - # 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 - } - - EnergySystemsExportFactory(self._city, user_input, '012', self._output_path).export() + def test_air_source_series_heat_pump_export(self): + EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='012', + output_path=self._output_path).export() df = pd.read_csv(self._output_path) self.assertEqual(df.shape, (13, 3)) 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: try: os.remove(self._output_path) diff --git a/unittests/test_energy_systems_water_to_water_hp.py b/unittests/test_energy_systems_water_to_water_hp.py index 27070163..95837aee 100644 --- a/unittests/test_energy_systems_water_to_water_hp.py +++ b/unittests/test_energy_systems_water_to_water_hp.py @@ -13,6 +13,21 @@ import pandas as pd 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): """ @@ -62,11 +77,11 @@ class TestEnergySystemsFactory(TestCase): '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) - print(df.shape) - #self.assertEqual(df.shape, (13, 3)) - #self.assertEqual(df.iloc[0, 1], 3045398.0) + self.assertEqual(df.shape, (13, 3)) + self.assertEqual(df.iloc[0, 1], 1031544.62) def tearDown(self) -> None: try: diff --git a/unittests/test_heat_pump_simulation.py b/unittests/test_heat_pump_simulation.py new file mode 100644 index 00000000..de47d227 --- /dev/null +++ b/unittests/test_heat_pump_simulation.py @@ -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) +