From 025fe02f71c92d2447ade828eca26e5131c7f1ac Mon Sep 17 00:00:00 2001 From: Peter Yefi Date: Tue, 17 Jan 2023 18:59:59 -0500 Subject: [PATCH 1/3] Read energy demand as an array of floats --- hub_api/city_info.py | 1 + hub_api/config.py | 0 utils/__init__.py | 1 + utils/hp_simulator.py | 16 +++++++++------- utils/misc.py | 17 +++++++++++++++++ 5 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 hub_api/config.py diff --git a/hub_api/city_info.py b/hub_api/city_info.py index 8971cd3..5cd9454 100644 --- a/hub_api/city_info.py +++ b/hub_api/city_info.py @@ -21,6 +21,7 @@ class CityInfo(Resource): pass @staticmethod + @role_required([UserRoles.Admin.value]) def get(): session = refresh_session(request) if session is None: diff --git a/hub_api/config.py b/hub_api/config.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__init__.py b/utils/__init__.py index 1de5e8f..311bf12 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,2 +1,3 @@ from .misc import validate_hp_model from .hp_simulator import HeatPumpSimulator +from .misc import expand_energy_demand diff --git a/utils/hp_simulator.py b/utils/hp_simulator.py index 42a0b6a..342c563 100644 --- a/utils/hp_simulator.py +++ b/utils/hp_simulator.py @@ -17,10 +17,10 @@ class HeatPumpSimulator: :param user_input: the user parameters for running simulation """ + self._user_input = user_input self._hp_type = user_input['HeatPumpType'].replace(" ", "_").lower() # 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 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' del self._user_input['HeatPumpType'] + del self._user_input['EnergyDemand'] model = self._user_input.pop('HeatPumpModel') - EnergySystemsExportFactory(self._city, - self._user_input, - model, - self._output_path, - self._user_input['SimType']).export(hp_type) - return str(self._output_path) + energy_demand_path = Path(Path(__file__).parent.parent / "data/energy_demand.txt") + return EnergySystemsExportFactory(city=self._city, + user_input=self._user_input, + hp_model=model, + output_path=None, + sim_type=self._user_input['SimType'], + demand_path=energy_demand_path).export(hp_type) diff --git a/utils/misc.py b/utils/misc.py index dc0b169..0f23955 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -3,6 +3,11 @@ Miscellaneous SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Project Author Peter Yefi peteryefi@gmail.com """ +from typing import List +from pathlib import Path +import numpy as np + + hp_models = { 'air_source': ['012', '015', '018', '023', '030', '033', '037', '044', '047', '057', '070', '087', '097', '102', '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']: return True 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) \ No newline at end of file From f893959c7808211ab9f0591e42d3c3558bcf773e Mon Sep 17 00:00:00 2001 From: Peter Yefi Date: Tue, 17 Jan 2023 19:00:22 -0500 Subject: [PATCH 2/3] Removed city attachment to session. City is read from the DB --- hub_api/city_info.py | 24 ++-- hub_api/config.py | 27 ++++ hub_api/construction.py | 24 ++-- hub_api/docs/openapi-specs.yml | 235 ++++++++++++++++++++++++++++++++- hub_api/energy_demand.py | 22 +-- hub_api/geometry.py | 14 +- hub_api/greenery.py | 31 +++-- hub_api/greenery_catalog.py | 2 - hub_api/heat_pump.py | 74 +++-------- hub_api/helpers/auth.py | 9 +- hub_api/lca.py | 46 +++---- hub_api/usage.py | 29 ++-- hub_api/usage_catalog.py | 1 + hub_api/user.py | 2 +- 14 files changed, 370 insertions(+), 170 deletions(-) diff --git a/hub_api/city_info.py b/hub_api/city_info.py index 5cd9454..080dece 100644 --- a/hub_api/city_info.py +++ b/hub_api/city_info.py @@ -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) \ No newline at end of file + return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}), status=400) diff --git a/hub_api/config.py b/hub_api/config.py index e69de29..1ca930e 100644 --- a/hub_api/config.py +++ b/hub_api/config.py @@ -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 diff --git a/hub_api/construction.py b/hub_api/construction.py index d5be55f..7d371b5 100644 --- a/hub_api/construction.py +++ b/hub_api/construction.py @@ -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) diff --git a/hub_api/docs/openapi-specs.yml b/hub_api/docs/openapi-specs.yml index afa35ab..78be2be 100644 --- a/hub_api/docs/openapi-specs.yml +++ b/hub_api/docs/openapi-specs.yml @@ -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 diff --git a/hub_api/energy_demand.py b/hub_api/energy_demand.py index 9c099fe..5685c3a 100644 --- a/hub_api/energy_demand.py +++ b/hub_api/energy_demand.py @@ -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) diff --git a/hub_api/geometry.py b/hub_api/geometry.py index f63c562..9fdd68d 100644 --- a/hub_api/geometry.py +++ b/hub_api/geometry.py @@ -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 diff --git a/hub_api/greenery.py b/hub_api/greenery.py index 17fb5fc..96dc84e 100644 --- a/hub_api/greenery.py +++ b/hub_api/greenery.py @@ -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)) diff --git a/hub_api/greenery_catalog.py b/hub_api/greenery_catalog.py index 1451626..504b86a 100644 --- a/hub_api/greenery_catalog.py +++ b/hub_api/greenery_catalog.py @@ -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 diff --git a/hub_api/heat_pump.py b/hub_api/heat_pump.py index 19ff78f..54aa214 100644 --- a/hub_api/heat_pump.py +++ b/hub_api/heat_pump.py @@ -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 diff --git a/hub_api/helpers/auth.py b/hub_api/helpers/auth.py index 2c6e141..76fd2bd 100644 --- a/hub_api/helpers/auth.py +++ b/hub_api/helpers/auth.py @@ -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) diff --git a/hub_api/lca.py b/hub_api/lca.py index e2f84e1..b4e516a 100644 --- a/hub_api/lca.py +++ b/hub_api/lca.py @@ -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) diff --git a/hub_api/usage.py b/hub_api/usage.py index f798c83..8d127f7 100644 --- a/hub_api/usage.py +++ b/hub_api/usage.py @@ -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)) diff --git a/hub_api/usage_catalog.py b/hub_api/usage_catalog.py index dfa1ec7..026df07 100644 --- a/hub_api/usage_catalog.py +++ b/hub_api/usage_catalog.py @@ -97,6 +97,7 @@ class ToJson: } return schedule_dictionary + class UsageCatalogEntry(Resource): def __init__(self): pass diff --git a/hub_api/user.py b/hub_api/user.py index 5462594..e2e97b5 100644 --- a/hub_api/user.py +++ b/hub_api/user.py @@ -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 From aa9cc24f29b352d2154a951d41afd96b17dbb8a0 Mon Sep 17 00:00:00 2001 From: Peter Yefi Date: Tue, 17 Jan 2023 19:02:49 -0500 Subject: [PATCH 3/3] Refactored city removal from session --- bootstrap.py | 14 +++++++------- gamification.py | 13 ------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index 79f2d92..0f195b6 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -29,7 +29,7 @@ from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive from hub_api.uptime import Uptime from hub_api.greenery import Greenery from hub_api.user import User, UserLogin -from flasgger import LazyJSONEncoder, LazyString, Swagger +from flasgger import LazyJSONEncoder, Swagger app = flask.Flask('gamification') 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(ConstructionCatalogEntries, '/v1.4/construction-catalog/entries') api.add_resource(ConstructionCatalogNames, '/v1.4/construction-catalog/names') -api.add_resource(Construction, '/v1.4/construction') +api.add_resource(Construction, '/v1.4/construction/') 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') +api.add_resource(EnergyDemand, '/v1.4/energy-demand/') # api.add_resource(LCA, '/v1.4/lca') -api.add_resource(MaterialLCACatalog, '/v1.4/material_lca_catalog/entries') -api.add_resource(MaterialLCACalculations, '/v1.4/material_lca_catalog/calculations') -api.add_resource(HeatPump, '/v1.4/heat-pump') +api.add_resource(MaterialLCACatalog, '/v1.4/material_lca_catalog/entries/') +api.add_resource(MaterialLCACalculations, '/v1.4/material_lca_catalog/calculations/') +api.add_resource(HeatPump, '/v1.4/heat-pump/') api.add_resource(User, '/v1.4/user') api.add_resource(UserLogin, '/v1.4/user/login') api.add_resource(SessionStart, '/v1.4/session/start') api.add_resource(SessionEnd, '/v1.4/session/end') api.add_resource(KeepSessionAlive, '/v1.4/session/keep_alive') -api.add_resource(CityInfo, '/v1.4/city_info') +api.add_resource(CityInfo, '/v1.4/city/') api.add_resource(City, '/v1.4/city') api.add_resource(Greenery, '/v1.4/greenery') diff --git a/gamification.py b/gamification.py index 1599e1e..211fa75 100644 --- a/gamification.py +++ b/gamification.py @@ -53,19 +53,6 @@ WeatherFactory('epw', city, file_name=montreal_weather_file).enrich() city.name = '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("/") def home():