Merge remote-tracking branch 'origin/main' into Transys

This commit is contained in:
Guille Gutierrez 2024-01-22 05:50:18 +01:00
commit 60f329ef4f
17 changed files with 52021 additions and 1404 deletions

View File

@ -8,6 +8,8 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
import json
import urllib.request
from pathlib import Path
import xmltodict
import hub.helpers.constants as cte
@ -28,12 +30,11 @@ class NrcanCatalog(Catalog):
Nrcan catalog class
"""
def __init__(self, path):
path = str(path / 'nrcan.xml')
self._schedules_path = Path(path / 'nrcan_schedules.json').resolve()
self._space_types_path = Path(path / 'nrcan_space_types.json').resolve()
self._space_compliance_path = Path(path / 'nrcan_space_compliance_2015.json').resolve()
self._content = None
self._schedules = {}
with open(path, 'r', encoding='utf-8') as xml:
self._metadata = xmltodict.parse(xml.read())
self._base_url = self._metadata['nrcan']['@base_url']
self._load_schedules()
self._content = Content(self._load_archetypes())
@ -55,11 +56,9 @@ class NrcanCatalog(Catalog):
return Schedule(hub_type, raw['values'], data_type, time_step, time_range, day_types)
def _load_schedules(self):
usage = self._metadata['nrcan']
url = f'{self._base_url}{usage["schedules"]}'
_schedule_types = []
with urllib.request.urlopen(url) as json_file:
schedules_type = json.load(json_file)
with open(self._schedules_path, 'r') as f:
schedules_type = json.load(f)
for schedule_type in schedules_type['tables']['schedules']['table']:
schedule = NrcanCatalog._extract_schedule(schedule_type)
if schedule_type['name'] not in _schedule_types:
@ -80,14 +79,11 @@ class NrcanCatalog(Catalog):
def _load_archetypes(self):
usages = []
name = self._metadata['nrcan']
url_1 = f'{self._base_url}{name["space_types"]}'
url_2 = f'{self._base_url}{name["space_types_compliance"]}'
with urllib.request.urlopen(url_1) as json_file:
space_types = json.load(json_file)['tables']['space_types']['table']
with open(self._space_types_path, 'r') as f:
space_types = json.load(f)['tables']['space_types']['table']
space_types = [st for st in space_types if st['space_type'] == 'WholeBuilding']
with urllib.request.urlopen(url_2) as json_file:
space_types_compliance = json.load(json_file)['tables']['space_compliance']['table']
with open(self._space_compliance_path, 'r') as f:
space_types_compliance = json.load(f)['tables']['space_compliance']['table']
space_types_compliance = [st for st in space_types_compliance if st['space_type'] == 'WholeBuilding']
space_types_dictionary = {}
for space_type in space_types_compliance:

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<nrcan base_url="https://raw.githubusercontent.com/NREL/openstudio-standards/master/lib/openstudio-standards/standards/necb/">
<space_types>NECB2015/data/space_types.json</space_types>
<space_types_compliance>NECB2015/qaqc/qaqc_data/space_compliance_2015.json</space_types_compliance>>
<schedules>NECB2015/data/schedules.json</schedules>
</nrcan>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -276,11 +276,11 @@ class Idf:
self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
def _write_schedules_file(self, usage, schedule):
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.dat').resolve())
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.csv').resolve())
with open(file_name, 'w', encoding='utf8') as file:
for value in schedule.values:
file.write(f'{str(value)},\n')
return file_name
return Path(file_name).name
def _add_file_schedule(self, usage, schedule, file_name):
_schedule = self._idf.newidfobject(self._FILE_SCHEDULE, Name=f'{schedule.type} schedules {usage}')
@ -403,7 +403,7 @@ class Idf:
self._idf.newidfobject(self._PEOPLE,
Name=f'{zone_name}_occupancy',
Zone_or_ZoneList_Name=zone_name,
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage_name}',
Number_of_People_Calculation_Method="People",
Number_of_People=number_of_people,
@ -420,7 +420,7 @@ class Idf:
self._idf.newidfobject(self._LIGHTS,
Name=f'{zone_name}_lights',
Zone_or_ZoneList_Name=zone_name,
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
Schedule_Name=f'Lighting schedules {thermal_zone.usage_name}',
Design_Level_Calculation_Method=method,
Watts_per_Zone_Floor_Area=watts_per_zone_floor_area,
@ -439,7 +439,7 @@ class Idf:
self._idf.newidfobject(self._APPLIANCES,
Fuel_Type=fuel_type,
Name=f'{zone_name}_appliance',
Zone_or_ZoneList_Name=zone_name,
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
Schedule_Name=f'Appliance schedules {thermal_zone.usage_name}',
Design_Level_Calculation_Method=method,
Power_per_Zone_Floor_Area=watts_per_zone_floor_area,
@ -453,7 +453,7 @@ class Idf:
_infiltration = thermal_zone.infiltration_rate_system_off * cte.HOUR_TO_SECONDS
self._idf.newidfobject(self._INFILTRATION,
Name=f'{zone_name}_infiltration',
Zone_or_ZoneList_Name=zone_name,
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
Schedule_Name=schedule,
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
Air_Changes_per_Hour=_infiltration
@ -464,7 +464,7 @@ class Idf:
_air_change = thermal_zone.mechanical_air_change * cte.HOUR_TO_SECONDS
self._idf.newidfobject(self._VENTILATION,
Name=f'{zone_name}_ventilation',
Zone_or_ZoneList_Name=zone_name,
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
Schedule_Name=schedule,
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
Air_Changes_per_Hour=_air_change
@ -627,8 +627,8 @@ class Idf:
self._idf.intersect_match()
def _add_shading(self, building):
for surface in building.surfaces:
shading = self._idf.newidfobject(self._SHADING, Name=f'{surface.name}')
for i, surface in enumerate(building.surfaces):
shading = self._idf.newidfobject(self._SHADING, Name=f'{building.name}_{i}')
coordinates = self._matrix_to_list(surface.solid_polygon.coordinates,
self._city.lower_corner)
shading.setcoords(coordinates)
@ -636,17 +636,17 @@ class Idf:
if solar_reflectance is None:
solar_reflectance = ConfigurationHelper().short_wave_reflectance
self._idf.newidfobject(self._SHADING_PROPERTY,
Shading_Surface_Name=f'{surface.name}',
Shading_Surface_Name=f'{building.name}_{i}',
Diffuse_Solar_Reflectance_of_Unglazed_Part_of_Shading_Surface=solar_reflectance,
Fraction_of_Shading_Surface_That_Is_Glazed=0)
def _add_pure_geometry(self, building, zone_name):
for surface in building.surfaces:
for index, surface in enumerate(building.surfaces):
outside_boundary_condition = 'Outdoors'
sun_exposure = 'SunExposed'
wind_exposure = 'WindExposed'
idf_surface_type = self.idf_surfaces[surface.type]
_kwargs = {'Name': f'{surface.name}',
_kwargs = {'Name': f'Building_{building.name}_surface_{index}',
'Surface_Type': idf_surface_type,
'Zone_Name': zone_name}
if surface.type == cte.GROUND:
@ -655,7 +655,7 @@ class Idf:
wind_exposure = 'NoWind'
if surface.percentage_shared is not None and surface.percentage_shared > 0.5:
outside_boundary_condition = 'Surface'
outside_boundary_condition_object = surface.name
outside_boundary_condition_object = f'Building_{building.name}_surface_{index}'
sun_exposure = 'NoSun'
wind_exposure = 'NoWind'
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
@ -680,12 +680,12 @@ class Idf:
def _add_surfaces(self, building, zone_name):
for thermal_zone in building.thermal_zones_from_internal_zones:
for boundary in thermal_zone.thermal_boundaries:
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
outside_boundary_condition = 'Outdoors'
sun_exposure = 'SunExposed'
wind_exposure = 'WindExposed'
_kwargs = {'Name': f'{boundary.parent_surface.name}',
_kwargs = {'Name': f'Building_{building.name}_surface_{index}',
'Surface_Type': idf_surface_type,
'Zone_Name': zone_name}
if boundary.parent_surface.type == cte.GROUND:
@ -694,7 +694,7 @@ class Idf:
wind_exposure = 'NoWind'
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5:
outside_boundary_condition = 'Surface'
outside_boundary_condition_object = boundary.parent_surface.name
outside_boundary_condition_object = f'Building_{building.name}_surface_{index}'
sun_exposure = 'NoSun'
wind_exposure = 'NoWind'
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
! HVAC: None.
!
Version,9.5;
Version,23.2;
Timestep,4;
@ -127,31 +127,25 @@
No, !- Do HVAC Sizing Simulation for Sizing Periods
1; !- Maximum Number of HVAC Sizing Simulation Passes
Output:Table:SummaryReports, AnnualBuildingUtilityPerformanceSummary,
DemandEndUseComponentsSummary,
SensibleHeatGainSummary,
InputVerificationandResultsSummary,
AdaptiveComfortSummary,
Standard62.1Summary,
ClimaticDataSummary,
EquipmentSummary,
EnvelopeSummary,
LightingSummary,
HVACSizingSummary,
SystemSummary,
ComponentSizingSummary,
OutdoorAirSummary,
ObjectCountSummary,
EndUseEnergyConsumptionOtherFuelsMonthly,
PeakEnergyEndUseOtherFuelsMonthly;
Output:VariableDictionary,Regular;
Output:Variable,*,Site Outdoor Air Drybulb Temperature,Timestep;
OutputControl:Table:Style, CommaAndHTML,JtoKWH;
Output:Variable,*,Site Outdoor Air Wetbulb Temperature,Timestep;
Output:Meter,DISTRICTHEATING:Facility,hourly;
Output:Meter,DISTRICTCOOLING:Facility,hourly;
Output:Meter,InteriorEquipment:Electricity,hourly;
Output:Meter,InteriorLights:Electricity,hourly;
Output:Variable,*,Site Outdoor Air Dewpoint Temperature,Timestep;
Output:Variable,*,Site Solar Azimuth Angle,Timestep;
Output:Variable,*,Site Solar Altitude Angle,Timestep;
Output:Variable,*,Site Direct Solar Radiation Rate per Area,Timestep;
Output:Variable,*,Site Diffuse Solar Radiation Rate per Area,Timestep;
OutputControl:Table:Style,
HTML; !- Column Separator
Output:Table:SummaryReports,
AllSummary; !- Report 1 Name
OutputControl:IlluminanceMap:Style,
Comma; !- Column separator

View File

@ -213,8 +213,6 @@ class Geojson:
polygon = Polygon(coordinates)
polygon.area = igh.ground_area(coordinates)
surfaces[-1] = Surface(polygon, polygon)
if len(surfaces) > 1:
raise ValueError('too many surfaces!!!!')
building = Building(f'{building_name}', surfaces, year_of_construction, function)
for alias in building_aliases:
building.add_alias(alias)

View File

@ -70,7 +70,7 @@ class InselMonthlyEnergyBalance:
total_day += value
for day_type in schedule.day_types:
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
* lighting_density / cte.WATTS_HOUR_TO_JULES
* lighting_density * cte.WATTS_HOUR_TO_JULES
lighting_demand.append(total_lighting * area)
for schedule in thermal_zone.appliances.schedules:
@ -79,7 +79,7 @@ class InselMonthlyEnergyBalance:
total_day += value
for day_type in schedule.day_types:
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
* appliances_density / cte.WATTS_HOUR_TO_JULES
* appliances_density * cte.WATTS_HOUR_TO_JULES
appliances_demand.append(total_appliances * area)
for schedule in thermal_zone.domestic_hot_water.schedules:
@ -89,7 +89,7 @@ class InselMonthlyEnergyBalance:
for day_type in schedule.day_types:
demand = (
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
* (service_temperature - cold_water[i_month]) / cte.WATTS_HOUR_TO_JULES
* (service_temperature - cold_water[i_month]) * cte.WATTS_HOUR_TO_JULES
)
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] * demand
domestic_hot_water_demand.append(total_dhw_demand * area)

View File

@ -4,10 +4,8 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project CoderPeter Yefi peteryefi@gmail.com
"""
import json
from typing import Dict
from hub.persistence.repositories.application import Application
from hub.persistence.repositories.city import City
from hub.persistence.repositories.city_object import CityObject
@ -99,17 +97,18 @@ class DBControl:
"""
return self._city_object.get_by_name_or_alias_in_cities(name, cities)
def buildings_info(self, request_values, city_id) -> [CityObject]:
def buildings_info(self, user_id, application_id, names_or_aliases) -> [CityObject]:
"""
Retrieve the buildings info from the database
:param request_values: Building names
:param city_id: City ID
:param user_id: User ID
:param application_id: Application ID
:param names_or_aliases: A list of names or alias for the buildings
:return: [CityObject]
"""
buildings = []
for name in request_values['names']:
buildings.append(self.building_info(name, city_id))
return buildings
results = self._city_object.get_by_name_or_alias_for_user_app(user_id, application_id, names_or_aliases)
if results is None:
return []
return results
def results(self, user_id, application_id, request_values, result_names=None) -> Dict:
"""

