Compare commits

...

35 Commits

Author SHA1 Message Date
34779af69a Check start session 2024-06-23 07:39:11 +02:00
55638fe147 Complete implementation of mongo logic 2024-06-22 17:00:20 +02:00
a9af75bc3b Partial implementation of mongo logic 2024-06-21 07:50:09 +02:00
7b45317d4b Partial implementation of mongo logic 2024-06-20 07:35:00 +02:00
bb0bd4d31f Add glb workflow 2024-06-14 06:37:01 +02:00
ce18c72fe3 remove prints 2024-06-14 06:11:57 +02:00
73641f985e implement idf generator 2024-06-14 06:09:24 +02:00
8fbfd4be89 implement idf generator 2024-06-10 06:31:49 +02:00
3be3121922 Add year of construction and function to meb and retofit, results
Add total heating area to meb results
2024-05-17 05:28:13 +02:00
d7634a0d45 improve style 2024-05-13 08:03:26 +02:00
5097c69761 Correct session helper error and typo 2024-05-10 19:34:36 +02:00
7a5fa1f09b reintroduce security 2024-05-09 15:55:45 +02:00
b49e70c0d2 complete the meb results 2024-05-09 15:36:58 +02:00
f5b48fee4e complete the meb results 2024-05-08 18:10:51 +02:00
921034a884 simplify gml call 2024-05-07 05:22:26 +02:00
d66ad362c7 remove debug session and return file as response 2024-05-03 06:38:15 +02:00
1371f3f2e5 this is quite hacky and will be better to serve the file 2024-05-03 06:26:04 +02:00
20c7425637 add tmp folder 2024-05-02 17:53:50 +02:00
7b1cc06454 add response folder 2024-05-02 17:41:11 +02:00
a322c59787 ep workflow completed 2024-05-02 17:28:53 +02:00
6d4c1437a7 meb workflow small corrections 2024-05-02 15:39:01 +02:00
d98674a286 meb workflow in the api should now be completed 2024-05-02 15:21:34 +02:00
31ebe48f68 partial implementation meb workflow in the api 2024-04-29 07:09:11 +02:00
c414de2635 partial implementation meb workflow in the api 2024-04-26 16:34:38 +02:00
3f35ce1c2c partial implementation meb workflow in the api 2024-04-26 07:14:29 +02:00
7753d69a54 First commit for ep and meb workflows 2024-04-25 07:19:21 +02:00
8cfd6ebe82 Bug fix 2024-04-18 07:32:32 +02:00
0623faaafc Add mock and improve performance 2024-04-12 07:56:37 +02:00
8223d6ff2a add the total heating area to the results 2024-01-29 07:00:01 +01:00
d059a75e8d correct retrofit results bug 2024-01-22 07:15:08 +01:00
087845f83f add co2 to requirements.txt 2024-01-18 07:13:11 +01:00
6f344c8f2c correct scenario mapping 2024-01-18 06:52:57 +01:00
3d3a20f560 - Remove useless prints
- Enable security
- Update apidocs
2023-11-28 07:49:59 +01:00
0661ab21ec Convert insel meb to object 2023-11-28 07:42:03 +01:00
ab4745bca9 correct response on invalid uuid 2023-10-03 04:43:31 -04:00
17 changed files with 631 additions and 374 deletions

View File

@ -18,16 +18,20 @@ import threading
import hub_api.helpers.session_helper as sh import hub_api.helpers.session_helper as sh
from hub_api.control.session import SessionStart, SessionEnd, KeepSessionAlive from hub_api.control.session import SessionStart, SessionEnd, KeepSessionAlive
from hub_api.control.uptime import Uptime from hub_api.control.uptime import Uptime
from hub_api.persistence.full_retrofit_results import FullRetrofitResults
from hub_api.persistence.retrofit_results import RetrofitResults from hub_api.persistence.retrofit_results import RetrofitResults
from hub_api.workflow.insel_montly_energy_balance import InselMonthlyEnergyBalance from hub_api.workflow.insel_montly_energy_balance import InselMonthlyEnergyBalance
from hub_api.workflow.costs import Costs from hub_api.workflow.costs import Costs
from hub_api.workflow.energy_plus import EnergyPlus from hub_api.workflow.energy_plus import EnergyPlus
from hub_api.workflow.glb import Glb
from hub_api.energy_plus.idf_generator import IdfGenerator from hub_api.energy_plus.idf_generator import IdfGenerator
from hub_api.config import Config
sh.begin_time = datetime.datetime.now() sh.begin_time = datetime.datetime.now()
app = flask.Flask('cerc_api') app = flask.Flask('cerc_api')
app.json_provider_class = LazyJSONEncoder app.json_provider_class = LazyJSONEncoder
app.config['MAX_CONTENT_LENGTH'] = int(Config.max_file_size())
api = Api(app) api = Api(app)
api.add_resource(Uptime, '/v1.4/uptime') api.add_resource(Uptime, '/v1.4/uptime')
@ -39,6 +43,7 @@ api.add_resource(KeepSessionAlive, '/v1.4/session/keep-alive')
# persistence # persistence
api.add_resource(RetrofitResults, '/v1.4/persistence/retrofit-results') api.add_resource(RetrofitResults, '/v1.4/persistence/retrofit-results')
api.add_resource(FullRetrofitResults, '/v1.4/persistence/full-retrofit-results')
# energy plus # energy plus
api.add_resource(IdfGenerator, '/v1.4/energy-plus/idf-generator') api.add_resource(IdfGenerator, '/v1.4/energy-plus/idf-generator')
@ -46,6 +51,7 @@ api.add_resource(IdfGenerator, '/v1.4/energy-plus/idf-generator')
# workflows # workflows
api.add_resource(Costs, '/v1.4/workflow/costs') api.add_resource(Costs, '/v1.4/workflow/costs')
api.add_resource(EnergyPlus, '/v1.4/workflow/energy-plus') api.add_resource(EnergyPlus, '/v1.4/workflow/energy-plus')
api.add_resource(Glb, '/v1.4/workflow/glb')
api.add_resource(InselMonthlyEnergyBalance, '/v1.4/workflow/insel-monthly-energy-balance') api.add_resource(InselMonthlyEnergyBalance, '/v1.4/workflow/insel-monthly-energy-balance')
yml_path = Path('./docs/openapi-specs.yml').resolve() yml_path = Path('./docs/openapi-specs.yml').resolve()
@ -76,7 +82,7 @@ def home():
return Response(headers={'Access-Control-Allow-Origin': '*'}) return Response(headers={'Access-Control-Allow-Origin': '*'})
sh.debug_mode = True sh.debug_mode = False
threading.Thread(target=sh.expired_sessions_collector, daemon=True, args="5").start() threading.Thread(target=sh.expired_sessions_collector, daemon=True, args="5").start()
app.run(port=15789, host="0.0.0.0", debug=sh.debug_mode) app.run(port=15789, host="0.0.0.0", debug=sh.debug_mode)

