Merge pull request 'api-v2' (#4) from api-v2 into main

Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/p_yefi/gamification_service_v2/pulls/4
This commit is contained in:
Peter Yefi 2023-01-17 19:04:51 -05:00
commit 588eef7f9d
19 changed files with 405 additions and 197 deletions

View File

@ -29,7 +29,7 @@ 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.greenery import Greenery
from hub_api.user import User, UserLogin from hub_api.user import User, UserLogin
from flasgger import LazyJSONEncoder, LazyString, Swagger from flasgger import LazyJSONEncoder, Swagger
app = flask.Flask('gamification') app = flask.Flask('gamification')
app.json_encoder = LazyJSONEncoder app.json_encoder = LazyJSONEncoder
@ -64,21 +64,21 @@ api.add_resource(GreeneryCatalogNames, '/v1.4/greenery-catalog/names')
api.add_resource(ConstructionCatalogEntry, '/v1.4/construction-catalog/entry') api.add_resource(ConstructionCatalogEntry, '/v1.4/construction-catalog/entry')
api.add_resource(ConstructionCatalogEntries, '/v1.4/construction-catalog/entries') api.add_resource(ConstructionCatalogEntries, '/v1.4/construction-catalog/entries')
api.add_resource(ConstructionCatalogNames, '/v1.4/construction-catalog/names') api.add_resource(ConstructionCatalogNames, '/v1.4/construction-catalog/names')
api.add_resource(Construction, '/v1.4/construction') api.add_resource(Construction, '/v1.4/construction/<int:city_id>')
api.add_resource(UsageCatalogEntry, '/v1.4/usage-catalog/entry') api.add_resource(UsageCatalogEntry, '/v1.4/usage-catalog/entry')
api.add_resource(UsageCatalogEntries, '/v1.4/usage-catalog/entries') api.add_resource(UsageCatalogEntries, '/v1.4/usage-catalog/entries')
api.add_resource(UsageCatalogNames, '/v1.4/usage-catalog/names') api.add_resource(UsageCatalogNames, '/v1.4/usage-catalog/names')
api.add_resource(Usage, '/v1.4/usage') api.add_resource(Usage, '/v1.4/usage')
api.add_resource(EnergyDemand, '/v1.4/energy-demand') api.add_resource(EnergyDemand, '/v1.4/energy-demand/<int:city_id>')
# api.add_resource(LCA, '/v1.4/lca') # api.add_resource(LCA, '/v1.4/lca')
api.add_resource(MaterialLCACatalog, '/v1.4/material_lca_catalog/entries') api.add_resource(MaterialLCACatalog, '/v1.4/material_lca_catalog/entries/<int:city_id>')
api.add_resource(MaterialLCACalculations, '/v1.4/material_lca_catalog/calculations') api.add_resource(MaterialLCACalculations, '/v1.4/material_lca_catalog/calculations/<int:city_id>')
api.add_resource(HeatPump, '/v1.4/heat-pump') 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')
api.add_resource(SessionEnd, '/v1.4/session/end') api.add_resource(SessionEnd, '/v1.4/session/end')
api.add_resource(KeepSessionAlive, '/v1.4/session/keep_alive') api.add_resource(KeepSessionAlive, '/v1.4/session/keep_alive')
api.add_resource(CityInfo, '/v1.4/city_info') 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') api.add_resource(Greenery, '/v1.4/greenery')

View File

@ -53,19 +53,6 @@ WeatherFactory('epw', city, file_name=montreal_weather_file).enrich()
city.name = 'Montreal' city.name = 'Montreal'
city.climate_reference_city = 'Montreal' city.climate_reference_city = 'Montreal'
# SRA Calculations
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
# Pass the city to the session helper to be used as default status.
sh.city = city
@app.route("/") @app.route("/")
def home(): def home():

View File

@ -2,11 +2,11 @@
City info City info
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
Code contributors: Peter Yefi peteryefi@gmail.com
""" """
import json 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.session_helper import refresh_session
from hub_api.helpers.auth import role_required from hub_api.helpers.auth import role_required
from persistence.models import UserRoles from persistence.models import UserRoles
from hub_logger import logger from hub_logger import logger
@ -14,20 +14,17 @@ from imports.geometry_factory import GeometryFactory
from pathlib import Path from pathlib import Path
from imports.db_factory import DBFactory from imports.db_factory import DBFactory
import os import os
from hub_api.config import Config
class CityInfo(Resource): class CityInfo(Resource, Config):
def __init__(self): def __init__(self):
pass super().__init__()
@staticmethod @role_required([UserRoles.Admin.value])
def get(): def get(self, city_id):
session = refresh_session(request) city = self.get_city(city_id)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
# TODO: this is only for dompark project and need to be removed in future versions. # TODO: this is only for dompark project and need to be removed in future versions.
floor_area = 0 floor_area = 0
wall_construction = 'unknown' wall_construction = 'unknown'
@ -70,11 +67,11 @@ class CityInfo(Resource):
} }
buildings = [building_dic] buildings = [building_dic]
response = {'city_name': 'Montreal', response = {'city_name': city.name,
'climate_reference_city': str(city.climate_reference_city), 'climate_reference_city': str(city.climate_reference_city),
'buildings': buildings 'buildings': buildings
} }
return Response(json.dumps(response), headers=headers) return Response(json.dumps(response), status=200)
class City(Resource): class City(Resource):
@ -82,7 +79,7 @@ class City(Resource):
def __init__(self): def __init__(self):
pass pass
@role_required([UserRoles.Admin.value]) @role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def post(self): def post(self):
allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'} allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'}
try: try:
@ -118,4 +115,4 @@ class City(Resource):
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)
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)

