Merge pull request 'Removed unneeded files' (#7) from api-v2 into main
Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/p_yefi/gamification_service_v2/pulls/7
This commit is contained in:
commit
01bbc9e746
46
bootstrap.py
46
bootstrap.py
|
@ -9,33 +9,16 @@ import flask
|
||||||
import yaml
|
import yaml
|
||||||
from flask_restful import Api
|
from flask_restful import Api
|
||||||
from hub_api.city_info import CityInfo, City
|
from hub_api.city_info import CityInfo, City
|
||||||
from hub_api.geometry import Geometry
|
|
||||||
from hub_api.greenery_catalog import GreeneryCatalogEntries
|
|
||||||
from hub_api.greenery_catalog import GreeneryCatalogEntry
|
|
||||||
from hub_api.greenery_catalog import GreeneryCatalogNames
|
|
||||||
from hub_api.construction_catalog import ConstructionCatalogEntries
|
|
||||||
from hub_api.construction_catalog import ConstructionCatalogEntry
|
|
||||||
from hub_api.construction_catalog import ConstructionCatalogNames
|
|
||||||
from hub_api.usage_catalog import UsageCatalogEntries
|
|
||||||
from hub_api.usage_catalog import UsageCatalogEntry
|
|
||||||
from hub_api.usage_catalog import UsageCatalogNames
|
|
||||||
from hub_api.heat_pump import HeatPump
|
|
||||||
from hub_api.lca import MaterialLCACatalog
|
|
||||||
from hub_api.lca import MaterialLCACalculations
|
|
||||||
from hub_api.construction import Construction
|
|
||||||
from hub_api.usage import Usage
|
|
||||||
from hub_api.energy_demand import EnergyDemand
|
|
||||||
from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive
|
from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive
|
||||||
from hub_api.uptime import Uptime
|
from hub_api.uptime import Uptime
|
||||||
from hub_api.greenery import Greenery
|
|
||||||
from hub_api.user import User, UserLogin
|
from hub_api.user import User, UserLogin
|
||||||
from flasgger import LazyJSONEncoder, Swagger
|
from flasgger import LazyJSONEncoder, Swagger
|
||||||
|
from flask import Response
|
||||||
|
|
||||||
app = flask.Flask('gamification')
|
app = flask.Flask('gamification')
|
||||||
app.json_encoder = LazyJSONEncoder
|
app.json_encoder = LazyJSONEncoder
|
||||||
api = Api(app)
|
api = Api(app)
|
||||||
|
|
||||||
|
|
||||||
with open("hub_api/docs/openapi-specs.yml", "r") as stream:
|
with open("hub_api/docs/openapi-specs.yml", "r") as stream:
|
||||||
swagger_config = {
|
swagger_config = {
|
||||||
"headers": [],
|
"headers": [],
|
||||||
|
@ -57,23 +40,6 @@ with open("hub_api/docs/openapi-specs.yml", "r") as stream:
|
||||||
print(exc)
|
print(exc)
|
||||||
|
|
||||||
api.add_resource(Uptime, '/v1.4/uptime')
|
api.add_resource(Uptime, '/v1.4/uptime')
|
||||||
api.add_resource(Geometry, '/v1.4/geometry')
|
|
||||||
api.add_resource(GreeneryCatalogEntry, '/v1.4/greenery-catalog/entry')
|
|
||||||
api.add_resource(GreeneryCatalogEntries, '/v1.4/greenery-catalog/entries')
|
|
||||||
api.add_resource(GreeneryCatalogNames, '/v1.4/greenery-catalog/names')
|
|
||||||
api.add_resource(ConstructionCatalogEntry, '/v1.4/construction-catalog/entry')
|
|
||||||
api.add_resource(ConstructionCatalogEntries, '/v1.4/construction-catalog/entries')
|
|
||||||
api.add_resource(ConstructionCatalogNames, '/v1.4/construction-catalog/names')
|
|
||||||
api.add_resource(Construction, '/v1.4/construction/<int:city_id>')
|
|
||||||
api.add_resource(UsageCatalogEntry, '/v1.4/usage-catalog/entry')
|
|
||||||
api.add_resource(UsageCatalogEntries, '/v1.4/usage-catalog/entries')
|
|
||||||
api.add_resource(UsageCatalogNames, '/v1.4/usage-catalog/names')
|
|
||||||
api.add_resource(Usage, '/v1.4/usage')
|
|
||||||
api.add_resource(EnergyDemand, '/v1.4/energy-demand/<int:city_id>')
|
|
||||||
# api.add_resource(LCA, '/v1.4/lca')
|
|
||||||
api.add_resource(MaterialLCACatalog, '/v1.4/material_lca_catalog/entries/<int:city_id>')
|
|
||||||
api.add_resource(MaterialLCACalculations, '/v1.4/material_lca_catalog/calculations/<int:city_id>')
|
|
||||||
api.add_resource(HeatPump, '/v1.4/heat-pump/<int:city_id>')
|
|
||||||
api.add_resource(User, '/v1.4/user')
|
api.add_resource(User, '/v1.4/user')
|
||||||
api.add_resource(UserLogin, '/v1.4/user/login')
|
api.add_resource(UserLogin, '/v1.4/user/login')
|
||||||
api.add_resource(SessionStart, '/v1.4/session/start')
|
api.add_resource(SessionStart, '/v1.4/session/start')
|
||||||
|
@ -81,4 +47,12 @@ 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')
|
||||||
api.add_resource(CityInfo, '/v1.4/city/<int:city_id>')
|
api.add_resource(CityInfo, '/v1.4/city/<int:city_id>')
|
||||||
api.add_resource(City, '/v1.4/city')
|
api.add_resource(City, '/v1.4/city')
|
||||||
api.add_resource(Greenery, '/v1.4/greenery')
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def home():
|
||||||
|
return Response(headers={'Access-Control-Allow-Origin': '*'})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(port=15789, host="0.0.0.0", debug=False)
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
"""
|
|
||||||
Main
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author name guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Project Collaborator name peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
from catalog_factories.construction_catalog_factory import ConstructionCatalogFactory
|
|
||||||
from catalog_factories.greenery_catalog_factory import GreeneryCatalogFactory
|
|
||||||
from catalog_factories.usage_catalog_factory import UsageCatalogFactory
|
|
||||||
from imports.construction_factory import ConstructionFactory
|
|
||||||
from imports.geometry_factory import GeometryFactory
|
|
||||||
from imports.life_cycle_assessment_factory import LifeCycleAssessment
|
|
||||||
# from imports.schedules_factory import SchedulesFactory
|
|
||||||
from imports.usage_factory import UsageFactory
|
|
||||||
from imports.weather_factory import WeatherFactory
|
|
||||||
from flask import Response
|
|
||||||
import hub_api.helpers.session_helper as sh
|
|
||||||
import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from bootstrap import app
|
|
||||||
|
|
||||||
sh.begin_time = datetime.datetime.now()
|
|
||||||
# initialize catalogs
|
|
||||||
sh.greenery_catalog = GreeneryCatalogFactory('nrel').catalog
|
|
||||||
sh.construction_catalog = ConstructionCatalogFactory('nrel').catalog
|
|
||||||
sh.usage_catalog = UsageCatalogFactory('comnet').catalog
|
|
||||||
|
|
||||||
# Enrich the city
|
|
||||||
data_path = (Path(__file__).parent / 'data').resolve()
|
|
||||||
rihno_path = (Path(data_path / 'dompark.3dm')).resolve()
|
|
||||||
city = GeometryFactory('rhino', rihno_path).city
|
|
||||||
|
|
||||||
for building in city.buildings:
|
|
||||||
# Rihno files have no information about the function or the year of construction
|
|
||||||
building.year_of_construction = 1995
|
|
||||||
building.function = 'industry'
|
|
||||||
building.human_readable_name = "Dompark"
|
|
||||||
|
|
||||||
ConstructionFactory('nrel', city).enrich()
|
|
||||||
UsageFactory('comnet', city).enrich()
|
|
||||||
# SchedulesFactory('comnet', city).enrich()
|
|
||||||
LifeCycleAssessment('material', city).enrich()
|
|
||||||
LifeCycleAssessment('machine', city).enrich()
|
|
||||||
LifeCycleAssessment('fuel', city).enrich()
|
|
||||||
LifeCycleAssessment('vehicle', city).enrich()
|
|
||||||
|
|
||||||
montreal_weather_file = (Path(__file__).parent / './data/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve()
|
|
||||||
city.climate_file = (data_path / f'{city.climate_reference_city}.cli').resolve()
|
|
||||||
WeatherFactory('epw', city, file_name=montreal_weather_file).enrich()
|
|
||||||
|
|
||||||
# Rihno files have no information about the building location
|
|
||||||
city.name = 'Montreal'
|
|
||||||
city.climate_reference_city = 'Montreal'
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def home():
|
|
||||||
return Response(headers={'Access-Control-Allow-Origin': '*'})
|
|
||||||
|
|
||||||
|
|
||||||
app.run(port=15789, host="0.0.0.0", debug=False)
|
|
|
@ -8,13 +8,15 @@ import json
|
||||||
from flask import Response, request, g
|
from flask import Response, request, g
|
||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
from hub_api.helpers.auth import role_required
|
from hub_api.helpers.auth import role_required
|
||||||
from persistence.models import UserRoles
|
from hub.persistence.models import UserRoles
|
||||||
from hub_logger import logger
|
from hub.hub_logger import logger
|
||||||
from imports.geometry_factory import GeometryFactory
|
from hub.imports.geometry_factory import GeometryFactory
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
from hub_api.config import Config
|
from hub_api.config import Config
|
||||||
|
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
|
||||||
class CityInfo(Resource, Config):
|
class CityInfo(Resource, Config):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -23,54 +25,17 @@ class CityInfo(Resource, Config):
|
||||||
@role_required([UserRoles.Admin.value])
|
@role_required([UserRoles.Admin.value])
|
||||||
def get(self, city_id):
|
def get(self, city_id):
|
||||||
city = self.get_city(city_id)
|
city = self.get_city(city_id)
|
||||||
|
print(city.name)
|
||||||
# TODO: this is only for dompark project and need to be removed in future versions.
|
if city:
|
||||||
floor_area = 0
|
return Response(response=json.dumps({
|
||||||
wall_construction = 'unknown'
|
'id': city.id, 'name': city.name, 'srs_name': city.srs_name,
|
||||||
floor_construction = 'unknown'
|
'time_zone': city.time_zone, 'version': city.city_version, 'country': city.country_code,
|
||||||
roof_construction = 'unknown'
|
'lat': city.latitude, 'lon': city.longitude, 'lower_corner': city.lower_corner,
|
||||||
window_type = 'unknown'
|
'upper_corner': city.upper_corner, 'created': city.created, 'updated': city.updated,
|
||||||
building_dic = {}
|
'user': {'id': city.user.id, 'name': city.user.name, 'email': city.user.email,
|
||||||
for building in city.buildings:
|
'role': city.user.role.value}
|
||||||
|
}, default=str), status=200, headers=headers)
|
||||||
usages = [] # This is only valid for dompark project as all the building have the same usage
|
return Response(response=json.dumps({'err_msg': 'City not found'}), status=404, headers=headers)
|
||||||
if building.lower_corner[2] == 0:
|
|
||||||
floor_area += building.floor_area
|
|
||||||
for internal_zone in building.internal_zones:
|
|
||||||
for usage_zone in internal_zone.usage_zones:
|
|
||||||
usages.append({'percentage': usage_zone.percentage, 'usage': usage_zone.usage})
|
|
||||||
for thermal_zone in internal_zone.thermal_zones:
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
if thermal_boundary.parent_surface.type == 'Ground':
|
|
||||||
floor_construction = thermal_boundary.construction_name
|
|
||||||
elif thermal_boundary.parent_surface.type == 'Wall':
|
|
||||||
wall_construction = thermal_boundary.construction_name
|
|
||||||
for thermal_opening in thermal_boundary.thermal_openings:
|
|
||||||
if thermal_opening.construction_name is not None:
|
|
||||||
window_type = thermal_opening.construction_name
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
roof_construction = thermal_boundary.construction_name
|
|
||||||
name = building.human_readable_name
|
|
||||||
year_of_construction = str(building.year_of_construction)
|
|
||||||
building_dic = {
|
|
||||||
'name': str(name),
|
|
||||||
'floor_area': str(floor_area),
|
|
||||||
'year_of_construction': str(year_of_construction),
|
|
||||||
'usages': usages,
|
|
||||||
'wall_construction': wall_construction,
|
|
||||||
'floor_construction': floor_construction,
|
|
||||||
'roof_construction': roof_construction,
|
|
||||||
'window_type': window_type,
|
|
||||||
'default_archetype': 'industry ASHRAE_2004:4A non_standard_dompark'
|
|
||||||
}
|
|
||||||
|
|
||||||
buildings = [building_dic]
|
|
||||||
response = {'city_name': city.name,
|
|
||||||
'climate_reference_city': str(city.climate_reference_city),
|
|
||||||
'buildings': buildings
|
|
||||||
}
|
|
||||||
return Response(json.dumps(response), status=200)
|
|
||||||
|
|
||||||
|
|
||||||
class City(Resource, Config):
|
class City(Resource, Config):
|
||||||
|
@ -108,9 +73,9 @@ class City(Resource, Config):
|
||||||
'user': {'id': saved_city.user.id, 'name': saved_city.user.name, 'email': saved_city.user.email,
|
'user': {'id': saved_city.user.id, 'name': saved_city.user.name, 'email': saved_city.user.email,
|
||||||
'role': saved_city.user.role.value}
|
'role': saved_city.user.role.value}
|
||||||
}, default=str), status=201)
|
}, default=str), status=201)
|
||||||
return Response(response=json.dumps(saved_city), status=200)
|
return Response(response=json.dumps(saved_city), status=200, headers=headers)
|
||||||
else:
|
else:
|
||||||
return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400)
|
return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400, headers=headers)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}), status=400)
|
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}), status=400, headers=headers)
|
||||||
|
|
|
@ -3,28 +3,34 @@ 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
|
||||||
"""
|
"""
|
||||||
from exports.db_factory import DBFactory as CityExportFactory
|
from hub.exports.db_factory import DBFactory as CityExportFactory
|
||||||
from imports.db_factory import DBFactory
|
from hub.imports.db_factory import DBFactory
|
||||||
import os
|
import os
|
||||||
import pickle
|
from hub.imports.user_factory import UserFactory
|
||||||
|
from hub.exports.user_factory import UserFactory as ExUserFactory
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.export_db_factory = CityExportFactory(db_name='hub_prod', app_env='PROD',
|
db_name = None
|
||||||
|
app_env = None
|
||||||
|
if os.getenv("FLASK_DEBUG") == 'production':
|
||||||
|
db_name = 'hub_prod'
|
||||||
|
app_env = 'PROD'
|
||||||
|
elif os.getenv("FLASK_DEBUG") == 'testing':
|
||||||
|
db_name = 'test_db'
|
||||||
|
app_env = 'TEST'
|
||||||
|
|
||||||
|
self.export_db_factory = CityExportFactory(db_name=db_name, app_env=app_env,
|
||||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||||
self.import_db_factory = DBFactory(db_name='hub_prod', app_env='PROD',
|
self.import_db_factory = DBFactory(db_name=db_name, app_env=app_env,
|
||||||
|
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||||
|
self.user_factory = UserFactory(db_name=db_name, app_env=app_env,
|
||||||
|
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||||
|
|
||||||
|
self.ex_user_factory = ExUserFactory(db_name=db_name, app_env=app_env,
|
||||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||||
|
|
||||||
def get_city(self, city_id):
|
def get_city(self, city_id):
|
||||||
city_obj = self.export_db_factory.get_city(city_id)
|
return self.export_db_factory.get_city(city_id)
|
||||||
city = pickle.loads(city_obj.city)
|
|
||||||
for building in city.buildings:
|
|
||||||
building.heated = True
|
|
||||||
building.cooled = True
|
|
||||||
building.attic_heated = 0
|
|
||||||
building.basement_heated = 0
|
|
||||||
for surface in building.surfaces:
|
|
||||||
surface.swr = 0.2
|
|
||||||
return city
|
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
"""
|
|
||||||
Construction
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Code contributors: Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from flask import Response, request
|
|
||||||
from flask_restful import Resource
|
|
||||||
from hub_api.config import Config
|
|
||||||
from city_model_structure.building_demand.layer import Layer
|
|
||||||
from city_model_structure.building_demand.material import Material
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
|
|
||||||
|
|
||||||
class Construction(Resource, Config):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def put(self, city_id):
|
|
||||||
city = self.get_city(city_id)
|
|
||||||
try:
|
|
||||||
building_names = request.json['building_names']
|
|
||||||
constructions = request.json['constructions']
|
|
||||||
for construction in constructions:
|
|
||||||
construction_type = construction['type']
|
|
||||||
layers = construction['layers']
|
|
||||||
for building_name in building_names:
|
|
||||||
for building in city.buildings:
|
|
||||||
if building.human_readable_name != building_name:
|
|
||||||
continue
|
|
||||||
for internal_zone in building.internal_zones:
|
|
||||||
for thermal_zone in internal_zone.thermal_zones:
|
|
||||||
thermal_zone.additional_thermal_bridge_u_value = request.json['extra_loses_due_to_thermal_bridges']
|
|
||||||
thermal_zone.indirect_heated_area_ratio = request.json['indirect_heated_ratio']
|
|
||||||
thermal_zone.infiltration_rate_system_on = request.json['infiltration_rate_for_ventilation_system_on']
|
|
||||||
thermal_zone.infiltration_rate_system_off = request.json['infiltration_rate_for_ventilation_system_off']
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
if thermal_boundary.parent_surface.type == construction_type:
|
|
||||||
_layers = []
|
|
||||||
for layer in layers:
|
|
||||||
_layer = Layer()
|
|
||||||
thermal_resistance = layer['material']['thermal_resistance']
|
|
||||||
conductivity = layer['material']['conductivity']
|
|
||||||
density = layer['material']['density']
|
|
||||||
specific_heat = layer['material']['specific_heat']
|
|
||||||
_layer.thickness = layer['thickness']
|
|
||||||
_material = Material()
|
|
||||||
_material.id = layer['material']['id']
|
|
||||||
_material.name = layer['material']['name']
|
|
||||||
_material.solar_absorptance = layer['material']['solar_absorptance']
|
|
||||||
_material.thermal_absorptance = layer['material']['thermal_absorptance']
|
|
||||||
_material.visible_absorptance = layer['material']['visible_absorptance']
|
|
||||||
_material.no_mass = layer['material']['no_mass']
|
|
||||||
_material.thermal_resistance = (thermal_resistance if thermal_resistance != '' else None)
|
|
||||||
_material.conductivity = (conductivity if conductivity != '' else None)
|
|
||||||
_material.density = (density if density != '' else None)
|
|
||||||
_material.specific_heat = (specific_heat if specific_heat != '' else None)
|
|
||||||
_layer.material = _material
|
|
||||||
_layers.append(_layer)
|
|
||||||
thermal_boundary.layers = _layers
|
|
||||||
if 'window' in construction.keys():
|
|
||||||
for thermal_opening in thermal_boundary.thermal_openings:
|
|
||||||
thermal_opening.frame_ratio = construction['window']['frame_ratio']
|
|
||||||
thermal_opening.g_value = construction['window']['g_value']
|
|
||||||
thermal_opening.overall_u_value = construction['window']['overall_u_value']
|
|
||||||
response = {'result': 'succeed'}
|
|
||||||
except KeyError as ex:
|
|
||||||
response = {'error': f'Mandatory parameter {ex} is missing'}
|
|
||||||
return Response(json.dumps(response), status=400)
|
|
||||||
|
|
||||||
return Response(json.dumps(response), status=200)
|
|
|
@ -1,196 +0,0 @@
|
||||||
"""
|
|
||||||
Construction catalog
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from flask import request, Response
|
|
||||||
from flask_restful import Resource
|
|
||||||
|
|
||||||
from catalog_factories.data_models.construction.archetype import Archetype
|
|
||||||
from catalog_factories.data_models.construction.construction import Construction
|
|
||||||
from catalog_factories.data_models.construction.material import Material
|
|
||||||
from catalog_factories.data_models.construction.window import Window
|
|
||||||
from hub_api.helpers.session_helper import refresh_session
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
|
|
||||||
|
|
||||||
class ToJson:
|
|
||||||
@staticmethod
|
|
||||||
def archetype_to_json(archetype):
|
|
||||||
constructions = []
|
|
||||||
for construction in archetype.constructions:
|
|
||||||
constructions.append(ToJson.construction_to_json(construction))
|
|
||||||
dictionary = {
|
|
||||||
'name': archetype.name,
|
|
||||||
'function': archetype.function,
|
|
||||||
'construction_period': archetype.construction_period,
|
|
||||||
'average_storey_height': archetype.average_storey_height,
|
|
||||||
'thermal_capacity': archetype.thermal_capacity,
|
|
||||||
'extra_loses_due_to_thermal_bridges': archetype.extra_loses_due_to_thermal_bridges,
|
|
||||||
'indirect_heated_ratio': archetype.indirect_heated_ratio,
|
|
||||||
'infiltration_rate_for_ventilation_system_off': archetype.infiltration_rate_for_ventilation_system_off,
|
|
||||||
'infiltration_rate_for_ventilation_system_on': archetype.infiltration_rate_for_ventilation_system_on,
|
|
||||||
'constructions': constructions
|
|
||||||
}
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def construction_to_json(construction):
|
|
||||||
layers = []
|
|
||||||
for layer in construction.layers:
|
|
||||||
layers.append(ToJson.layer_to_json(layer))
|
|
||||||
dictionary = {'name': construction.name,
|
|
||||||
'type': construction.type,
|
|
||||||
'layers': layers
|
|
||||||
}
|
|
||||||
if construction.window is not None:
|
|
||||||
dictionary['window_ratio'] = construction.window_ratio
|
|
||||||
dictionary['window'] = ToJson.window_to_json(construction.window)
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def window_to_json(window):
|
|
||||||
if window is None:
|
|
||||||
return {}
|
|
||||||
dictionary = {
|
|
||||||
'name': window.name,
|
|
||||||
'frame_ratio': window.frame_ratio,
|
|
||||||
'g_value': window.g_value,
|
|
||||||
'overall_u_value': str(window.overall_u_value)
|
|
||||||
}
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def layer_to_json(layer):
|
|
||||||
dictionary = {'name': layer.name,
|
|
||||||
'thickness': layer.thickness,
|
|
||||||
'material': ToJson.material_to_json(layer.material),
|
|
||||||
}
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def material_to_json(material):
|
|
||||||
dictionary = {'id': material.id,
|
|
||||||
'name': material.name,
|
|
||||||
'solar_absorptance': material.solar_absorptance,
|
|
||||||
'thermal_absorptance': material.thermal_absorptance,
|
|
||||||
'visible_absorptance': material.visible_absorptance,
|
|
||||||
'no_mass': str(material.no_mass),
|
|
||||||
'thermal_resistance': '',
|
|
||||||
'conductivity': '',
|
|
||||||
'density': '',
|
|
||||||
'specific_heat': ''
|
|
||||||
}
|
|
||||||
if material.no_mass:
|
|
||||||
dictionary['thermal_resistance'] = material.thermal_resistance
|
|
||||||
else:
|
|
||||||
dictionary['conductivity'] = material.conductivity
|
|
||||||
dictionary['density'] = material.density
|
|
||||||
dictionary['specific_heat'] = material.specific_heat
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
|
|
||||||
class ConstructionCatalogEntry(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def post(self):
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.construction_catalog
|
|
||||||
name = None
|
|
||||||
if request.data == b'' or request.json['name'] is None:
|
|
||||||
response = {'error': 'Mandatory parameter "name" is missing'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
||||||
try:
|
|
||||||
name = request.json['name']
|
|
||||||
entry = catalog.get_entry(name)
|
|
||||||
output = {}
|
|
||||||
if isinstance(entry, Archetype):
|
|
||||||
output['archetypes'] = ToJson.archetype_to_json(entry)
|
|
||||||
if isinstance(entry, Construction):
|
|
||||||
output['constructions'] = ToJson.construction_to_json(entry)
|
|
||||||
if isinstance(entry, Material):
|
|
||||||
output['materials'] = ToJson.material_to_json(entry)
|
|
||||||
if isinstance(entry, Window):
|
|
||||||
output['windows'] = ToJson.window_to_json(entry)
|
|
||||||
return Response(json.dumps(output), headers=headers)
|
|
||||||
except IndexError:
|
|
||||||
response = {'error': f'Name "{name}" unknown'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class ConstructionCatalogEntries(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def post(self):
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.construction_catalog
|
|
||||||
category = None
|
|
||||||
if request.data != b'':
|
|
||||||
category = request.json['category']
|
|
||||||
output = {}
|
|
||||||
if category is None:
|
|
||||||
output = {'archetypes': [], 'constructions':[], 'materials':[], 'windows':[]}
|
|
||||||
content = catalog.entries()
|
|
||||||
for archetype in content.archetypes:
|
|
||||||
output['archetypes'].append(ToJson.archetype_to_json(archetype))
|
|
||||||
for construction in content.constructions:
|
|
||||||
output['constructions'].append(ToJson.construction_to_json(construction))
|
|
||||||
for material in content.materials:
|
|
||||||
output['materials'].append(ToJson.material_to_json(material))
|
|
||||||
for window in content.windows:
|
|
||||||
output['windows'].append(ToJson.window_to_json(window))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
output[category] = []
|
|
||||||
content = catalog.entries(category)
|
|
||||||
for entry in content:
|
|
||||||
if isinstance(entry, Archetype):
|
|
||||||
output[category].append(ToJson.archetype_to_json(entry))
|
|
||||||
if isinstance(entry, Construction):
|
|
||||||
output[category].append(ToJson.construction_to_json(entry))
|
|
||||||
if isinstance(entry, Material):
|
|
||||||
output[category].append(ToJson.material_to_json(entry))
|
|
||||||
if isinstance(entry, Window):
|
|
||||||
output[category].append(ToJson.window_to_json(entry))
|
|
||||||
except ValueError:
|
|
||||||
output = {'error': f'Category "{category}" unknown'}
|
|
||||||
return Response(json.dumps(output), headers=headers, status=400)
|
|
||||||
return Response(json.dumps(output), headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
class ConstructionCatalogNames(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def post():
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.construction_catalog
|
|
||||||
category = None
|
|
||||||
if request.data != b'':
|
|
||||||
category = request.json['category']
|
|
||||||
if category is None:
|
|
||||||
return Response(json.dumps(catalog.names()), headers=headers)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
return Response(json.dumps(catalog.names(category)), headers=headers)
|
|
||||||
except ValueError:
|
|
||||||
response = {'error': f'Category "{category}" unknown'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
|
@ -3,7 +3,7 @@ info:
|
||||||
description: NextGen Cities Institute Gamification API
|
description: NextGen Cities Institute Gamification API
|
||||||
termsOfService: http://swagger.io/terms/
|
termsOfService: http://swagger.io/terms/
|
||||||
contact:
|
contact:
|
||||||
email: peteryefi@gmail.com
|
email: nextgen-cities@gmail.com
|
||||||
version: 1.4
|
version: 1.4
|
||||||
externalDocs:
|
externalDocs:
|
||||||
description: Find out more about Swagger
|
description: Find out more about Swagger
|
||||||
|
@ -78,72 +78,6 @@ paths:
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
$ref: '#/components/schemas/ApiResponse'
|
||||||
security:
|
security:
|
||||||
- BearerAuth: []
|
- BearerAuth: []
|
||||||
/v1.4/heat-pump/{city_id}:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- heatpump
|
|
||||||
summary: Create a heat pump simulation
|
|
||||||
operationId: createHeatpump
|
|
||||||
description: heatpump simulation with existing catalog data
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: appId
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
description: the Id of the application access this API
|
|
||||||
- in: path
|
|
||||||
name: city_id
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
required: true
|
|
||||||
description: Numeric ID of the city to get
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
$ref: '#/components/schemas/HeatPump'
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Heatpump simulation created successfully
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/HeatPumpRes'
|
|
||||||
'400':
|
|
||||||
description: Bad Request
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'404':
|
|
||||||
description: Not found
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'403':
|
|
||||||
description: Forbidden
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'401':
|
|
||||||
description: Unauthorized
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'500':
|
|
||||||
description: Internal server error
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: [ ]
|
|
||||||
/v1.4/city/{city_id}:
|
/v1.4/city/{city_id}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -166,7 +100,7 @@ paths:
|
||||||
description: Numeric ID of the city to get
|
description: Numeric ID of the city to get
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: City created successfully
|
description: City retrieved successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
@ -342,233 +276,6 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
$ref: '#/components/schemas/ApiResponse'
|
||||||
/v1.4/energy-demand/{city_id}:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- energy-demand
|
|
||||||
summary: Get energy demand
|
|
||||||
description: Retrieve energy demand data
|
|
||||||
operationId: getEnergyDemand
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: appId
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
description: the Id of the application access this API
|
|
||||||
- in: path
|
|
||||||
name: city_id
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
required: true
|
|
||||||
description: Numeric ID of the city to get
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Successfully retrieved energy demand data
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/EnergyDemand'
|
|
||||||
'400':
|
|
||||||
description: Bad Request
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'403':
|
|
||||||
description: Forbidden
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'401':
|
|
||||||
description: Unauthorized
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'500':
|
|
||||||
description: Internal server error
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: [ ]
|
|
||||||
/v1.4/construction/{city_id}:
|
|
||||||
put:
|
|
||||||
tags:
|
|
||||||
- construction
|
|
||||||
summary: updates a city with building construction
|
|
||||||
description: updates a city with building construction
|
|
||||||
operationId: updateConstruction
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: appId
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
description: the Id of the application access this API
|
|
||||||
- in: path
|
|
||||||
name: city_id
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
required: true
|
|
||||||
description: Numeric ID of the city to get
|
|
||||||
requestBody:
|
|
||||||
description: creates building construction catalog
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Construction'
|
|
||||||
application/xml:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Construction'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: city updated with building construction successfully
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Construction'
|
|
||||||
'400':
|
|
||||||
description: Bad Request
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'403':
|
|
||||||
description: Forbidden
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'401':
|
|
||||||
description: Unauthorized
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'500':
|
|
||||||
description: Internal server error
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: [ ]
|
|
||||||
/v1.4/construction-catalog/entries:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- construction-catalog
|
|
||||||
summary: creates building construction catalog entries
|
|
||||||
description: create the construction catalog entries
|
|
||||||
operationId: catalogEntries
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: appId
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
description: the Id of the application access this API
|
|
||||||
requestBody:
|
|
||||||
description: creates building construction catalog
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ConstructionCatalogEntries'
|
|
||||||
application/xml:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ConstructionCatalogEntries'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Construction catalog entries created successfully
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ConstructionCatalogEntries'
|
|
||||||
'400':
|
|
||||||
description: Bad Request
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'403':
|
|
||||||
description: Forbidden
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'401':
|
|
||||||
description: Unauthorized
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'500':
|
|
||||||
description: Internal server error
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: [ ]
|
|
||||||
/v1.4/construction-catalog/entry:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- construction-catalog
|
|
||||||
summary: creates building construction catalog
|
|
||||||
description: create the construction catalog of a building
|
|
||||||
operationId: catalogEntry
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: appId
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
description: the Id of the application access this API
|
|
||||||
requestBody:
|
|
||||||
description: creates building construction catalog
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ConstructionCatalogEntry'
|
|
||||||
application/xml:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ConstructionCatalogEntry'
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: Construction catalog created successfully
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ConstructionCatalogEntry'
|
|
||||||
'400':
|
|
||||||
description: Bad Request
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'403':
|
|
||||||
description: Forbidden
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'401':
|
|
||||||
description: Unauthorized
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
'500':
|
|
||||||
description: Internal server error
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ApiResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: [ ]
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
City:
|
City:
|
||||||
|
@ -619,211 +326,6 @@ components:
|
||||||
updated:
|
updated:
|
||||||
type: string
|
type: string
|
||||||
example: 2023-01-15 18:40:54.64877
|
example: 2023-01-15 18:40:54.64877
|
||||||
HeatPump:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
StartYear:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 10
|
|
||||||
EndYear:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 10
|
|
||||||
MaximumHPEnergyInput:
|
|
||||||
type: float
|
|
||||||
example: 9.8382
|
|
||||||
HoursOfStorageAtMaxDemand:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 9
|
|
||||||
BuildingSuppTemp:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 40
|
|
||||||
TemperatureDifference:
|
|
||||||
type: float
|
|
||||||
example: 9.8382
|
|
||||||
FuelLHV:
|
|
||||||
type: float
|
|
||||||
example: 9.8382
|
|
||||||
FuelPrice:
|
|
||||||
type: float
|
|
||||||
example: 9.8382
|
|
||||||
FuelEF:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 40
|
|
||||||
FuelDensity:
|
|
||||||
type: float
|
|
||||||
example: 9.8382
|
|
||||||
HPSupTemp:
|
|
||||||
type: float
|
|
||||||
example: 9.8382
|
|
||||||
HeatPumpType:
|
|
||||||
type: string
|
|
||||||
example: Water to Water HP
|
|
||||||
enum:
|
|
||||||
- Water to Water HP
|
|
||||||
- Air Source HP
|
|
||||||
HeatPumpModel:
|
|
||||||
type: string
|
|
||||||
example: Water to Water HP
|
|
||||||
enum:
|
|
||||||
- ClimateMaster 156 kW
|
|
||||||
- ClimateMaster 256 kW
|
|
||||||
- ClimateMaster 335 kW
|
|
||||||
- 012
|
|
||||||
- 015
|
|
||||||
- 018
|
|
||||||
- 023
|
|
||||||
- 030
|
|
||||||
- 033
|
|
||||||
- 037
|
|
||||||
- 044
|
|
||||||
- 047
|
|
||||||
- 057
|
|
||||||
- 070
|
|
||||||
- 087
|
|
||||||
- 097
|
|
||||||
- 102
|
|
||||||
- 120
|
|
||||||
- 130
|
|
||||||
- 140
|
|
||||||
SimType:
|
|
||||||
type: int
|
|
||||||
example: 1
|
|
||||||
format: int64
|
|
||||||
EnergyDemand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: float
|
|
||||||
example: [ 610.610, 754.746, 288.338 ]
|
|
||||||
required:
|
|
||||||
- SimType
|
|
||||||
- EnergyDemand
|
|
||||||
- HeatPumpModel
|
|
||||||
- HeatPumpType
|
|
||||||
- FuelEF
|
|
||||||
- FuelDensity
|
|
||||||
- FuelPrice
|
|
||||||
- FuelLHV
|
|
||||||
- BuildingSuppTemp
|
|
||||||
- TemperatureDifference
|
|
||||||
- HoursOfStorageAtMaxDemand
|
|
||||||
- MaximumHPEnergyInput
|
|
||||||
- StartYear
|
|
||||||
- EndYear
|
|
||||||
ConstructionCatalogEntry:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
example: 189.1-2009 Nonres 4B Ext Wall Mass
|
|
||||||
required:
|
|
||||||
- name
|
|
||||||
ConstructionCatalogEntries:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
category:
|
|
||||||
type: string
|
|
||||||
example: archetypes
|
|
||||||
required:
|
|
||||||
- category
|
|
||||||
Construction:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
building_names:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
example: ["Dompark"]
|
|
||||||
extra_loses_due_to_thermal_bridges:
|
|
||||||
type: float
|
|
||||||
example: 0.15
|
|
||||||
indirect_heated_ratio:
|
|
||||||
type: float
|
|
||||||
example: 0.15
|
|
||||||
infiltration_rate_for_ventilation_system_off:
|
|
||||||
type: float
|
|
||||||
example: 0.5
|
|
||||||
infiltration_rate_for_ventilation_system_on:
|
|
||||||
type: float
|
|
||||||
example: 0.0
|
|
||||||
constructions:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
example: 189.1-2009 Res 4B Ext Wall Steel-Framed
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
example: Wall
|
|
||||||
layers:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
$ref: '#components/schemas/Layers'
|
|
||||||
required:
|
|
||||||
- building_names
|
|
||||||
- extra_loses_due_to_thermal_bridges
|
|
||||||
- indirect_heated_ratio
|
|
||||||
- infiltration_rate_for_ventilation_system_off
|
|
||||||
- infiltration_rate_for_ventilation_system_on
|
|
||||||
- constructions
|
|
||||||
Layers:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
example: Layer 1
|
|
||||||
thickness:
|
|
||||||
type: float
|
|
||||||
example: 0.0
|
|
||||||
material:
|
|
||||||
type: object
|
|
||||||
$ref: '#/components/schemas/Material'
|
|
||||||
Material:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 12
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
example: MAT-SHEAT
|
|
||||||
solar_absorptance:
|
|
||||||
type: float
|
|
||||||
example: 0.7
|
|
||||||
thermal_absorptance:
|
|
||||||
type: float
|
|
||||||
example: 0.9
|
|
||||||
visible_absorptance:
|
|
||||||
type: float
|
|
||||||
example: 0.7
|
|
||||||
no_mass:
|
|
||||||
type: string
|
|
||||||
example: "True"
|
|
||||||
thermal_resistance:
|
|
||||||
type: float
|
|
||||||
example: 0.36256
|
|
||||||
conductivity:
|
|
||||||
type: string
|
|
||||||
density:
|
|
||||||
type: string
|
|
||||||
specific_heat:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- id
|
|
||||||
- name
|
|
||||||
- solar_absorptance
|
|
||||||
- thermal_absorptance
|
|
||||||
- visible_absorptance
|
|
||||||
- no_mass
|
|
||||||
- thermal_resistance
|
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -851,48 +353,6 @@ components:
|
||||||
- email
|
- email
|
||||||
- password
|
- password
|
||||||
- role
|
- role
|
||||||
HeatPumpRes:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
hourly_electricity_demand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
daily_electricity_demand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
monthly_electricity_demand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
daily_fossil_consumption:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
monthly_fossil_consumption:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
EnergyDemand:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
heating_demand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
cooling_demand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
lighting_demand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
appliances_demand:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
Login:
|
Login:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -924,7 +384,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
requestBodies:
|
requestBodies:
|
||||||
User:
|
User:
|
||||||
description: Pet object that needs to be added to the store
|
description: User object that is to be created
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
|
|
@ -1,396 +0,0 @@
|
||||||
import json
|
|
||||||
from flask import request, Response
|
|
||||||
from flask_restful import Resource
|
|
||||||
from pathlib import Path
|
|
||||||
from geomeppy import IDF
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
import hub_api.helpers.session_helper as sh
|
|
||||||
import helpers.constants as cte
|
|
||||||
import csv
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
from hub_api.config import Config
|
|
||||||
|
|
||||||
|
|
||||||
class EnergyDemand(Resource, Config):
|
|
||||||
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
|
|
||||||
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
|
|
||||||
_SURFACE = 'BUILDINGSURFACE:DETAILED'
|
|
||||||
_WINDOW_SURFACE = 'FENESTRATIONSURFACE:DETAILED'
|
|
||||||
_CONSTRUCTION = 'CONSTRUCTION'
|
|
||||||
_MATERIAL = 'MATERIAL'
|
|
||||||
_MATERIAL_NOMASS = 'MATERIAL:NOMASS'
|
|
||||||
_ROUGHNESS = 'MediumRough'
|
|
||||||
_HOURLY_SCHEDULE = 'SCHEDULE:DAY:HOURLY'
|
|
||||||
_COMPACT_SCHEDULE = 'SCHEDULE:COMPACT'
|
|
||||||
_FILE_SCHEDULE = 'SCHEDULE:FILE'
|
|
||||||
_ZONE = 'ZONE'
|
|
||||||
_LIGHTS = 'LIGHTS'
|
|
||||||
_PEOPLE = 'PEOPLE'
|
|
||||||
_APPLIANCES = 'OTHEREQUIPMENT'
|
|
||||||
_HEATING_COOLING = 'THERMOSTATSETPOINT:DUALSETPOINT'
|
|
||||||
_INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE'
|
|
||||||
_BUILDING_SURFACE = 'BuildingSurfaceDetailed'
|
|
||||||
_SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS'
|
|
||||||
_ON_OFF = 'On/Off'
|
|
||||||
_FRACTION = 'Fraction'
|
|
||||||
_ANY_NUMBER = 'Any Number'
|
|
||||||
_CONTINUOUS = 'Continuous'
|
|
||||||
_DISCRETE = 'Discrete'
|
|
||||||
_BUILDING = 'BUILDING'
|
|
||||||
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
|
|
||||||
_LOCATION = 'SITE:LOCATION'
|
|
||||||
_WINDOW_MATERIAL_SIMPLE = 'WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM'
|
|
||||||
_WINDOW = 'WINDOW'
|
|
||||||
_MATERIAL_ROOFVEGETATION = 'MATERIAL:ROOFVEGETATION'
|
|
||||||
_SIMPLE = 'Simple'
|
|
||||||
|
|
||||||
idf_surfaces = {
|
|
||||||
# todo: make an enum for all the surface types
|
|
||||||
cte.WALL: 'wall',
|
|
||||||
cte.GROUND: 'floor',
|
|
||||||
cte.ROOF: 'roof'
|
|
||||||
}
|
|
||||||
idf_usage = {
|
|
||||||
# todo: make an enum for all the usage types
|
|
||||||
cte.RESIDENTIAL: 'residential_building'
|
|
||||||
}
|
|
||||||
idf_type_limits = {
|
|
||||||
cte.ON_OFF: 'on/off',
|
|
||||||
cte.FRACTION: 'Fraction',
|
|
||||||
cte.ANY_NUMBER: 'Any Number',
|
|
||||||
cte.CONTINUOUS: 'Continuous',
|
|
||||||
cte.DISCRETE: 'Discrete',
|
|
||||||
cte.TEMPERATURE: 'Any Number'
|
|
||||||
}
|
|
||||||
idf_day_types = {
|
|
||||||
cte.MONDAY: 'Monday',
|
|
||||||
cte.TUESDAY: 'Tuesday',
|
|
||||||
cte.WEDNESDAY: 'Wednesday',
|
|
||||||
cte.THURSDAY: 'Thursday',
|
|
||||||
cte.FRIDAY: 'Friday',
|
|
||||||
cte.SATURDAY: 'Saturday',
|
|
||||||
cte.SUNDAY: 'Sunday',
|
|
||||||
cte.HOLIDAY: 'Holidays',
|
|
||||||
cte.WINTER_DESIGN_DAY: 'WinterDesignDay',
|
|
||||||
cte.SUMMER_DESIGN_DAY: 'SummerDesignDay'
|
|
||||||
}
|
|
||||||
idf_schedule_types = {
|
|
||||||
'compact': 'Compact',
|
|
||||||
cte.DAY: 'Day',
|
|
||||||
cte.WEEK: 'Week',
|
|
||||||
cte.YEAR: 'Year',
|
|
||||||
'file': 'File'
|
|
||||||
}
|
|
||||||
idf_schedule_data_type = {
|
|
||||||
'compact': 'Compact',
|
|
||||||
'hourly': 'Hourly',
|
|
||||||
'daily': 'Daily',
|
|
||||||
'interval': 'Interval',
|
|
||||||
'list': 'List',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# this class is mostly hardcoded, as is intended to be used only for Dompark project,
|
|
||||||
# other projects should use the normal idf workflow instead.
|
|
||||||
super().__init__()
|
|
||||||
self._output_path = Path(Path(__file__).parent.parent / 'tmp').resolve()
|
|
||||||
self._data_path = Path(Path(__file__).parent.parent / 'data').resolve()
|
|
||||||
self._city = None
|
|
||||||
self._greenery_percentage = 0
|
|
||||||
|
|
||||||
def _set_layers(self, _idf, name, layers, vegetation=None):
|
|
||||||
if vegetation is not None:
|
|
||||||
_kwargs = {'Name': name, 'Outside_Layer': vegetation.name}
|
|
||||||
for i in range(0, len(layers)):
|
|
||||||
_kwargs[f'Layer_{i + 2}'] = layers[i].material.name
|
|
||||||
else:
|
|
||||||
_kwargs = {'Name': name, 'Outside_Layer': layers[0].material.name}
|
|
||||||
for i in range(1, len(layers)):
|
|
||||||
_kwargs[f'Layer_{i + 1}'] = layers[i].material.name
|
|
||||||
_idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
|
||||||
|
|
||||||
def _update_constructions(self, _idf, ground, roof, wall, vegetation):
|
|
||||||
for construction in _idf.idfobjects[self._CONSTRUCTION]:
|
|
||||||
if construction.Name == 'Project ground floor':
|
|
||||||
# floor
|
|
||||||
self._set_layers(_idf, 'user_floor', ground)
|
|
||||||
elif construction.Name == 'Dompark Roof':
|
|
||||||
# roof
|
|
||||||
self._set_layers(_idf, 'user_roof', roof)
|
|
||||||
elif construction.Name == 'Dompark Roof Vegetation':
|
|
||||||
# roof
|
|
||||||
self._set_layers(_idf, 'user_roof_vegetation', roof, vegetation)
|
|
||||||
elif construction.Name == 'Dompark Wall':
|
|
||||||
# wall
|
|
||||||
self._set_layers(_idf, 'user_wall', wall)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
for surface in _idf.idfobjects[self._SURFACE]:
|
|
||||||
if surface.Construction_Name == 'Project ground floor':
|
|
||||||
# floor
|
|
||||||
surface.Construction_Name = 'user_floor'
|
|
||||||
elif surface.Construction_Name == 'Dompark Wall':
|
|
||||||
# wall
|
|
||||||
surface.Construction_Name = 'user_wall'
|
|
||||||
elif surface.Construction_Name == 'Dompark Roof' or surface.Construction_Name == 'Dompark Roof Vegetation':
|
|
||||||
# roof
|
|
||||||
surface.Construction_Name = 'user_roof'
|
|
||||||
if self._greenery_percentage > 0:
|
|
||||||
if surface.Name in sh.roofs_associated_to_percentage[str(self._greenery_percentage)]:
|
|
||||||
surface.Construction_Name = 'user_roof_vegetation'
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for window in _idf.idfobjects[self._WINDOW_SURFACE]:
|
|
||||||
window.Construction_Name = 'window_construction_1'
|
|
||||||
|
|
||||||
def _add_material(self, _idf, layers):
|
|
||||||
for layer in layers:
|
|
||||||
for material in _idf.idfobjects[self._MATERIAL]:
|
|
||||||
if material.Name == layer.material.name:
|
|
||||||
return
|
|
||||||
for material in _idf.idfobjects[self._MATERIAL_NOMASS]:
|
|
||||||
if material.Name == layer.material.name:
|
|
||||||
return
|
|
||||||
if str(layer.material.no_mass) == 'True':
|
|
||||||
_idf.newidfobject(self._MATERIAL_NOMASS,
|
|
||||||
Name=layer.material.name,
|
|
||||||
Roughness=self._ROUGHNESS,
|
|
||||||
Thermal_Resistance=layer.material.thermal_resistance,
|
|
||||||
Thermal_Absorptance=layer.material.thermal_absorptance,
|
|
||||||
Solar_Absorptance=layer.material.solar_absorptance,
|
|
||||||
Visible_Absorptance=layer.material.visible_absorptance
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_idf.newidfobject(self._MATERIAL,
|
|
||||||
Name=layer.material.name,
|
|
||||||
Roughness=self._ROUGHNESS,
|
|
||||||
Thickness=layer.thickness,
|
|
||||||
Conductivity=layer.material.conductivity,
|
|
||||||
Density=layer.material.density,
|
|
||||||
Specific_Heat=layer.material.specific_heat,
|
|
||||||
Thermal_Absorptance=layer.material.thermal_absorptance,
|
|
||||||
Solar_Absorptance=layer.material.solar_absorptance,
|
|
||||||
Visible_Absorptance=layer.material.visible_absorptance
|
|
||||||
)
|
|
||||||
|
|
||||||
def _add_vegetation_material(self, _idf, vegetation):
|
|
||||||
for vegetation_material in _idf.idfobjects[self._MATERIAL_ROOFVEGETATION]:
|
|
||||||
if vegetation_material.Name == vegetation.name:
|
|
||||||
return
|
|
||||||
soil = vegetation.soil
|
|
||||||
height = 0
|
|
||||||
leaf_area_index = 0
|
|
||||||
leaf_reflectivity = 0
|
|
||||||
leaf_emissivity = 0
|
|
||||||
minimal_stomatal_resistance = 0
|
|
||||||
for plant in vegetation.plants:
|
|
||||||
percentage = float(plant.percentage) / 100
|
|
||||||
height += percentage * float(plant.height)
|
|
||||||
leaf_area_index += percentage * float(plant.leaf_area_index)
|
|
||||||
leaf_reflectivity += percentage * float(plant.leaf_reflectivity)
|
|
||||||
leaf_emissivity += percentage * float(plant.leaf_emissivity)
|
|
||||||
minimal_stomatal_resistance += percentage * float(plant.minimal_stomatal_resistance)
|
|
||||||
_idf.newidfobject(self._MATERIAL_ROOFVEGETATION,
|
|
||||||
Name=vegetation.name,
|
|
||||||
Height_of_Plants=height,
|
|
||||||
Leaf_Area_Index=leaf_area_index,
|
|
||||||
Leaf_Reflectivity=leaf_reflectivity,
|
|
||||||
Leaf_Emissivity=leaf_emissivity,
|
|
||||||
Minimum_Stomatal_Resistance=minimal_stomatal_resistance,
|
|
||||||
Soil_Layer_Name=soil.name,
|
|
||||||
Roughness=soil.roughness,
|
|
||||||
Thickness=vegetation.soil_thickness,
|
|
||||||
Conductivity_of_Dry_Soil=soil.dry_conductivity,
|
|
||||||
Density_of_Dry_Soil=soil.dry_density,
|
|
||||||
Specific_Heat_of_Dry_Soil=soil.dry_specific_heat,
|
|
||||||
Thermal_Absorptance=soil.thermal_absorptance,
|
|
||||||
Solar_Absorptance=soil.solar_absorptance,
|
|
||||||
Visible_Absorptance=soil.visible_absorptance,
|
|
||||||
Saturation_Volumetric_Moisture_Content_of_the_Soil_Layer=
|
|
||||||
soil.saturation_volumetric_moisture_content,
|
|
||||||
Residual_Volumetric_Moisture_Content_of_the_Soil_Layer=
|
|
||||||
soil.residual_volumetric_moisture_content,
|
|
||||||
Initial_Volumetric_Moisture_Content_of_the_Soil_Layer=
|
|
||||||
soil.initial_volumetric_moisture_content,
|
|
||||||
Moisture_Diffusion_Calculation_Method=self._SIMPLE
|
|
||||||
)
|
|
||||||
|
|
||||||
def _add_window_construction_and_material(self, _idf, window):
|
|
||||||
name = 'glazing_1'
|
|
||||||
_kwargs = {'Name': name, 'UFactor': window.overall_u_value,
|
|
||||||
'Solar_Heat_Gain_Coefficient': window.g_value}
|
|
||||||
_idf.newidfobject(self._WINDOW_MATERIAL_SIMPLE, **_kwargs)
|
|
||||||
window_construction_name = 'window_construction_1'
|
|
||||||
_kwargs = {'Name': window_construction_name, 'Outside_Layer': name}
|
|
||||||
return _idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
|
||||||
|
|
||||||
def _add_materials(self, _idf):
|
|
||||||
building = self._city.buildings[0]
|
|
||||||
ground_surface = building.grounds[0]
|
|
||||||
roof_surface = building.roofs[0]
|
|
||||||
wall_surface = building.walls[0]
|
|
||||||
internal_zone = building.internal_zones[0]
|
|
||||||
thermal_zone = internal_zone.thermal_zones[0]
|
|
||||||
ground = None
|
|
||||||
roof = None
|
|
||||||
roof_vegetation = None
|
|
||||||
wall = None
|
|
||||||
window = None
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
if thermal_boundary.parent_surface.id == wall_surface.id:
|
|
||||||
wall = thermal_boundary.layers
|
|
||||||
if thermal_boundary.parent_surface.id == roof_surface.id:
|
|
||||||
roof = thermal_boundary.layers
|
|
||||||
roof_vegetation = thermal_boundary.vegetation
|
|
||||||
if thermal_boundary.parent_surface.id == ground_surface.id:
|
|
||||||
ground = thermal_boundary.layers
|
|
||||||
if thermal_boundary.thermal_openings is not None and len(thermal_boundary.thermal_openings) > 0:
|
|
||||||
window = thermal_boundary.thermal_openings[0]
|
|
||||||
if ground is not None and roof is not None and wall is not None and window is not None:
|
|
||||||
# we have all the needed surfaces type
|
|
||||||
break
|
|
||||||
self._add_material(_idf, ground)
|
|
||||||
self._add_material(_idf, roof)
|
|
||||||
self._add_material(_idf, wall)
|
|
||||||
if roof_vegetation is not None:
|
|
||||||
self._add_vegetation_material(_idf, roof_vegetation)
|
|
||||||
self._update_constructions(_idf, ground, roof, wall, roof_vegetation)
|
|
||||||
self._add_window_construction_and_material(_idf, window)
|
|
||||||
|
|
||||||
def _add_standard_compact_hourly_schedule(self, _idf, schedule_name, schedules):
|
|
||||||
_kwargs = {'Name': f'{schedule_name}',
|
|
||||||
'Schedule_Type_Limits_Name': self.idf_type_limits[schedules[0].data_type],
|
|
||||||
'Field_1': 'Through: 12/31'}
|
|
||||||
for j, schedule in enumerate(schedules):
|
|
||||||
_val = schedule.values
|
|
||||||
_new_field = ''
|
|
||||||
for day_type in schedule.day_types:
|
|
||||||
_new_field += f' {self.idf_day_types[day_type]}'
|
|
||||||
_kwargs[f'Field_{j * 25 + 2}'] = f'For:{_new_field}'
|
|
||||||
for i in range(0, len(_val)):
|
|
||||||
_kwargs[f'Field_{j * 25 + 3 + i}'] = f'Until: {i + 1:02d}:00,{_val[i]}'
|
|
||||||
_idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
|
|
||||||
|
|
||||||
def _add_schedules(self, _idf, thermal_zone):
|
|
||||||
self._add_standard_compact_hourly_schedule(_idf, 'user_occupancy_schedule',
|
|
||||||
thermal_zone.occupancy.occupancy_schedules)
|
|
||||||
self._add_standard_compact_hourly_schedule(_idf, 'user_lighting_schedule',
|
|
||||||
thermal_zone.lighting.schedules)
|
|
||||||
self._add_standard_compact_hourly_schedule(_idf, 'user_appliances_schedule',
|
|
||||||
thermal_zone.appliances.schedules)
|
|
||||||
self._add_standard_compact_hourly_schedule(_idf, 'user_heating_schedule',
|
|
||||||
thermal_zone.thermal_control.heating_set_point_schedules)
|
|
||||||
self._add_standard_compact_hourly_schedule(_idf, 'user_cooling_schedule',
|
|
||||||
thermal_zone.thermal_control.cooling_set_point_schedules)
|
|
||||||
|
|
||||||
def _add_usage(self, _idf):
|
|
||||||
_thermal_zone = None
|
|
||||||
for building in self._city.buildings:
|
|
||||||
for internal_zone in building.internal_zones:
|
|
||||||
for thermal_zone in internal_zone.thermal_zones:
|
|
||||||
_thermal_zone = thermal_zone
|
|
||||||
# Dompark project share schedules and usages among all the buildings so we could add just the first one
|
|
||||||
break
|
|
||||||
break
|
|
||||||
break
|
|
||||||
self._add_schedules(_idf, _thermal_zone)
|
|
||||||
fraction_radiant = _thermal_zone.occupancy.sensible_radiative_internal_gain / \
|
|
||||||
(_thermal_zone.occupancy.sensible_radiative_internal_gain +
|
|
||||||
_thermal_zone.occupancy.sensible_convective_internal_gain)
|
|
||||||
for idf_object in _idf.idfobjects[self._PEOPLE]:
|
|
||||||
idf_object['Number_of_People_Schedule_Name'] = 'user_occupancy_schedule'
|
|
||||||
idf_object['People_per_Zone_Floor_Area'] = _thermal_zone.occupancy.occupancy_density
|
|
||||||
idf_object['Fraction_Radiant'] = fraction_radiant
|
|
||||||
|
|
||||||
for idf_object in _idf.idfobjects[self._LIGHTS]:
|
|
||||||
idf_object['Schedule_Name'] = 'user_lighting_schedule'
|
|
||||||
idf_object['Watts_per_Zone_Floor_Area'] = _thermal_zone.lighting.density
|
|
||||||
for idf_object in _idf.idfobjects[self._APPLIANCES]:
|
|
||||||
idf_object['Schedule_Name'] = 'user_appliances_schedule'
|
|
||||||
idf_object['Power_per_Zone_Floor_Area'] = _thermal_zone.appliances.density
|
|
||||||
for idf_object in _idf.idfobjects[self._HEATING_COOLING]:
|
|
||||||
idf_object['Heating_Setpoint_Temperature_Schedule_Name'] = 'user_heating_schedule'
|
|
||||||
idf_object['Cooling_Setpoint_Temperature_Schedule_Name'] = 'user_cooling_schedule'
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def get(self, city_id):
|
|
||||||
payload = request.get_json()
|
|
||||||
self._city = self.get_city(city_id)
|
|
||||||
|
|
||||||
self._greenery_percentage = round(float(payload['greenery_percentage']) / 10) * 10
|
|
||||||
output_file = str((self._output_path / 'dompark.idf').resolve())
|
|
||||||
idd_file = str((self._data_path / 'energy+.idd').resolve())
|
|
||||||
epw_file = str((self._data_path / 'CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve())
|
|
||||||
idf_file = str((self._data_path / 'dompark.idf').resolve())
|
|
||||||
IDF.setiddname(idd_file)
|
|
||||||
_idf = IDF(idf_file, epw_file)
|
|
||||||
self._add_materials(_idf)
|
|
||||||
self._add_usage(_idf)
|
|
||||||
_idf.newidfobject(
|
|
||||||
"OUTPUT:VARIABLE",
|
|
||||||
Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy",
|
|
||||||
Reporting_Frequency="Hourly",
|
|
||||||
)
|
|
||||||
_idf.newidfobject(
|
|
||||||
"OUTPUT:VARIABLE",
|
|
||||||
Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy",
|
|
||||||
Reporting_Frequency="Hourly",
|
|
||||||
)
|
|
||||||
# From EnergyPlus documentation: Lights Electric Energy [J]
|
|
||||||
# The lighting electrical consumption including ballasts, if present. These will have the same value as Lights
|
|
||||||
# Total Heating Energy (above).
|
|
||||||
_idf.newidfobject(
|
|
||||||
"OUTPUT:VARIABLE",
|
|
||||||
Variable_Name="Lights Total Heating Energy",
|
|
||||||
Reporting_Frequency="Hourly",
|
|
||||||
)
|
|
||||||
_idf.newidfobject(
|
|
||||||
"OUTPUT:VARIABLE",
|
|
||||||
Variable_Name="Other Equipment Total Heating Energy",
|
|
||||||
Reporting_Frequency="Hourly",
|
|
||||||
)
|
|
||||||
|
|
||||||
_idf.match()
|
|
||||||
_idf.saveas(str(output_file))
|
|
||||||
_idf.run(expandobjects=True, readvars=True, output_directory=self._output_path, output_prefix='dompark_')
|
|
||||||
# Todo: set the heating and cooling
|
|
||||||
heating = []
|
|
||||||
cooling = []
|
|
||||||
lighting = []
|
|
||||||
appliances = []
|
|
||||||
with open((self._output_path / f'dompark_out.csv').resolve()) as f:
|
|
||||||
reader = csv.reader(f, delimiter=',')
|
|
||||||
for row in reader:
|
|
||||||
if '00:00' in row[0]:
|
|
||||||
cooling_value = 0.0
|
|
||||||
heating_value = 0.0
|
|
||||||
lighting_value = 0.0
|
|
||||||
appliances_value = 0.0
|
|
||||||
for i in range(1, 38):
|
|
||||||
lighting_value += float(row[i])
|
|
||||||
for i in range(38, 73):
|
|
||||||
appliances_value += float(row[i])
|
|
||||||
for i in range(73, 133, 2):
|
|
||||||
heating_value += float(row[i])
|
|
||||||
cooling_value += float(row[i + 1])
|
|
||||||
cooling.append(cooling_value)
|
|
||||||
heating.append(heating_value)
|
|
||||||
lighting.append(lighting_value)
|
|
||||||
appliances.append(appliances_value)
|
|
||||||
|
|
||||||
files = glob.glob(f'{self._output_path}/dompark*')
|
|
||||||
for file in files:
|
|
||||||
os.remove(file)
|
|
||||||
continue
|
|
||||||
|
|
||||||
response = {'heating_demand': heating,
|
|
||||||
'cooling_demand': cooling,
|
|
||||||
'lighting_demand': lighting,
|
|
||||||
'appliances_demand': appliances
|
|
||||||
}
|
|
||||||
return Response(json.dumps(response), status=200)
|
|
|
@ -1,25 +0,0 @@
|
||||||
"""
|
|
||||||
Geometry
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Code contributors: Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
from flask import make_response, send_file
|
|
||||||
from flask_restful import Resource
|
|
||||||
from pathlib import Path
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
|
|
||||||
|
|
||||||
class Geometry(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
data_path = (Path(__file__).parent.parent / 'data').resolve()
|
|
||||||
self._gtlf_path = (Path(data_path / 'DomparkBuilding.gltf')).resolve()
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def get(self):
|
|
||||||
response = make_response(send_file(self._gtlf_path,
|
|
||||||
as_attachment=True,
|
|
||||||
mimetype='model/gltf+json, model/gltf-binary'))
|
|
||||||
return response
|
|
|
@ -1,84 +0,0 @@
|
||||||
"""
|
|
||||||
Greenery
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Code contributors: Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from flask import Response, request
|
|
||||||
from flask_restful import Resource
|
|
||||||
from hub_api.config import Config
|
|
||||||
from city_model_structure.greenery.vegetation import Vegetation
|
|
||||||
from city_model_structure.greenery.soil import Soil
|
|
||||||
from city_model_structure.greenery.plant import Plant
|
|
||||||
import helpers.constants as cte
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
|
|
||||||
|
|
||||||
class Greenery(Resource, Config):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value])
|
|
||||||
def put(self, city_id):
|
|
||||||
|
|
||||||
city = self.get_city(city_id)
|
|
||||||
try:
|
|
||||||
greenery_percentage = request.json['greenery_percentage']
|
|
||||||
if greenery_percentage == 0:
|
|
||||||
response = {'result': 'succeed'}
|
|
||||||
return Response(json.dumps(response), status=200)
|
|
||||||
|
|
||||||
building_names = request.json['building_names']
|
|
||||||
vegetation_requested = request.json['vegetation']
|
|
||||||
vegetation_name = vegetation_requested['vegetation_name']
|
|
||||||
soil_thickness = vegetation_requested['soil_thickness']
|
|
||||||
soil_name = vegetation_requested['soil_name']
|
|
||||||
roughness = vegetation_requested['soil_roughness']
|
|
||||||
dry_conductivity = vegetation_requested['dry_soil_conductivity']
|
|
||||||
dry_density = vegetation_requested['dry_soil_density']
|
|
||||||
dry_specific_heat = vegetation_requested['dry_soil_specific_heat']
|
|
||||||
thermal_absorptance = vegetation_requested['soil_thermal_absorptance']
|
|
||||||
solar_absorptance = vegetation_requested['soil_solar_absorptance']
|
|
||||||
visible_absorptance = vegetation_requested['soil_visible_absorptance']
|
|
||||||
saturation_volumetric_moisture_content = vegetation_requested['soil_saturation_volumetric_moisture_content']
|
|
||||||
residual_volumetric_moisture_content = vegetation_requested['soil_residual_volumetric_moisture_content']
|
|
||||||
soil = Soil(soil_name, roughness, dry_conductivity, dry_density, dry_specific_heat, thermal_absorptance,
|
|
||||||
solar_absorptance, visible_absorptance, saturation_volumetric_moisture_content,
|
|
||||||
residual_volumetric_moisture_content)
|
|
||||||
soil.initial_volumetric_moisture_content = '0.1'
|
|
||||||
plant_percentages = vegetation_requested['plant_percentages']
|
|
||||||
plants = []
|
|
||||||
for plant_percentage in plant_percentages:
|
|
||||||
plant_name = plant_percentage['plant_name']
|
|
||||||
height = plant_percentage['plant_height']
|
|
||||||
leaf_area_index = plant_percentage['plant_leaf_area_index']
|
|
||||||
leaf_reflectivity = plant_percentage['plant_leaf_reflectivity']
|
|
||||||
leaf_emissivity = plant_percentage['plant_leaf_emissivity']
|
|
||||||
minimal_stomatal_resistance = plant_percentage['plant_minimal_stomatal_resistance']
|
|
||||||
co2_sequestration = plant_percentage['plant_co2_sequestration']
|
|
||||||
grows_on_soils = plant_percentage['plant_grows_on']
|
|
||||||
plant = Plant(plant_name, height, leaf_area_index, leaf_reflectivity, leaf_emissivity,
|
|
||||||
minimal_stomatal_resistance, co2_sequestration, grows_on_soils)
|
|
||||||
plant.percentage = plant_percentage['plant_percentage']
|
|
||||||
plants.append(plant)
|
|
||||||
vegetation = Vegetation(vegetation_name, soil, soil_thickness, plants)
|
|
||||||
for building_name in building_names:
|
|
||||||
for building in city.buildings:
|
|
||||||
if building.human_readable_name != building_name:
|
|
||||||
continue
|
|
||||||
for internal_zone in building.internal_zones:
|
|
||||||
for thermal_zone in internal_zone.thermal_zones:
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
if thermal_boundary.type == cte.ROOF:
|
|
||||||
thermal_boundary.vegetation = vegetation
|
|
||||||
|
|
||||||
response = {'result': 'succeed'}
|
|
||||||
except KeyError as ex:
|
|
||||||
response = {'error': f'Mandatory parameter {ex} is missing'}
|
|
||||||
return Response(json.dumps(response), status=400)
|
|
||||||
|
|
||||||
return Response(json.dumps(response))
|
|
|
@ -1,178 +0,0 @@
|
||||||
"""
|
|
||||||
Greenery catalog
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
from flask import request, Response
|
|
||||||
from flask_restful import Resource
|
|
||||||
from catalog_factories.data_models.greenery.plant import Plant
|
|
||||||
from catalog_factories.data_models.greenery.soil import Soil
|
|
||||||
from catalog_factories.data_models.greenery.vegetation import Vegetation
|
|
||||||
from hub_api.helpers.session_helper import refresh_session
|
|
||||||
|
|
||||||
|
|
||||||
class ToJson:
|
|
||||||
@staticmethod
|
|
||||||
def plant_to_json(plant, percentage=None):
|
|
||||||
dictionary = {
|
|
||||||
'plant_percentage': percentage,
|
|
||||||
'plant_name': plant.name,
|
|
||||||
'plant_category': plant.category,
|
|
||||||
'plant_height': plant.height,
|
|
||||||
'plant_leaf_area_index': plant.leaf_area_index,
|
|
||||||
'plant_leaf_reflectivity': plant.leaf_reflectivity,
|
|
||||||
'plant_leaf_emissivity': plant.leaf_emissivity,
|
|
||||||
'plant_minimal_stomatal_resistance': plant.minimal_stomatal_resistance,
|
|
||||||
'plant_co2_sequestration': plant.co2_sequestration,
|
|
||||||
'plant_grows_on': []
|
|
||||||
}
|
|
||||||
if percentage is None:
|
|
||||||
dictionary.pop('plant_percentage')
|
|
||||||
soils = []
|
|
||||||
for soil in plant.grows_on:
|
|
||||||
soil_dic = ToJson.soil_to_json(soil)
|
|
||||||
soils.append(soil_dic)
|
|
||||||
dictionary['plant_grows_on'] = soils
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def soil_to_json(soil):
|
|
||||||
dictionary = {'soil_name': soil.name,
|
|
||||||
'soil_roughness': str(soil.roughness),
|
|
||||||
'soil_dry_conductivity': soil.dry_conductivity,
|
|
||||||
'soil_dry_density': soil.dry_density,
|
|
||||||
'soil_dry_specific_heat': soil.dry_specific_heat,
|
|
||||||
'soil_thermal_absortance': soil.thermal_absorptance,
|
|
||||||
'soil_solar_absortance': soil.solar_absorptance,
|
|
||||||
'soil_visible_absortance': soil.visible_absorptance,
|
|
||||||
'soil_saturation_volumetric_moisture_content': soil.saturation_volumetric_moisture_content,
|
|
||||||
'soil_residual_volumetric_moisture_content': soil.residual_volumetric_moisture_content,
|
|
||||||
'soil_initial_volumetric_moisture_content': soil.initial_volumetric_moisture_content
|
|
||||||
}
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def vegetation_to_json(vegetation):
|
|
||||||
|
|
||||||
dictionary = {'vegetation_name': vegetation.name,
|
|
||||||
'vegetation_category': vegetation.category,
|
|
||||||
'soil_thickness': vegetation.soil_thickness,
|
|
||||||
'management': str(vegetation.management),
|
|
||||||
'air_gap': vegetation.air_gap,
|
|
||||||
'soil_name': vegetation.soil_name,
|
|
||||||
'soil_roughness': str(vegetation.soil_roughness),
|
|
||||||
'dry_soil_conductivity': vegetation.dry_soil_conductivity,
|
|
||||||
'dry_soil_density': vegetation.dry_soil_density,
|
|
||||||
'dry_soil_specific_heat': vegetation.dry_soil_specific_heat,
|
|
||||||
'soil_thermal_absorptance': vegetation.soil_thermal_absorptance,
|
|
||||||
'soil_solar_absorptance': vegetation.soil_solar_absorptance,
|
|
||||||
'soil_visible_absorptance': vegetation.soil_visible_absorptance,
|
|
||||||
'soil_saturation_volumetric_moisture_content': vegetation.soil_saturation_volumetric_moisture_content,
|
|
||||||
'soil_residual_volumetric_moisture_content': vegetation.soil_residual_volumetric_moisture_content,
|
|
||||||
'soil_initial_volumetric_moisture_content': vegetation.soil_initial_volumetric_moisture_content,
|
|
||||||
'plant_percentages': []
|
|
||||||
}
|
|
||||||
percentages = []
|
|
||||||
for percentage in vegetation.plant_percentages:
|
|
||||||
percentage_dic = ToJson.plant_to_json(percentage, percentage.percentage)
|
|
||||||
percentages.append(percentage_dic)
|
|
||||||
dictionary['plant_percentages'] = percentages
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
|
|
||||||
class GreeneryCatalogEntry(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def post():
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.greenery_catalog
|
|
||||||
name = None
|
|
||||||
if request.data == b'' or request.json['name'] is None:
|
|
||||||
response = {'error': 'Mandatory parameter "name" is missing'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
||||||
try:
|
|
||||||
name = request.json['name']
|
|
||||||
entry = catalog.get_entry(name)
|
|
||||||
output = {}
|
|
||||||
if isinstance(entry, Vegetation):
|
|
||||||
output['vegetations'] = ToJson.vegetation_to_json(entry)
|
|
||||||
if isinstance(entry, Plant):
|
|
||||||
output['plants'] = ToJson.plant_to_json(entry)
|
|
||||||
if isinstance(entry, Soil):
|
|
||||||
output['soils'] = ToJson.soil_to_json(entry)
|
|
||||||
return Response(json.dumps(output), headers=headers)
|
|
||||||
except IndexError:
|
|
||||||
response = {'error': f'Name "{name}" unknown'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class GreeneryCatalogEntries(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def post():
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.greenery_catalog
|
|
||||||
category = None
|
|
||||||
if request.data != b'':
|
|
||||||
category = request.json['category']
|
|
||||||
output = {}
|
|
||||||
if category is None:
|
|
||||||
content = catalog.entries()
|
|
||||||
output = {'vegetations': [], 'plants': [], 'soils': []}
|
|
||||||
for vegetation in content.vegetations:
|
|
||||||
output['vegetations'].append(ToJson.vegetation_to_json(vegetation))
|
|
||||||
for plant in content.plants:
|
|
||||||
output['plants'].append(ToJson.plant_to_json(plant))
|
|
||||||
for soil in content.soils:
|
|
||||||
output['soils'].append(ToJson.soil_to_json(soil))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
content = catalog.entries(category)
|
|
||||||
output[category] = []
|
|
||||||
for entry in content:
|
|
||||||
if isinstance(entry, Vegetation):
|
|
||||||
output[category].append(ToJson.vegetation_to_json(entry))
|
|
||||||
if isinstance(entry, Plant):
|
|
||||||
output[category].append(ToJson.plant_to_json(entry))
|
|
||||||
if isinstance(entry, Soil):
|
|
||||||
output[category].append(ToJson.soil_to_json(entry))
|
|
||||||
except ValueError:
|
|
||||||
output = {'error': f'Category "{category}" unknown'}
|
|
||||||
return Response(json.dumps(output), headers=headers, status=400)
|
|
||||||
return Response(json.dumps(output), headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
class GreeneryCatalogNames(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def post():
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.greenery_catalog
|
|
||||||
category = None
|
|
||||||
if request.data != b'':
|
|
||||||
category = request.json['category']
|
|
||||||
if category is None:
|
|
||||||
return Response(json.dumps(catalog.names()), headers=headers)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
return Response(json.dumps(catalog.names(category)), headers=headers)
|
|
||||||
except ValueError:
|
|
||||||
response = {'error': f'Category "{category}" unknown'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
|
@ -1,39 +0,0 @@
|
||||||
"""
|
|
||||||
HeatPump Service
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Project Author Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
|
|
||||||
from flask import request, Response
|
|
||||||
from hub_api.config import Config
|
|
||||||
from flask_restful import Resource
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
from utils import HeatPumpSimulator
|
|
||||||
from utils import validate_hp_model
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
from utils import expand_energy_demand
|
|
||||||
from hub_logger import logger
|
|
||||||
|
|
||||||
|
|
||||||
class HeatPump(Config, Resource):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def post(self, city_id):
|
|
||||||
payload = request.get_json()
|
|
||||||
city = self.get_city(city_id)
|
|
||||||
if validate_hp_model(payload['HeatPumpType'], payload['HeatPumpModel']):
|
|
||||||
# expand energy demand values
|
|
||||||
expand_energy_demand(payload['EnergyDemand'])
|
|
||||||
try:
|
|
||||||
# Run simulation and return output file here
|
|
||||||
hp_simulator = HeatPumpSimulator(city, payload)
|
|
||||||
results = hp_simulator.run_hp_simulation()
|
|
||||||
return Response(json.dumps(results), status=200)
|
|
||||||
except Exception as err:
|
|
||||||
logger.error(err)
|
|
||||||
return Response(json.dumps({'error_message': 'Sorry an error occurred while running HP Simulation'}))
|
|
||||||
else:
|
|
||||||
return Response(json.dumps({'error_message': 'Wrong heat pump type/model combination'}), status=400)
|
|
|
@ -11,8 +11,8 @@ import os
|
||||||
from jwt.utils import get_int_from_datetime
|
from jwt.utils import get_int_from_datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import request, g
|
from flask import request, g
|
||||||
from hub_logger import logger
|
from hub.hub_logger import logger
|
||||||
from persistence.models import UserRoles
|
from hub.persistence.models import UserRoles
|
||||||
from jwt.exceptions import JWTException
|
from jwt.exceptions import JWTException
|
||||||
|
|
||||||
instance = JWT()
|
instance = JWT()
|
||||||
|
|
128
hub_api/lca.py
128
hub_api/lca.py
|
@ -1,128 +0,0 @@
|
||||||
"""
|
|
||||||
LCA
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author name Atiya
|
|
||||||
Code contributors: Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
from flask import Response
|
|
||||||
from flask_restful import Resource
|
|
||||||
from lca_calculations import LcaCalculations
|
|
||||||
from itertools import groupby
|
|
||||||
from operator import itemgetter
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
from hub_api.config import Config
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MaterialLCACatalog(Resource, Config):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_lca_value(city, nrel_id = None):
|
|
||||||
nrel_material = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "14", "15", "16", "18", "20", "21",
|
|
||||||
"22", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114"]
|
|
||||||
lca_material = ["12", "32", "21", "25", "11", "20", "40", "27", "29", "28", "19", "40", "41", "25", "32", "32", "32", "21", "41",
|
|
||||||
"21", "11", "33", "28", "11", "40", "1", "28", "41", "0", "0", "0", "0", "0", ]
|
|
||||||
keys = ['nrel_material_id', 'lca_material_id']
|
|
||||||
material_mapping = [dict(zip(keys, i)) for i in zip(nrel_material, lca_material)]
|
|
||||||
|
|
||||||
if nrel_id is None:
|
|
||||||
materials_lca = {"material_lca_catalog": []}
|
|
||||||
for mat in material_mapping:
|
|
||||||
|
|
||||||
if mat["nrel_material_id"] not in ("110", "111", "112", "113", "114"):
|
|
||||||
for material in city.lca_materials:
|
|
||||||
if(int(mat["lca_material_id"]) == material.id):
|
|
||||||
material_lca_catalog = {}
|
|
||||||
material_lca_catalog['nrel_material_id'] = mat["nrel_material_id"]
|
|
||||||
material_lca_catalog['embodied_carbon'] = material.embodied_carbon
|
|
||||||
mat_end_of_life = LcaCalculations(city).end_life_carbon(material.type)
|
|
||||||
material_lca_catalog['end_of_life_carbon'] = mat_end_of_life
|
|
||||||
materials_lca["material_lca_catalog"].append(material_lca_catalog)
|
|
||||||
|
|
||||||
else:
|
|
||||||
material_lca_catalog = {}
|
|
||||||
material_lca_catalog['nrel_material_id'] = mat["nrel_material_id"]
|
|
||||||
material_lca_catalog['embodied_carbon'] = 0.0
|
|
||||||
material_lca_catalog['end_of_life_carbon'] = 0.0
|
|
||||||
materials_lca["material_lca_catalog"].append(material_lca_catalog)
|
|
||||||
|
|
||||||
return materials_lca
|
|
||||||
|
|
||||||
else:
|
|
||||||
for mat in material_mapping:
|
|
||||||
if mat["nrel_material_id"] == str(nrel_id):
|
|
||||||
for material in city.lca_materials:
|
|
||||||
if (material.id == int(mat["lca_material_id"])):
|
|
||||||
return material.embodied_carbon, material.id, material.type, material.name, material.density
|
|
||||||
# return material.embodied_carbon
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def get(self, city_id):
|
|
||||||
city = self.get_city(city_id)
|
|
||||||
try:
|
|
||||||
return Response(json.dumps(self.get_lca_value(city)), status=200)
|
|
||||||
except ValueError:
|
|
||||||
response = {'err_msg': f'No Catalog Available'}
|
|
||||||
return Response(json.dumps(response), status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class MaterialLCACalculations(Resource, Config):
|
|
||||||
"""
|
|
||||||
LCA class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def get(self, city_id):
|
|
||||||
"""
|
|
||||||
Auto-method for processing the lca request
|
|
||||||
:return: lca demand
|
|
||||||
"""
|
|
||||||
city = self.get_city(city_id)
|
|
||||||
|
|
||||||
materials_lca = {'Wall': [], 'Ground': [], 'Roof': []}
|
|
||||||
for building in city.buildings:
|
|
||||||
for internal_zone in building.internal_zones:
|
|
||||||
for thermal_zone in internal_zone.thermal_zones:
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
for i, layer in enumerate(thermal_boundary.layers):
|
|
||||||
material_layers_lca = {}
|
|
||||||
if layer.material.no_mass == False:
|
|
||||||
embodied_carbon, mat_id, mat_type, mat_name, mat_density = MaterialLCACatalog.get_lca_value(city, layer.material.id)
|
|
||||||
material_layers_lca['layer_name'] = f'Layer {i+1}'
|
|
||||||
material_layers_lca['nrel_material_id'] = layer.material.id
|
|
||||||
material_layers_lca['lca_material_id'] = mat_id
|
|
||||||
material_layers_lca['embodied_carbon'] = (layer.thickness * thermal_boundary.opaque_area) * mat_density * embodied_carbon
|
|
||||||
mat_end_of_life = LcaCalculations(city).end_life_carbon(mat_type)
|
|
||||||
material_end_of_life = mat_end_of_life * (layer.thickness * thermal_boundary.opaque_area) * mat_density
|
|
||||||
material_layers_lca['end_of_life_per_layer'] = material_end_of_life
|
|
||||||
materials_lca[thermal_boundary.type].append(material_layers_lca)
|
|
||||||
else:
|
|
||||||
material_layers_lca['layer_name'] = f'Layer {i+1}'
|
|
||||||
material_layers_lca['nrel_material_id'] = layer.material.id
|
|
||||||
material_layers_lca['lca_material_id'] = mat_id
|
|
||||||
material_layers_lca['embodied_carbon'] = (layer.thickness * thermal_boundary.opaque_area) * mat_density * embodied_carbon
|
|
||||||
material_layers_lca['end_of_life_per_layer'] = 0.0
|
|
||||||
materials_lca[thermal_boundary.type].append(material_layers_lca)
|
|
||||||
|
|
||||||
materials_embodied_carbon = {'Wall': [], 'Ground': [], 'Roof': []}
|
|
||||||
for key, value in materials_lca.items():
|
|
||||||
boundary_layers = sorted(value, key=itemgetter('layer_name'))
|
|
||||||
for b_layer, layer_properties in groupby(boundary_layers, key=itemgetter('layer_name')):
|
|
||||||
sum_embodied = 0.0
|
|
||||||
sum_end_of_life = 0.0
|
|
||||||
total_embodied_carbon = {}
|
|
||||||
for k in layer_properties:
|
|
||||||
sum_embodied += k["embodied_carbon"]
|
|
||||||
sum_end_of_life += k["end_of_life_per_layer"]
|
|
||||||
total_embodied_carbon['layer_name'] = b_layer
|
|
||||||
total_embodied_carbon['embodied_carbon'] = sum_embodied
|
|
||||||
total_embodied_carbon['end_of_life_carbon'] = sum_end_of_life
|
|
||||||
materials_embodied_carbon[key].append(total_embodied_carbon)
|
|
||||||
|
|
||||||
return Response(json.dumps(materials_embodied_carbon), status=200)
|
|
154
hub_api/usage.py
154
hub_api/usage.py
|
@ -1,154 +0,0 @@
|
||||||
"""
|
|
||||||
Usage
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Code contributors: Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
from flask import Response, request
|
|
||||||
from flask_restful import Resource
|
|
||||||
|
|
||||||
from city_model_structure.attributes.schedule import Schedule
|
|
||||||
from city_model_structure.building_demand.appliances import Appliances
|
|
||||||
from city_model_structure.building_demand.lighting import Lighting
|
|
||||||
from city_model_structure.building_demand.occupancy import Occupancy
|
|
||||||
from city_model_structure.building_demand.thermal_control import ThermalControl
|
|
||||||
from city_model_structure.building_demand.usage_zone import UsageZone
|
|
||||||
import helpers.constants as cte
|
|
||||||
from hub_api.helpers.auth import role_required
|
|
||||||
from hub_api.config import Config
|
|
||||||
from persistence.models import UserRoles
|
|
||||||
|
|
||||||
|
|
||||||
class Usage(Resource, Config):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
|
||||||
def put(self, city_id):
|
|
||||||
|
|
||||||
city = self.get_city(city_id)
|
|
||||||
catalog = request.json['usage_catalog']
|
|
||||||
|
|
||||||
usage_name = None
|
|
||||||
try:
|
|
||||||
building_names = request.json['building_names']
|
|
||||||
usages = request.json['usages']
|
|
||||||
seconds_in_hour = cte.MINUTES_TO_SECONDS * cte.HOUR_TO_MINUTES
|
|
||||||
for building_name in building_names:
|
|
||||||
for building in city.buildings:
|
|
||||||
if building.human_readable_name != building_name:
|
|
||||||
continue
|
|
||||||
for internal_zone in building.internal_zones:
|
|
||||||
internal_zone.usage_zones = []
|
|
||||||
for usage in usages:
|
|
||||||
usage_entry = catalog.get_entry(usage['name'])
|
|
||||||
occupancy_schedules = []
|
|
||||||
for schedule in usage_entry.occupancy.schedules:
|
|
||||||
occupancy_schedule = Schedule()
|
|
||||||
occupancy_schedule.type = schedule.type
|
|
||||||
occupancy_schedule.values = schedule.values
|
|
||||||
occupancy_schedule.data_type = schedule.data_type
|
|
||||||
occupancy_schedule.time_step = schedule.time_step
|
|
||||||
occupancy_schedule.time_range = schedule.time_range
|
|
||||||
occupancy_schedule.day_types = schedule.day_types
|
|
||||||
occupancy_schedules.append(occupancy_schedule)
|
|
||||||
occupancy = Occupancy()
|
|
||||||
occupancy.occupancy_density = usage_entry.occupancy.occupancy_density
|
|
||||||
occupancy.sensible_convective_internal_gain = usage_entry.occupancy.sensible_convective_internal_gain
|
|
||||||
occupancy.sensible_radiative_internal_gain = usage_entry.occupancy.sensible_radiative_internal_gain
|
|
||||||
occupancy.latent_internal_gain = usage_entry.occupancy.latent_internal_gain
|
|
||||||
occupancy.occupancy_schedules = occupancy_schedules
|
|
||||||
|
|
||||||
lighting_schedules = []
|
|
||||||
for schedule in usage_entry.lighting.schedules:
|
|
||||||
lighting_schedule = Schedule()
|
|
||||||
lighting_schedule.type = schedule.type
|
|
||||||
lighting_schedule.values = schedule.values
|
|
||||||
lighting_schedule.data_type = schedule.data_type
|
|
||||||
lighting_schedule.time_step = schedule.time_step
|
|
||||||
lighting_schedule.time_range = schedule.time_range
|
|
||||||
lighting_schedule.day_types = schedule.day_types
|
|
||||||
lighting_schedules.append(lighting_schedule)
|
|
||||||
lighting = Lighting()
|
|
||||||
lighting.density = usage_entry.lighting.density
|
|
||||||
lighting.convective_fraction = usage_entry.lighting.convective_fraction
|
|
||||||
lighting.radiative_fraction = usage_entry.lighting.radiative_fraction
|
|
||||||
lighting.latent_fraction = usage_entry.lighting.latent_fraction
|
|
||||||
lighting.schedules = lighting_schedules
|
|
||||||
appliances_schedules = []
|
|
||||||
for schedule in usage_entry.appliances.schedules:
|
|
||||||
appliances_schedule = Schedule()
|
|
||||||
appliances_schedule.type = schedule.type
|
|
||||||
appliances_schedule.values = schedule.values
|
|
||||||
appliances_schedule.data_type = schedule.data_type
|
|
||||||
appliances_schedule.time_step = schedule.time_step
|
|
||||||
appliances_schedule.time_range = schedule.time_range
|
|
||||||
appliances_schedule.day_types = schedule.day_types
|
|
||||||
appliances_schedules.append(appliances_schedule)
|
|
||||||
appliances = Appliances()
|
|
||||||
appliances.density = usage_entry.appliances.density
|
|
||||||
appliances.convective_fraction = usage_entry.appliances.convective_fraction
|
|
||||||
appliances.radiative_fraction = usage_entry.appliances.radiative_fraction
|
|
||||||
appliances.latent_fraction = usage_entry.appliances.latent_fraction
|
|
||||||
appliances.schedules = appliances_schedules
|
|
||||||
hvac_schedules = []
|
|
||||||
for schedule in usage_entry.thermal_control.hvac_availability_schedules:
|
|
||||||
hvac_schedule = Schedule()
|
|
||||||
hvac_schedule.type = schedule.type
|
|
||||||
hvac_schedule.values = schedule.values
|
|
||||||
hvac_schedule.data_type = schedule.data_type
|
|
||||||
hvac_schedule.time_step = schedule.time_step
|
|
||||||
hvac_schedule.time_range = schedule.time_range
|
|
||||||
hvac_schedule.day_types = schedule.day_types
|
|
||||||
hvac_schedules.append(hvac_schedule)
|
|
||||||
heating_schedules = []
|
|
||||||
for schedule in usage_entry.thermal_control.heating_set_point_schedules:
|
|
||||||
heating_schedule = Schedule()
|
|
||||||
heating_schedule.type = schedule.type
|
|
||||||
heating_schedule.values = schedule.values
|
|
||||||
heating_schedule.data_type = schedule.data_type
|
|
||||||
heating_schedule.time_step = schedule.time_step
|
|
||||||
heating_schedule.time_range = schedule.time_range
|
|
||||||
heating_schedule.day_types = schedule.day_types
|
|
||||||
heating_schedules.append(heating_schedule)
|
|
||||||
cooling_schedules = []
|
|
||||||
for schedule in usage_entry.thermal_control.cooling_set_point_schedules:
|
|
||||||
cooling_schedule = Schedule()
|
|
||||||
cooling_schedule.type = schedule.type
|
|
||||||
cooling_schedule.values = schedule.values
|
|
||||||
cooling_schedule.data_type = schedule.data_type
|
|
||||||
cooling_schedule.time_step = schedule.time_step
|
|
||||||
cooling_schedule.time_range = schedule.time_range
|
|
||||||
cooling_schedule.day_types = schedule.day_types
|
|
||||||
cooling_schedules.append(cooling_schedule)
|
|
||||||
thermal_control = ThermalControl()
|
|
||||||
thermal_control.mean_heating_set_point = usage_entry.thermal_control.mean_heating_set_point
|
|
||||||
thermal_control.heating_set_back = usage_entry.thermal_control.heating_set_back
|
|
||||||
thermal_control.mean_cooling_set_point = usage_entry.thermal_control.mean_cooling_set_point
|
|
||||||
thermal_control.hvac_availability_schedules = hvac_schedules
|
|
||||||
thermal_control.heating_set_point_schedules = heating_schedules
|
|
||||||
thermal_control.cooling_set_point_schedules = cooling_schedules
|
|
||||||
usage_zone = UsageZone()
|
|
||||||
usage_zone.usage = usage_entry.usage
|
|
||||||
usage_zone.percentage = usage['percentage']
|
|
||||||
usage_zone.hours_day = usage_entry.hours_day
|
|
||||||
usage_zone.days_year = usage_entry.days_year
|
|
||||||
usage_zone.mechanical_air_change = usage_entry.mechanical_air_change
|
|
||||||
if usage_entry.mechanical_air_change is None:
|
|
||||||
usage_zone.mechanical_air_change = ((usage_entry.ventilation_rate * internal_zone.area) /
|
|
||||||
internal_zone.volume) * seconds_in_hour
|
|
||||||
usage_zone.occupancy = occupancy
|
|
||||||
usage_zone.lighting = lighting
|
|
||||||
usage_zone.appliances = appliances
|
|
||||||
usage_zone.thermal_control = thermal_control
|
|
||||||
internal_zone.usage_zones.append(usage_zone)
|
|
||||||
response = {'result': 'succeed'}
|
|
||||||
except KeyError as ex:
|
|
||||||
response = {'error': f'Mandatory parameter {ex} is missing'}
|
|
||||||
return Response(json.dumps(response), status=400)
|
|
||||||
except IndexError:
|
|
||||||
response = {'error': f'Name "{usage_name}" unknown'}
|
|
||||||
return Response(json.dumps(response), status=400)
|
|
||||||
return Response(json.dumps(response))
|
|
|
@ -1,156 +0,0 @@
|
||||||
"""
|
|
||||||
Construction catalog
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from flask import request, Response
|
|
||||||
from flask_restful import Resource
|
|
||||||
|
|
||||||
from hub_api.helpers.session_helper import refresh_session
|
|
||||||
|
|
||||||
|
|
||||||
class ToJson:
|
|
||||||
@staticmethod
|
|
||||||
def usage_to_json(usage):
|
|
||||||
dictionary = {
|
|
||||||
'usage': usage.usage,
|
|
||||||
'hours_day': usage.hours_day,
|
|
||||||
'days_year': usage.days_year,
|
|
||||||
'mechanical_air_change': usage.mechanical_air_change if usage.mechanical_air_change is not None else '',
|
|
||||||
'ventilation_rate': usage.ventilation_rate if usage.ventilation_rate is not None else '',
|
|
||||||
'occupancy': ToJson.occupancy_to_json(usage.occupancy),
|
|
||||||
'lighting': ToJson.lighting_to_json(usage.lighting),
|
|
||||||
'appliances': ToJson.appliances_to_json(usage.appliances),
|
|
||||||
'thermal_control': ToJson.thermal_control_to_json(usage.thermal_control)
|
|
||||||
}
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def occupancy_to_json(occupancy):
|
|
||||||
dictionary = {
|
|
||||||
'occupancy_density': occupancy.occupancy_density,
|
|
||||||
'sensible_convective_internal_gain': occupancy.sensible_convective_internal_gain,
|
|
||||||
'sensible_radiative_internal_gain': occupancy.sensible_radiative_internal_gain,
|
|
||||||
'latent_internal_gain': occupancy.latent_internal_gain,
|
|
||||||
'schedules': []
|
|
||||||
}
|
|
||||||
for schedule in occupancy.schedules:
|
|
||||||
dictionary['schedules'].append(ToJson.schedule_to_json(schedule))
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def lighting_to_json(lighting):
|
|
||||||
dictionary = {
|
|
||||||
'density': lighting.density,
|
|
||||||
'convective_fraction': lighting.convective_fraction,
|
|
||||||
'radiative_fraction': lighting.radiative_fraction,
|
|
||||||
'latent_fraction': lighting.latent_fraction,
|
|
||||||
'schedules': []
|
|
||||||
}
|
|
||||||
for schedule in lighting.schedules:
|
|
||||||
dictionary['schedules'].append(ToJson.schedule_to_json(schedule))
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def appliances_to_json(appliances):
|
|
||||||
dictionary = {
|
|
||||||
'density': appliances.density,
|
|
||||||
'convective_fraction': appliances.convective_fraction,
|
|
||||||
'radiative_fraction': appliances.radiative_fraction,
|
|
||||||
'latent_fraction': appliances.latent_fraction,
|
|
||||||
'schedules': []
|
|
||||||
}
|
|
||||||
for schedule in appliances.schedules:
|
|
||||||
dictionary['schedules'].append(ToJson.schedule_to_json(schedule))
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def thermal_control_to_json(thermal_control):
|
|
||||||
dictionary = {
|
|
||||||
'mean_heating_set_point': thermal_control.mean_heating_set_point,
|
|
||||||
'heating_set_back': thermal_control.heating_set_back,
|
|
||||||
'mean_cooling_set_point': thermal_control.mean_cooling_set_point,
|
|
||||||
'hvac_availability_schedules': [],
|
|
||||||
'heating_set_point_schedules': [],
|
|
||||||
'cooling_set_point_schedules': [],
|
|
||||||
}
|
|
||||||
for schedule in thermal_control.hvac_availability_schedules:
|
|
||||||
dictionary['hvac_availability_schedules'].append(ToJson.schedule_to_json(schedule))
|
|
||||||
for schedule in thermal_control.heating_set_point_schedules:
|
|
||||||
dictionary['heating_set_point_schedules'].append(ToJson.schedule_to_json(schedule))
|
|
||||||
for schedule in thermal_control.cooling_set_point_schedules:
|
|
||||||
dictionary['cooling_set_point_schedules'].append(ToJson.schedule_to_json(schedule))
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def schedule_to_json(schedule):
|
|
||||||
schedule_dictionary = {
|
|
||||||
'type': schedule.type,
|
|
||||||
'data_type': schedule.data_type,
|
|
||||||
'time_step': schedule.time_step,
|
|
||||||
'time_range': schedule.time_range,
|
|
||||||
'day_types': schedule.day_types,
|
|
||||||
'values': schedule.values,
|
|
||||||
}
|
|
||||||
return schedule_dictionary
|
|
||||||
|
|
||||||
|
|
||||||
class UsageCatalogEntry(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def post():
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.usage_catalog
|
|
||||||
|
|
||||||
name = None
|
|
||||||
if request.data == b'' or request.json['name'] is None:
|
|
||||||
response = {'error': 'Mandatory parameter "name" is missing'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
||||||
try:
|
|
||||||
name = request.json['name']
|
|
||||||
entry = catalog.get_entry(name)
|
|
||||||
output = {'usages': [ToJson.usage_to_json(entry)]}
|
|
||||||
return Response(json.dumps(output), headers=headers)
|
|
||||||
except IndexError:
|
|
||||||
response = {'error': f'Name "{name}" unknown'}
|
|
||||||
return Response(json.dumps(response), headers=headers, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class UsageCatalogEntries(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def post():
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.usage_catalog
|
|
||||||
output = {'usages': []}
|
|
||||||
content = catalog.entries()
|
|
||||||
for usage in content.usages:
|
|
||||||
output['usages'].append(ToJson.usage_to_json(usage))
|
|
||||||
return Response(json.dumps(output), headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
class UsageCatalogNames(Resource):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def post():
|
|
||||||
session = refresh_session(request)
|
|
||||||
if session is None:
|
|
||||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
|
||||||
headers = session.headers
|
|
||||||
catalog = session.usage_catalog
|
|
||||||
return Response(json.dumps(catalog.names()), headers=headers)
|
|
|
@ -6,18 +6,17 @@ Copyright © 2023 Project Author Peter Yefi peteryefi@gmail.com
|
||||||
import json
|
import json
|
||||||
from flask import Response, request
|
from flask import Response, request
|
||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
from imports.user_factory import UserFactory
|
from hub.hub_logger import logger
|
||||||
from exports.user_factory import UserFactory as ExUserFactory
|
|
||||||
import os
|
|
||||||
from hub_logger import logger
|
|
||||||
from hub_api.helpers.auth import generate_auth_token, role_required
|
from hub_api.helpers.auth import generate_auth_token, role_required
|
||||||
from persistence.models import UserRoles
|
from hub.persistence.models import UserRoles
|
||||||
|
from hub_api.config import Config
|
||||||
|
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
|
||||||
class User(Resource):
|
class User(Resource, Config):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.user_factory = UserFactory(db_name='hub_prod', app_env='PROD',
|
super().__init__()
|
||||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value])
|
@role_required([UserRoles.Admin.value])
|
||||||
def post(self):
|
def post(self):
|
||||||
|
@ -26,12 +25,14 @@ class User(Resource):
|
||||||
user = self.user_factory.create_user(name=payload["name"], email=payload["email"], password=payload["password"],
|
user = self.user_factory.create_user(name=payload["name"], email=payload["email"], password=payload["password"],
|
||||||
role=payload["role"])
|
role=payload["role"])
|
||||||
if type(user) is dict:
|
if type(user) is dict:
|
||||||
return Response(response=json.dumps(user), status=400)
|
return Response(response=json.dumps(user), status=400, headers=headers)
|
||||||
return Response(response=json.dumps({'user': {'id': user.id, 'name': user.name, 'email': user.email,
|
return Response(response=json.dumps({'user': {'id': user.id, 'name': user.name, 'email': user.email,
|
||||||
'password': user.password, 'role': user.role.value}}), status=201)
|
'password': user.password, 'role': user.role.value}}), status=201,
|
||||||
|
headers=headers)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating user'}), status=400)
|
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating user'}), status=400,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
@role_required([UserRoles.Admin.value])
|
@role_required([UserRoles.Admin.value])
|
||||||
def put(self):
|
def put(self):
|
||||||
|
@ -40,25 +41,24 @@ class User(Resource):
|
||||||
res = self.user_factory.update_user(user_id=payload['id'], name=payload['name'], password=payload['password'],
|
res = self.user_factory.update_user(user_id=payload['id'], name=payload['name'], password=payload['password'],
|
||||||
role=payload['role'], email=payload['email'])
|
role=payload['role'], email=payload['email'])
|
||||||
if res:
|
if res:
|
||||||
return Response(response=json.dumps(res), status=400)
|
return Response(response=json.dumps(res), status=400, headers=headers)
|
||||||
return Response(response=json.dumps({'success': 'user updated successfully'}), status=200)
|
return Response(response=json.dumps({'success': 'user updated successfully'}), status=200, headers=headers)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return Response(response=json.dumps({'err_msg': 'Sorry, an error occurred while updating user'}),
|
return Response(response=json.dumps({'err_msg': 'Sorry, an error occurred while updating user'}),
|
||||||
status=400)
|
status=400, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
class UserLogin(Resource):
|
class UserLogin(Resource, Config):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.user_factory = ExUserFactory(db_name='hub_prod', app_env='PROD',
|
super().__init__()
|
||||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
try:
|
try:
|
||||||
payload = request.get_json()
|
payload = request.get_json()
|
||||||
user = self.user_factory.login_user(email=payload["email"], password=payload["password"])
|
user = self.ex_user_factory.login_user(email=payload["email"], password=payload["password"])
|
||||||
if type(user) is dict:
|
if type(user) is dict:
|
||||||
return Response(response=json.dumps(user), status=400)
|
return Response(response=json.dumps(user), status=400, headers=headers)
|
||||||
user = user[0]
|
user = user[0]
|
||||||
user_dict = {
|
user_dict = {
|
||||||
'user': {
|
'user': {
|
||||||
|
@ -70,7 +70,7 @@ class UserLogin(Resource):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
user_dict['token'] = generate_auth_token(user_dict)
|
user_dict['token'] = generate_auth_token(user_dict)
|
||||||
return Response(response=json.dumps(user_dict), status=200)
|
return Response(response=json.dumps(user_dict), status=200, headers=headers)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return Response(response=json.dumps({'err_msg': 'An error occurred while authenticating user'}), status=400)
|
return Response(response=json.dumps({'err_msg': 'An error occurred while authenticating user'}), status=400)
|
||||||
|
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
58
tests/base_test.py
Normal file
58
tests/base_test.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import os
|
||||||
|
from unittest import TestCase
|
||||||
|
from bootstrap import app
|
||||||
|
from hub.persistence.base_repo import BaseRepo
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from hub.persistence.models import City
|
||||||
|
from hub.persistence.models import User
|
||||||
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
|
||||||
|
|
||||||
|
# function to ensure tests run in order shown in fle
|
||||||
|
def arrange():
|
||||||
|
order = {}
|
||||||
|
|
||||||
|
def ordered(f):
|
||||||
|
order[f.__name__] = len(order)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def compare(a, b):
|
||||||
|
return [1, -1][order[a] < order[b]]
|
||||||
|
|
||||||
|
return ordered, compare
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTest(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for payment resource
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
os.environ['FLASK_DEBUG'] = 'testing'
|
||||||
|
cls.app = app
|
||||||
|
cls.client = cls.app.test_client()
|
||||||
|
|
||||||
|
# Create test database
|
||||||
|
repo = BaseRepo(db_name='test_db', app_env='TEST', dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||||
|
eng = create_engine(f'postgresql://{repo.config.get_db_user()}@/{repo.config.get_db_user()}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# delete test database if it exists
|
||||||
|
conn = eng.connect()
|
||||||
|
conn.execute('commit')
|
||||||
|
conn.execute('DROP DATABASE test_db')
|
||||||
|
conn.close()
|
||||||
|
except ProgrammingError as err:
|
||||||
|
print(f'Database does not exist. Nothing to delete')
|
||||||
|
|
||||||
|
cnn = eng.connect()
|
||||||
|
cnn.execute('commit')
|
||||||
|
cnn.execute("CREATE DATABASE test_db")
|
||||||
|
cnn.close()
|
||||||
|
User.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
City.__table__.create(bind=repo.engine, checkfirst=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls) -> None:
|
||||||
|
pass
|
124
tests/test_user.py
Normal file
124
tests/test_user.py
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import json
|
||||||
|
from .base_test import BaseTest, arrange
|
||||||
|
import unittest
|
||||||
|
from hub_api.helpers.auth import generate_auth_token
|
||||||
|
|
||||||
|
ordered, compare = arrange()
|
||||||
|
unittest.defaultTestLoader.sortTestMethodsUsing = compare
|
||||||
|
|
||||||
|
|
||||||
|
class UserTest(BaseTest):
|
||||||
|
"""
|
||||||
|
Tests for User API endpoints
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
# Call setUp from parent
|
||||||
|
super().setUpClass()
|
||||||
|
cls.user_dict = {"user": {
|
||||||
|
"name": "Test User",
|
||||||
|
"email": "testuser@gmail.com",
|
||||||
|
"password": "TestUser@12345",
|
||||||
|
"role": "Admin",
|
||||||
|
}}
|
||||||
|
cls.token = generate_auth_token(cls.user_dict)
|
||||||
|
|
||||||
|
@ordered
|
||||||
|
def test_create_user_by_non_admin(self):
|
||||||
|
# When
|
||||||
|
res = self.client.post('/v1.4/user', data=json.dumps(self.user_dict['user']))
|
||||||
|
# Then
|
||||||
|
self.assertEqual('Invalid payload', res.json['messages'])
|
||||||
|
self.assertEqual(400, res.status_code)
|
||||||
|
|
||||||
|
|
||||||
|
@ordered
|
||||||
|
def test_create_user_by_admin(self):
|
||||||
|
# When
|
||||||
|
res = self.client.post('/v1.4/user',
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json', 'Authorization': 'Bearer ' + str(self.token)
|
||||||
|
},
|
||||||
|
data=json.dumps(self.user_dict['user']))
|
||||||
|
# Then
|
||||||
|
user = res.json
|
||||||
|
self.assertEqual(201, res.status_code)
|
||||||
|
self.assertEqual(type(user['user']), dict)
|
||||||
|
self.assertEqual(user['user']['email'], self.user_dict['user']['email'])
|
||||||
|
self.assertEqual(user['user']['role'], self.user_dict['user']['role'])
|
||||||
|
|
||||||
|
@ordered
|
||||||
|
def test_create_user_with_existing_email(self):
|
||||||
|
# When
|
||||||
|
res = self.client.post('/v1.4/user',
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json', 'Authorization': 'Bearer ' + str(self.token)
|
||||||
|
},
|
||||||
|
data=json.dumps(self.user_dict['user']))
|
||||||
|
# Then
|
||||||
|
self.assertEqual('user with testuser@gmail.com email already exists', res.json['message'])
|
||||||
|
self.assertEqual(400, res.status_code)
|
||||||
|
|
||||||
|
@ordered
|
||||||
|
def test_create_user_with_weak_password(self):
|
||||||
|
# When
|
||||||
|
self.user_dict['user']['password'] = '1234'
|
||||||
|
self.user_dict['user']['email'] = 'new@gmail.com'
|
||||||
|
res = self.client.post('/v1.4/user',
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json', 'Authorization': 'Bearer ' + str(self.token)
|
||||||
|
},
|
||||||
|
data=json.dumps(self.user_dict['user']))
|
||||||
|
# Then
|
||||||
|
self.assertEqual('Sorry an error occurred while creating user', res.json['err_msg'])
|
||||||
|
self.assertEqual(400, res.status_code)
|
||||||
|
|
||||||
|
|
||||||
|
@ordered
|
||||||
|
def test_login_user_with_wrong_credential(self):
|
||||||
|
# When
|
||||||
|
res = self.client.post('/v1.4/user/login',
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data=json.dumps({'email': 'wrong@gmail.com', 'password': 'wrong'}))
|
||||||
|
|
||||||
|
# Then
|
||||||
|
message = res.json
|
||||||
|
self.assertEqual('user not found', message['message'])
|
||||||
|
self.assertEqual(400, res.status_code)
|
||||||
|
|
||||||
|
@ordered
|
||||||
|
def test_login_user_with_correct_credential(self):
|
||||||
|
# When
|
||||||
|
self.user_dict['user']['password'] = 'TestUser@12345'
|
||||||
|
self.user_dict['user']['email'] = 'testuser@gmail.com'
|
||||||
|
login_data = {
|
||||||
|
"email": self.user_dict['user']['email'],
|
||||||
|
"password": self.user_dict['user']['password']
|
||||||
|
}
|
||||||
|
res = self.client.post('/v1.4/user/login', headers={'Content-Type': 'application/json'},
|
||||||
|
data=json.dumps(login_data))
|
||||||
|
|
||||||
|
# Then
|
||||||
|
user = res.json
|
||||||
|
self.assertEqual(user['user']['email'], self.user_dict['user']['email'])
|
||||||
|
self.assertEqual(user['user']['role'], self.user_dict['user']['role'])
|
||||||
|
self.assertIsNotNone(user['token'])
|
||||||
|
self.assertEqual(200, res.status_code)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls) -> None:
|
||||||
|
# Call tearDown from parent
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
test_suite = unittest.TestSuite()
|
||||||
|
test_suite.addTest(unittest.TestLoader(
|
||||||
|
).loadTestsFromTestCase(UserTest))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.TextTestRunner(verbosity=2).run(suite())
|
Loading…
Reference in New Issue
Block a user