Compare commits

..

62 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
087cc7dc97 Remove dataframes in mockup 2023-08-10 13:39:25 -04:00
62f0b0bda0 thermal_zones to thermal_zones_from_internal_zones 2023-08-08 16:03:43 -04:00
242b59bfff properties documented 2023-08-08 16:02:13 -04:00
31b52f6d37 Merge remote-tracking branch 'origin/main' into main
# Conflicts:
#	hub_api/mockup/building.py
2023-08-08 16:01:15 -04:00
e58d8d4cc9 properties documented 2023-08-08 15:59:06 -04:00
b96617bb21 correct retrofit results to apply costs and co2 to all scenariosa 2023-08-03 12:38:19 -04:00
b06a1d3d53 correct expired sessions collector 2023-08-03 10:42:41 -04:00
cerc
cb9b9bb251 server modifications 2023-08-03 09:53:26 -04:00
beed9f4e22 complete results 2023-08-02 15:47:09 -04:00
d9a464e6d9 cost completed 2023-08-01 16:40:49 -04:00
fdf3491dc1 partial completion of results 2023-07-31 16:56:37 -04:00
f0438e6776 partial completion of results 2023-07-31 16:55:39 -04:00
aeedbd370c pv reduction factor to pv area 2023-07-31 14:51:23 -04:00
bc377bc1fb Partial completion for costs and co2 emitions 2023-07-31 14:48:17 -04:00
98a60fa0a1 Merge remote-tracking branch 'origin/main' into main 2023-07-31 14:42:21 -04:00
1a4a72bc1a changed tuple to catalog archetype 2023-07-31 14:42:13 -04:00
305eca2718 Merge remote-tracking branch 'origin/main' 2023-07-31 14:31:55 -04:00
c30bf275c5 Partial completion for costs and co2 emitions 2023-07-31 14:31:50 -04:00
bc94ea04c5 Merge remote-tracking branch 'origin/main' into main 2023-07-31 13:32:40 -04:00
20fc9fa6a5 first approach to building mockup model 2023-07-31 13:32:28 -04:00
1a38c83dee Redefine api structure
Add future methods
Update documentation
2023-07-28 14:54:30 -04:00
52bb8afa13 correct API 2023-07-28 08:26:02 -04:00
ec90dd1e0d Redefine api structure
Add future methods
Update documentation
2023-07-21 16:59:56 -04:00
01281e4b31 Redefine api structure
Add future methods
Update documentation
2023-07-21 11:48:00 -04:00
72b90557ff Redefine api structure
Add future methods
Update documentation
2023-07-21 11:43:52 -04:00
68ae20f85d correct API 2023-07-20 20:15:24 -04:00
d02b1cd838 reintroduce security for reverse lockup 2023-04-13 09:49:10 -04:00
23 changed files with 1364 additions and 26740 deletions

View File

@ -9,6 +9,7 @@ import datetime
import flask import flask
import yaml import yaml
from pathlib import Path
from flasgger import LazyJSONEncoder, Swagger from flasgger import LazyJSONEncoder, Swagger
from flask import Response from flask import Response
from flask_restful import Api from flask_restful import Api
@ -17,12 +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.buildings.meb import Meb from hub_api.persistence.full_retrofit_results import FullRetrofitResults
from hub_api.geolocation.reverse import Reverse from hub_api.persistence.retrofit_results import RetrofitResults
from hub_api.workflow.insel_montly_energy_balance import InselMonthlyEnergyBalance
from hub_api.workflow.costs import Costs
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.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')
@ -30,26 +39,35 @@ api.add_resource(Uptime, '/v1.4/uptime')
# Session # Session
api.add_resource(SessionStart, '/v1.4/session/start') api.add_resource(SessionStart, '/v1.4/session/start')
api.add_resource(SessionEnd, '/v1.4/session/end') api.add_resource(SessionEnd, '/v1.4/session/end')
api.add_resource(KeepSessionAlive, '/v1.4/session/keep_alive') api.add_resource(KeepSessionAlive, '/v1.4/session/keep-alive')
# Buildings # persistence
api.add_resource(Meb, '/v1.4/buildings/meb') api.add_resource(RetrofitResults, '/v1.4/persistence/retrofit-results')
api.add_resource(FullRetrofitResults, '/v1.4/persistence/full-retrofit-results')
# GeoLocation # energy plus
api.add_resource(Reverse, '/v1.4/geolocation/reverse/<latitude>/<longitude>', endpoint='reverse') api.add_resource(IdfGenerator, '/v1.4/energy-plus/idf-generator')
with open("hub_api/docs/openapi-specs.yml", "r") as stream: # workflows
api.add_resource(Costs, '/v1.4/workflow/costs')
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')
yml_path = Path('./docs/openapi-specs.yml').resolve()
with open(yml_path, "r") as stream:
swagger_config = { swagger_config = {
"headers": [], "headers": [],
"specs": [ "specs": [
{ {
"endpoint": '/api/apispec/', "endpoint": '/api/apispec',
"route": '/api/apispec/apispec.json', "route": '/api/apispec/apispec.json',
"rule_filter": lambda rule: True, # all in "rule_filter": lambda rule: True, # all in
"model_filter": lambda tag: True, # all in "model_filter": lambda tag: True, # all in
} }
], ],
"static_url_path": "/api/static/", "static_url_path": "/api/static",
"specs_route": "/api/api-docs/", "specs_route": "/api/api-docs/",
"openapi": "3.0.0" "openapi": "3.0.0"
} }
@ -63,7 +81,8 @@ with open("hub_api/docs/openapi-specs.yml", "r") as stream:
def home(): 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

@ -1,40 +0,0 @@
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 session, refresh_session
class Meb(Resource, Config):
def __init__(self):
super().__init__()
def post(self):
"""
API call for requesting a specified list of enriched buildings
"""
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()
results = self.export_db_factory.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
city_name = next(iter(results))
for building_results in results[city_name]:
values = []
for value in building_results['insel meb']:
key = next(iter(value))
values.append({key: json.loads(value[key])})
building_results['insel meb'] = values
return Response(json.dumps({'result': 'succeed', 'results': results}), status=200, headers=token)

View File

@ -3,24 +3,73 @@ 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
from hub.exports.db_factory import DBFactory as CityExportFactory import hub.helpers.dictionaries
from hub.imports.db_factory import DBFactory from hub.persistence.db_control import DBControl
from hub.persistence.repository import Repository
from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory
from pymongo import MongoClient
class Config: class Config:
def __init__(self): def __init__(self):
dotenv_path = "{}".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 = f'{dotenv_path}/.local/etc/hub_api/.env' dotenv_path = Path(dotenv_path).resolve()
environment = 'TEST' environment = 'PROD'
database_name = 'montreal_retrofit_test' load_dotenv(dotenv_path=dotenv_path)
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._repository = Repository(db_name=self._database_name, app_env=environment, dotenv_path=dotenv_path)
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]
self.export_db_factory = CityExportFactory(db_name=database_name, app_env=environment,
dotenv_path=dotenv_path) @property
self.import_db_factory = DBFactory(db_name=database_name, app_env=environment, def database(self):
dotenv_path=dotenv_path) return self._database
@property
def repository(self):
return self._repository
@property
def energy_systems_catalog(self):
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
@ -22,9 +22,13 @@ class SessionStart(Resource, Config):
def put(self): def put(self):
username = request.headers.get('username', None) username = request.headers.get('username', None)
password = request.headers.get('password', None) password = request.headers.get('password', None)
application_uuid = request.headers.get('application-uuid', None) try:
application_uuid = uuid.UUID(request.headers.get('application-uuid', None))
user_info = self.database.user_login(name=username, password=password, application_uuid=application_uuid)
except (ValueError, SQLAlchemyError, TypeError):
return Response(json.dumps({'error': 'unauthorized'}), status=403)
ip = request.remote_addr ip = request.remote_addr
user_info = self.export_db_factory.user_login(name=username, password=password, application_uuid=application_uuid) 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())
@ -36,45 +40,45 @@ class SessionStart(Resource, Config):
'application_id': user_info.application_id, 'application_id': user_info.application_id,
'application_uuid': application_uuid, 'application_uuid': application_uuid,
'ip': ip, 'ip': ip,
'cities': [] 'scenarios': []
} }
cities = self.export_db_factory.cities_by_user_and_application(user_info.id, user_info.application_id) cities = self.database.cities_by_user_and_application(user_info.id, user_info.application_id)
for city in cities: for city in cities:
session['cities'].append({ if city.scenario not in session['scenarios']:
"name": city.name, session['scenarios'].append(city.scenario)
"geometric_level_of_detail": city.level_of_detail
})
sessions[session_id] = session sessions[session_id] = session
response = Response(json.dumps({'cities': session['cities'], 'result': 'OK'}), status=200) response = Response(json.dumps({'scenarios': session['scenarios'], 'result': 'OK'}), status=200)
response.headers['session_id'] = session_id response.headers['session_id'] = session_id
response.headers['token'] = token response.headers['token'] = token
return response return response
return Response(json.dumps({'error': 'unauthorized'}), status=403) return Response(json.dumps({'error': 'unauthorized'}), status=403)
class SessionEnd(Resource): class SessionEnd(Resource):
def __init__(self): def __init__(self):
pass pass
@staticmethod @staticmethod
def put(): def put():
session_id = request.headers.get('session_id', None) session_id = request.headers.get('session-id', None)
token = request.headers.get('token', None) token = request.headers.get('token', None)
application_uuid = request.headers.get('application_uuid', None) application_uuid = request.headers.get('application-uuid', None)
if remove_session(session_id, token, application_uuid): if remove_session(session_id, token, application_uuid):
return Response(json.dumps({'result': 'succeed'}), status=200) return Response(json.dumps({'result': 'succeed'}), status=200)
return Response(json.dumps({'error': 'unauthorized'}), status=403) return Response(json.dumps({'error': 'unauthorized'}), status=403)
class KeepSessionAlive(Resource): class KeepSessionAlive(Resource):
def __init__(self): def __init__(self):
pass pass
@staticmethod @staticmethod
def put(): def put():
session_id = request.headers.get('session_id', None) session_id = request.headers.get('session-id', None)
token = request.headers.get('token', None) token = request.headers.get('token', None)
application_uuid = request.headers.get('application_uuid', None) application_uuid = request.headers.get('application-uuid', None)
_session = refresh_session(session_id, token, application_uuid) _session = refresh_session(session_id, token, application_uuid)
if _session is None: if _session is None:

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +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 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.helpers.session_helper import refresh_session, session
class IdfGenerator(Resource, Config):
def __init__(self):
self._tmp_path = (Path(__file__).parent / 'tmp').resolve()
super().__init__()
def post(self):
"""
API call generate the IDF file for the input data
"""
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

@ -1,43 +0,0 @@
import json
import math
from pathlib import Path
from flask import Response, request
from flask.views import MethodView
from hub_api.config import Config
from hub_api.helpers.session_helper import refresh_session
class Reverse(MethodView, Config):
def __init__(self):
super().__init__()
self._reverse_path = Path(Path(__file__).parent.parent / 'data/cities15000.txt').resolve()
def get(self, latitude: float, longitude: float):
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']}
latitude = float(latitude)
longitude = float(longitude)
distance = math.inf
country = 'unknown'
city = 'unknown'
with open(self._reverse_path, 'r') as f:
for line_number, line in enumerate(f):
fields = line.split('\t')
file_city_name = fields[1]
file_latitude = float(fields[4])
file_longitude = float(fields[5])
file_country_code = fields[8]
new_distance = math.sqrt(pow((latitude-file_latitude),2) + pow((longitude-file_longitude),2))
if distance > new_distance:
distance = new_distance
country = file_country_code
city = file_city_name
return Response(json.dumps({'country': country, 'city':city}), status=200, headers=token)

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
@ -16,17 +20,26 @@ construction_catalog = None
usage_catalog = None usage_catalog = None
debug_mode = False debug_mode = False
def expired_sessions_collector(session_timeout_duration): def expired_sessions_collector(session_timeout_duration):
""" """
Goes through each session in sessions and removes expired session(s) Goes through each session in sessions and removes expired session(s)
""" """
while True: while True:
if bool(sessions): if bool(sessions):
for _session in list(sessions): _sessions = deepcopy(sessions)
_expire = datetime.datetime.strptime(sessions[session]['expire'], '%Y-%m-%d %H:%M:%S.%f') for session_uuid in _sessions:
_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 with session_id: ", session, "expired.") print("session for user: ", _sessions[session_uuid]['user'], "expired.")
del sessions[session] response_path = (Path(__file__).parent.parent / f'response_files/{session_uuid}').resolve()
if response_path.exists():
shutil.rmtree(response_path)
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))
@ -38,7 +51,7 @@ def _validate_session(session_id, token, application_uuid):
_session = session(session_id) _session = session(session_id)
if debug_mode: if debug_mode:
token = _session['token'] token = _session['token']
return bool(_session) and (_session['token'] == token) and _session['application_uuid'] == application_uuid return bool(_session) and (_session['token'] == token) and str(_session['application_uuid']) == application_uuid
except KeyError: except KeyError:
return False return False

231
hub_api/mockup/building.py Normal file
View File

@ -0,0 +1,231 @@
"""
Mockup Building module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import hub.helpers.constants as cte
from hub.helpers.dictionaries import Dictionaries
from hub_api.mockup.properties import *
class Building:
"""
Building class
"""
def __init__(self, building_info, results, catalog_archetype):
self._function = building_info.function
self._area = building_info.area
self._volume = building_info.volume
self._total_heating_area = building_info.total_heating_area
self._wall_area = building_info.wall_area
self._windows_area = building_info.windows_area
self._roof_area = building_info.roof_area
self._total_pv_area = building_info.total_pv_area
self._energy_systems_archetype_name = building_info.system_name
self._heating_consumption = {
cte.YEAR: results['insel meb']['yearly_heating_consumption'],
cte.MONTH: results['insel meb']['monthly_heating_consumption']
}
self._cooling_consumption = {
cte.YEAR: results['insel meb']['yearly_cooling_consumption'],
cte.MONTH: results['insel meb']['monthly_cooling_consumption']
}
self._domestic_hot_water_consumption = {
cte.YEAR: results['insel meb']['yearly_domestic_hot_water_consumption'],
cte.MONTH: results['insel meb']['monthly_domestic_hot_water_consumption']
}
self._lighting_electrical_demand = {
cte.YEAR: results['insel meb']['yearly_lighting_electrical_demand'],
cte.MONTH: results['insel meb']['monthly_lighting_electrical_demand']
}
self._appliances_electrical_demand = {
cte.YEAR: results['insel meb']['yearly_appliances_electrical_demand'],
cte.MONTH: results['insel meb']['monthly_appliances_electrical_demand']
}
self._heating_peak_load = {
cte.YEAR: results['insel meb']['yearly_heating_peak_load'],
cte.MONTH: results['insel meb']['monthly_heating_peak_load']
}
self._cooling_peak_load = {
cte.YEAR: results['insel meb']['yearly_cooling_peak_load'],
cte.MONTH: results['insel meb']['monthly_cooling_peak_load']
}
self._lighting_peak_load = {
cte.YEAR: results['insel meb']['yearly_lighting_peak_load'],
cte.MONTH: results['insel meb']['monthly_lighting_peak_load']
}
self._appliances_peak_load = {
cte.YEAR: results['insel meb']['yearly_appliances_peak_load'],
cte.MONTH: results['insel meb']['monthly_appliances_peak_load']
}
self._onsite_electrical_production = {
cte.YEAR: results['insel meb']['yearly_on_site_electrical_production'],
cte.MONTH: results['insel meb']['monthly_on_site_electrical_production']
}
self._catalog_archetype = catalog_archetype
@property
def function(self):
"""
Get building function
:return: str
"""
return self._function
@property
def volume(self):
"""
Get building volume in m3
:return: float
"""
return self._volume
@property
def thermal_zones_from_internal_zones(self) -> [ThermalZone]:
"""
Get building thermal zones
:return: [ThermalZone]
"""
ground = ThermalBoundary()
ground.type = 'Ground'
ground.opaque_area = self._area
roof = ThermalBoundary()
roof.type = 'Roof'
roof.opaque_area = self._roof_area
wall = ThermalBoundary()
wall.type = 'Wall'
wall.opaque_area = self._wall_area
wall.window_ratio = self._windows_area / (self._windows_area + self._wall_area)
thermal_zone = ThermalZone()
thermal_zone.total_floor_area = self._total_heating_area
thermal_zone.thermal_boundaries = [roof, wall, ground]
return [thermal_zone]
@property
def roofs(self) -> [Roof]:
"""
Get building roofs
:return: [Roof]
"""
polygon = Polygon()
polygon.area = self._roof_area
roof = Roof()
roof.solid_polygon = polygon
roof.solar_collectors_area_reduction_factor = self._total_pv_area / self._roof_area
return [roof]
@property
def heating_consumption(self):
"""
Get building heating consumption in J
:return: dict
"""
return self._heating_consumption
@property
def cooling_consumption(self):
"""
Get building cooling consumption in J
:return: dict
"""
return self._cooling_consumption
@property
def domestic_hot_water_consumption(self):
"""
Get building domestic hot water consumption in J
:return: dict
"""
return self._domestic_hot_water_consumption
@property
def lighting_electrical_demand(self):
"""
Get building lighting demand in J
:return: dict
"""
return self._lighting_electrical_demand
@property
def appliances_electrical_demand(self):
"""
Get building appliances electrical demand in J
:return: dict
"""
return self._appliances_electrical_demand
@property
def heating_peak_load(self):
"""
Get building heating peak load in W
:return: dict
"""
return self._heating_peak_load
@property
def cooling_peak_load(self):
"""
Get building cooling peak load in W
:return: dict
"""
return self._cooling_peak_load
@property
def lighting_peak_load(self):
"""
Get building lighting peak load in W
:return: dict
"""
return self._lighting_peak_load
@property
def appliances_peak_load(self):
"""
Get building appliances peak load in W
:return: dict
"""
return self._appliances_peak_load
@property
def onsite_electrical_production(self):
"""
Get building onsite electrical production in J
:return: dict
"""
return self._onsite_electrical_production
@property
def energy_systems_archetype_name(self):
"""
Get energy systems archetype name
:return: dict
"""
return self._energy_systems_archetype_name
@property
def energy_systems(self) -> [EnergySystem]:
"""
Get building energy systems
:return: [EnergySystem]
"""
_energy_systems = []
for system in self._catalog_archetype.systems:
_hub_demand_types = []
for demand_type in system.demand_types:
# todo: generalize this when we have more catalogs
_hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type])
demands = _hub_demand_types
fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[system.generation_systems[0].fuel_type]
generic_generation_system = GenericGenerationSystem()
generic_generation_system.fuel_type = fuel_type
generation_system = GenerationSystem()
generation_system.generic_generation_system = generic_generation_system
energy_system = EnergySystem()
energy_system.generation_system = generation_system
energy_system.demand_types = demands
_energy_systems.append(energy_system)
return _energy_systems

View File

@ -0,0 +1,267 @@
"""
Properties module to be used by mockup building
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from __future__ import annotations
class EnergySystem:
"""
EnergySystem class
"""
def __init__(self):
self._generation_system = None
self._demand_types = None
@property
def generation_system(self) -> GenerationSystem:
"""
Get building generation system
:return: GenerationSystem
"""
return self._generation_system
@generation_system.setter
def generation_system(self, value):
"""
Set building generation system
:param value: GenerationSystem
"""
self._generation_system = value
@property
def demand_types(self):
"""
Get demand types covered by an energy system
:return: [str]
"""
return self._demand_types
@demand_types.setter
def demand_types(self, value):
"""
Set demand types covered by an energy system
:param value: [str]
"""
self._demand_types = value
class GenerationSystem:
"""
GenerationSystem class
"""
def __init__(self):
self._generic_generation_system = None
@property
def generic_generation_system(self) -> GenericGenerationSystem:
"""
Get generic generation system
:return: GenericGenerationSystem
"""
return self._generic_generation_system
@generic_generation_system.setter
def generic_generation_system(self, value):
"""
Set generic generation system
:param value: GenericGenerationSystem
"""
self._generic_generation_system = value
class GenericGenerationSystem:
"""
GenericGenerationSystem class
"""
def __init__(self):
self._fuel_type = None
@property
def fuel_type(self):
"""
Get fuel type of generic generation system
:return: str
"""
return self._fuel_type
@fuel_type.setter
def fuel_type(self, value):
"""
Set fuel type of generic generation system
:param value: str
"""
self._fuel_type = value
class ThermalZone:
"""
ThermalZone class
"""
def __init__(self):
self._thermal_boundaries = None
self._total_floor_area = None
@property
def thermal_boundaries(self) -> [ThermalBoundary]:
"""
Get thermal boundaries
:return: [ThermalBoundary]
"""
return self._thermal_boundaries
@thermal_boundaries.setter
def thermal_boundaries(self, value):
"""
Set thermal boundaries
:param value: [ThermalBoundary]
"""
self._thermal_boundaries = value
@property
def total_floor_area(self):
"""
Get total floor area in m2
:return: float
"""
return self._total_floor_area
@total_floor_area.setter
def total_floor_area(self, value):
"""
Get total floor area in m2
:param value: float
"""
self._total_floor_area = value
class ThermalBoundary:
"""
ThermalBoundary class
"""
def __init__(self):
self._type = None
self._opaque_area = None
self._window_ratio = None
@property
def type(self):
"""
Get thermal boundary type
:return: str
"""
return self._type
@type.setter
def type(self, value):
"""
Set thermal boundary type
:param value: str
"""
self._type = value
@property
def opaque_area(self):
"""
Get thermal boundary opaque area in m2
:return: float
"""
return self._opaque_area
@opaque_area.setter
def opaque_area(self, value):
"""
Set thermal boundary opaque area in m2
:param value: float
"""
self._opaque_area = value
@property
def window_ratio(self):
"""
Get thermal boundary window ratio
:return: float
"""
return self._window_ratio
@window_ratio.setter
def window_ratio(self, value):
"""
Set thermal boundary window ratio
:param value: float
"""
self._window_ratio = value
class Roof:
"""
Roof class
"""
def __init__(self):
self._solid_polygon = None
self._solar_collectors_area_reduction_factor = None
@property
def solid_polygon(self) -> Polygon:
"""
Get solid polygon
:return: Polygon
"""
return self._solid_polygon
@solid_polygon.setter
def solid_polygon(self, value):
"""
Set solid polygon
:param value: Polygon
"""
self._solid_polygon = value
@property
def solar_collectors_area_reduction_factor(self):
"""
Get solar-collectors-area reduction factor
:return: float
"""
return self._solar_collectors_area_reduction_factor
@solar_collectors_area_reduction_factor.setter
def solar_collectors_area_reduction_factor(self, value):
"""
Set solar-collectors-area reduction factor
:param value: float
"""
self._solar_collectors_area_reduction_factor = value
class Polygon:
"""
Polygon class
"""
def __init__(self):
self._area = None
@property
def area(self):
"""
Get polygon area in m2
:return: float
"""
return self._area
@area.setter
def area(self, value):
"""
Set polygon area in m2
:param value: float
"""
self._area = value

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

@ -0,0 +1,142 @@
import json
import threading
from co2_emission.co2_emission import Co2Emission
from costs.cost import Cost
from flask import Response, request
from flask_restful import Resource
from hub_api.config import Config
from hub_api.helpers.session_helper import session, refresh_session
from hub_api.mockup.building import Building
from hub_api.persistence.mock import dic
class RetrofitResults(Resource, Config):
def __init__(self):
super().__init__()
self._scenario_ids = {'current status': 0,
'skin retrofit': 1,
'system retrofit and pv': 2,
'skin and system retrofit with pv': 3
}
def _calculate_building(self, building_results, user_id, application_id, scenario, scenario_id):
building_info = self.database.building(building_results['building'], user_id, application_id, scenario)
archetype = self.energy_systems_catalog.get_entry(building_info.system_name)
mockup_building = Building(building_info, building_results, archetype)
life_cycle = Cost(mockup_building, retrofit_scenario=scenario_id).life_cycle
operational_co2 = Co2Emission(mockup_building).operational_co2
global_capital_costs = life_cycle[f'Scenario {scenario_id}']['global_capital_costs']
global_operational_costs = life_cycle[f'Scenario {scenario_id}']['global_operational_costs']
global_capital_incomes = life_cycle[f'Scenario {scenario_id}']['global_capital_incomes']
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'] = {
'total_capital_costs_skin': life_cycle[f'Scenario {scenario_id}']['total_capital_costs_skin'],
'total_capital_costs_systems': life_cycle[f'Scenario {scenario_id}']['total_capital_costs_systems'],
'end_of_life_costs': life_cycle[f'Scenario {scenario_id}']['end_of_life_costs'],
'total_operational_costs': life_cycle[f'Scenario {scenario_id}']['total_operational_costs'],
'total_maintenance_costs': life_cycle[f'Scenario {scenario_id}']['total_maintenance_costs'],
'operational_incomes': life_cycle[f'Scenario {scenario_id}']['operational_incomes'],
'capital_incomes': life_cycle[f'Scenario {scenario_id}']['capital_incomes'],
'global_capital_costs': {
'B2010_opaque_walls': global_capital_costs['B2010_opaque_walls'].tolist(),
'B2020_transparent': global_capital_costs['B2020_transparent'].tolist(),
'B3010_opaque_roof': global_capital_costs['B3010_opaque_roof'].tolist(),
'B10_superstructure': global_capital_costs['B10_superstructure'].tolist(),
'D3020_heat_generating_systems': global_capital_costs['D3020_heat_generating_systems'].tolist(),
'D3030_cooling_generation_systems': global_capital_costs['D3030_cooling_generation_systems'].tolist(),
'D3080_other_hvac_ahu': global_capital_costs['D3080_other_hvac_ahu'].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(),
},
'global_end_of_life_costs': life_cycle[f'Scenario {scenario_id}']['global_end_of_life_costs'][
'End_of_life_costs'].tolist(),
'global_operational_costs': {
'fixed_costs_electricity_peak': global_operational_costs['Fixed_costs_electricity_peak'].tolist(),
'fixed_costs_electricity_monthly': global_operational_costs['Fixed_costs_electricity_monthly'].tolist(),
'variable_costs_electricity': global_operational_costs['Variable_costs_electricity'].tolist(),
'fixed_costs_gas': global_operational_costs['Fixed_costs_gas'].tolist(),
'variable_costs_gas': global_operational_costs['Variable_costs_gas'].tolist()
},
'global_maintenance_costs': {
'heating_maintenance': global_maintenance_costs['Heating_maintenance'].tolist(),
'cooling_maintenance': global_maintenance_costs['Cooling_maintenance'].tolist(),
'pv_maintenance': global_maintenance_costs['PV_maintenance'].tolist(),
},
'global_operational_incomes': life_cycle[f'Scenario {scenario_id}']['global_operational_incomes'][
'Incomes electricity'].tolist(),
'global_capital_incomes': {
'subsidies_construction': global_capital_incomes['Subsidies construction'].tolist(),
'subsidies_hvac': global_capital_incomes['Subsidies HVAC'].tolist(),
'subsidies_pv': global_capital_incomes['Subsidies PV'].tolist()
}
}
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)
# 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