27
hub_api/config.py Normal file
View File

@ -0,0 +1,27 @@
"""
Config
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Project Peter Yefi peteryefi@gmail.com
"""
from exports.db_factory import DBFactory as CityExportFactory
import os
import pickle
class Config:
def __init__(self):
self.factory = CityExportFactory(db_name='hub_prod', app_env='PROD',
dotenv_path="{}/.env".format(os.path.expanduser('~')))
def get_city(self, city_id):
city_obj = self.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

View File

@ -2,29 +2,27 @@
Construction Construction
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
Code contributors: Peter Yefi peteryefi@gmail.com
""" """
import json import json
import uuid import uuid
from flask import Response, request from flask import Response, request
from flask_restful import Resource 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.layer import Layer
from city_model_structure.building_demand.material import Material from city_model_structure.building_demand.material import Material
from hub_api.helpers.session_helper import refresh_session from persistence.models import UserRoles
from hub_api.helpers.auth import role_required
class Construction(Resource): class Construction(Resource, Config):
def __init__(self): def __init__(self):
pass super().__init__()
@staticmethod @role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def put(): def put(self, city_id):
session = refresh_session(request) city = self.get_city(city_id)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
try: try:
building_names = request.json['building_names'] building_names = request.json['building_names']
constructions = request.json['constructions'] constructions = request.json['constructions']
@ -73,6 +71,6 @@ class Construction(Resource):
response = {'result': 'succeed'} response = {'result': 'succeed'}
except KeyError as ex: except KeyError as ex:
response = {'error': f'Mandatory parameter {ex} is missing'} response = {'error': f'Mandatory parameter {ex} is missing'}
return Response(json.dumps(response), headers=headers, status=400) return Response(json.dumps(response), status=400)
return Response(json.dumps(response), headers=headers) return Response(json.dumps(response), status=200)

View File

@ -78,7 +78,131 @@ 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}:
get:
tags:
- city
summary: Get a city
operationId: getCity
description: Retrieve a city with a given city ID
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: City created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/City'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
'404':
description: City 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/user: /v1.4/user:
post: post:
tags: tags:
@ -268,6 +392,86 @@ 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 ]
User: User:
type: object type: object
properties: properties:
@ -289,6 +493,29 @@ components:
enum: enum:
- Admin - Admin
- Hub_Reader - Hub_Reader
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
Login: Login:
type: object type: object
properties: properties:
@ -325,14 +552,14 @@ components:
application/xml: application/xml:
schema: schema:
$ref: '#/components/schemas/User' $ref: '#/components/schemas/User'
UserArray: CityArray:
description: List of user object description: List of city object
content: content:
application/json: application/json:
schema: schema:
type: array type: array
items: items:
$ref: '#/components/schemas/User' $ref: '#/components/schemas/City'
securitySchemes: securitySchemes:
BearerAuth: BearerAuth:
type: http type: http

View File

@ -5,13 +5,15 @@ from pathlib import Path
from geomeppy import IDF from geomeppy import IDF
import os import os
import glob import glob
from hub_api.helpers.session_helper import refresh_session
import hub_api.helpers.session_helper as sh import hub_api.helpers.session_helper as sh
import helpers.constants as cte import helpers.constants as cte
import csv import csv
from hub_api.helpers.auth import role_required
from persistence.models import UserRoles
from hub_api.config import Config
class EnergyDemand(Resource): class EnergyDemand(Resource, Config):
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT' _THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM' _IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
_SURFACE = 'BUILDINGSURFACE:DETAILED' _SURFACE = 'BUILDINGSURFACE:DETAILED'
@ -92,6 +94,7 @@ class EnergyDemand(Resource):
def __init__(self): def __init__(self):
# this class is mostly hardcoded, as is intended to be used only for Dompark project, # this class is mostly hardcoded, as is intended to be used only for Dompark project,
# other projects should use the normal idf workflow instead. # other projects should use the normal idf workflow instead.
super().__init__()
self._output_path = Path(Path(__file__).parent.parent / 'tmp').resolve() self._output_path = Path(Path(__file__).parent.parent / 'tmp').resolve()
self._data_path = Path(Path(__file__).parent.parent / 'data').resolve() self._data_path = Path(Path(__file__).parent.parent / 'data').resolve()
self._city = None self._city = None
@ -314,13 +317,12 @@ class EnergyDemand(Resource):
return return
def get(self): @role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
session = refresh_session(request) def get(self, city_id):
if session is None: payload = request.get_json()
return Response(json.dumps({'error': 'invalid session'}), status=401) self._city = self.get_city(city_id)
headers = session.headers
self._city = session.city self._greenery_percentage = round(float(payload['greenery_percentage']) / 10) * 10
self._greenery_percentage = round(float(session.greenery_percentage) / 10) * 10
output_file = str((self._output_path / 'dompark.idf').resolve()) output_file = str((self._output_path / 'dompark.idf').resolve())
idd_file = str((self._data_path / 'energy+.idd').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()) epw_file = str((self._data_path / 'CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve())
@ -391,4 +393,4 @@ class EnergyDemand(Resource):
'lighting_demand': lighting, 'lighting_demand': lighting,
'appliances_demand': appliances 'appliances_demand': appliances
} }
return Response(json.dumps(response), headers=headers) return Response(json.dumps(response), status=200)

View File

@ -2,14 +2,14 @@
Geometry Geometry
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
Code contributors: Peter Yefi peteryefi@gmail.com
""" """
import json from flask import make_response, send_file
from flask import make_response, send_file, request, Response
from flask_restful import Resource from flask_restful import Resource
from pathlib import Path from pathlib import Path
from hub_api.helpers.auth import role_required
from hub_api.helpers.session_helper import refresh_session from persistence.models import UserRoles
class Geometry(Resource): class Geometry(Resource):
@ -17,13 +17,9 @@ class Geometry(Resource):
data_path = (Path(__file__).parent.parent / 'data').resolve() data_path = (Path(__file__).parent.parent / 'data').resolve()
self._gtlf_path = (Path(data_path / 'DomparkBuilding.gltf')).resolve() self._gtlf_path = (Path(data_path / 'DomparkBuilding.gltf')).resolve()
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def get(self): def get(self):
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
response = make_response(send_file(self._gtlf_path, response = make_response(send_file(self._gtlf_path,
as_attachment=True, as_attachment=True,
mimetype='model/gltf+json, model/gltf-binary')) mimetype='model/gltf+json, model/gltf-binary'))
response.headers['session_id'] = session.id
response.headers['token'] = session.token
return response return response