View File

@ -3,13 +3,16 @@ Config
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Project Peter Yefi peteryefi@gmail.com Copyright © 2023 Project Peter Yefi peteryefi@gmail.com
""" """
import distutils
import os import os
import platform import platform
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv
import hub.helpers.dictionaries
from hub.persistence.db_control import DBControl from hub.persistence.db_control import DBControl
from hub.persistence.repository import Repository from hub.persistence.repository import Repository
from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory
from pymongo import MongoClient
class Config: class Config:
@ -18,13 +21,26 @@ class Config:
dotenv_path = "{}/.local/etc/hub_api/.env".format(os.path.expanduser('~')) dotenv_path = "{}/.local/etc/hub_api/.env".format(os.path.expanduser('~'))
if platform.system() == 'Linux': if platform.system() == 'Linux':
dotenv_path = Path(dotenv_path).resolve() dotenv_path = Path(dotenv_path).resolve()
environment = 'PROD'
environment = 'TEST' load_dotenv(dotenv_path=dotenv_path)
database_name = 'montreal_retrofit_test' self._database_name = os.getenv(f'{environment}_DB_NAME')
self._database = DBControl(db_name=self._database_name, app_env=environment, dotenv_path=dotenv_path)
self._database = DBControl(db_name=database_name, app_env=environment, dotenv_path=dotenv_path) self._repository = Repository(db_name=self._database_name, app_env=environment, dotenv_path=dotenv_path)
self._repository = Repository(db_name=database_name, app_env=environment, dotenv_path=dotenv_path)
self._energy_systems_catalog = EnergySystemsCatalogFactory('montreal_custom').catalog self._energy_systems_catalog = EnergySystemsCatalogFactory('montreal_custom').catalog
self._dictionaries = {
'pluto': hub.helpers.dictionaries.Dictionaries().pluto_function_to_hub_function,
'htf': hub.helpers.dictionaries.Dictionaries().hft_function_to_hub_function,
'montreal': hub.helpers.dictionaries.Dictionaries().montreal_function_to_hub_function,
'alkis': hub.helpers.dictionaries.Dictionaries().alkis_function_to_hub_function,
'eilat': hub.helpers.dictionaries.Dictionaries().eilat_function_to_hub_function
}
# mongodb
_mongodb = os.getenv(f'{environment}_MONGO_DB')
_mongodb_database = os.getenv(f'{environment}_MONGO_DB_DATABASE')
self._mongodb_collection_prefix = os.getenv(f'{environment}_MONGO_DB_COLLECTION_PREFIX')
_client = MongoClient(_mongodb)
self._montreal_retrofit_db = _client[_mongodb_database]
@property @property
def database(self): def database(self):
@ -37,3 +53,23 @@ class Config:
@property @property
def energy_systems_catalog(self): def energy_systems_catalog(self):
return self._energy_systems_catalog return self._energy_systems_catalog
@staticmethod
def max_file_size():
return 10 * 1024 * 1024 # 10 MB
@property
def insel(self):
return distutils.spawn.find_executable('insel')
@property
def sra(self):
return distutils.spawn.find_executable('sra')
@property
def montreal_retrofit_db(self):
return self._montreal_retrofit_db
@property
def mongodb_collection_prefix(self):
return self._mongodb_collection_prefix

View File