16
hub_api/tests/test_api.py Normal file
View File

@ -0,0 +1,16 @@
from unittest import TestCase
from hub_api.config import Config
from hub.persistence.models import City, Application, CityObject, SimulationResults, User
class TestApi(TestCase):
def test_create_database(self):
config = Config()
Application.__table__.create(bind=config.repository.engine, checkfirst=True)
User.__table__.create(bind=config.repository.engine, checkfirst=True)
City.__table__.create(bind=config.repository.engine, checkfirst=True)
CityObject.__table__.create(bind=config.repository.engine, checkfirst=True)
SimulationResults.__table__.create(bind=config.repository.engine, checkfirst=True)
config.database.create_user('Administrator', )
self.assertTrue(True)

14
hub_api/workflow/costs.py Normal file
View File

@ -0,0 +1,14 @@
from flask_restful import Resource
from hub_api.config import Config
class Costs(Resource, Config):
def __init__(self):
super().__init__()
def post(self):
"""
API call for performing the cost workflow
"""
raise NotImplementedError()

View File

@ -0,0 +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 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.helpers.session_helper import refresh_session
class EnergyPlus(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)
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

@ -0,0 +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 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.helpers.session_helper import refresh_session
class InselMonthlyEnergyBalance(Resource, Config):
def __init__(self):
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):
"""
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()
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

@ -27,3 +27,9 @@ flasgger
cerc-hub cerc-hub
python-dotenv python-dotenv
mapbox_earcut mapbox_earcut
cerc-costs
cerc-co2-emission
werkzeug
sqlalchemy
pathlib
pymongo