View File

@ -2,36 +2,35 @@
Greenery Greenery
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Copyright © 2022 Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: 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 hub_api.config import Config
from city_model_structure.greenery.vegetation import Vegetation from city_model_structure.greenery.vegetation import Vegetation
from city_model_structure.greenery.soil import Soil from city_model_structure.greenery.soil import Soil
from city_model_structure.greenery.plant import Plant from city_model_structure.greenery.plant import Plant
import helpers.constants as cte import helpers.constants as cte
from hub_api.helpers.session_helper import refresh_session from persistence.models import UserRoles
from hub_api.helpers.auth import role_required
class Greenery(Resource): class Greenery(Resource, Config):
def __init__(self): def __init__(self):
pass super().__init__()
@staticmethod @role_required([UserRoles.Admin.value])
def put(): def put(self, city_id):
session = refresh_session(request)
if session is None: city = self.get_city(city_id)
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
try: try:
session.greenery_percentage = request.json['greenery_percentage'] greenery_percentage = request.json['greenery_percentage']
if session.greenery_percentage == 0: if greenery_percentage == 0:
response = {'result': 'succeed'} response = {'result': 'succeed'}
return Response(json.dumps(response), headers=headers) return Response(json.dumps(response), status=200)
building_names = request.json['building_names'] building_names = request.json['building_names']
vegetation_requested = request.json['vegetation'] vegetation_requested = request.json['vegetation']
@ -80,6 +79,6 @@ class Greenery(Resource):
response = {'result': 'succeed'} response = {'result': 'succeed'}
except KeyError as ex: except KeyError as ex:
response = {'error': f'Mandatory parameter {ex} is missing'} response = {'error': f'Mandatory parameter {ex} is missing'}
return Response(json.dumps(response), headers=headers, status=400) return Response(json.dumps(response), status=400)
return Response(json.dumps(response), headers=headers) return Response(json.dumps(response))

View File

@ -5,10 +5,8 @@ Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
""" """
import json import json
from flask import request, Response from flask import request, Response
from flask_restful import Resource from flask_restful import Resource
from catalog_factories.data_models.greenery.plant import Plant from catalog_factories.data_models.greenery.plant import Plant
from catalog_factories.data_models.greenery.soil import Soil from catalog_factories.data_models.greenery.soil import Soil
from catalog_factories.data_models.greenery.vegetation import Vegetation from catalog_factories.data_models.greenery.vegetation import Vegetation

View File

@ -5,77 +5,35 @@ Copyright © 2022 Project Author Peter Yefi peteryefi@gmail.com
""" """
import json import json
from flask import send_file, request, make_response, Response from flask import request, Response
from flask_apispec import use_kwargs, doc from hub_api.config import Config
from flask_apispec.views import MethodResource
from flask_restful import Resource from flask_restful import Resource
from marshmallow import Schema, fields
from hub_api.helpers.auth import role_required from hub_api.helpers.auth import role_required
from hub_api.helpers.session_helper import refresh_session
from utils import HeatPumpSimulator from utils import HeatPumpSimulator
from utils import validate_hp_model from utils import validate_hp_model
from persistence.models import UserRoles from persistence.models import UserRoles
from utils import expand_energy_demand
from hub_logger import logger
class HeatPumpPostData(Schema): class HeatPump(Config, Resource):
"""
Defines post data for heat-pump simulation
"""
StartYear = fields.Integer(required=True, description='Start year for simulation data')
EndYear = fields.Integer(required=True, description='End year for simulation data')
MaximumHPEnergyInput = fields.Float(required=True, description='Maximum heat pump energy input')
HoursOfStorageAtMaxDemand = fields.Integer(required=True, description='Hours of storage at maximum demand')
BuildingSuppTemp = fields.Integer(required=True, description='Building supply temperature')
TemperatureDifference = fields.Float(required=True, description='Temperature difference')
FuelLHV = fields.Float(required=True, description='Fuel LHV')
FuelPrice = fields.Float(required=True, description='Fuel price')
FuelEF = fields.Integer(required=True, description='Fuel EF')
FuelDensity = fields.Float(required=True, description='Fuel Density')
HPSupTemp = fields.Float(required=True, description='Heat pump supply temperature')
HeatPumpType = fields.String(required=True, description='Type of Heat pump',
enum=['Water to Water HP', 'Air Source HP'])
HeatPumpModel = fields.String(required=True, description='Model of heat pump to run simulation for',
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 = fields.Integer(required=True, description='Series or Parallel simulation [0 for series, 1 for parallel',
enum=[0, 1])
class HeatPump(MethodResource, Resource):
def __init__(self): def __init__(self):
pass super().__init__()
@doc(description='Heat pump simulation run', tags=['HeatPump'])
@use_kwargs(HeatPumpPostData)
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value]) @role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def post(self, **kwargs): def post(self, city_id):
session = refresh_session(request) payload = request.get_json()
if session is None: city = self.get_city(city_id)
return Response(json.dumps({'error': 'invalid session'}), status=401) if validate_hp_model(payload['HeatPumpType'], payload['HeatPumpModel']):
city = session.city # expand energy demand values
if validate_hp_model(kwargs['HeatPumpType'], kwargs['HeatPumpModel']): expand_energy_demand(payload['EnergyDemand'])
try: try:
# Run simulation and return output file here # Run simulation and return output file here
hp_simulator = HeatPumpSimulator(city, kwargs) hp_simulator = HeatPumpSimulator(city, payload)
result_file = hp_simulator.run_hp_simulation() results = hp_simulator.run_hp_simulation()
response = self._send_response(result_file, session) return Response(json.dumps(results), status=200)
return response
except Exception as err: except Exception as err:
print(err) logger.error(err)
return Response(json.dumps({'error_message': 'Sorry an error occurred while running HP Simulation'})) return Response(json.dumps({'error_message': 'Sorry an error occurred while running HP Simulation'}))
else: else:
return Response(json.dumps({'error_message': 'Wrong heat pump type/model combination'}), status=400) return Response(json.dumps({'error_message': 'Wrong heat pump type/model combination'}), status=400)
@staticmethod
def _send_response(result_file, session):
"""
Sends insel results file after simulation
:param result_file: the insel output file
:param session: session variable
:return CSV output file
"""
response = make_response(send_file(result_file, as_attachment=True, mimetype='text/csv'))
response.headers['session_id'] = session.id
response.headers['token'] = session.token
return response

View File

@ -1,3 +1,9 @@
"""
HeatPump Service
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Project Author Peter Yefi peteryefi@gmail.com
"""
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Dict from typing import Dict
from jwt import JWT, jwk_from_pem from jwt import JWT, jwk_from_pem
@ -48,14 +54,11 @@ def role_required(roles: [str]):
try: try:
token = request.headers['Authorization'].split()[1] token = request.headers['Authorization'].split()[1]
user = validate_auth_token(token) user = validate_auth_token(token)
if user is None: if user is None:
return {'messages': 'You have not been authenticated'}, 401 return {'messages': 'You have not been authenticated'}, 401
allowed = auth_module(user['user']) allowed = auth_module(user['user'])
if user['user']['role'] == UserRoles.Admin.value and 'localhost' not in request.headers['Host']: if user['user']['role'] == UserRoles.Admin.value and 'localhost' not in request.headers['Host']:
allowed = False allowed = False
if not allowed: if not allowed:
return {'messages': 'You are not authorized'}, 403 return {'messages': 'You are not authorized'}, 403
return f(*args, **kwargs) return f(*args, **kwargs)