@ -7,7 +7,7 @@ Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
import datetime import datetime
import json import json
import uuid import uuid
from sqlalchemy.exc import SQLAlchemyError
from flask import request, Response from flask import request, Response
from flask_restful import Resource from flask_restful import Resource
@ -25,9 +25,10 @@ class SessionStart(Resource, Config):
try: try:
application_uuid = uuid.UUID(request.headers.get('application-uuid', None)) application_uuid = uuid.UUID(request.headers.get('application-uuid', None))
user_info = self.database.user_login(name=username, password=password, application_uuid=application_uuid) user_info = self.database.user_login(name=username, password=password, application_uuid=application_uuid)
except ValueError: except (ValueError, SQLAlchemyError, TypeError):
return Response(json.dumps({'error': 'unauthorized'}), status=403) return Response(json.dumps({'error': 'unauthorized'}), status=403)
ip = request.remote_addr ip = request.remote_addr
print(f'received valid login from {ip} {user_info}')
if user_info: if user_info:
session_id = str(uuid.uuid4()) session_id = str(uuid.uuid4())
token = str(uuid.uuid4()) token = str(uuid.uuid4())

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,54 @@
import datetime
import json
import os
from pathlib import Path
from flask import request, Response, make_response, send_file
from flask_restful import Resource from flask_restful import Resource
from hub.city_model_structure.city import City
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
from hub.version import __version__ as version
from hub_api.config import Config from hub_api.config import Config
from hub_api.helpers.session_helper import refresh_session, session
class IdfGenerator(Resource, Config): class IdfGenerator(Resource, Config):
def __init__(self): def __init__(self):
self._tmp_path = (Path(__file__).parent / 'tmp').resolve()
super().__init__() super().__init__()
def post(self): def post(self):
""" """
API call generate the IDF file for the input data API call generate the IDF file for the input data
""" """
raise NotImplementedError() session_id = request.headers.get('session-id', None)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
else:
token = _session['token']
application_id = session(session_id)['application_id']
user_id = session(session_id)['user_id']
tmp_path = (self._tmp_path / token).resolve()
try:
os.mkdir(tmp_path)
except FileExistsError:
pass
payload = request.get_json()
for key, value in payload.items():
db_city = self.database.get_city(self.database.building(value, user_id, application_id, key).city_id)
if version != db_city.hub_release:
return Response(json.dumps({
'error': 'The selected building belongs to an old hub release and cannot be loaded.'
}), status=422)
idf_file = tmp_path/db_city.name
city = City.load_compressed(db_city.pickle_path, idf_file)
EnergyBuildingsExportsFactory('idf', city, tmp_path, target_buildings=[value]).export()
response = make_response(send_file(idf_file))
response.headers['token'] = token
return response

2
hub_api/energy_plus/tmp/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -3,9 +3,13 @@ Session helper
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
""" """
import uuid
import datetime import datetime
import shutil
import time import time
import uuid
from copy import deepcopy
from glob import glob
from pathlib import Path
sessions = {} sessions = {}
begin_time = None begin_time = None
@ -23,14 +27,19 @@ def expired_sessions_collector(session_timeout_duration):
""" """
while True: while True:
if bool(sessions): if bool(sessions):
for session_uuid in sessions: _sessions = deepcopy(sessions)
print(sessions) for session_uuid in _sessions:
print(sessions[session_uuid]['expire']) _expire = datetime.datetime.strptime(_sessions[session_uuid]['expire'], '%Y-%m-%d %H:%M:%S.%f')
_expire = datetime.datetime.strptime(sessions[session_uuid]['expire'], '%Y-%m-%d %H:%M:%S.%f')
if _expire < datetime.datetime.now(): if _expire < datetime.datetime.now():
print("session for user: ", sessions[session_uuid]['user'], "expired.") print("session for user: ", _sessions[session_uuid]['user'], "expired.")
response_path = (Path(__file__).parent.parent / f'response_files/{session_uuid}').resolve()
if response_path.exists():
shutil.rmtree(response_path)
del sessions[session_uuid] del sessions[session_uuid]
existing_directories = glob(f'{Path(__file__).parent.parent.resolve()}/response_files/*')
for directory in existing_directories:
if directory not in _sessions.keys():
shutil.rmtree(directory)
time.sleep(60 * int(session_timeout_duration)) time.sleep(60 * int(session_timeout_duration))

View File