View File

@ -14,6 +14,7 @@ from sqlalchemy.orm import Session
from hub.city_model_structure.building import Building
from hub.persistence.models import CityObject as Model
from hub.persistence.models import City as CityModel
from hub.persistence.repository import Repository
@ -73,7 +74,7 @@ class CityObject(Repository):
with Session(self.engine) as session:
session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
{'name': building.name,
'alias': building.alias,
'aliases': building.aliases,
'object_type': building.type,
'year_of_construction': building.year_of_construction,
'function': building.function,
@ -135,6 +136,26 @@ class CityObject(Repository):
logging.error('Error while fetching city object by name and city, empty result %s', err)
raise IndexError from err
def get_by_name_or_alias_for_user_app(self, user_id, application_id, names) -> Union[Model, None]:
"""
Fetch city objects belonging to the user and application where the name or alias is in the names list
:param user_id: User ID
:param application_id: Application ID
:param names: a list of building aliases or names
:return [CityObject] or None
"""
with Session(self.engine) as session:
cities = session.execute(select(CityModel).where(
CityModel.user_id == user_id, CityModel.application_id == application_id
)).all()
ids = [c[0].id for c in cities]
buildings = session.execute(select(Model).where(
Model.city_id.in_(ids), Model.name.in_(names)
))
results = [r[0] for r in buildings]
print(ids, buildings)
return None
def get_by_name_or_alias_and_city(self, name, city_id) -> Union[Model, None]:
"""
Fetch a city object based on name and city id

View File

@ -1,4 +1,4 @@
"""
Hub version number
"""
__version__ = '0.1.8.34'
__version__ = '0.1.8.36'

View File

@ -100,6 +100,7 @@ setup(
('hub/data/geolocation', glob.glob('hub/data/geolocation/*.txt')),
('hub/data/greenery', glob.glob('hub/data/greenery/*.xml')),
('hub/data/usage', glob.glob('hub/data/usage/*.xml')),
('hub/data/usage', glob.glob('hub/data/usage/*.json')),
('hub/data/usage', glob.glob('hub/data/usage/*.xlsx')),
('hub/data/weather', glob.glob('hub/data/weather/*.dat')),
('hub/data/weather/epw', glob.glob('hub/data/weather/epw/*.epw')),

View File

@ -6,7 +6,6 @@ Project Coder Peter Yefi peteryefi@gmail.com
"""
import distutils.spawn
import glob
import json
import logging
import os
import subprocess
@ -286,6 +285,8 @@ TestDBFactory
results, city_object_id=db_building_id)
self.assertEqual(17, len(city_objects_id), 'wrong number of results')
self.assertIsNotNone(city_objects_id[0], 'city_object_id is None')
results = control.database.results(control.user_id, control.application_id, request_values)
for _id in city_objects_id:
control.database.delete_results_by_name('insel meb', city_object_id=_id)
control.database.delete_city(city_id)