View File

@ -2,19 +2,23 @@
LCA LCA
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2021 Project Author name Atiya Copyright © 2021 Project Author name Atiya
Code contributors: Peter Yefi peteryefi@gmail.com
""" """
import json import json
from flask import request, Response from flask import Response
from flask_restful import Resource from flask_restful import Resource
from lca_calculations import LcaCalculations from lca_calculations import LcaCalculations
from hub_api.helpers.session_helper import refresh_session
from itertools import groupby from itertools import groupby
from operator import itemgetter 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):
class MaterialLCACatalog(Resource, Config):
def __init__(self): def __init__(self):
pass super().__init__()
@staticmethod @staticmethod
def get_lca_value(city, nrel_id = None): def get_lca_value(city, nrel_id = None):
@ -56,40 +60,30 @@ class MaterialLCACatalog(Resource):
return material.embodied_carbon, material.id, material.type, material.name, material.density return material.embodied_carbon, material.id, material.type, material.name, material.density
# return material.embodied_carbon # return material.embodied_carbon
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def get(self, city_id):
def get(self): city = self.get_city(city_id)
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
try: try:
return Response(json.dumps(self.get_lca_value(city)), headers=headers) return Response(json.dumps(self.get_lca_value(city)), status=200)
except ValueError: except ValueError:
response = {'error': f'No Catalog Available'} response = {'err_msg': f'No Catalog Available'}
return Response(json.dumps(response), headers=headers, status=400) return Response(json.dumps(response), status=400)
class MaterialLCACalculations(Resource): class MaterialLCACalculations(Resource, Config):
""" """
LCA class LCA class
""" """
def __init__(self): def __init__(self):
pass super().__init__()
@staticmethod @role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def get(): def get(self, city_id):
""" """
Auto-method for processing the lca request Auto-method for processing the lca request
:return: lca demand :return: lca demand
""" """
session = refresh_session(request) city = self.get_city(city_id)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
materials_lca = {'Wall': [], 'Ground': [], 'Roof': []} materials_lca = {'Wall': [], 'Ground': [], 'Roof': []}
for building in city.buildings: for building in city.buildings:
@ -131,4 +125,4 @@ class MaterialLCACalculations(Resource):
total_embodied_carbon['end_of_life_carbon'] = sum_end_of_life total_embodied_carbon['end_of_life_carbon'] = sum_end_of_life
materials_embodied_carbon[key].append(total_embodied_carbon) materials_embodied_carbon[key].append(total_embodied_carbon)
return Response(json.dumps(materials_embodied_carbon), headers=headers) return Response(json.dumps(materials_embodied_carbon), status=200)

View File

@ -2,6 +2,7 @@
Usage Usage
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
Code contributors: Peter Yefi peteryefi@gmail.com
""" """
import json import json
@ -14,22 +15,22 @@ from city_model_structure.building_demand.lighting import Lighting
from city_model_structure.building_demand.occupancy import Occupancy 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.thermal_control import ThermalControl
from city_model_structure.building_demand.usage_zone import UsageZone from city_model_structure.building_demand.usage_zone import UsageZone
from hub_api.helpers.session_helper import refresh_session
import helpers.constants as cte 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): class Usage(Resource, Config):
def __init__(self): def __init__(self):
pass 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']
@staticmethod
def put():
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
catalog = session.usage_catalog
usage_name = None usage_name = None
try: try:
building_names = request.json['building_names'] building_names = request.json['building_names']
@ -146,8 +147,8 @@ class Usage(Resource):
response = {'result': 'succeed'} response = {'result': 'succeed'}
except KeyError as ex: except KeyError as ex:
response = {'error': f'Mandatory parameter {ex} is missing'} response = {'error': f'Mandatory parameter {ex} is missing'}
return Response(json.dumps(response), headers=headers, status=400) return Response(json.dumps(response), status=400)
except IndexError: except IndexError:
response = {'error': f'Name "{usage_name}" unknown'} response = {'error': f'Name "{usage_name}" unknown'}
return Response(json.dumps(response), headers=headers, status=400) return Response(json.dumps(response), status=400)
return Response(json.dumps(response), headers=headers) return Response(json.dumps(response))