@ -10,7 +10,6 @@ from hub.helpers.dictionaries import Dictionaries
from hub_api.mockup.properties import * from hub_api.mockup.properties import *
import pandas as pd
class Building: class Building:
@ -28,45 +27,44 @@ class Building:
self._total_pv_area = building_info.total_pv_area self._total_pv_area = building_info.total_pv_area
self._energy_systems_archetype_name = building_info.system_name self._energy_systems_archetype_name = building_info.system_name
self._heating_consumption = { self._heating_consumption = {
cte.YEAR: results['yearly_heating_consumption'], cte.YEAR: results['insel meb']['yearly_heating_consumption'],
cte.MONTH: results['monthly_heating_consumption'] cte.MONTH: results['insel meb']['monthly_heating_consumption']
} }
print(self._heating_consumption)
self._cooling_consumption = { self._cooling_consumption = {
cte.YEAR: results['yearly_cooling_consumption'], cte.YEAR: results['insel meb']['yearly_cooling_consumption'],
cte.MONTH: results['monthly_cooling_consumption'] cte.MONTH: results['insel meb']['monthly_cooling_consumption']
} }
self._domestic_hot_water_consumption = { self._domestic_hot_water_consumption = {
cte.YEAR: results['yearly_domestic_hot_water_consumption'], cte.YEAR: results['insel meb']['yearly_domestic_hot_water_consumption'],
cte.MONTH: results['monthly_domestic_hot_water_consumption'] cte.MONTH: results['insel meb']['monthly_domestic_hot_water_consumption']
} }
self._lighting_electrical_demand = { self._lighting_electrical_demand = {
cte.YEAR: results['yearly_lighting_electrical_demand'], cte.YEAR: results['insel meb']['yearly_lighting_electrical_demand'],
cte.MONTH: results['monthly_lighting_electrical_demand'] cte.MONTH: results['insel meb']['monthly_lighting_electrical_demand']
} }
self._appliances_electrical_demand = { self._appliances_electrical_demand = {
cte.YEAR: results['yearly_appliances_electrical_demand'], cte.YEAR: results['insel meb']['yearly_appliances_electrical_demand'],
cte.MONTH: results['monthly_appliances_electrical_demand'] cte.MONTH: results['insel meb']['monthly_appliances_electrical_demand']
} }
self._heating_peak_load = { self._heating_peak_load = {
cte.YEAR: results['yearly_heating_peak_load'], cte.YEAR: results['insel meb']['yearly_heating_peak_load'],
cte.MONTH: results['monthly_heating_peak_load'] cte.MONTH: results['insel meb']['monthly_heating_peak_load']
} }
self._cooling_peak_load = { self._cooling_peak_load = {
cte.YEAR: results['yearly_cooling_peak_load'], cte.YEAR: results['insel meb']['yearly_cooling_peak_load'],
cte.MONTH: results['monthly_cooling_peak_load'] cte.MONTH: results['insel meb']['monthly_cooling_peak_load']
} }
self._lighting_peak_load = { self._lighting_peak_load = {
cte.YEAR: results['yearly_lighting_peak_load'], cte.YEAR: results['insel meb']['yearly_lighting_peak_load'],
cte.MONTH: results['monthly_lighting_peak_load'] cte.MONTH: results['insel meb']['monthly_lighting_peak_load']
} }
self._appliances_peak_load = { self._appliances_peak_load = {
cte.YEAR: results['yearly_appliances_peak_load'], cte.YEAR: results['insel meb']['yearly_appliances_peak_load'],
cte.MONTH: results['monthly_appliances_peak_load'] cte.MONTH: results['insel meb']['monthly_appliances_peak_load']
} }
self._onsite_electrical_production = { self._onsite_electrical_production = {
cte.YEAR: results['yearly_on_site_electrical_production'], cte.YEAR: results['insel meb']['yearly_on_site_electrical_production'],
cte.MONTH: results['monthly_on_site_electrical_production'] cte.MONTH: results['insel meb']['monthly_on_site_electrical_production']
} }
self._catalog_archetype = catalog_archetype self._catalog_archetype = catalog_archetype
@ -215,14 +213,13 @@ class Building:
:return: [EnergySystem] :return: [EnergySystem]
""" """
_energy_systems = [] _energy_systems = []
for system in self._catalog_archetype.systems: for system in self._catalog_archetype.systems:
_hub_demand_types = [] _hub_demand_types = []
for demand_type in system.demand_types: for demand_type in system.demand_types:
# todo: generalize this when we have more catalogs # todo: generalize this when we have more catalogs
_hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type]) _hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type])
demands = _hub_demand_types demands = _hub_demand_types
fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[system.generation_system.fuel_type] fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[system.generation_systems[0].fuel_type]
generic_generation_system = GenericGenerationSystem() generic_generation_system = GenericGenerationSystem()
generic_generation_system.fuel_type = fuel_type generic_generation_system.fuel_type = fuel_type
generation_system = GenerationSystem() generation_system = GenerationSystem()

View File

@ -0,0 +1,45 @@
import json
from flask import Response, request
from flask_restful import Resource
from hub_api.config import Config
from hub_api.helpers.session_helper import refresh_session
from hub_api.persistence.mock import dic
class FullRetrofitResults(Resource, Config):
def __init__(self):
super().__init__()
def post(self):
session_id = request.headers.get('session-id', None)
if session_id == "deece4fa-6809-42b1-a4e6-36e9f3c6edc2":
return Response(json.dumps(dic), status=200)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
results = {'current status': [],
'skin retrofit': [],
'system retrofit and pv': [],
'skin and system retrofit with pv': []
}
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
else:
response_token = {'token': _session['token']}
json_request = request.get_json()
for scenario in json_request['scenarios']:
for key, buildings in scenario.items():
mongodb_collection = f'{self.mongodb_collection_prefix}{key.replace(" ", "_")}'
building_query = ''
for building in buildings:
building_query = f'{building_query} {{"alias": "{building}"}},'
query = f'{{"$or": [{building_query[:-1]}]}}'
cursor = self.montreal_retrofit_db[mongodb_collection].find(json.loads(query))
for result in cursor:
del result['_id']
result['building'] = result['alias']
results[key].append(result)
return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=response_token)

File diff suppressed because one or more lines are too long

View File

@ -1,71 +1,48 @@
import json import json
import threading
from co2_emission.co2_emission import Co2Emission
from costs.cost import Cost
from flask import Response, request from flask import Response, request
from flask_restful import Resource from flask_restful import Resource
from costs.cost import Cost
from co2_emission.co2_emission import Co2Emission
from hub_api.config import Config from hub_api.config import Config
from hub_api.helpers.session_helper import session, refresh_session from hub_api.helpers.session_helper import session, refresh_session
from hub_api.mockup.building import Building from hub_api.mockup.building import Building
from hub_api.persistence.mock import dic
class RetrofitResults(Resource, Config): class RetrofitResults(Resource, Config):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._scenario_ids = {'current status': 0,
'skin retrofit': 1,
'system retrofit and pv': 2,
'skin and system retrofit with pv': 3
}
def post(self): def _calculate_building(self, building_results, user_id, application_id, scenario, scenario_id):
"""
API call for requesting a specified list of enriched persistence
"""
# todo: cost and co2 libraries are using default canadians values, in the future need to be optionally API configurable
session_id = request.headers.get('session-id', None)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
token = {'token': _session['token']}
application_id = session(session_id)['application_id']
user_id = session(session_id)['user_id']
payload = request.get_json()
if 'scenarios' not in payload:
return Response(json.dumps({'error': 'Bad request'}), status=400, headers=token)
results = self.database.results(user_id, application_id, payload)
if results == {}:
# no data found for the given parameters
return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=token)
# deserialize the response to return pure json
for scenario in results:
for building_results in results[scenario]:
values = []
building_info = self.database.building(building_results['building'], user_id, application_id, scenario) building_info = self.database.building(building_results['building'], user_id, application_id, scenario)
results_dictionary = {}
archetype = self.energy_systems_catalog.get_entry(building_info.system_name) archetype = self.energy_systems_catalog.get_entry(building_info.system_name)
for value in building_results['insel meb']: mockup_building = Building(building_info, building_results, archetype)
key = next(iter(value)) life_cycle = Cost(mockup_building, retrofit_scenario=scenario_id).life_cycle
values.append({key: json.loads(str(value[key]))})
results_dictionary[key] = json.loads(str(value[key]))
building_results['insel meb'] = values
mockup_building = Building(building_info, results_dictionary, archetype)
life_cycle = Cost(mockup_building, retrofit_scenario=scenario).life_cycle
operational_co2 = Co2Emission(mockup_building).operational_co2 operational_co2 = Co2Emission(mockup_building).operational_co2
global_capital_costs = life_cycle[f'Scenario {scenario}']['global_capital_costs'] global_capital_costs = life_cycle[f'Scenario {scenario_id}']['global_capital_costs']
global_operational_costs = life_cycle[f'Scenario {scenario}']['global_operational_costs'] global_operational_costs = life_cycle[f'Scenario {scenario_id}']['global_operational_costs']
global_capital_incomes = life_cycle[f'Scenario {scenario}']['global_capital_incomes'] global_capital_incomes = life_cycle[f'Scenario {scenario_id}']['global_capital_incomes']
global_maintenance_costs = life_cycle[f'Scenario {scenario}']['global_maintenance_costs'] global_maintenance_costs = life_cycle[f'Scenario {scenario_id}']['global_maintenance_costs']
building_results['total_heating_area'] = building_info.total_heating_area
building_results['year_of_construction'] = building_info.year_of_construction
building_results['function'] = building_info.function
building_results['costs'] = { building_results['costs'] = {
'total_capital_costs_skin': life_cycle[f'Scenario {scenario}']['total_capital_costs_skin'], 'total_capital_costs_skin': life_cycle[f'Scenario {scenario_id}']['total_capital_costs_skin'],
'total_capital_costs_systems': life_cycle[f'Scenario {scenario}']['total_capital_costs_systems'], 'total_capital_costs_systems': life_cycle[f'Scenario {scenario_id}']['total_capital_costs_systems'],
'end_of_life_costs': life_cycle[f'Scenario {scenario}']['end_of_life_costs'], 'end_of_life_costs': life_cycle[f'Scenario {scenario_id}']['end_of_life_costs'],
'total_operational_costs': life_cycle[f'Scenario {scenario}']['total_operational_costs'], 'total_operational_costs': life_cycle[f'Scenario {scenario_id}']['total_operational_costs'],
'total_maintenance_costs': life_cycle[f'Scenario {scenario}']['total_maintenance_costs'], 'total_maintenance_costs': life_cycle[f'Scenario {scenario_id}']['total_maintenance_costs'],
'operational_incomes': life_cycle[f'Scenario {scenario}']['operational_incomes'], 'operational_incomes': life_cycle[f'Scenario {scenario_id}']['operational_incomes'],
'capital_incomes': life_cycle[f'Scenario {scenario}']['capital_incomes'], 'capital_incomes': life_cycle[f'Scenario {scenario_id}']['capital_incomes'],
'global_capital_costs': { 'global_capital_costs': {
'B2010_opaque_walls': global_capital_costs['B2010_opaque_walls'].tolist(), 'B2010_opaque_walls': global_capital_costs['B2010_opaque_walls'].tolist(),
'B2020_transparent': global_capital_costs['B2020_transparent'].tolist(), 'B2020_transparent': global_capital_costs['B2020_transparent'].tolist(),
@ -77,7 +54,8 @@ class RetrofitResults(Resource, Config):
'D5020_lighting_and_branch_wiring': global_capital_costs['D5020_lighting_and_branch_wiring'].tolist(), 'D5020_lighting_and_branch_wiring': global_capital_costs['D5020_lighting_and_branch_wiring'].tolist(),
'D301010_photovoltaic_system': global_capital_costs['D301010_photovoltaic_system'].tolist(), 'D301010_photovoltaic_system': global_capital_costs['D301010_photovoltaic_system'].tolist(),
}, },
'global_end_of_life_costs': life_cycle[f'Scenario {scenario}']['global_end_of_life_costs']['End_of_life_costs'].tolist(), 'global_end_of_life_costs': life_cycle[f'Scenario {scenario_id}']['global_end_of_life_costs'][
'End_of_life_costs'].tolist(),
'global_operational_costs': { 'global_operational_costs': {
'fixed_costs_electricity_peak': global_operational_costs['Fixed_costs_electricity_peak'].tolist(), 'fixed_costs_electricity_peak': global_operational_costs['Fixed_costs_electricity_peak'].tolist(),
'fixed_costs_electricity_monthly': global_operational_costs['Fixed_costs_electricity_monthly'].tolist(), 'fixed_costs_electricity_monthly': global_operational_costs['Fixed_costs_electricity_monthly'].tolist(),
@ -90,7 +68,8 @@ class RetrofitResults(Resource, Config):
'cooling_maintenance': global_maintenance_costs['Cooling_maintenance'].tolist(), 'cooling_maintenance': global_maintenance_costs['Cooling_maintenance'].tolist(),
'pv_maintenance': global_maintenance_costs['PV_maintenance'].tolist(), 'pv_maintenance': global_maintenance_costs['PV_maintenance'].tolist(),
}, },
'global_operational_incomes': life_cycle[f'Scenario {scenario}']['global_operational_incomes']['Incomes electricity'].tolist(), 'global_operational_incomes': life_cycle[f'Scenario {scenario_id}']['global_operational_incomes'][
'Incomes electricity'].tolist(),
'global_capital_incomes': { 'global_capital_incomes': {
'subsidies_construction': global_capital_incomes['Subsidies construction'].tolist(), 'subsidies_construction': global_capital_incomes['Subsidies construction'].tolist(),
'subsidies_hvac': global_capital_incomes['Subsidies HVAC'].tolist(), 'subsidies_hvac': global_capital_incomes['Subsidies HVAC'].tolist(),
@ -98,4 +77,66 @@ class RetrofitResults(Resource, Config):
} }
} }
building_results['operational_co2'] = operational_co2 building_results['operational_co2'] = operational_co2
def post(self):
"""
API call for requesting a specified list of enriched persistence
"""
# todo: cost and co2 libs are using default canadians values, in the future need to be optionally API configurable
session_id = request.headers.get('session-id', None)
if session_id == "deece4fa-6809-42b1-a4e6-36e9f3c6edc2":
return Response(json.dumps(dic), status=200)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
else:
token = {'token': _session['token']}
application_id = session(session_id)['application_id']
user_id = session(session_id)['user_id']
payload = request.get_json()
if 'scenarios' not in payload:
return Response(json.dumps({'error': 'Bad request'}), status=400, headers=token)
results = self.database.results(user_id, application_id, payload)
if results == {}:
# no data found for the given parameters
return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=token) return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=token)
# deserialize the response to return pure json
t = []
for scenario in results:
scenario_id = self._scenario_ids[scenario]
for building_results in results[scenario]:
f = threading.Thread(
target=self._calculate_building,
args=(building_results, user_id, application_id, scenario, scenario_id)
)
t.append(f)
f.start()
for f in t:
f.join()
return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=token)
def get(self):
session_id = request.headers.get('session-id', None)
if session_id == "deece4fa-6809-42b1-a4e6-36e9f3c6edc2":
return Response(json.dumps(dic), status=200)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
results = {'meb': []}
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
else:
response_token = {'token': _session['token']}
buildings = request.get_json()['buildings']
building_query = ''
for building in buildings:
building_query = f'{building_query} {{"alias": "{building}"}},'
query = f'{{"$or": [{building_query[:-1]}]}}'
cursor = self.mongodb_meb.find(json.loads(query))
for result in cursor:
del result['_id']
results['meb'].append(result)
return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=response_token)

2
hub_api/response_files/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -1,14 +1,117 @@
import json
import os
import shutil
import zipfile
from pathlib import Path
from flask import Response, request, send_file, make_response
from flask_restful import Resource from flask_restful import Resource
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.geometry_factory import GeometryFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
from werkzeug.utils import secure_filename
from hub_api.config import Config from hub_api.config import Config
from hub_api.helpers.session_helper import refresh_session
class EnergyPlus(Resource, Config): class EnergyPlus(Resource, Config):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._extensions = ['.geojson', '.gml']
self._tmp_path = (Path(__file__).parent / 'tmp').resolve()
self._response_path = (Path(__file__).parent.parent / 'response_files').resolve()
self._city = None
def _allowed_extensions(self, filename):
self._file_extension = Path(filename).suffix
return self._file_extension in self._extensions
def _geojson(self, file_path):
try:
height_field = request.form.get('height_field')
year_of_construction_field = request.form.get('year_of_construction_field')
function_field = request.form.get('function_field')
function_dictionary = self._dictionaries[request.form.get('function_to_hub')]
return GeometryFactory('geojson',
path=file_path,
height_field=height_field,
year_of_construction_field=year_of_construction_field,
function_field=function_field,
function_to_hub=function_dictionary).city
except KeyError:
return None
def _citygml(self, file_path):
try:
year_of_construction_field = request.form.get('year_of_construction_field')
function_field = request.form.get('function_field')
function_dictionary = self._dictionaries[request.form.get('function_dictionary_name')]
hub_crs = request.form.get('hub_crs')
return GeometryFactory('citygml',
path=file_path,
year_of_construction_field=year_of_construction_field,
function_field=function_field,
function_to_hub=function_dictionary,
hub_crs=hub_crs).city
except KeyError:
return None
def post(self): def post(self):
""" """
API call for performing the energy plus workflow API call for performing the monthly energy balance workflow
""" """
raise NotImplementedError() session_id = request.headers.get('session-id', None)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
else:
response_token = {'token': _session['token']}
tmp_path = (self._tmp_path / token).resolve()
response_path = (self._response_path / session_id).resolve()
try:
os.mkdir(tmp_path)
except FileExistsError:
pass
try:
os.mkdir(response_path)
except FileExistsError:
pass
geometry_file = request.files['geometry_file']
if not self._allowed_extensions(geometry_file.filename):
shutil.rmtree(tmp_path)
return Response(json.dumps({'error': 'Unsupported media type'}), status=415, headers=response_token)
filename = secure_filename(geometry_file.filename)
file_path = os.path.join(tmp_path, filename)
geometry_file.save(file_path)
if self._file_extension == '.geojson':
self._city = self._geojson(file_path)
else:
self._city = self._citygml(file_path)
if self._city is None:
shutil.rmtree(tmp_path)
return Response(json.dumps({'error': 'Bad request'}), status=400, headers=response_token)
construction_handler = request.form.get('construction_handler')
usage_handler = request.form.get('usage_handler')
WeatherFactory('epw', self._city).enrich()
ConstructionFactory(construction_handler, self._city).enrich()
UsageFactory(usage_handler, self._city).enrich()
_idf = EnergyBuildingsExportsFactory('idf', self._city, tmp_path).export()
_idf.run()
result_files = [
str((tmp_path / f'{self._city.name}_out.csv').resolve()),
str((tmp_path / f'{self._city.name}_out.eso').resolve()),
str((tmp_path / f'{self._city.name}.idf').resolve()),
]
result_zip = (response_path / f'{token}.zip').resolve()
with zipfile.ZipFile(result_zip, 'w') as zf:
for result_file in result_files:
zf.write(result_file, Path(result_file).name)
shutil.rmtree(tmp_path)
response = make_response(send_file(result_zip))
response.headers['token'] = token
return response

105
hub_api/workflow/glb.py Normal file
View File

@ -0,0 +1,105 @@
import glob
import json
import os
import shutil
import zipfile
from pathlib import Path
from flask import Response, request, send_file, make_response
from flask_restful import Resource
from hub.exports.exports_factory import ExportsFactory
from hub.imports.geometry_factory import GeometryFactory
from werkzeug.utils import secure_filename
from hub_api.config import Config
from hub_api.helpers.session_helper import refresh_session
class Glb(Resource, Config):
def __init__(self):
super().__init__()
self._extensions = ['.geojson', '.gml']
self._tmp_path = (Path(__file__).parent / 'tmp').resolve()
self._response_path = (Path(__file__).parent.parent / 'response_files').resolve()
self._city = None
def _allowed_extensions(self, filename):
self._file_extension = Path(filename).suffix
return self._file_extension in self._extensions
def _geojson(self, file_path):
try:
height_field = request.form.get('height_field')
year_of_construction_field = request.form.get('year_of_construction_field')
function_field = request.form.get('function_field')
function_dictionary = self._dictionaries[request.form.get('function_to_hub')]
return GeometryFactory('geojson',
path=file_path,
height_field=height_field,
year_of_construction_field=year_of_construction_field,
function_field=function_field,
function_to_hub=function_dictionary).city
except KeyError:
return None
def _citygml(self, file_path):
try:
year_of_construction_field = request.form.get('year_of_construction_field')
function_field = request.form.get('function_field')
function_dictionary = self._dictionaries[request.form.get('function_dictionary_name')]
hub_crs = request.form.get('hub_crs')
return GeometryFactory('citygml',
path=file_path,
year_of_construction_field=year_of_construction_field,
function_field=function_field,
function_to_hub=function_dictionary,
hub_crs=hub_crs).city
except KeyError:
return None
def post(self):
"""
API call for performing the monthly energy balance workflow
"""
session_id = request.headers.get('session-id', None)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
else:
response_token = {'token': _session['token']}
tmp_path = (self._tmp_path / token).resolve()
response_path = (self._response_path / session_id).resolve()
try:
os.mkdir(tmp_path)
except FileExistsError:
pass
try:
os.mkdir(response_path)
except FileExistsError:
pass
geometry_file = request.files['geometry_file']
if not self._allowed_extensions(geometry_file.filename):
shutil.rmtree(tmp_path)
return Response(json.dumps({'error': 'Unsupported media type'}), status=415, headers=response_token)
filename = secure_filename(geometry_file.filename)
file_path = os.path.join(tmp_path, filename)
geometry_file.save(file_path)
if self._file_extension == '.geojson':
self._city = self._geojson(file_path)
else:
self._city = self._citygml(file_path)
if self._city is None:
shutil.rmtree(tmp_path)
return Response(json.dumps({'error': 'Bad request'}), status=400, headers=response_token)
ExportsFactory('glb', self._city, tmp_path).export()
result_files = glob.glob(f'{tmp_path}/*.glb')
result_zip = (response_path / f'{token}.zip').resolve()
with zipfile.ZipFile(result_zip, 'w') as zf:
for result_file in result_files:
zf.write(result_file, Path(result_file).name)
shutil.rmtree(tmp_path)
response = make_response(send_file(result_zip))
response.headers['token'] = token
return response

View File

@ -1,14 +1,137 @@
import json
import os
import shutil
import subprocess
from pathlib import Path
import hub.helpers.constants as cte
from flask import Response, request
from flask_restful import Resource from flask_restful import Resource
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
from hub.exports.exports_factory import ExportsFactory
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.geometry_factory import GeometryFactory
from hub.imports.results_factory import ResultFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
from werkzeug.utils import secure_filename
from hub_api.config import Config from hub_api.config import Config
from hub_api.helpers.session_helper import refresh_session
class InselMonthlyEnergyBalance(Resource, Config): class InselMonthlyEnergyBalance(Resource, Config):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._extensions = ['.geojson', '.gml']
self._tmp_path = (Path(__file__).parent / 'tmp').resolve()
self._city = None
def _allowed_extensions(self, filename):
self._file_extension = Path(filename).suffix
return self._file_extension in self._extensions
def _geojson(self, file_path):
try:
height_field = request.form.get('height_field')
year_of_construction_field = request.form.get('year_of_construction_field')
function_field = request.form.get('function_field')
function_dictionary = self._dictionaries[request.form.get('function_to_hub')]
return GeometryFactory('geojson',
path=file_path,
height_field=height_field,
year_of_construction_field=year_of_construction_field,
function_field=function_field,
function_to_hub=function_dictionary).city
except KeyError:
return None
def _citygml(self, file_path):
# try:
year_of_construction_field = request.form.get('year_of_construction_field')
if year_of_construction_field == '':
year_of_construction_field = None
function_field = request.form.get('function_field')
if function_field == '':
function_field = None
function_dictionary = self._dictionaries[request.form.get('function_to_hub')]
return GeometryFactory('citygml',
path=file_path,
year_of_construction_field=year_of_construction_field,
function_field=function_field,
function_to_hub=function_dictionary).city
# except KeyError:
# return None
def post(self): def post(self):
""" """
API call for performing the monthly energy balance workflow API call for performing the monthly energy balance workflow
""" """
raise NotImplementedError() session_id = request.headers.get('session-id', None)
token = request.headers.get('token', None)
application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid)
if _session is None:
return Response(json.dumps({'error': 'unauthorized'}), status=403)
else:
response_token = {'token': _session['token']}
tmp_path = (self._tmp_path / token).resolve()
try:
os.mkdir(tmp_path)
except FileExistsError:
pass
geometry_file = request.files['geometry_file']
if not self._allowed_extensions(geometry_file.filename):
shutil.rmtree(tmp_path)
return Response(json.dumps({'error': 'Unsupported media type'}), status=415, headers=response_token)
filename = secure_filename(geometry_file.filename)
file_path = os.path.join(tmp_path, filename)
geometry_file.save(file_path)
if self._file_extension == '.geojson':
self._city = self._geojson(file_path)
else:
self._city = self._citygml(file_path)
if self._city is None:
shutil.rmtree(tmp_path)
return Response(json.dumps({'error': 'Bad request'}), status=400, headers=response_token)
construction_handler = request.form.get('construction_handler')
usage_handler = request.form.get('usage_handler')
WeatherFactory('epw', self._city).enrich()
ConstructionFactory(construction_handler, self._city).enrich()
UsageFactory(usage_handler, self._city).enrich()
ExportsFactory('sra', self._city, tmp_path).export()
sra_file = str((tmp_path / f'{self._city.name}_sra.xml').resolve())
subprocess.run([self.sra, sra_file], stdout=subprocess.DEVNULL)
ResultFactory('sra', self._city, tmp_path).enrich()
EnergyBuildingsExportsFactory('insel_monthly_energy_balance', self._city, tmp_path).export()
for building in self._city.buildings:
insel_path = (tmp_path / f'{building.name}.insel')
subprocess.run([self.insel, str(insel_path)], stdout=subprocess.DEVNULL)
ResultFactory('insel_monthly_energy_balance', self._city, tmp_path).enrich()
results = {}
for building in self._city.buildings:
results[building.name] = {
'total_heating_area': building.floor_area * building.storeys_above_ground,
'year_of_construction': building.year_of_construction,
'function': building.function,
'monthly_heating_demand': building.heating_demand[cte.MONTH],
'yearly_heating_demand': building.heating_demand[cte.YEAR],
'monthly_cooling_demand': building.cooling_demand[cte.MONTH],
'yearly_cooling_demand': building.cooling_demand[cte.YEAR],
'monthly_lighting_peak_load': building.lighting_peak_load[cte.MONTH],
'yearly_lighting_peak_load': building.lighting_peak_load[cte.YEAR],
'monthly_appliances_peak_load': building.appliances_peak_load[cte.MONTH],
'yearly_appliances_peak_load': building.appliances_peak_load[cte.YEAR],
'monthly_cooling_peak_load': building.cooling_peak_load[cte.MONTH],
'yearly_cooling_peak_load': building.cooling_peak_load[cte.YEAR],
'monthly_heating_peak_load': building.heating_peak_load[cte.MONTH],
'yearly_heating_peak_load': building.heating_peak_load[cte.YEAR],
'monthly_lighting_electrical_demand': building.lighting_electrical_demand[cte.MONTH],
'yearly_lighting_electrical_demand': building.lighting_electrical_demand[cte.YEAR],
'monthly_appliances_electrical_demand': building.appliances_electrical_demand[cte.MONTH],
'yearly_appliances_electrical_demand': building.appliances_electrical_demand[cte.YEAR],
'monthly_domestic_hot_water_heat_demand': building.domestic_hot_water_heat_demand[cte.MONTH],
'yearly_domestic_hot_water_heat_demand': building.domestic_hot_water_heat_demand[cte.YEAR],
}
shutil.rmtree(tmp_path)
return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=response_token)

2
hub_api/workflow/tmp/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.gitignore
!.gitignore

View File

@ -28,3 +28,8 @@ cerc-hub
python-dotenv python-dotenv
mapbox_earcut mapbox_earcut
cerc-costs cerc-costs
cerc-co2-emission
werkzeug
sqlalchemy
pathlib
pymongo