Removed city attachment to session. City is read from the DB

This commit is contained in:
Peter Yefi 2023-01-17 19:00:22 -05:00
parent 025fe02f71
commit f893959c78
14 changed files with 370 additions and 170 deletions

View File

@ -2,11 +2,11 @@
City info
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, g
from flask_restful import Resource
from hub_api.helpers.session_helper import refresh_session
from hub_api.helpers.auth import role_required
from persistence.models import UserRoles
from hub_logger import logger
@ -14,21 +14,17 @@ from imports.geometry_factory import GeometryFactory
from pathlib import Path
from imports.db_factory import DBFactory
import os
from hub_api.config import Config
class CityInfo(Resource):
class CityInfo(Resource, Config):
def __init__(self):
pass
super().__init__()
@staticmethod
@role_required([UserRoles.Admin.value])
def get():
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
def get(self, city_id):
city = self.get_city(city_id)
city = session.city
# TODO: this is only for dompark project and need to be removed in future versions.
floor_area = 0
wall_construction = 'unknown'
@ -71,11 +67,11 @@ class CityInfo(Resource):
}
buildings = [building_dic]
response = {'city_name': 'Montreal',
response = {'city_name': city.name,
'climate_reference_city': str(city.climate_reference_city),
'buildings': buildings
}
return Response(json.dumps(response), headers=headers)
return Response(json.dumps(response), status=200)
class City(Resource):
@ -83,7 +79,7 @@ class City(Resource):
def __init__(self):
pass
@role_required([UserRoles.Admin.value])
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def post(self):
allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'}
try:
@ -119,4 +115,4 @@ class City(Resource):
return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400)
except Exception as 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)

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
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 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):
pass
super().__init__()
@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
@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']
@ -73,6 +71,6 @@ class Construction(Resource):
response = {'result': 'succeed'}
except KeyError as ex:
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'
security:
- 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:
post:
tags:
@ -268,6 +392,86 @@ components:
updated:
type: string
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:
type: object
properties:
@ -289,6 +493,29 @@ components:
enum:
- Admin
- 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:
type: object
properties:
@ -325,14 +552,14 @@ components:
application/xml:
schema:
$ref: '#/components/schemas/User'
UserArray:
description: List of user object
CityArray:
description: List of city object
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
$ref: '#/components/schemas/City'
securitySchemes:
BearerAuth:
type: http

View File

@ -5,13 +5,15 @@ from pathlib import Path
from geomeppy import IDF
import os
import glob
from hub_api.helpers.session_helper import refresh_session
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):
class EnergyDemand(Resource, Config):
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
_SURFACE = 'BUILDINGSURFACE:DETAILED'
@ -92,6 +94,7 @@ class EnergyDemand(Resource):
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
@ -314,13 +317,12 @@ class EnergyDemand(Resource):
return
def get(self):
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
self._city = session.city
self._greenery_percentage = round(float(session.greenery_percentage) / 10) * 10
@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())
@ -391,4 +393,4 @@ class EnergyDemand(Resource):
'lighting_demand': lighting,
'appliances_demand': appliances
}
return Response(json.dumps(response), headers=headers)
return Response(json.dumps(response), status=200)

View File