View File

@ -97,6 +97,7 @@ class ToJson:
} }
return schedule_dictionary return schedule_dictionary
class UsageCatalogEntry(Resource): class UsageCatalogEntry(Resource):
def __init__(self): def __init__(self):
pass pass

View File

@ -1,7 +1,7 @@
""" """
HeatPump Service HeatPump Service
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Project Author Peter Yefi peteryefi@gmail.com Copyright © 2023 Project Author Peter Yefi peteryefi@gmail.com
""" """
import json import json
from flask import Response, request from flask import Response, request

View File

@ -1,2 +1,3 @@
from .misc import validate_hp_model from .misc import validate_hp_model
from .hp_simulator import HeatPumpSimulator from .hp_simulator import HeatPumpSimulator
from .misc import expand_energy_demand

View File

@ -17,10 +17,10 @@ class HeatPumpSimulator:
:param user_input: the user parameters for running simulation :param user_input: the user parameters for running simulation
""" """
self._user_input = user_input self._user_input = user_input
self._hp_type = user_input['HeatPumpType'].replace(" ", "_").lower() self._hp_type = user_input['HeatPumpType'].replace(" ", "_").lower()
# file to have results after simulation is run # file to have results after simulation is run
self._output_path = Path(Path(__file__).parent.parent / "data/dompark_{}.csv".format(self._hp_type))
self._city = city self._city = city
EnergySystemsFactory(user_input['HeatPumpType'].lower(), self._city).enrich() EnergySystemsFactory(user_input['HeatPumpType'].lower(), self._city).enrich()
@ -32,10 +32,12 @@ class HeatPumpSimulator:
""" """
hp_type = 'water' if 'water' in self._hp_type else 'air' hp_type = 'water' if 'water' in self._hp_type else 'air'
del self._user_input['HeatPumpType'] del self._user_input['HeatPumpType']
del self._user_input['EnergyDemand']
model = self._user_input.pop('HeatPumpModel') model = self._user_input.pop('HeatPumpModel')
EnergySystemsExportFactory(self._city, energy_demand_path = Path(Path(__file__).parent.parent / "data/energy_demand.txt")
self._user_input, return EnergySystemsExportFactory(city=self._city,
model, user_input=self._user_input,
self._output_path, hp_model=model,
self._user_input['SimType']).export(hp_type) output_path=None,
return str(self._output_path) sim_type=self._user_input['SimType'],
demand_path=energy_demand_path).export(hp_type)

View File

@ -3,6 +3,11 @@ Miscellaneous
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Project Author Peter Yefi peteryefi@gmail.com Copyright © 2022 Project Author Peter Yefi peteryefi@gmail.com
""" """
from typing import List
from pathlib import Path
import numpy as np
hp_models = { hp_models = {
'air_source': ['012', '015', '018', '023', '030', '033', '037', '044', '047', '057', '070', '087', '097', '102', 'air_source': ['012', '015', '018', '023', '030', '033', '037', '044', '047', '057', '070', '087', '097', '102',
'120', '130', '140'], '120', '130', '140'],
@ -26,3 +31,15 @@ def validate_hp_model(hp_type: str, model: str) -> bool:
if model in hp_models['water_to_water']: if model in hp_models['water_to_water']:
return True return True
return False return False
def expand_energy_demand(hourly_energy_demand: List[float]):
"""
Replicates each value in the list 11 times and persist the values to a file
:param hourly_energy_demand: a list of hourly energy demand data
"""
energy_demand = Path(Path(__file__).parent.parent / "data/energy_demand.txt")
with open(energy_demand, 'w') as demand_file:
repeated_demand_values = np.repeat(hourly_energy_demand, 12).tolist()
for demand in repeated_demand_values:
demand_file.write("%.6f\n" % demand)