Clean up and define session start message
This commit is contained in:
parent
5f6b7339c4
commit
f39ffedc87
@ -14,11 +14,12 @@ from flask import Response
|
||||
from flask_restful import Api
|
||||
|
||||
import hub_api.helpers.session_helper as sh
|
||||
from hub_api.construction_catalog import ConstructionCatalogEntry, ConstructionCatalogEntries, ConstructionCatalogNames
|
||||
from hub_api.greenery_catalog import GreeneryCatalogEntry, GreeneryCatalogEntries, GreeneryCatalogNames
|
||||
from hub_api.catalogs.construction_catalog import ConstructionCatalogEntry, ConstructionCatalogEntries, ConstructionCatalogNames
|
||||
from hub_api.catalogs.greenery_catalog import GreeneryCatalogEntry, GreeneryCatalogEntries, GreeneryCatalogNames
|
||||
from hub_api.catalogs.usage_catalog import UsageCatalogEntry, UsageCatalogEntries, UsageCatalogNames
|
||||
from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive
|
||||
from hub_api.uptime import Uptime
|
||||
from hub_api.usage_catalog import UsageCatalogEntry, UsageCatalogEntries, UsageCatalogNames
|
||||
|
||||
|
||||
sh.begin_time = datetime.datetime.now()
|
||||
app = flask.Flask('cerc_api')
|
||||
|
1
cache/.gitignore
vendored
1
cache/.gitignore
vendored
@ -1 +0,0 @@
|
||||
!.gitignore
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
8764
data/Montreal.cli
8764
data/Montreal.cli
File diff suppressed because it is too large
Load Diff
8764
data/None.cli
8764
data/None.cli
File diff suppressed because it is too large
Load Diff
BIN
data/dompark.3dm
BIN
data/dompark.3dm
Binary file not shown.
15600
data/dompark.idf
15600
data/dompark.idf
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@
|
||||
,Monthly HP Electricity Demand (kWh),Monthly Fuel Consumption of Auxiliary Heater (m3)
|
||||
Jan,1889256.0,7.91282225
|
||||
Feb,1682865.12,6.06568241
|
||||
Mar,1387834.62,0.068873927
|
||||
Apr,590983.75,-0.0
|
||||
May,0.0,11.7348537
|
||||
Jun,0.0,1.51432967
|
||||
Jul,0.0,0.327990711
|
||||
Aug,0.0,0.534922004
|
||||
Sept,0.0,5.72120714
|
||||
Oct,372460.781,0.000307567185
|
||||
Nov,829471.875,-0.0
|
||||
Dec,1673153.38,2.49482107
|
||||
Total,8426025.526,36.375810449184996
|
|
103532
data/energy+.idd
103532
data/energy+.idd
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
156
hub_api/catalogs/usage_catalog.py
Normal file
156
hub_api/catalogs/usage_catalog.py
Normal file
@ -0,0 +1,156 @@
|
||||
"""
|
||||
Construction catalog
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Project Author name guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from flask import request, Response
|
||||
from flask_restful import Resource
|
||||
|
||||
from hub_api.helpers.session_helper import refresh_session
|
||||
|
||||
|
||||
class ToJson:
|
||||
@staticmethod
|
||||
def usage_to_json(usage):
|
||||
dictionary = {
|
||||
'usage': usage.usage,
|
||||
'hours_day': usage.hours_day,
|
||||
'days_year': usage.days_year,
|
||||
'mechanical_air_change': usage.mechanical_air_change if usage.mechanical_air_change is not None else '',
|
||||
'ventilation_rate': usage.ventilation_rate if usage.ventilation_rate is not None else '',
|
||||
'occupancy': ToJson.occupancy_to_json(usage.occupancy),
|
||||
'lighting': ToJson.lighting_to_json(usage.lighting),
|
||||
'appliances': ToJson.appliances_to_json(usage.appliances),
|
||||
'thermal_control': ToJson.thermal_control_to_json(usage.thermal_control)
|
||||
}
|
||||
return dictionary
|
||||
|
||||
@staticmethod
|
||||
def occupancy_to_json(occupancy):
|
||||
dictionary = {
|
||||
'occupancy_density': occupancy.occupancy_density,
|
||||
'sensible_convective_internal_gain': occupancy.sensible_convective_internal_gain,
|
||||
'sensible_radiative_internal_gain': occupancy.sensible_radiative_internal_gain,
|
||||
'latent_internal_gain': occupancy.latent_internal_gain,
|
||||
'schedules': []
|
||||
}
|
||||
for schedule in occupancy.schedules:
|
||||
dictionary['schedules'].append(ToJson.schedule_to_json(schedule))
|
||||
return dictionary
|
||||
|
||||
@staticmethod
|
||||
def lighting_to_json(lighting):
|
||||
dictionary = {
|
||||
'density': lighting.density,
|
||||
'convective_fraction': lighting.convective_fraction,
|
||||
'radiative_fraction': lighting.radiative_fraction,
|
||||
'latent_fraction': lighting.latent_fraction,
|
||||
'schedules': []
|
||||
}
|
||||
for schedule in lighting.schedules:
|
||||
dictionary['schedules'].append(ToJson.schedule_to_json(schedule))
|
||||
return dictionary
|
||||
|
||||
@staticmethod
|
||||
def appliances_to_json(appliances):
|
||||
dictionary = {
|
||||
'density': appliances.density,
|
||||
'convective_fraction': appliances.convective_fraction,
|
||||
'radiative_fraction': appliances.radiative_fraction,
|
||||
'latent_fraction': appliances.latent_fraction,
|
||||
'schedules': []
|
||||
}
|
||||
for schedule in appliances.schedules:
|
||||
dictionary['schedules'].append(ToJson.schedule_to_json(schedule))
|
||||
return dictionary
|
||||
|
||||
@staticmethod
|
||||
def thermal_control_to_json(thermal_control):
|
||||
dictionary = {
|
||||
'mean_heating_set_point': thermal_control.mean_heating_set_point,
|
||||
'heating_set_back': thermal_control.heating_set_back,
|
||||
'mean_cooling_set_point': thermal_control.mean_cooling_set_point,
|
||||
'hvac_availability_schedules': [],
|
||||
'heating_set_point_schedules': [],
|
||||
'cooling_set_point_schedules': [],
|
||||
}
|
||||
for schedule in thermal_control.hvac_availability_schedules:
|
||||
dictionary['hvac_availability_schedules'].append(ToJson.schedule_to_json(schedule))
|
||||
for schedule in thermal_control.heating_set_point_schedules:
|
||||
dictionary['heating_set_point_schedules'].append(ToJson.schedule_to_json(schedule))
|
||||
for schedule in thermal_control.cooling_set_point_schedules:
|
||||
dictionary['cooling_set_point_schedules'].append(ToJson.schedule_to_json(schedule))
|
||||
return dictionary
|
||||
|
||||
@staticmethod
|
||||
def schedule_to_json(schedule):
|
||||
schedule_dictionary = {
|
||||
'type': schedule.type,
|
||||
'data_type': schedule.data_type,
|
||||
'time_step': schedule.time_step,
|
||||
'time_range': schedule.time_range,
|
||||
'day_types': schedule.day_types,
|
||||
'values': schedule.values,
|
||||
}
|
||||
return schedule_dictionary
|
||||
|
||||
|
||||
class UsageCatalogEntry(Resource):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def post():
|
||||
session = refresh_session(request)
|
||||
if session is None:
|
||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
||||
headers = session.headers
|
||||
catalog = session.usage_catalog
|
||||
|
||||
name = None
|
||||
if request.data == b'' or request.json['name'] is None:
|
||||
response = {'error': 'Mandatory parameter "name" is missing'}
|
||||
return Response(json.dumps(response), headers=headers, status=400)
|
||||
try:
|
||||
name = request.json['name']
|
||||
entry = catalog.get_entry(name)
|
||||
output = {'usages': [ToJson.usage_to_json(entry)]}
|
||||
return Response(json.dumps(output), headers=headers)
|
||||
except IndexError:
|
||||
response = {'error': f'Name "{name}" unknown'}
|
||||
return Response(json.dumps(response), headers=headers, status=400)
|
||||
|
||||
|
||||
class UsageCatalogEntries(Resource):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def post():
|
||||
session = refresh_session(request)
|
||||
if session is None:
|
||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
||||
headers = session.headers
|
||||
catalog = session.usage_catalog
|
||||
output = {'usages': []}
|
||||
content = catalog.entries()
|
||||
for usage in content.usages:
|
||||
output['usages'].append(ToJson.usage_to_json(usage))
|
||||
return Response(json.dumps(output), headers=headers)
|
||||
|
||||
|
||||
class UsageCatalogNames(Resource):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def post():
|
||||
session = refresh_session(request)
|
||||
if session is None:
|
||||
return Response(json.dumps({'error': 'invalid session'}), status=401)
|
||||
headers = session.headers
|
||||
catalog = session.usage_catalog
|
||||
return Response(json.dumps(catalog.names()), headers=headers)
|
@ -24,13 +24,69 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Uptime'
|
||||
$ref: '#/components/schemas/uptime'
|
||||
/v1.4/session/start:
|
||||
put:
|
||||
parameters:
|
||||
- in: header
|
||||
name: username
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the application username accessing this API
|
||||
- in: header
|
||||
name: password
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the password for the user accessing this API
|
||||
- in: header
|
||||
name: application_id
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: the Id of the application accessing this API
|
||||
tags:
|
||||
- Session start
|
||||
summary: Starts an user session
|
||||
operationId: session_start
|
||||
description: Authenticate and initialize an user session in the api
|
||||
responses:
|
||||
'200':
|
||||
description: Authorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/authorized'
|
||||
'403':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/unauthorized'
|
||||
components:
|
||||
schemas:
|
||||
Uptime:
|
||||
uptime:
|
||||
type: object
|
||||
properties:
|
||||
uptime:
|
||||
type: string
|
||||
format: hh:mm:ss.ms
|
||||
example: "00:09:53.600281"
|
||||
authorized:
|
||||
type: object
|
||||
properties:
|
||||
session_id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "ad0720ed-0f31-4f3e-9686-1177d4624ec1"
|
||||
token:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "660d1aa0-d24f-4cb1-902d-13c7bd29793c"
|
||||
unauthorized:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
example: "unauthorized"
|
@ -1,396 +0,0 @@
|
||||
import json
|
||||
from flask import request, Response
|
||||
from flask_restful import Resource
|
||||
from pathlib import Path
|
||||
from geomeppy import IDF
|
||||
import os
|
||||
import glob
|
||||
import hub_api.helpers.session_helper as sh
|
||||
import hub.helpers.constants as cte
|
||||
import csv
|
||||
from hub_api.helpers.auth import role_required
|
||||
from hub.persistence.models import UserRoles
|
||||
from hub_api.config import Config
|
||||
|
||||
|
||||
class EnergyDemand(Resource, Config):
|
||||
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
|
||||
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
|
||||
_SURFACE = 'BUILDINGSURFACE:DETAILED'
|
||||
_WINDOW_SURFACE = 'FENESTRATIONSURFACE:DETAILED'
|
||||
_CONSTRUCTION = 'CONSTRUCTION'
|
||||
_MATERIAL = 'MATERIAL'
|
||||
_MATERIAL_NOMASS = 'MATERIAL:NOMASS'
|
||||
_ROUGHNESS = 'MediumRough'
|
||||
_HOURLY_SCHEDULE = 'SCHEDULE:DAY:HOURLY'
|
||||
_COMPACT_SCHEDULE = 'SCHEDULE:COMPACT'
|
||||
_FILE_SCHEDULE = 'SCHEDULE:FILE'
|
||||
_ZONE = 'ZONE'
|
||||
_LIGHTS = 'LIGHTS'
|
||||
_PEOPLE = 'PEOPLE'
|
||||
_APPLIANCES = 'OTHEREQUIPMENT'
|
||||
_HEATING_COOLING = 'THERMOSTATSETPOINT:DUALSETPOINT'
|
||||
_INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE'
|
||||
_BUILDING_SURFACE = 'BuildingSurfaceDetailed'
|
||||
_SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS'
|
||||
_ON_OFF = 'On/Off'
|
||||
_FRACTION = 'Fraction'
|
||||
_ANY_NUMBER = 'Any Number'
|
||||
_CONTINUOUS = 'Continuous'
|
||||
_DISCRETE = 'Discrete'
|
||||
_BUILDING = 'BUILDING'
|
||||
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
|
||||
_LOCATION = 'SITE:LOCATION'
|
||||
_WINDOW_MATERIAL_SIMPLE = 'WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM'
|
||||
_WINDOW = 'WINDOW'
|
||||
_MATERIAL_ROOFVEGETATION = 'MATERIAL:ROOFVEGETATION'
|
||||
_SIMPLE = 'Simple'
|
||||
|
||||
idf_surfaces = {
|
||||
# todo: make an enum for all the surface types
|
||||
cte.WALL: 'wall',
|
||||
cte.GROUND: 'floor',
|
||||
cte.ROOF: 'roof'
|
||||
}
|
||||
idf_usage = {
|
||||
# todo: make an enum for all the usage types
|
||||
cte.RESIDENTIAL: 'residential_building'
|
||||
}
|
||||
idf_type_limits = {
|
||||
cte.ON_OFF: 'on/off',
|
||||
cte.FRACTION: 'Fraction',
|
||||
cte.ANY_NUMBER: 'Any Number',
|
||||
cte.CONTINUOUS: 'Continuous',
|
||||
cte.DISCRETE: 'Discrete',
|
||||
cte.TEMPERATURE: 'Any Number'
|
||||
}
|
||||
idf_day_types = {
|
||||
cte.MONDAY: 'Monday',
|
||||
cte.TUESDAY: 'Tuesday',
|
||||
cte.WEDNESDAY: 'Wednesday',
|
||||
cte.THURSDAY: 'Thursday',
|
||||
cte.FRIDAY: 'Friday',
|
||||
cte.SATURDAY: 'Saturday',
|
||||
cte.SUNDAY: 'Sunday',
|
||||
cte.HOLIDAY: 'Holidays',
|
||||
cte.WINTER_DESIGN_DAY: 'WinterDesignDay',
|
||||
cte.SUMMER_DESIGN_DAY: 'SummerDesignDay'
|
||||
}
|
||||
idf_schedule_types = {
|
||||
'compact': 'Compact',
|
||||
cte.DAY: 'Day',
|
||||
cte.WEEK: 'Week',
|
||||
cte.YEAR: 'Year',
|
||||
'file': 'File'
|
||||
}
|
||||
idf_schedule_data_type = {
|
||||
'compact': 'Compact',
|
||||
'hourly': 'Hourly',
|
||||
'daily': 'Daily',
|
||||
'interval': 'Interval',
|
||||
'list': 'List',
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
# this class is mostly hardcoded, as is intended to be used only for Dompark project,
|
||||
# other projects should use the normal idf workflow instead.
|
||||
super().__init__()
|
||||
self._output_path = Path(Path(__file__).parent.parent / 'tmp').resolve()
|
||||
self._data_path = Path(Path(__file__).parent.parent / 'data').resolve()
|
||||
self._city = None
|
||||
self._greenery_percentage = 0
|
||||
|
||||
def _set_layers(self, _idf, name, layers, vegetation=None):
|
||||
if vegetation is not None:
|
||||
_kwargs = {'Name': name, 'Outside_Layer': vegetation.name}
|
||||
for i in range(0, len(layers)):
|
||||
_kwargs[f'Layer_{i + 2}'] = layers[i].material.name
|
||||
else:
|
||||
_kwargs = {'Name': name, 'Outside_Layer': layers[0].material.name}
|
||||
for i in range(1, len(layers)):
|
||||
_kwargs[f'Layer_{i + 1}'] = layers[i].material.name
|
||||
_idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||
|
||||
def _update_constructions(self, _idf, ground, roof, wall, vegetation):
|
||||
for construction in _idf.idfobjects[self._CONSTRUCTION]:
|
||||
if construction.Name == 'Project ground floor':
|
||||
# floor
|
||||
self._set_layers(_idf, 'user_floor', ground)
|
||||
elif construction.Name == 'Dompark Roof':
|
||||
# roof
|
||||
self._set_layers(_idf, 'user_roof', roof)
|
||||
elif construction.Name == 'Dompark Roof Vegetation':
|
||||
# roof
|
||||
self._set_layers(_idf, 'user_roof_vegetation', roof, vegetation)
|
||||
elif construction.Name == 'Dompark Wall':
|
||||
# wall
|
||||
self._set_layers(_idf, 'user_wall', wall)
|
||||
else:
|
||||
continue
|
||||
for surface in _idf.idfobjects[self._SURFACE]:
|
||||
if surface.Construction_Name == 'Project ground floor':
|
||||
# floor
|
||||
surface.Construction_Name = 'user_floor'
|
||||
elif surface.Construction_Name == 'Dompark Wall':
|
||||
# wall
|
||||
surface.Construction_Name = 'user_wall'
|
||||
elif surface.Construction_Name == 'Dompark Roof' or surface.Construction_Name == 'Dompark Roof Vegetation':
|
||||
# roof
|
||||
surface.Construction_Name = 'user_roof'
|
||||
if self._greenery_percentage > 0:
|
||||
if surface.Name in sh.roofs_associated_to_percentage[str(self._greenery_percentage)]:
|
||||
surface.Construction_Name = 'user_roof_vegetation'
|
||||
else:
|
||||
continue
|
||||
|
||||
for window in _idf.idfobjects[self._WINDOW_SURFACE]:
|
||||
window.Construction_Name = 'window_construction_1'
|
||||
|
||||
def _add_material(self, _idf, layers):
|
||||
for layer in layers:
|
||||
for material in _idf.idfobjects[self._MATERIAL]:
|
||||
if material.Name == layer.material.name:
|
||||
return
|
||||
for material in _idf.idfobjects[self._MATERIAL_NOMASS]:
|
||||
if material.Name == layer.material.name:
|
||||
return
|
||||
if str(layer.material.no_mass) == 'True':
|
||||
_idf.newidfobject(self._MATERIAL_NOMASS,
|
||||
Name=layer.material.name,
|
||||
Roughness=self._ROUGHNESS,
|
||||
Thermal_Resistance=layer.material.thermal_resistance,
|
||||
Thermal_Absorptance=layer.material.thermal_absorptance,
|
||||
Solar_Absorptance=layer.material.solar_absorptance,
|
||||
Visible_Absorptance=layer.material.visible_absorptance
|
||||
)
|
||||
else:
|
||||
_idf.newidfobject(self._MATERIAL,
|
||||
Name=layer.material.name,
|
||||
Roughness=self._ROUGHNESS,
|
||||
Thickness=layer.thickness,
|
||||
Conductivity=layer.material.conductivity,
|
||||
Density=layer.material.density,
|
||||
Specific_Heat=layer.material.specific_heat,
|
||||
Thermal_Absorptance=layer.material.thermal_absorptance,
|
||||
Solar_Absorptance=layer.material.solar_absorptance,
|
||||
Visible_Absorptance=layer.material.visible_absorptance
|
||||
)
|
||||
|
||||
def _add_vegetation_material(self, _idf, vegetation):
|
||||
for vegetation_material in _idf.idfobjects[self._MATERIAL_ROOFVEGETATION]:
|
||||
if vegetation_material.Name == vegetation.name:
|
||||
return
|
||||
soil = vegetation.soil
|
||||
height = 0
|
||||
leaf_area_index = 0
|
||||
leaf_reflectivity = 0
|
||||
leaf_emissivity = 0
|
||||
minimal_stomatal_resistance = 0
|
||||
for plant in vegetation.plants:
|
||||
percentage = float(plant.percentage) / 100
|
||||
height += percentage * float(plant.height)
|
||||
leaf_area_index += percentage * float(plant.leaf_area_index)
|
||||
leaf_reflectivity += percentage * float(plant.leaf_reflectivity)
|
||||
leaf_emissivity += percentage * float(plant.leaf_emissivity)
|
||||
minimal_stomatal_resistance += percentage * float(plant.minimal_stomatal_resistance)
|
||||
_idf.newidfobject(self._MATERIAL_ROOFVEGETATION,
|
||||
Name=vegetation.name,
|
||||
Height_of_Plants=height,
|
||||
Leaf_Area_Index=leaf_area_index,
|
||||
Leaf_Reflectivity=leaf_reflectivity,
|
||||
Leaf_Emissivity=leaf_emissivity,
|
||||
Minimum_Stomatal_Resistance=minimal_stomatal_resistance,
|
||||
Soil_Layer_Name=soil.name,
|
||||
Roughness=soil.roughness,
|
||||
Thickness=vegetation.soil_thickness,
|
||||
Conductivity_of_Dry_Soil=soil.dry_conductivity,
|
||||
Density_of_Dry_Soil=soil.dry_density,
|
||||
Specific_Heat_of_Dry_Soil=soil.dry_specific_heat,
|
||||
Thermal_Absorptance=soil.thermal_absorptance,
|
||||
Solar_Absorptance=soil.solar_absorptance,
|
||||
Visible_Absorptance=soil.visible_absorptance,
|
||||
Saturation_Volumetric_Moisture_Content_of_the_Soil_Layer=
|
||||
soil.saturation_volumetric_moisture_content,
|
||||
Residual_Volumetric_Moisture_Content_of_the_Soil_Layer=
|
||||
soil.residual_volumetric_moisture_content,
|
||||
Initial_Volumetric_Moisture_Content_of_the_Soil_Layer=
|
||||
soil.initial_volumetric_moisture_content,
|
||||
Moisture_Diffusion_Calculation_Method=self._SIMPLE
|
||||
)
|
||||
|
||||
def _add_window_construction_and_material(self, _idf, window):
|
||||
name = 'glazing_1'
|
||||
_kwargs = {'Name': name, 'UFactor': window.overall_u_value,
|
||||
'Solar_Heat_Gain_Coefficient': window.g_value}
|
||||
_idf.newidfobject(self._WINDOW_MATERIAL_SIMPLE, **_kwargs)
|
||||
window_construction_name = 'window_construction_1'
|
||||
_kwargs = {'Name': window_construction_name, 'Outside_Layer': name}
|
||||
return _idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||
|
||||
def _add_materials(self, _idf):
|
||||
building = self._city.buildings[0]
|
||||
ground_surface = building.grounds[0]
|
||||
roof_surface = building.roofs[0]
|
||||
wall_surface = building.walls[0]
|
||||
internal_zone = building.internal_zones[0]
|
||||
thermal_zone = internal_zone.thermal_zones[0]
|
||||
ground = None
|
||||
roof = None
|
||||
roof_vegetation = None
|
||||
wall = None
|
||||
window = None
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
if thermal_boundary.parent_surface.id == wall_surface.id:
|
||||
wall = thermal_boundary.layers
|
||||
if thermal_boundary.parent_surface.id == roof_surface.id:
|
||||
roof = thermal_boundary.layers
|
||||
roof_vegetation = thermal_boundary.vegetation
|
||||
if thermal_boundary.parent_surface.id == ground_surface.id:
|
||||
ground = thermal_boundary.layers
|
||||
if thermal_boundary.thermal_openings is not None and len(thermal_boundary.thermal_openings) > 0:
|
||||
window = thermal_boundary.thermal_openings[0]
|
||||
if ground is not None and roof is not None and wall is not None and window is not None:
|
||||
# we have all the needed surfaces type
|
||||
break
|
||||
self._add_material(_idf, ground)
|
||||
self._add_material(_idf, roof)
|
||||
self._add_material(_idf, wall)
|
||||
if roof_vegetation is not None:
|
||||
self._add_vegetation_material(_idf, roof_vegetation)
|
||||
self._update_constructions(_idf, ground, roof, wall, roof_vegetation)
|
||||
self._add_window_construction_and_material(_idf, window)
|
||||
|
||||
def _add_standard_compact_hourly_schedule(self, _idf, schedule_name, schedules):
|
||||
_kwargs = {'Name': f'{schedule_name}',
|
||||
'Schedule_Type_Limits_Name': self.idf_type_limits[schedules[0].data_type],
|
||||
'Field_1': 'Through: 12/31'}
|
||||
for j, schedule in enumerate(schedules):
|
||||
_val = schedule.values
|
||||
_new_field = ''
|
||||
for day_type in schedule.day_types:
|
||||
_new_field += f' {self.idf_day_types[day_type]}'
|
||||
_kwargs[f'Field_{j * 25 + 2}'] = f'For:{_new_field}'
|
||||
for i in range(0, len(_val)):
|
||||
_kwargs[f'Field_{j * 25 + 3 + i}'] = f'Until: {i + 1:02d}:00,{_val[i]}'
|
||||
_idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
|
||||
|
||||
def _add_schedules(self, _idf, thermal_zone):
|
||||
self._add_standard_compact_hourly_schedule(_idf, 'user_occupancy_schedule',
|
||||
thermal_zone.occupancy.occupancy_schedules)
|
||||
self._add_standard_compact_hourly_schedule(_idf, 'user_lighting_schedule',
|
||||
thermal_zone.lighting.schedules)
|
||||
self._add_standard_compact_hourly_schedule(_idf, 'user_appliances_schedule',
|
||||
thermal_zone.appliances.schedules)
|
||||
self._add_standard_compact_hourly_schedule(_idf, 'user_heating_schedule',
|
||||
thermal_zone.thermal_control.heating_set_point_schedules)
|
||||
self._add_standard_compact_hourly_schedule(_idf, 'user_cooling_schedule',
|
||||
thermal_zone.thermal_control.cooling_set_point_schedules)
|
||||
|
||||
def _add_usage(self, _idf):
|
||||
_thermal_zone = None
|
||||
for building in self._city.buildings:
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
_thermal_zone = thermal_zone
|
||||
# Dompark project share schedules and usages among all the buildings so we could add just the first one
|
||||
break
|
||||
break
|
||||
break
|
||||
self._add_schedules(_idf, _thermal_zone)
|
||||
fraction_radiant = _thermal_zone.occupancy.sensible_radiative_internal_gain / \
|
||||
(_thermal_zone.occupancy.sensible_radiative_internal_gain +
|
||||
_thermal_zone.occupancy.sensible_convective_internal_gain)
|
||||
for idf_object in _idf.idfobjects[self._PEOPLE]:
|
||||
idf_object['Number_of_People_Schedule_Name'] = 'user_occupancy_schedule'
|
||||
idf_object['People_per_Zone_Floor_Area'] = _thermal_zone.occupancy.occupancy_density
|
||||
idf_object['Fraction_Radiant'] = fraction_radiant
|
||||
|
||||
for idf_object in _idf.idfobjects[self._LIGHTS]:
|
||||
idf_object['Schedule_Name'] = 'user_lighting_schedule'
|
||||
idf_object['Watts_per_Zone_Floor_Area'] = _thermal_zone.lighting.density
|
||||
for idf_object in _idf.idfobjects[self._APPLIANCES]:
|
||||
idf_object['Schedule_Name'] = 'user_appliances_schedule'
|
||||
idf_object['Power_per_Zone_Floor_Area'] = _thermal_zone.appliances.density
|
||||
for idf_object in _idf.idfobjects[self._HEATING_COOLING]:
|
||||
idf_object['Heating_Setpoint_Temperature_Schedule_Name'] = 'user_heating_schedule'
|
||||
idf_object['Cooling_Setpoint_Temperature_Schedule_Name'] = 'user_cooling_schedule'
|
||||
|
||||
return
|
||||
|
||||
@role_required([UserRoles.Admin.value, UserRoles.Hub_Reader.value])
|
||||
def get(self, city_id):
|
||||
payload = request.get_json()
|
||||
self._city = self.get_city(city_id)
|
||||
|
||||
self._greenery_percentage = round(float(payload['greenery_percentage']) / 10) * 10
|
||||
output_file = str((self._output_path / 'dompark.idf').resolve())
|
||||
idd_file = str((self._data_path / 'energy+.idd').resolve())
|
||||
epw_file = str((self._data_path / 'CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve())
|
||||
idf_file = str((self._data_path / 'dompark.idf').resolve())
|
||||
IDF.setiddname(idd_file)
|
||||
_idf = IDF(idf_file, epw_file)
|
||||
self._add_materials(_idf)
|
||||
self._add_usage(_idf)
|
||||
_idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
_idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
# From EnergyPlus documentation: Lights Electric Energy [J]
|
||||
# The lighting electrical consumption including ballasts, if present. These will have the same value as Lights
|
||||
# Total Heating Energy (above).
|
||||
_idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Lights Total Heating Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
_idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Other Equipment Total Heating Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
|
||||
_idf.match()
|
||||
_idf.saveas(str(output_file))
|
||||
_idf.run(expandobjects=True, readvars=True, output_directory=self._output_path, output_prefix='dompark_')
|
||||
# Todo: set the heating and cooling
|
||||
heating = []
|
||||
cooling = []
|
||||
lighting = []
|
||||
appliances = []
|
||||
with open((self._output_path / f'dompark_out.csv').resolve()) as f:
|
||||
reader = csv.reader(f, delimiter=',')
|
||||
for row in reader:
|
||||
if '00:00' in row[0]:
|
||||
cooling_value = 0.0
|
||||
heating_value = 0.0
|
||||
lighting_value = 0.0
|
||||
appliances_value = 0.0
|
||||
for i in range(1, 38):
|
||||
lighting_value += float(row[i])
|
||||
for i in range(38, 73):
|
||||
appliances_value += float(row[i])
|
||||
for i in range(73, 133, 2):
|
||||
heating_value += float(row[i])
|
||||
cooling_value += float(row[i + 1])
|
||||
cooling.append(cooling_value)
|
||||
heating.append(heating_value)
|
||||
lighting.append(lighting_value)
|
||||
appliances.append(appliances_value)
|
||||
|
||||
files = glob.glob(f'{self._output_path}/dompark*')
|
||||
for file in files:
|
||||
os.remove(file)
|
||||
continue
|
||||
|
||||
response = {'heating_demand': heating,
|
||||
'cooling_demand': cooling,
|
||||
'lighting_demand': lighting,
|
||||
'appliances_demand': appliances
|
||||
}
|
||||
return Response(json.dumps(response), status=200)
|
@ -1,76 +0,0 @@
|
||||
"""
|
||||
HeatPump Service
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2023 Project Author Peter Yefi peteryefi@gmail.com
|
||||
"""
|
||||
import json
|
||||
from flask import Response, request
|
||||
from flask_restful import Resource
|
||||
from hub.hub_logger import logger
|
||||
from hub_api.helpers.auth import generate_auth_token, role_required
|
||||
from hub.persistence.models import UserRoles
|
||||
from hub_api.config import Config
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
|
||||
class User(Resource, Config):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@role_required([UserRoles.Admin.value])
|
||||
def post(self):
|
||||
try:
|
||||
payload = request.get_json()
|
||||
user = self.user_factory.create_user(name=payload["name"], email=payload["email"], password=payload["password"],
|
||||
role=payload["role"])
|
||||
if type(user) is dict:
|
||||
return Response(response=json.dumps(user), status=400, headers=headers)
|
||||
return Response(response=json.dumps({'user': {'id': user.id, 'name': user.name, 'email': user.email,
|
||||
'password': user.password, 'role': user.role.value}}), status=201,
|
||||
headers=headers)
|
||||
except Exception as err:
|
||||
logger.error(err)
|
||||
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating user'}), status=400,
|
||||
headers=headers)
|
||||
|
||||
@role_required([UserRoles.Admin.value])
|
||||
def put(self):
|
||||
try:
|
||||
payload = request.get_json()
|
||||
res = self.user_factory.update_user(user_id=payload['id'], name=payload['name'], password=payload['password'],
|
||||
role=payload['role'], email=payload['email'])
|
||||
if res:
|
||||
return Response(response=json.dumps(res), status=400, headers=headers)
|
||||
return Response(response=json.dumps({'success': 'user updated successfully'}), status=200, headers=headers)
|
||||
except Exception as err:
|
||||
logger.error(err)
|
||||
return Response(response=json.dumps({'err_msg': 'Sorry, an error occurred while updating user'}),
|
||||
status=400, headers=headers)
|
||||
|
||||
|
||||
class UserLogin(Resource, Config):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def post(self):
|
||||
try:
|
||||
payload = request.get_json()
|
||||
user = self.ex_user_factory.login_user(email=payload["email"], password=payload["password"])
|
||||
if type(user) is dict:
|
||||
return Response(response=json.dumps(user), status=400, headers=headers)
|
||||
user = user[0]
|
||||
user_dict = {
|
||||
'user': {
|
||||
'id': user.id,
|
||||
'name': user.name,
|
||||
'email': user.email,
|
||||
'password': user.password,
|
||||
'role': user.role.value,
|
||||
}
|
||||
}
|
||||
user_dict['token'] = generate_auth_token(user_dict)
|
||||
return Response(response=json.dumps(user_dict), status=200, headers=headers)
|
||||
except Exception as err:
|
||||
logger.error(err)
|
||||
return Response(response=json.dumps({'err_msg': 'An error occurred while authenticating user'}), status=400)
|
@ -25,3 +25,5 @@ jwt==1.3.1
|
||||
flagger==3.1.0
|
||||
flasgger
|
||||
cerc-hub
|
||||
python-dotenv
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user