@ -2,14 +2,14 @@
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
"""
import json
from flask import make_response, send_file, request, Response
from flask import make_response, send_file
from flask_restful import Resource
from pathlib import Path
from hub_api.helpers.session_helper import refresh_session
from hub_api.helpers.auth import role_required
from persistence.models import UserRoles
class Geometry(Resource):
@ -17,13 +17,9 @@ class Geometry(Resource):
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):
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,
as_attachment=True,
mimetype='model/gltf+json, model/gltf-binary'))
response.headers['session_id'] = session.id
response.headers['token'] = session.token
return response

View File

@ -2,36 +2,35 @@
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 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):
pass
super().__init__()
@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
@role_required([UserRoles.Admin.value])
def put(self, city_id):
city = self.get_city(city_id)
try:
session.greenery_percentage = request.json['greenery_percentage']
if session.greenery_percentage == 0:
greenery_percentage = request.json['greenery_percentage']
if greenery_percentage == 0:
response = {'result': 'succeed'}
return Response(json.dumps(response), headers=headers)
return Response(json.dumps(response), status=200)
building_names = request.json['building_names']
vegetation_requested = request.json['vegetation']
@ -80,6 +79,6 @@ class Greenery(Resource):
response = {'result': 'succeed'}
except KeyError as ex:
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
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

View File

@ -5,77 +5,35 @@ Copyright © 2022 Project Author Peter Yefi peteryefi@gmail.com
"""
import json
from flask import send_file, request, make_response, Response
from flask_apispec import use_kwargs, doc
from flask_apispec.views import MethodResource
from flask import request, Response
from hub_api.config import Config
from flask_restful import Resource
from marshmallow import Schema, fields
from hub_api.helpers.auth import role_required
from hub_api.helpers.session_helper import refresh_session
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 HeatPumpPostData(Schema):
"""
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):
class HeatPump(Config, Resource):
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])
def post(self, **kwargs):
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
city = session.city
if validate_hp_model(kwargs['HeatPumpType'], kwargs['HeatPumpModel']):
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, kwargs)
result_file = hp_simulator.run_hp_simulation()
response = self._send_response(result_file, session)
return response
hp_simulator = HeatPumpSimulator(city, payload)
results = hp_simulator.run_hp_simulation()
return Response(json.dumps(results), status=200)
except Exception as err:
print(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)
@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 typing import Dict
from jwt import JWT, jwk_from_pem
@ -48,14 +54,11 @@ def role_required(roles: [str]):
try:
token = request.headers['Authorization'].split()[1]
user = validate_auth_token(token)
if user is None:
return {'messages': 'You have not been authenticated'}, 401
allowed = auth_module(user['user'])
if user['user']['role'] == UserRoles.Admin.value and 'localhost' not in request.headers['Host']:
allowed = False
if not allowed:
return {'messages': 'You are not authorized'}, 403
return f(*args, **kwargs)

View File

@ -2,19 +2,23 @@
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 request, Response
from flask import Response
from flask_restful import Resource
from lca_calculations import LcaCalculations
from hub_api.helpers.session_helper import refresh_session
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):
class MaterialLCACatalog(Resource, Config):
def __init__(self):
pass
super().__init__()
@staticmethod
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
def get(self):
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
@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)), headers=headers)
return Response(json.dumps(self.get_lca_value(city)), status=200)
except ValueError:
response = {'error': f'No Catalog Available'}
return Response(json.dumps(response), headers=headers, status=400)
response = {'err_msg': f'No Catalog Available'}
return Response(json.dumps(response), status=400)
class MaterialLCACalculations(Resource):
class MaterialLCACalculations(Resource, Config):
"""
LCA class
"""
def __init__(self):
pass
super().__init__()
@staticmethod
def get():
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
def get(self, city_id):
"""
Auto-method for processing the lca request
:return: lca demand
"""
session = refresh_session(request)
if session is None:
return Response(json.dumps({'error': 'invalid session'}), status=401)
headers = session.headers
city = session.city
city = self.get_city(city_id)
materials_lca = {'Wall': [], 'Ground': [], 'Roof': []}
for building in city.buildings:
@ -131,4 +125,4 @@ class MaterialLCACalculations(Resource):
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), headers=headers)
return Response(json.dumps(materials_embodied_carbon), status=200)

View File

@ -2,6 +2,7 @@
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
@ -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.thermal_control import ThermalControl
from city_model_structure.building_demand.usage_zone import UsageZone
from hub_api.helpers.session_helper import refresh_session
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):
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
try:
building_names = request.json['building_names']
@ -146,8 +147,8 @@ class Usage(Resource):
response = {'result': 'succeed'}
except KeyError as ex:
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:
response = {'error': f'Name "{usage_name}" unknown'}
return Response(json.dumps(response), headers=headers, status=400)
return Response(json.dumps(response), headers=headers)
return Response(json.dumps(response), status=400)
return Response(json.dumps(response))

View File

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

View File

@ -1,7 +1,7 @@
"""
HeatPump Service
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
from flask import Response, request