View File

@ -5,11 +5,8 @@ Copyright © 2022 Concordia CERC group
Project Coder Peter Yefi peteryefi@gmail.com
"""
import distutils.spawn
import glob
import json
import logging
import os
import subprocess
import unittest
from pathlib import Path
from unittest import TestCase
@ -18,19 +15,7 @@ import sqlalchemy.exc
from sqlalchemy import create_engine
from sqlalchemy.exc import ProgrammingError
import hub.helpers.constants as cte
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
from hub.exports.exports_factory import ExportsFactory
from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.energy_systems_factory import EnergySystemsFactory
from hub.imports.geometry_factory import GeometryFactory
from hub.imports.results_factory import ResultFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
from hub.persistence.db_control import DBControl
from hub.persistence.models import City, Application, CityObject, SimulationResults
from hub.persistence.models import User, UserRoles
from hub.persistence.repository import Repository
@ -129,24 +114,12 @@ TestDBFactory
"""
@unittest.skipIf(control.skip_test, control.skip_reason)
def test_retrieve_results(self):
def test_buildings_info(self):
request_values = {
"scenarios": [
{
"current status": ["01002777", "01002773", "01036804"]
},
{
"skin retrofit": ["01002777", "01002773", "01036804"]
},
{
"system retrofit and pv": ["01002777", "01002773", "01036804"]
},
{
"skin and system retrofit with pv": ["01002777", "01002773", "01036804"]
}
"buildings": [
"01002777", "01002773", "01036804"
]
}
results = control.database.results(control.user_id, control.application_id, request_values)
results = control.database.buildings_info(control.user_id, control.application_id, request_values)
self.assertEqual(12, len(results), 'wrong number of results')
print(results)

View File

@ -143,7 +143,6 @@ class TestExports(TestCase):
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
print(self._output_path)
try:
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
except Exception: