diff --git a/hub/.gitignore b/hub/.gitignore index 8f8bdd78..e23b0042 100644 --- a/hub/.gitignore +++ b/hub/.gitignore @@ -1,10 +1,12 @@ !.gitignore -/venv/ +**/venv/ .idea/ /development_tests/ /data/energy_systems/heat_pumps/*.csv /data/energy_systems/heat_pumps/*.insel .DS_Store -.env -hub/logs +**/.env +**/hub/logs/ **/__pycache__/ +**/.idea/ + diff --git a/hub/DEPLOYMENT.md b/hub/DEPLOYMENT.md index 4bbc1788..422b1aa7 100644 --- a/hub/DEPLOYMENT.md +++ b/hub/DEPLOYMENT.md @@ -1,5 +1,20 @@ ## Installing PostgreSQL Database Server on Linux (Ubuntu) ## -Execute the *install_postgresql_linux.sh* script to install PostgreSQL database + +In the terminal, add the key to the keyring + +` +sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - +` + +Update your repositories with + +`sudo apt-get update` + +Install postgresql + +sudo apt-get install postgresql +` *NB: PostgreSQL DB Server runs on a default port of 5432.* ## Installing PostgreSQL Database Server on Windows ## @@ -59,7 +74,7 @@ from hub.persistence import DBSetup from pathlib import Path dotenv_path = (Path(__file__).parent / '.env').resolve() -DBSetup(db_name='hub_db', app_env='PROD', dotenv_path=dotenv_path) +DBSetup(db_name='hub_db', app_env='PROD', dotenv_path=dotenv_path, admin_password="your password here", application_uuid="your admin application uuid") ``` The *DBSetUp* class also creates a default admin user with default credentials that can be changed. with the import UserFactory class. The admin user (name, email, password and role) is logged into the console after it is created by the diff --git a/hub/catalog_factories/construction/construction_helper.py b/hub/catalog_factories/construction/construction_helper.py new file mode 100644 index 00000000..a93c2584 --- /dev/null +++ b/hub/catalog_factories/construction/construction_helper.py @@ -0,0 +1,46 @@ +from hub.helpers import constants as cte + + +class ConstructionHelper: + """ + Construction helper class + """ + _reference_standard_to_construction_period = { + 'non_standard_dompark': '1900 - 2004', + 'ASHRAE 90.1_2004': '2004 - 2009', + 'ASHRAE 189.1_2009': '2009 - PRESENT' + } + + _nrel_surfaces_types_to_hub_types = { + 'exterior wall': cte.WALL, + 'interior wall': cte.INTERIOR_WALL, + 'ground wall': cte.GROUND_WALL, + 'exterior slab': cte.GROUND, + 'attic floor': cte.ATTIC_FLOOR, + 'interior slab': cte.INTERIOR_SLAB, + 'roof': cte.ROOF + } + + _nrcan_surfaces_types_to_hub_types = { + 'Wall_Outdoors': cte.WALL, + 'RoofCeiling_Outdoors': cte.ROOF, + 'Floor_Outdoors': cte.ATTIC_FLOOR, + 'Window_Outdoors': cte.WINDOW, + 'Skylight_Outdoors': cte.SKYLIGHT, + 'Door_Outdoors': cte.DOOR, + 'Wall_Ground': cte.GROUND_WALL, + 'RoofCeiling_Ground': cte.GROUND_WALL, + 'Floor_Ground': cte.GROUND + } + + @property + def reference_standard_to_construction_period(self): + return self._reference_standard_to_construction_period + + @property + def nrel_surfaces_types_to_hub_types(self): + return self._nrel_surfaces_types_to_hub_types + + @property + def nrcan_surfaces_types_to_hub_types(self): + return self._nrcan_surfaces_types_to_hub_types diff --git a/hub/catalog_factories/construction/construction_helpers.py b/hub/catalog_factories/construction/construction_helpers.py deleted file mode 100644 index 22299464..00000000 --- a/hub/catalog_factories/construction/construction_helpers.py +++ /dev/null @@ -1,43 +0,0 @@ -from hub.helpers import constants as cte - -nrel_to_function = { - 'residential': cte.RESIDENTIAL, - 'midrise apartment': cte.MID_RISE_APARTMENT, - 'high-rise apartment': cte.HIGH_RISE_APARTMENT, - 'small office': cte.SMALL_OFFICE, - 'medium office': cte.MEDIUM_OFFICE, - 'large office': cte.LARGE_OFFICE, - 'primary school': cte.PRIMARY_SCHOOL, - 'secondary school': cte.SECONDARY_SCHOOL, - 'stand-alone retail': cte.STAND_ALONE_RETAIL, - 'hospital': cte.HOSPITAL, - 'outpatient healthcare': cte.OUT_PATIENT_HEALTH_CARE, - 'strip mall': cte.STRIP_MALL, - 'supermarket': cte.SUPERMARKET, - 'warehouse': cte.WAREHOUSE, - 'quick service restaurant': cte.QUICK_SERVICE_RESTAURANT, - 'full service restaurant': cte.FULL_SERVICE_RESTAURANT, - 'small hotel': cte.SMALL_HOTEL, - 'large hotel': cte.LARGE_HOTEL, - 'industry': cte.INDUSTRY -} - -nrcan_to_function = { - 'residential': cte.RESIDENTIAL, -} - -reference_standard_to_construction_period = { - 'non_standard_dompark': '1900 - 2004', - 'ASHRAE 90.1_2004': '2004 - 2009', - 'ASHRAE 189.1_2009': '2009 - PRESENT' -} - -nrel_surfaces_types_to_hub_types = { - 'exterior wall': cte.WALL, - 'interior wall': cte.INTERIOR_WALL, - 'ground wall': cte.GROUND_WALL, - 'exterior slab': cte.GROUND, - 'attic floor': cte.ATTIC_FLOOR, - 'interior slab': cte.INTERIOR_SLAB, - 'roof': cte.ROOF -} \ No newline at end of file diff --git a/hub/catalog_factories/construction/nrcan_catalog.py b/hub/catalog_factories/construction/nrcan_catalog.py new file mode 100644 index 00000000..1ace7524 --- /dev/null +++ b/hub/catalog_factories/construction/nrcan_catalog.py @@ -0,0 +1,124 @@ +""" +NRCAN construction catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import json +import urllib.request +import xmltodict + +from hub.catalog_factories.catalog import Catalog +from hub.catalog_factories.data_models.usages.content import Content +from hub.catalog_factories.construction.construction_helper import ConstructionHelper +from hub.catalog_factories.data_models.construction.construction import Construction +from hub.catalog_factories.data_models.construction.archetype import Archetype + + +class NrcanCatalog(Catalog): + def __init__(self, path): + path = str(path / 'nrcan.xml') + self._content = None + self._g_value_per_hdd = [] + self._thermal_transmittance_per_hdd_and_surface = {} + self._window_ratios = {} + with open(path) as xml: + self._metadata = xmltodict.parse(xml.read()) + self._base_url_archetypes = self._metadata['nrcan']['@base_url_archetypes'] + self._base_url_construction = self._metadata['nrcan']['@base_url_construction'] + self._load_window_ratios() + self._load_construction_values() + self._content = Content(self._load_archetypes()) + + def _load_window_ratios(self): + for standard in self._metadata['nrcan']['standards_per_function']['standard']: + url = f'{self._base_url_archetypes}{standard["file_location"]}' + # todo: read from file + self._window_ratios = {'Mean': 0.2, 'North': 0.2, 'East': 0.2, 'South': 0.2, 'West': 0.2} + + def _load_construction_values(self): + for standard in self._metadata['nrcan']['standards_per_period']['standard']: + g_value_url = f'{self._base_url_construction}{standard["g_value_location"]}' + punc = '() Catalog: """ diff --git a/hub/catalog_factories/cost/montreal_custom_catalog.py b/hub/catalog_factories/cost/montreal_custom_catalog.py new file mode 100644 index 00000000..d4cf0c10 --- /dev/null +++ b/hub/catalog_factories/cost/montreal_custom_catalog.py @@ -0,0 +1,170 @@ +""" +Cost catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import xmltodict +from hub.catalog_factories.catalog import Catalog +from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost +from hub.catalog_factories.data_models.cost.envelope import Envelope +from hub.catalog_factories.data_models.cost.systems import Systems +from hub.catalog_factories.data_models.cost.hvac import Hvac +from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost +from hub.catalog_factories.data_models.cost.income import Income +from hub.catalog_factories.data_models.cost.archetype import Archetype +from hub.catalog_factories.data_models.cost.content import Content + + +class MontrealCustomCatalog(Catalog): + def __init__(self, path): + path = str(path / 'montreal_costs.xml') + with open(path) as xml: + self._archetypes = xmltodict.parse(xml.read(), force_list='archetype') + + # store the full catalog data model in self._content + self._content = Content(self._load_archetypes()) + + @staticmethod + def _get_threesome(entry): + _reposition = float(entry['reposition']['#text']) + _investment = float(entry['initial_investment']['#text']) + _lifetime = float(entry['lifetime_equipment']['#text']) + return _reposition, _investment, _lifetime + + def _get_capital_costs(self, entry): + structural = float(entry['structural']['#text']) + sub_structural = float(entry['sub_structural']['#text']) + surface_finish = float(entry['surface_finish']['#text']) + engineer = float(entry['engineer']['#text']) + opaque_reposition, opaque_initial_investment, opaque_lifetime = \ + self._get_threesome(entry['envelope']['opaque']) + transparent_reposition, transparent_initial_investment, transparent_lifetime = \ + self._get_threesome(entry['envelope']['transparent']) + envelope = Envelope(opaque_reposition, + opaque_initial_investment, + opaque_lifetime, + transparent_reposition, + transparent_initial_investment, + transparent_lifetime) + heating_equipment_reposition, heating_equipment_initial_investment, heating_equipment_lifetime = \ + self._get_threesome(entry['systems']['hvac']['heating_equipment_cost']) + heating_equipment_reposition = heating_equipment_reposition / 1000 + heating_equipment_initial_investment = heating_equipment_initial_investment / 1000 + cooling_equipment_reposition, cooling_equipment_initial_investment, cooling_equipment_lifetime = \ + self._get_threesome(entry['systems']['hvac']['cooling_equipment_cost']) + cooling_equipment_reposition = cooling_equipment_reposition / 1000 + cooling_equipment_initial_investment = cooling_equipment_initial_investment / 1000 + general_hvac_equipment_reposition, general_hvac_equipment_initial_investment, general_hvac_equipment_lifetime = \ + self._get_threesome(entry['systems']['hvac']['general_hvac_equipment_cost']) + general_hvac_equipment_reposition = general_hvac_equipment_reposition * 3600 + general_hvac_equipment_initial_investment = general_hvac_equipment_initial_investment * 3600 + hvac = Hvac(heating_equipment_reposition, heating_equipment_initial_investment, heating_equipment_lifetime, + cooling_equipment_reposition, cooling_equipment_initial_investment, cooling_equipment_lifetime, + general_hvac_equipment_reposition, general_hvac_equipment_initial_investment, + general_hvac_equipment_lifetime) + + photovoltaic_system_reposition, photovoltaic_system_initial_investment, photovoltaic_system_lifetime = \ + self._get_threesome(entry['systems']['photovoltaic_system']) + other_conditioning_systems_reposition, other_conditioning_systems_initial_investment, \ + other_conditioning_systems_lifetime = self._get_threesome(entry['systems']['other_systems']) + lighting_reposition, lighting_initial_investment, lighting_lifetime = \ + self._get_threesome(entry['systems']['lighting']) + systems = Systems(hvac, + photovoltaic_system_reposition, + photovoltaic_system_initial_investment, + photovoltaic_system_lifetime, + other_conditioning_systems_reposition, + other_conditioning_systems_initial_investment, + other_conditioning_systems_lifetime, + lighting_reposition, + lighting_initial_investment, + lighting_lifetime) + _capital_cost = CapitalCost(structural, + sub_structural, + envelope, + systems, + surface_finish, + engineer) + + return _capital_cost + + @staticmethod + def _get_operational_costs(entry): + fuel_type = entry['fuel']['@fuel_type'] + fuel_fixed_operational_monthly = float(entry['fuel']['fixed']['fixed_monthly']['#text']) + fuel_fixed_operational_peak = float(entry['fuel']['fixed']['fixed_power']['#text']) / 1000 + fuel_variable_operational = float(entry['fuel']['variable']['#text']) / 1000 / 3600 + heating_equipment_maintenance = float(entry['maintenance']['heating_equipment']['#text']) / 1000 + cooling_equipment_maintenance = float(entry['maintenance']['cooling_equipment']['#text']) / 1000 + general_hvac_equipment_maintenance = float(entry['maintenance']['general_hvac_equipment']['#text']) * 3600 + photovoltaic_system_maintenance = float(entry['maintenance']['photovoltaic_system']['#text']) + other_systems_maintenance = float(entry['maintenance']['other_systems']['#text']) + co2_emissions = float(entry['CO2_cost']['#text']) + _operational_cost = OperationalCost(fuel_type, + fuel_fixed_operational_monthly, + fuel_fixed_operational_peak, + fuel_variable_operational, + heating_equipment_maintenance, + cooling_equipment_maintenance, + general_hvac_equipment_maintenance, + photovoltaic_system_maintenance, + other_systems_maintenance, + co2_emissions) + return _operational_cost + + def _load_archetypes(self): + _catalog_archetypes = [] + archetypes = self._archetypes['archetypes']['archetype'] + for archetype in archetypes: + function = archetype['@function'] + municipality = archetype['@municipality'] + currency = archetype['@currency'] + capital_cost = self._get_capital_costs(archetype['capital_cost']) + operational_cost = self._get_operational_costs(archetype['operational_cost']) + end_of_life_cost = float(archetype['end_of_life_cost']['#text']) + construction = float(archetype['incomes']['subsidies']['construction_subsidy']['#text']) + hvac = float(archetype['incomes']['subsidies']['hvac_subsidy']['#text']) + photovoltaic_system = float(archetype['incomes']['subsidies']['photovoltaic_subsidy']['#text']) + electricity_exports = float(archetype['incomes']['energy_exports']['electricity']['#text']) / 1000 / 3600 + heat_exports = float(archetype['incomes']['energy_exports']['heat']['#text']) / 1000 / 3600 + co2 = float(archetype['incomes']['CO2_income']['#text']) + income = Income(construction, hvac, photovoltaic_system, electricity_exports, heat_exports, co2) + _catalog_archetypes.append(Archetype(function, + municipality, + currency, + capital_cost, + operational_cost, + end_of_life_cost, + income)) + + return _catalog_archetypes + + def names(self, category=None): + """ + Get the catalog elements names + :parm: for costs catalog category filter does nothing as there is only one category (archetypes) + """ + _names = {'archetypes': []} + for archetype in self._content.archetypes: + _names['archetypes'].append(archetype.name) + return _names + + def entries(self, category=None): + """ + Get the catalog elements + :parm: for costs catalog category filter does nothing as there is only one category (archetypes) + """ + return self._content + + def get_entry(self, name): + """ + Get one catalog element by names + :parm: entry name + """ + for entry in self._content.archetypes: + if entry.name.lower() == name.lower(): + return entry + raise IndexError(f"{name} doesn't exists in the catalog") diff --git a/hub/catalog_factories/costs_catalog_factory.py b/hub/catalog_factories/costs_catalog_factory.py new file mode 100644 index 00000000..51bafe17 --- /dev/null +++ b/hub/catalog_factories/costs_catalog_factory.py @@ -0,0 +1,39 @@ +""" +Cost catalog publish the life cycle cost +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from pathlib import Path +from typing import TypeVar +from hub.catalog_factories.cost.montreal_custom_catalog import MontrealCustomCatalog + +Catalog = TypeVar('Catalog') + + +class CostCatalogFactory: + """ + CostsCatalogFactory class + """ + def __init__(self, file_type, base_path=None): + if base_path is None: + base_path = Path(Path(__file__).parent.parent / 'data/costs') + self._catalog_type = '_' + file_type.lower() + self._path = base_path + + @property + def _montreal_custom(self): + """ + Retrieve Montreal Custom catalog + """ + return MontrealCustomCatalog(self._path) + + @property + def catalog(self) -> Catalog: + """ + Return a cost catalog + :return: CostCatalog + """ + return getattr(self, self._catalog_type, lambda: None) diff --git a/hub/catalog_factories/data_models/construction/content.py b/hub/catalog_factories/data_models/construction/content.py index 8590e03d..3fa2f596 100644 --- a/hub/catalog_factories/data_models/construction/content.py +++ b/hub/catalog_factories/data_models/construction/content.py @@ -16,7 +16,7 @@ class Content: @property def archetypes(self): """ - All archetypes in the catalogUsageZone + All archetypes in the catalog """ return self._archetypes diff --git a/hub/catalog_factories/data_models/cost/archetype.py b/hub/catalog_factories/data_models/cost/archetype.py new file mode 100644 index 00000000..69ba2654 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/archetype.py @@ -0,0 +1,85 @@ +""" +Archetype catalog Cost +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +""" + +from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost +from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost +from hub.catalog_factories.data_models.cost.income import Income + + +class Archetype: + def __init__(self, function, municipality, currency, capital_cost, operational_cost, end_of_life_cost, income): + self._function = function + self._municipality = municipality + self._currency = currency + self._capital_cost = capital_cost + self._operational_cost = operational_cost + self._end_of_life_cost = end_of_life_cost + self._income = income + + @property + def name(self): + """ + Get name + :return: string + """ + return f'{self._municipality}_{self._function}' + + @property + def function(self): + """ + Get function + :return: string + """ + return self._function + + @property + def municipality(self): + """ + Get municipality + :return: string + """ + return self._municipality + + @property + def currency(self): + """ + Get currency + :return: string + """ + return self._currency + + @property + def capital_cost(self) -> CapitalCost: + """ + Get capital cost + :return: CapitalCost + """ + return self._capital_cost + + @property + def operational_cost(self) -> OperationalCost: + """ + Get operational cost + :return: OperationalCost + """ + return self._operational_cost + + @property + def end_of_life_cost(self): + """ + Get end of life cost in given currency + :return: float + """ + return self._end_of_life_cost + + @property + def income(self) -> Income: + """ + Get income + :return: Income + """ + return self._income diff --git a/hub/catalog_factories/data_models/cost/capital_cost.py b/hub/catalog_factories/data_models/cost/capital_cost.py new file mode 100644 index 00000000..6d5f2504 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/capital_cost.py @@ -0,0 +1,68 @@ +""" +Cost catalog CapitalCost +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from hub.catalog_factories.data_models.cost.envelope import Envelope +from hub.catalog_factories.data_models.cost.systems import Systems + + +class CapitalCost: + def __init__(self, structural, sub_structural, envelope, systems, surface_finish, engineer): + self._structural = structural + self._sub_structural = sub_structural + self._envelope = envelope + self._systems = systems + self._surface_finish = surface_finish + self._engineer = engineer + + @property + def structural(self): + """ + Get structural cost per building volume in currency/m3 + :return: float + """ + return self._structural + + @property + def sub_structural(self): + """ + Get sub structural cost per building foot-print in currency/m2 + :return: float + """ + return self._sub_structural + + @property + def envelope(self) -> Envelope: + """ + Get envelope cost + :return: Envelope + """ + return self._envelope + + @property + def systems(self) -> Systems: + """ + Get systems cost + :return: Systems + """ + return self._systems + + @property + def surface_finish(self): + """ + Get surface finish cost per external surfaces areas in currency/m2 + :return: float + """ + return self._surface_finish + + @property + def engineer(self): + """ + Get engineer cost in % + :return: float + """ + return self._engineer diff --git a/hub/catalog_factories/data_models/cost/content.py b/hub/catalog_factories/data_models/cost/content.py new file mode 100644 index 00000000..aa12e2a4 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/content.py @@ -0,0 +1,19 @@ +""" +Cost catalog content +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + + +class Content: + def __init__(self, archetypes): + self._archetypes = archetypes + + @property + def archetypes(self): + """ + All archetypes in the catalog + """ + return self._archetypes diff --git a/hub/catalog_factories/data_models/cost/envelope.py b/hub/catalog_factories/data_models/cost/envelope.py new file mode 100644 index 00000000..c2d2d419 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/envelope.py @@ -0,0 +1,66 @@ +""" +Envelope costs from Cost catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + + +class Envelope: + def __init__(self, opaque_reposition, opaque_initial_investment, opaque_lifetime, + transparent_reposition, transparent_initial_investment, transparent_lifetime): + self._opaque_reposition = opaque_reposition + self._opaque_initial_investment = opaque_initial_investment + self._opaque_lifetime = opaque_lifetime + self._transparent_reposition = transparent_reposition + self._transparent_initial_investment = transparent_initial_investment + self._transparent_lifetime = transparent_lifetime + + @property + def opaque_reposition(self): + """ + Get reposition costs for opaque envelope per area of external opaque surfaces in currency/m2 + :return: float + """ + return self._opaque_reposition + + @property + def opaque_initial_investment(self): + """ + Get initial investment for opaque envelope per area of external opaque surfaces in currency/m2 + :return: float + """ + return self._opaque_initial_investment + + @property + def opaque_lifetime(self): + """ + Get lifetime of opaque envelope in years + :return: float + """ + return self._opaque_lifetime + + @property + def transparent_reposition(self): + """ + Get reposition costs for transparent envelope per area of windows in currency/m2 + :return: float + """ + return self._transparent_reposition + + @property + def transparent_initial_investment(self): + """ + Get initial investment for transparent envelope per area of windows in currency/m2 + :return: float + """ + return self._transparent_initial_investment + + @property + def transparent_lifetime(self): + """ + Get lifetime of transparent envelope in years + :return: float + """ + return self._transparent_lifetime diff --git a/hub/catalog_factories/data_models/cost/hvac.py b/hub/catalog_factories/data_models/cost/hvac.py new file mode 100644 index 00000000..01b6c7b7 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/hvac.py @@ -0,0 +1,96 @@ +""" +Hvac costs +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca +""" + + +class Hvac: + def __init__(self, heating_equipment_reposition, heating_equipment_initial_investment, + heating_equipment_lifetime, cooling_equipment_reposition, + cooling_equipment_initial_investment, cooling_equipment_lifetime, + general_hvac_equipment_reposition, general_hvac_equipment_initial_investment, + general_hvac_equipment_lifetime): + + self._heating_equipment_reposition = heating_equipment_reposition + self._heating_equipment_initial_investment = heating_equipment_initial_investment + self._heating_equipment_lifetime = heating_equipment_lifetime + self._cooling_equipment_reposition = cooling_equipment_reposition + self._cooling_equipment_initial_investment = cooling_equipment_initial_investment + self._cooling_equipment_lifetime = cooling_equipment_lifetime + self._general_hvac_equipment_reposition = general_hvac_equipment_reposition + self._general_hvac_equipment_initial_investment = general_hvac_equipment_initial_investment + self._general_hvac_equipment_lifetime = general_hvac_equipment_lifetime + + @property + def heating_equipment_reposition(self): + """ + Get reposition costs of heating equipment per peak-load in currency/W + :return: float + """ + return self._heating_equipment_reposition + + @property + def heating_equipment_initial_investment(self): + """ + Get initial investment costs of heating equipment per peak-load in currency/W + :return: float + """ + return self._heating_equipment_initial_investment + + @property + def heating_equipment_lifetime(self): + """ + Get lifetime of heating equipment in years + :return: float + """ + return self._heating_equipment_lifetime + + @property + def cooling_equipment_reposition(self): + """ + Get reposition costs of cooling equipment per peak-load in currency/W + :return: float + """ + return self._cooling_equipment_reposition + + @property + def cooling_equipment_initial_investment(self): + """ + Get initial investment costs of cooling equipment per peak-load in currency/W + :return: float + """ + return self._cooling_equipment_initial_investment + + @property + def cooling_equipment_lifetime(self): + """ + Get lifetime of cooling equipment in years + :return: float + """ + return self._cooling_equipment_lifetime + + @property + def general_hvac_equipment_reposition(self): + """ + Get reposition costs of general hvac equipment per peak-air-flow in currency/(m3/s) + :return: float + """ + return self._general_hvac_equipment_reposition + + @property + def general_hvac_equipment_initial_investment(self): + """ + Get initial investment costs of cooling equipment per peak-air-flow in currency/(m3/s) + :return: float + """ + return self._general_hvac_equipment_initial_investment + + @property + def general_hvac_equipment_lifetime(self): + """ + Get lifetime of cooling equipment in years + :return: float + """ + return self._general_hvac_equipment_lifetime diff --git a/hub/catalog_factories/data_models/cost/income.py b/hub/catalog_factories/data_models/cost/income.py new file mode 100644 index 00000000..9bc975f2 --- /dev/null +++ b/hub/catalog_factories/data_models/cost/income.py @@ -0,0 +1,64 @@ +""" +Income from costs catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca +""" + + +class Income: + def __init__(self, construction, hvac, photovoltaic_system, electricity_exports, heat_exports, co2): + self._construction = construction + self._hvac = hvac + self._photovoltaic_system = photovoltaic_system + self._electricity_exports = electricity_exports + self._heat_exports = heat_exports + self._co2 = co2 + + @property + def construction(self): + """ + Get construction subsidy in % of total investment construction cost + :return: float + """ + return self._construction + + @property + def hvac(self): + """ + Get hvac subsidy in % of total investment HVAC cost + :return: float + """ + return self._hvac + + @property + def photovoltaic_system(self): + """ + Get photovoltaic system subsidy in % of total investment photovoltaic cost + :return: float + """ + return self._photovoltaic_system + + @property + def electricity_exports(self): + """ + Get electricity exports gains in currency/J + :return: float + """ + return self._construction + + @property + def heat_exports(self): + """ + Get heat exports gains in currency/J + :return: float + """ + return self._heat_exports + + @property + def co2(self): + """ + Get co2 income in currency/kg + :return: float + """ + return self._co2 diff --git a/hub/catalog_factories/data_models/cost/operational_cost.py b/hub/catalog_factories/data_models/cost/operational_cost.py new file mode 100644 index 00000000..b1a7b3aa --- /dev/null +++ b/hub/catalog_factories/data_models/cost/operational_cost.py @@ -0,0 +1,104 @@ +""" +Cost catalog OperationalCost +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + + +class OperationalCost: + def __init__(self, fuel_type, fuel_fixed_operational_monthly, fuel_fixed_operational_peak, + fuel_variable_operational, heating_equipment_maintenance, cooling_equipment_maintenance, + general_hvac_equipment_maintenance, photovoltaic_system_maintenance, other_systems_maintenance, + co2_emissions): + self._fuel_type = fuel_type + self._fuel_fixed_operational_monthly = fuel_fixed_operational_monthly + self._fuel_fixed_operational_peak = fuel_fixed_operational_peak + self._fuel_variable_operational = fuel_variable_operational + self._heating_equipment_maintenance = heating_equipment_maintenance + self._cooling_equipment_maintenance = cooling_equipment_maintenance + self._general_hvac_equipment_maintenance = general_hvac_equipment_maintenance + self._photovoltaic_system_maintenance = photovoltaic_system_maintenance + self._other_systems_maintenance = other_systems_maintenance + self._co2_emissions = co2_emissions + + @property + def fuel_type(self): + """ + Get fuel type + :return: string + """ + return self._fuel_type + + @property + def fuel_fixed_operational_monthly(self): + """ + Get fuel fixed operational cost in currency/month + :return: float + """ + return self._fuel_fixed_operational_monthly + + @property + def fuel_fixed_operational_peak(self): + """ + Get fuel fixed operational cost per peak power in currency/W + :return: float + """ + return self._fuel_fixed_operational_peak + + @property + def fuel_variable_operational(self): + """ + Get fuel variable operational cost in currency/J + :return: float + """ + return self._fuel_variable_operational + + @property + def heating_equipment_maintenance(self): + """ + Get heating equipment maintenance cost per peak power in currency/W + :return: float + """ + return self._heating_equipment_maintenance + + @property + def cooling_equipment_maintenance(self): + """ + Get cooling equipment maintenance cost per peak power in currency/W + :return: float + """ + return self._cooling_equipment_maintenance + + @property + def general_hvac_equipment_maintenance(self): + """ + Get general hvac equipment maintenance cost per peak-air-flow in currency/(m3/s) + :return: float + """ + return self._general_hvac_equipment_maintenance + + @property + def photovoltaic_system_maintenance(self): + """ + Get photovoltaic system maintenance cost per panels area in currency/m2 + :return: float + """ + return self._photovoltaic_system_maintenance + + @property + def other_systems_maintenance(self): + """ + Get other systems' maintenance cost per building's foot-print area in currency/m2 + :return: float + """ + return self._other_systems_maintenance + + @property + def co2_emissions(self): + """ + Get CO2 emissions cost in currency/kg + :return: float + """ + return self._co2_emissions diff --git a/hub/catalog_factories/data_models/cost/systems.py b/hub/catalog_factories/data_models/cost/systems.py new file mode 100644 index 00000000..7f2dc34e --- /dev/null +++ b/hub/catalog_factories/data_models/cost/systems.py @@ -0,0 +1,106 @@ +""" +Systems cost catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from hub.catalog_factories.data_models.cost.hvac import Hvac + + +class Systems: + def __init__(self, hvac, photovoltaic_system_reposition, photovoltaic_system_initial_investment, + photovoltaic_system_lifetime, other_conditioning_systems_reposition, + other_conditioning_systems_initial_investment, other_conditioning_systems_lifetime, + lighting_reposition, lighting_initial_investment, lighting_lifetime): + self._hvac = hvac + self._photovoltaic_system_reposition = photovoltaic_system_reposition + self._photovoltaic_system_initial_investment = photovoltaic_system_initial_investment + self._photovoltaic_system_lifetime = photovoltaic_system_lifetime + self._other_conditioning_systems_reposition = other_conditioning_systems_reposition + self._other_conditioning_systems_initial_investment = other_conditioning_systems_initial_investment + self._other_conditioning_systems_lifetime = other_conditioning_systems_lifetime + self._lighting_reposition = lighting_reposition + self._lighting_initial_investment = lighting_initial_investment + self._lighting_lifetime = lighting_lifetime + + @property + def hvac(self) -> Hvac: + """ + Get hvac capital cost + :return: Hvac + """ + return self._hvac + + @property + def photovoltaic_system_reposition(self): + """ + Get photovoltaic system reposition cost per area of panels in currency/m2 + :return: float + """ + return self._photovoltaic_system_reposition + + @property + def photovoltaic_system_initial_investment(self): + """ + Get photovoltaic system initial investment per area of panels in currency/m2 + :return: float + """ + return self._photovoltaic_system_initial_investment + + @property + def photovoltaic_system_lifetime(self): + """ + Get photovoltaic system lifetime in years + :return: float + """ + return self._photovoltaic_system_lifetime + + @property + def other_conditioning_systems_reposition(self): + """ + Get other conditioning systems reposition cost per building's foot-print area in currency/m2 + :return: float + """ + return self._other_conditioning_systems_reposition + + @property + def other_conditioning_systems_initial_investment(self): + """ + Get other conditioning systems initial investment per building's foot-print area in currency/m2 + :return: float + """ + return self._other_conditioning_systems_initial_investment + + @property + def other_conditioning_systems_lifetime(self): + """ + Get other conditioning systems lifetime in years + :return: float + """ + return self._other_conditioning_systems_lifetime + + @property + def lighting_reposition(self): + """ + Get lighting reposition cost per building's foot-print area in currency/m2 + :return: float + """ + return self._lighting_reposition + + @property + def lighting_initial_investment(self): + """ + Get lighting initial investment per building's foot-print area in currency/m2 + :return: float + """ + return self._lighting_initial_investment + + @property + def lighting_lifetime(self): + """ + Get lighting lifetime in years + :return: float + """ + return self._lighting_lifetime diff --git a/hub/catalog_factories/data_models/usages/internal_gain.py b/hub/catalog_factories/data_models/usages/internal_gain.py index ee7a0ec8..f1d8ad7f 100644 --- a/hub/catalog_factories/data_models/usages/internal_gain.py +++ b/hub/catalog_factories/data_models/usages/internal_gain.py @@ -5,6 +5,7 @@ Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ + class InternalGain: """ InternalGain class diff --git a/hub/catalog_factories/data_models/usages/usage.py b/hub/catalog_factories/data_models/usages/usage.py index fb0d06ed..95868b21 100644 --- a/hub/catalog_factories/data_models/usages/usage.py +++ b/hub/catalog_factories/data_models/usages/usage.py @@ -13,7 +13,7 @@ from hub.catalog_factories.data_models.usages.thermal_control import ThermalCont class Usage: - def __init__(self, usage, + def __init__(self, name, hours_day, days_year, mechanical_air_change, @@ -22,7 +22,7 @@ class Usage: lighting, appliances, thermal_control): - self._usage = usage + self._name = name self._hours_day = hours_day self._days_year = days_year self._mechanical_air_change = mechanical_air_change @@ -34,12 +34,12 @@ class Usage: self._thermal_control = thermal_control @property - def usage(self) -> Union[None, str]: + def name(self) -> Union[None, str]: """ - Get usage zone usage + Get usage zone usage name :return: None or str """ - return self._usage + return self._name @property def hours_day(self) -> Union[None, float]: diff --git a/hub/catalog_factories/greenery_catalog_factory.py b/hub/catalog_factories/greenery_catalog_factory.py index f0c38df6..80640918 100644 --- a/hub/catalog_factories/greenery_catalog_factory.py +++ b/hub/catalog_factories/greenery_catalog_factory.py @@ -8,8 +8,11 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca from pathlib import Path from typing import TypeVar from hub.catalog_factories.greenery.greenery_catalog import GreeneryCatalog +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type Catalog = TypeVar('Catalog') + class GreeneryCatalogFactory: """ GreeneryCatalogFactory class @@ -18,6 +21,11 @@ class GreeneryCatalogFactory: if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/greenery') self._catalog_type = '_' + file_type.lower() + class_funcs = validate_import_export_type(GreeneryCatalogFactory) + if self._catalog_type not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._path = base_path @property diff --git a/hub/catalog_factories/usage/comnet_catalog.py b/hub/catalog_factories/usage/comnet_catalog.py index e8011ae4..90a54314 100644 --- a/hub/catalog_factories/usage/comnet_catalog.py +++ b/hub/catalog_factories/usage/comnet_catalog.py @@ -28,6 +28,7 @@ class ComnetCatalog(Catalog): self._archetypes = self._read_archetype_file() self._schedules = self._read_schedules_file() + # todo: comment with @Guille, this hypotheses should go in the import factory? sensible_convective = ch().comnet_occupancy_sensible_convective sensible_radiative = ch().comnet_occupancy_sensible_radiant lighting_convective = ch().comnet_lighting_convective @@ -41,7 +42,8 @@ class ComnetCatalog(Catalog): for schedule_key in self._archetypes['schedules_key']: comnet_usage = schedule_key schedule_name = self._archetypes['schedules_key'][schedule_key] - hours_day = self._calculate_hours_day(schedule_name) + hours_day = None + days_year = None occupancy_archetype = self._archetypes['occupancy'][comnet_usage] lighting_archetype = self._archetypes['lighting'][comnet_usage] appliances_archetype = self._archetypes['plug loads'][comnet_usage] @@ -86,29 +88,9 @@ class ComnetCatalog(Catalog): self._schedules[schedule_name]['Receptacle']) # get thermal control - max_heating_setpoint = cte.MIN_FLOAT - min_heating_setpoint = cte.MAX_FLOAT - - for schedule in self._schedules[schedule_name]['HtgSetPt']: - if schedule.values is None: - max_heating_setpoint = None - min_heating_setpoint = None - break - if max(schedule.values) > max_heating_setpoint: - max_heating_setpoint = max(schedule.values) - if min(schedule.values) < min_heating_setpoint: - min_heating_setpoint = min(schedule.values) - - min_cooling_setpoint = cte.MAX_FLOAT - for schedule in self._schedules[schedule_name]['ClgSetPt']: - if schedule.values is None: - min_cooling_setpoint = None - break - if min(schedule.values) < min_cooling_setpoint: - min_cooling_setpoint = min(schedule.values) - thermal_control = ThermalControl(max_heating_setpoint, - min_heating_setpoint, - min_cooling_setpoint, + thermal_control = ThermalControl(None, + None, + None, self._schedules[schedule_name]['HVAC Avail'], self._schedules[schedule_name]['HtgSetPt'], self._schedules[schedule_name]['ClgSetPt'] @@ -116,7 +98,7 @@ class ComnetCatalog(Catalog): usages.append(Usage(comnet_usage, hours_day, - 365, + days_year, mechanical_air_change, ventilation_rate, occupancy, @@ -202,16 +184,6 @@ class ComnetCatalog(Catalog): 'schedules_key': schedules_key } - def _calculate_hours_day(self, function): - days = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, cte.SUNDAY, cte.HOLIDAY] - number_of_days_per_type = [51, 50, 50, 50, 50, 52, 52, 10] - total = 0 - for schedule in self._schedules[function]['HVAC Avail']: - yearly_days = number_of_days_per_type[days.index(schedule.day_types[0])] - for value in schedule.values: - total += value * yearly_days - return total / 365 - def names(self, category=None): """ Get the catalog elements names @@ -219,7 +191,7 @@ class ComnetCatalog(Catalog): """ _names = {'usages': []} for usage in self._content.usages: - _names['usages'].append(usage.usage) + _names['usages'].append(usage.name) return _names def entries(self, category=None): @@ -235,6 +207,6 @@ class ComnetCatalog(Catalog): :parm: entry name """ for usage in self._content.usages: - if usage.usage.lower() == name.lower(): + if usage.name.lower() == name.lower(): return usage raise IndexError(f"{name} doesn't exists in the catalog") diff --git a/hub/catalog_factories/usage/nrcan_catalog.py b/hub/catalog_factories/usage/nrcan_catalog.py index 432cb515..6e12b682 100644 --- a/hub/catalog_factories/usage/nrcan_catalog.py +++ b/hub/catalog_factories/usage/nrcan_catalog.py @@ -3,6 +3,7 @@ NRCAN usage catalog SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import json @@ -32,10 +33,6 @@ class NrcanCatalog(Catalog): self._load_schedules() self._content = Content(self._load_archetypes()) - def _calculate_hours_day(self, function): - # todo: pilar need to check how to calculate this value - return 24 - @staticmethod def _extract_schedule(raw): nrcan_schedule_type = raw['category'] @@ -54,59 +51,84 @@ 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']['standards']['usage'] + usage = self._metadata['nrcan'] url = f'{self._base_url}{usage["schedules_location"]}' + _schedule_types = [] with urllib.request.urlopen(url) as json_file: schedules_type = json.load(json_file) for schedule_type in schedules_type['tables']['schedules']['table']: schedule = NrcanCatalog._extract_schedule(schedule_type) - if schedule is not None: - self._schedules[schedule_type['name']] = schedule + if schedule_type['name'] not in _schedule_types: + _schedule_types.append(schedule_type['name']) + if schedule is not None: + self._schedules[schedule_type['name']] = [schedule] + else: + if schedule is not None: + _schedules = self._schedules[schedule_type['name']] + _schedules.append(schedule) + self._schedules[schedule_type['name']] = _schedules - def _get_schedule(self, name): + def _get_schedules(self, name): if name in self._schedules: return self._schedules[name] def _load_archetypes(self): usages = [] - usage = self._metadata['nrcan']['standards']['usage'] - url = f'{self._base_url}{usage["space_types_location"]}' + name = self._metadata['nrcan'] + url = f'{self._base_url}{name["space_types_location"]}' with urllib.request.urlopen(url) as json_file: space_types = json.load(json_file)['tables']['space_types']['table'] - space_types = [st for st in space_types if st['building_type'] == 'Space Function'] +# space_types = [st for st in space_types if st['building_type'] == 'Space Function'] + space_types = [st for st in space_types if st['space_type'] == 'WholeBuilding'] for space_type in space_types: - usage_type = space_type['space_type'] - mechanical_air_change = space_type['ventilation_air_changes'] - ventilation_rate = space_type['ventilation_per_area'] - if ventilation_rate == 0: - ventilation_rate = space_type['ventilation_per_person'] - hours_day = self._calculate_hours_day(usage_type) - days_year = 365 +# usage_type = space_type['space_type'] + usage_type = space_type['building_type'] occupancy_schedule_name = space_type['occupancy_schedule'] lighting_schedule_name = space_type['lighting_schedule'] appliance_schedule_name = space_type['electric_equipment_schedule'] - # thermal control + hvac_schedule_name = space_type['exhaust_schedule'] + if 'FAN' in hvac_schedule_name: + hvac_schedule_name = hvac_schedule_name.replace('FAN', 'Fan') heating_setpoint_schedule_name = space_type['heating_setpoint_schedule'] cooling_setpoint_schedule_name = space_type['cooling_setpoint_schedule'] - occupancy_schedule = self._get_schedule(occupancy_schedule_name) - lighting_schedule = self._get_schedule(lighting_schedule_name) - appliance_schedule = self._get_schedule(appliance_schedule_name) - heating_schedule = self._get_schedule(heating_setpoint_schedule_name) - cooling_schedule = self._get_schedule(cooling_setpoint_schedule_name) + occupancy_schedule = self._get_schedules(occupancy_schedule_name) + lighting_schedule = self._get_schedules(lighting_schedule_name) + appliance_schedule = self._get_schedules(appliance_schedule_name) + heating_schedule = self._get_schedules(heating_setpoint_schedule_name) + cooling_schedule = self._get_schedules(cooling_setpoint_schedule_name) + hvac_availability = self._get_schedules(hvac_schedule_name) occupancy_density = space_type['occupancy_per_area'] - lighting_density = space_type['lighting_per_area'] + + # ACH + mechanical_air_change = space_type['ventilation_air_changes'] + # cfm/ft2 to m3/m2.s + ventilation_rate = space_type['ventilation_per_area'] / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS) + if ventilation_rate == 0: + # cfm/person to m3/m2.s + ventilation_rate = space_type['ventilation_per_person'] / occupancy_density\ + / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS) + + # W/sqft to W/m2 + lighting_density = space_type['lighting_per_area'] * cte.METERS_TO_FEET * cte.METERS_TO_FEET lighting_radiative_fraction = space_type['lighting_fraction_radiant'] + lighting_convective_fraction = 0 if lighting_radiative_fraction is not None: lighting_convective_fraction = 1 - lighting_radiative_fraction lighting_latent_fraction = 0 - appliances_density = space_type['electric_equipment_per_area'] + # W/sqft to W/m2 + appliances_density = space_type['electric_equipment_per_area'] * cte.METERS_TO_FEET * cte.METERS_TO_FEET appliances_radiative_fraction = space_type['electric_equipment_fraction_radiant'] - if appliances_radiative_fraction is not None: - appliances_convective_fraction = 1 - appliances_radiative_fraction appliances_latent_fraction = space_type['electric_equipment_fraction_latent'] + appliances_convective_fraction = 0 + if appliances_radiative_fraction is not None and appliances_latent_fraction is not None: + appliances_convective_fraction = 1 - appliances_radiative_fraction - appliances_latent_fraction - occupancy = Occupancy(occupancy_density, 0, 0, 0, occupancy_schedule) + occupancy = Occupancy(occupancy_density, + None, + None, + None, + occupancy_schedule) lighting = Lighting(lighting_density, lighting_convective_fraction, lighting_radiative_fraction, @@ -117,20 +139,14 @@ class NrcanCatalog(Catalog): appliances_radiative_fraction, appliances_latent_fraction, appliance_schedule) - if heating_schedule is not None: - thermal_control = ThermalControl(max(heating_schedule.values), - min(heating_schedule.values), - min(cooling_schedule.values), - None, - heating_schedule, - cooling_schedule) - else: - thermal_control = ThermalControl(None, - None, - None, - None, - None, - None) + thermal_control = ThermalControl(None, + None, + None, + hvac_availability, + heating_schedule, + cooling_schedule) + hours_day = None + days_year = None usages.append(Usage(usage_type, hours_day, days_year, @@ -149,7 +165,7 @@ class NrcanCatalog(Catalog): """ _names = {'usages': []} for usage in self._content.usages: - _names['usages'].append(usage.usage) + _names['usages'].append(usage.name) return _names def entries(self, category=None): @@ -165,6 +181,6 @@ class NrcanCatalog(Catalog): :parm: entry name """ for usage in self._content.usages: - if usage.usage.lower() == name.lower(): + if usage.name.lower() == name.lower(): return usage raise IndexError(f"{name} doesn't exists in the catalog") diff --git a/hub/catalog_factories/usage/usage_helper.py b/hub/catalog_factories/usage/usage_helper.py index ee477b9e..e2886bc9 100644 --- a/hub/catalog_factories/usage/usage_helper.py +++ b/hub/catalog_factories/usage/usage_helper.py @@ -19,6 +19,7 @@ class UsageHelper: 'Equipment': cte.APPLIANCES, 'Thermostat Setpoint Cooling': cte.COOLING_SET_POINT, # Compose 'Thermostat Setpoint' + 'Cooling' 'Thermostat Setpoint Heating': cte.HEATING_SET_POINT, # Compose 'Thermostat Setpoint' + 'Heating' + 'Fan': cte.HVAC_AVAILABILITY } _nrcan_data_type_to_hub_data_type = { 'FRACTION': cte.FRACTION, @@ -58,31 +59,6 @@ class UsageHelper: } - _usage_to_hft = { - cte.RESIDENTIAL: 'residential', - cte.SINGLE_FAMILY_HOUSE: 'Single family house', - cte.MULTI_FAMILY_HOUSE: 'Multi-family house', - cte.EDUCATION: 'education', - cte.SCHOOL_WITHOUT_SHOWER: 'school without shower', - cte.SCHOOL_WITH_SHOWER: 'school with shower', - cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'retail', - cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'retail shop / refrigerated food', - cte.HOTEL: 'hotel', - cte.HOTEL_MEDIUM_CLASS: 'hotel (Medium-class)', - cte.DORMITORY: 'dormitory', - cte.INDUSTRY: 'industry', - cte.RESTAURANT: 'restaurant', - cte.HEALTH_CARE: 'health care', - cte.RETIREMENT_HOME_OR_ORPHANAGE: 'Home for the aged or orphanage', - cte.OFFICE_AND_ADMINISTRATION: 'office and administration', - cte.EVENT_LOCATION: 'event location', - cte.HALL: 'hall', - cte.SPORTS_LOCATION: 'sport location', - cte.LABOR: 'Labor', - cte.GREEN_HOUSE: 'green house', - cte.NON_HEATED: 'non-heated' - } - _comnet_days = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, @@ -92,31 +68,6 @@ class UsageHelper: cte.SUNDAY, cte.HOLIDAY] - _usage_to_comnet = { - cte.RESIDENTIAL: 'BA Multifamily', - cte.SINGLE_FAMILY_HOUSE: 'BA Multifamily', - cte.MULTI_FAMILY_HOUSE: 'BA Multifamily', - cte.EDUCATION: 'BA School/University', - cte.SCHOOL_WITHOUT_SHOWER: 'BA School/University', - cte.SCHOOL_WITH_SHOWER: 'BA School/University', - cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'BA Retail', - cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'BA Retail', - cte.HOTEL: 'BA Hotel', - cte.HOTEL_MEDIUM_CLASS: 'BA Hotel', - cte.DORMITORY: 'BA Dormitory', - cte.INDUSTRY: 'BA Manufacturing Facility', - cte.RESTAURANT: 'BA Dining: Family', - cte.HEALTH_CARE: 'BA Hospital', - cte.RETIREMENT_HOME_OR_ORPHANAGE: 'BA Multifamily', - cte.OFFICE_AND_ADMINISTRATION: 'BA Office', - cte.EVENT_LOCATION: 'BA Convention Center', - cte.HALL: 'BA Convention Center', - cte.SPORTS_LOCATION: 'BA Sports Arena', - cte.LABOR: 'BA Gymnasium', - cte.GREEN_HOUSE: cte.GREEN_HOUSE, - cte.NON_HEATED: cte.NON_HEATED - } - _comnet_data_type_to_hub_data_type = { 'Fraction': cte.FRACTION, 'OnOff': cte.ON_OFF, @@ -166,18 +117,6 @@ class UsageHelper: def comnet_days(self): return self._comnet_days - @staticmethod - def comnet_from_hub_usage(usage): - """ - Get Comnet usage from the given internal usage key - :param usage: str - :return: str - """ - try: - return UsageHelper._usage_to_comnet[usage] - except KeyError: - sys.stderr.write('Error: keyword not found to translate from hub_usage to comnet usage.\n') - @staticmethod def schedules_key(usage): """ @@ -190,15 +129,3 @@ class UsageHelper: except KeyError: sys.stderr.write('Error: Comnet keyword not found. An update of the Comnet files might have been ' 'done changing the keywords.\n') - - @staticmethod - def hft_from_hub_usage(usage): - """ - Get HfT usage from the given internal usage key - :param usage: str - :return: str - """ - try: - return UsageHelper._usage_to_hft[usage] - except KeyError: - sys.stderr.write('Error: keyword not found to translate from hub_usage to hft usage.\n') diff --git a/hub/catalog_factories/usage_catalog_factory.py b/hub/catalog_factories/usage_catalog_factory.py index a554748a..26aa6631 100644 --- a/hub/catalog_factories/usage_catalog_factory.py +++ b/hub/catalog_factories/usage_catalog_factory.py @@ -9,6 +9,8 @@ from pathlib import Path from typing import TypeVar from hub.catalog_factories.usage.comnet_catalog import ComnetCatalog from hub.catalog_factories.usage.nrcan_catalog import NrcanCatalog +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type Catalog = TypeVar('Catalog') @@ -17,6 +19,11 @@ class UsageCatalogFactory: if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/usage') self._catalog_type = '_' + file_type.lower() + class_funcs = validate_import_export_type(UsageCatalogFactory) + if self._catalog_type not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._path = base_path @property diff --git a/hub/city_model_structure/attributes/polygon.py b/hub/city_model_structure/attributes/polygon.py index 94d47725..2755d399 100644 --- a/hub/city_model_structure/attributes/polygon.py +++ b/hub/city_model_structure/attributes/polygon.py @@ -9,9 +9,13 @@ from __future__ import annotations import math import sys from typing import List +from hub.hub_logger import logger import numpy as np from trimesh import Trimesh import trimesh.intersections +import trimesh.creation +import trimesh.geometry +from shapely.geometry.polygon import Polygon as shapley_polygon from hub.city_model_structure.attributes.plane import Plane from hub.city_model_structure.attributes.point import Point @@ -22,6 +26,7 @@ class Polygon: """ Polygon class """ + # todo: review with @Guille: Points, Coordinates, Vertices, Faces def __init__(self, coordinates): self._area = None @@ -66,20 +71,6 @@ class Polygon: """ return self._coordinates - @staticmethod - def _module(vector): - x2 = vector[0] ** 2 - y2 = vector[1] ** 2 - z2 = vector[2] ** 2 - return math.sqrt(x2+y2+z2) - - @staticmethod - def _scalar_product(vector_0, vector_1): - x = vector_0[0] * vector_1[0] - y = vector_0[1] * vector_1[1] - z = vector_0[2] * vector_1[2] - return x+y+z - def contains_point(self, point): """ Determines if the given point is contained by the current polygon @@ -98,9 +89,9 @@ class Polygon: vector_1[0] = vector_1[0] - point.coordinates[0] vector_1[1] = vector_1[1] - point.coordinates[1] vector_1[2] = vector_1[2] - point.coordinates[2] - module = Polygon._module(vector_0) * Polygon._module(vector_1) + module = np.linalg.norm(vector_0) * np.linalg.norm(vector_1) - scalar_product = Polygon._scalar_product(vector_0, vector_1) + scalar_product = np.dot(vector_0, vector_1) angle = np.pi/2 if module != 0: angle = abs(np.arcsin(scalar_product / module)) @@ -150,69 +141,17 @@ class Polygon: Get surface area in square meters :return: float """ - # New method to calculate area if self._area is None: - if len(self.points) < 3: - sys.stderr.write('Warning: the area of a line or point cannot be calculated 1. Area = 0\n') - return 0 - alpha = 0 - vec_1 = self.points[1].coordinates - self.points[0].coordinates - for i in range(2, len(self.points)): - vec_2 = self.points[i].coordinates - self.points[0].coordinates - alpha += self._angle_between_vectors(vec_1, vec_2) - if alpha == 0: - sys.stderr.write('Warning: the area of a line or point cannot be calculated 2. Area = 0\n') - return 0 - horizontal_points = self._points_rotated_to_horizontal - area = 0 - for i in range(0, len(horizontal_points) - 1): - point = horizontal_points[i] - next_point = horizontal_points[i + 1] - area += (next_point[1] + point[1]) / 2 * (next_point[0] - point[0]) - next_point = horizontal_points[0] - point = horizontal_points[len(horizontal_points) - 1] - area += (next_point[1] + point[1]) / 2 * (next_point[0] - point[0]) - self._area = abs(area) + self._area = 0 + for triangle in self.triangles: + ab = np.zeros(3) + ac = np.zeros(3) + for i in range(0, 3): + ab[i] = triangle.coordinates[1][i] - triangle.coordinates[0][i] + ac[i] = triangle.coordinates[2][i] - triangle.coordinates[0][i] + self._area += np.linalg.norm(np.cross(ab, ac)) / 2 return self._area - @property - def _points_rotated_to_horizontal(self): - """ - polygon points rotated to horizontal - :return: [float] - """ - z_vector = [0, 0, 1] - normal_vector = self.normal - horizontal_points = [] - x = normal_vector[0] - y = normal_vector[1] - - if x == 0 and y == 0: - # Already horizontal - for point in self.points: - horizontal_points.append([point.coordinates[0], point.coordinates[1], 0]) - else: - alpha = self._angle_between_vectors(normal_vector, z_vector) - rotation_line = np.cross(normal_vector, z_vector) - third_axis = np.cross(normal_vector, rotation_line) - w_1 = rotation_line / np.linalg.norm(rotation_line) - w_2 = normal_vector - w_3 = third_axis / np.linalg.norm(third_axis) - rotation_matrix = np.array([[1, 0, 0], - [0, np.cos(alpha), -np.sin(alpha)], - [0, np.sin(alpha), np.cos(alpha)]]) - base_matrix = np.array([w_1, w_2, w_3]) - rotation_base_matrix = np.matmul(base_matrix.transpose(), rotation_matrix.transpose()) - rotation_base_matrix = np.matmul(rotation_base_matrix, base_matrix) - - if rotation_base_matrix is None: - sys.stderr.write('Warning: rotation base matrix returned None\n') - else: - for point in self.points: - new_point = np.matmul(rotation_base_matrix, point.coordinates) - horizontal_points.append(new_point) - return horizontal_points - @property def normal(self) -> np.ndarray: """ @@ -275,283 +214,74 @@ class Polygon: return alpha return -alpha - def triangulate(self) -> List[Polygon]: - """ - Triangulates a polygon following the ear clipping methodology - :return: list[triangles] - """ - # todo: review triangulate_polygon in - # https://github.com/mikedh/trimesh/blob/dad11126742e140ef46ba12f8cb8643c83356467/trimesh/creation.py#L415, - # it had a problem with a class called 'triangle', but, if solved, - # it could be a very good substitute of this method - # this method is very dirty and has an infinite loop solved with a counter!! - if self._triangles is None: - points_list = self.points_list - normal = self.normal - if np.linalg.norm(normal) == 0: - sys.stderr.write('Not able to triangulate polygon\n') - return [self] - # are points concave or convex? - total_points_list, concave_points, convex_points = self._starting_lists(points_list, normal) - - # list of ears - ears = [] - j = 0 - while (len(concave_points) > 3 or len(convex_points) != 0) and j < 100: - j += 1 - for i in range(0, len(concave_points)): - ear = self._triangle(points_list, total_points_list, concave_points[i]) - rest_points = [] - for points in total_points_list: - rest_points.append(list(self.coordinates[points])) - if self._is_ear(ear, rest_points): - ears.append(ear) - point_to_remove = concave_points[i] - previous_point_in_list, next_point_in_list = self._enveloping_points(point_to_remove, - total_points_list) - total_points_list.remove(point_to_remove) - concave_points.remove(point_to_remove) - # Was any of the adjacent points convex? -> check if changed status to concave - for convex_point in convex_points: - if convex_point == previous_point_in_list: - concave_points, convex_points, end_loop = self._if_concave_change_status(normal, - points_list, - convex_point, - total_points_list, - concave_points, - convex_points, - previous_point_in_list) - if end_loop: - break - continue - if convex_point == next_point_in_list: - concave_points, convex_points, end_loop = self._if_concave_change_status(normal, - points_list, - convex_point, - total_points_list, - concave_points, - convex_points, - next_point_in_list) - if end_loop: - break - continue - break - if len(total_points_list) <= 3 and len(convex_points) > 0: - sys.stderr.write('Not able to triangulate polygon\n') - return [self] - if j >= 100: - sys.stderr.write('Not able to triangulate polygon\n') - return [self] - last_ear = self._triangle(points_list, total_points_list, concave_points[1]) - ears.append(last_ear) - self._triangles = ears - return self._triangles - @staticmethod - def _starting_lists(points_list, normal) -> [List[float], List[float], List[float]]: - """ - creates the list of vertices (points) that define the polygon (total_points_list), together with other two lists - separating points between convex and concave - :param points_list: points_list - :param normal: normal - :return: list[point], list[point], list[point] - """ - concave_points = [] - convex_points = [] - # lists of concave and convex points - # case 1: first point - point = points_list[0:3] - previous_point = points_list[len(points_list) - 3:] - next_point = points_list[3:6] - index = 0 - total_points_list = [index] - if Polygon._point_is_concave(normal, point, previous_point, next_point): - concave_points.append(index) - else: - convex_points.append(index) - # case 2: all points except first and last - for i in range(0, int((len(points_list) - 6) / 3)): - point = points_list[(i + 1) * 3:(i + 2) * 3] - previous_point = points_list[i * 3:(i + 1) * 3] - next_point = points_list[(i + 2) * 3:(i + 3) * 3] - index = i + 1 - total_points_list.append(index) - if Polygon._point_is_concave(normal, point, previous_point, next_point): - concave_points.append(index) - else: - convex_points.append(index) - # case 3: last point - point = points_list[len(points_list) - 3:] - previous_point = points_list[len(points_list) - 6:len(points_list) - 3] - next_point = points_list[0:3] - index = int(len(points_list) / 3) - 1 - total_points_list.append(index) - if Polygon._point_is_concave(normal, point, previous_point, next_point): - concave_points.append(index) - else: - convex_points.append(index) - return total_points_list, concave_points, convex_points + def triangle_mesh(vertices, normal): + min_x = 1e16 + min_y = 1e16 + min_z = 1e16 + for vertex in vertices: + if vertex[0] < min_x: + min_x = vertex[0] + if vertex[1] < min_y: + min_y = vertex[1] + if vertex[2] < min_z: + min_z = vertex[2] - @staticmethod - def _triangle(points_list, total_points_list, point_position) -> Polygon: - """ - creates a triangular polygon out of three points - :param points_list: points_list - :param total_points_list: [point] - :param point_position: int - :return: polygon - """ - index = point_position * 3 - previous_point_index, next_point_index = Polygon._enveloping_points_indices(point_position, total_points_list) - points = points_list[previous_point_index:previous_point_index + 3] - points = np.append(points, points_list[index:index + 3]) - points = np.append(points, points_list[next_point_index:next_point_index + 3]) - rows = points.size // 3 - points = points.reshape(rows, 3) - triangle = Polygon(points) - return triangle + new_vertices = [] + for vertex in vertices: + vertex = [vertex[0]-min_x, vertex[1]-min_y, vertex[2]-min_z] + new_vertices.append(vertex) - @staticmethod - def _enveloping_points_indices(point_position, total_points_list): - """ - due to the fact that the lists are not circular, a method to find the previous and next points - of an specific one is needed - :param point_position: int - :param total_points_list: [point] - :return: int, int - """ - previous_point_index = None - next_point_index = None - if point_position == total_points_list[0]: - previous_point_index = total_points_list[len(total_points_list) - 1] * 3 - next_point_index = total_points_list[1] * 3 - if point_position == total_points_list[len(total_points_list) - 1]: - previous_point_index = total_points_list[len(total_points_list) - 2] * 3 - next_point_index = total_points_list[0] * 3 - for i in range(1, len(total_points_list) - 1): - if point_position == total_points_list[i]: - previous_point_index = total_points_list[i - 1] * 3 - next_point_index = total_points_list[i + 1] * 3 - return previous_point_index, next_point_index + transformation_matrix = trimesh.geometry.plane_transform(origin=new_vertices[0], normal=normal) - @staticmethod - def _enveloping_points(point_to_remove, total_points_list): - """ - due to the fact that the lists are not circular, a method to find the previous and next points - of an specific one is needed - :param point_to_remove: point - :param total_points_list: [point] - :return: point, point - """ - index = total_points_list.index(point_to_remove) - if index == 0: - previous_point_in_list = total_points_list[len(total_points_list) - 1] - next_point_in_list = total_points_list[1] - elif index == len(total_points_list) - 1: - previous_point_in_list = total_points_list[len(total_points_list) - 2] - next_point_in_list = total_points_list[0] - else: - previous_point_in_list = total_points_list[index - 1] - next_point_in_list = total_points_list[index + 1] - return previous_point_in_list, next_point_in_list + coordinates = [] + for vertex in vertices: + transformed_vertex = [vertex[0]-min_x, vertex[1]-min_y, vertex[2]-min_z, 1] + transformed_vertex = np.dot(transformation_matrix, transformed_vertex) + coordinate = [transformed_vertex[0], transformed_vertex[1]] + coordinates.append(coordinate) - @staticmethod - def _is_ear(ear, points) -> bool: - """ - finds whether a triangle is an ear of the polygon - :param ear: polygon - :param points: [point] - :return: boolean - """ - area_ear = ear.area - for point in points: - area_points = 0 - point_is_not_vertex = True + polygon = shapley_polygon(coordinates) + + try: + vertices_2d, faces = trimesh.creation.triangulate_polygon(polygon, engine='triangle') + mesh = Trimesh(vertices=vertices, faces=faces) + + # check orientation + normal_sum = 0 for i in range(0, 3): - if abs(np.linalg.norm(point) - np.linalg.norm(ear.coordinates[i])) < 0.0001: - point_is_not_vertex = False - break - if point_is_not_vertex: - for i in range(0, 3): - if i != 2: - new_points = ear.coordinates[i][:] - new_points = np.append(new_points, ear.coordinates[i + 1][:]) - new_points = np.append(new_points, point[:]) - else: - new_points = ear.coordinates[i][:] - new_points = np.append(new_points, point[:]) - new_points = np.append(new_points, ear.coordinates[0][:]) - rows = new_points.size // 3 - new_points = new_points.reshape(rows, 3) - new_triangle = Polygon(new_points) - area_points += new_triangle.area - if abs(area_points - area_ear) < 1e-6: - # point_inside_ear = True - return False - return True + normal_sum += normal[i] + mesh.face_normals[0][i] - @staticmethod - def _if_concave_change_status(normal, points_list, convex_point, total_points_list, - concave_points, convex_points, point_in_list) -> [List[float], List[float], bool]: - """ - checks whether an convex specific point change its status to concave after removing one ear in the polygon - returning the new convex and concave points lists together with a flag advising that the list of total points - already 3 and, therefore, the triangulation must be finished. - :param normal: normal - :param points_list: points_list - :param convex_point: int - :param total_points_list: [point] - :param concave_points: [point] - :param convex_points: [point] - :param point_in_list: int - :return: list[points], list[points], boolean - """ - end_loop = False - point = points_list[point_in_list * 3:(point_in_list + 1) * 3] - pointer = total_points_list.index(point_in_list) - 1 - if pointer < 0: - pointer = len(total_points_list) - 1 - previous_point = points_list[total_points_list[pointer] * 3:total_points_list[pointer] * 3 + 3] - pointer = total_points_list.index(point_in_list) + 1 - if pointer >= len(total_points_list): - pointer = 0 - next_point = points_list[total_points_list[pointer] * 3:total_points_list[pointer] * 3 + 3] - if Polygon._point_is_concave(normal, point, previous_point, next_point): - if concave_points[0] > convex_point: - concave_points.insert(0, convex_point) - elif concave_points[len(concave_points) - 1] < convex_point: - concave_points.append(convex_point) - else: - for point_index in range(0, len(concave_points) - 1): - if concave_points[point_index] < convex_point < concave_points[point_index + 1]: - concave_points.insert(point_index + 1, convex_point) - convex_points.remove(convex_point) - end_loop = True - return concave_points, convex_points, end_loop + if abs(normal_sum) <= 1E-10: + new_faces = [] + for face in faces: + new_face = [] + for i in range(0, len(face)): + new_face.append(face[len(face)-i-1]) + new_faces.append(new_face) + mesh = Trimesh(vertices=vertices, faces=new_faces) - @staticmethod - def _point_is_concave(normal, point, previous_point, next_point) -> bool: - """ - returns whether a point is concave - :param normal: normal - :param point: point - :param previous_point: point - :param next_point: point - :return: boolean - """ - is_concave = False - accepted_error = 0.1 - points = np.append(previous_point, point) - points = np.append(points, next_point) - rows = points.size // 3 - points = points.reshape(rows, 3) - triangle = Polygon(points) - error_sum = 0 - for i in range(0, len(normal)): - error_sum += triangle.normal[i] - normal[i] - if np.abs(error_sum) < accepted_error: - is_concave = True - return is_concave + return mesh + + except ValueError: + logger.error(f'Not able to triangulate polygon\n') + sys.stderr.write(f'Not able to triangulate polygon\n') + _vertices = [[0, 0, 0], [0, 0, 1], [0, 1, 0]] + _faces = [[0, 1, 2]] + return Trimesh(vertices=_vertices, faces=_faces) + + @property + def triangles(self) -> List[Polygon]: + if self._triangles is None: + self._triangles = [] + _mesh = self.triangle_mesh(self.coordinates, self.normal) + for face in _mesh.faces: + points = [] + for vertex in face: + points.append(self.coordinates[vertex]) + polygon = Polygon(points) + self._triangles.append(polygon) + return self._triangles @staticmethod def _angle_between_vectors(vec_1, vec_2): @@ -652,12 +382,12 @@ class Polygon: @property def vertices(self) -> np.ndarray: """ - Get polyhedron vertices + Get polygon vertices :return: np.ndarray(int) """ if self._vertices is None: vertices, self._vertices = [], [] - _ = [vertices.extend(s.coordinates) for s in self.triangulate()] + _ = [vertices.extend(s.coordinates) for s in self.triangles] for vertex_1 in vertices: found = False for vertex_2 in self._vertices: @@ -677,17 +407,17 @@ class Polygon: @property def faces(self) -> List[List[int]]: """ - Get polyhedron triangular faces + Get polygon triangular faces :return: [face] """ if self._faces is None: self._faces = [] - for polygon in self.triangulate(): + for polygon in self.triangles: face = [] points = polygon.coordinates if len(points) != 3: - sub_polygons = polygon.triangulate() + sub_polygons = polygon.triangles # todo: I modified this! To be checked @Guille if len(sub_polygons) >= 1: for sub_polygon in sub_polygons: diff --git a/hub/city_model_structure/attributes/polyhedron.py b/hub/city_model_structure/attributes/polyhedron.py index f27a8ff1..553f1e26 100644 --- a/hub/city_model_structure/attributes/polyhedron.py +++ b/hub/city_model_structure/attributes/polyhedron.py @@ -91,7 +91,7 @@ class Polyhedron: face = [] points = polygon.coordinates if len(points) != 3: - sub_polygons = polygon.triangulate() + sub_polygons = polygon.triangles # todo: I modified this! To be checked @Guille if len(sub_polygons) >= 1: for sub_polygon in sub_polygons: diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index e9c6f29b..d5129142 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -6,8 +6,10 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +import sys from typing import List, Union import numpy as np +from hub.hub_logger import logger import hub.helpers.constants as cte from hub.city_model_structure.building_demand.surface import Surface from hub.city_model_structure.city_object import CityObject @@ -34,7 +36,7 @@ class Building(CityObject): self._roof_type = None self._internal_zones = None self._shell = None - self._human_readable_name = None + self._alias = None self._type = 'building' self._heating = dict() self._cooling = dict() @@ -46,20 +48,31 @@ class Building(CityObject): self._roofs = [] self._walls = [] self._internal_walls = [] + self._ground_walls = [] + self._attic_floors = [] + self._interior_slabs = [] for surface_id, surface in enumerate(self.surfaces): self._min_x = min(self._min_x, surface.lower_corner[0]) self._min_y = min(self._min_y, surface.lower_corner[1]) self._min_z = min(self._min_z, surface.lower_corner[2]) surface.id = surface_id - # todo: consider all type of surfaces, not only these four if surface.type == cte.GROUND: self._grounds.append(surface) elif surface.type == cte.WALL: self._walls.append(surface) elif surface.type == cte.ROOF: self._roofs.append(surface) - else: + elif surface.type == cte.INTERIOR_WALL: self._internal_walls.append(surface) + elif surface.type == cte.GROUND_WALL: + self._ground_walls.append(surface) + elif surface.type == cte.ATTIC_FLOOR: + self._attic_floors.append(surface) + elif surface.type == cte.INTERIOR_SLAB: + self._interior_slabs.append(surface) + else: + logger.error(f'Building {self.name} [alias {self.alias}] has an unexpected surface type {surface.type}.\n') + sys.stderr.write(f'Building {self.name} [alias {self.alias}] has an unexpected surface type {surface.type}.\n') @property def shell(self) -> Polyhedron: @@ -397,23 +410,34 @@ class Building(CityObject): if self.internal_zones is None: return False for internal_zone in self.internal_zones: - if internal_zone.usage_zones is not None: - for usage_zone in internal_zone.usage_zones: - if usage_zone.thermal_control is not None: + if internal_zone.usages is not None: + for usage in internal_zone.usages: + if usage.thermal_control is not None: return True return False @property - def human_readable_name(self): + def alias(self): """ - Get the human-readable name for the building + Get the alias name for the building :return: str """ - return self._human_readable_name + return self._alias - @human_readable_name.setter - def human_readable_name(self, value): + @alias.setter + def alias(self, value): """ - Set the human-readable name for the building + Set the alias name for the building """ - self._human_readable_name = value + self._alias = value + + @property + def usages_percentage(self): + """ + Get the usages and percentages for the building + """ + _usage = '' + for internal_zone in self.internal_zones: + for usage in internal_zone.usages: + _usage = f'{_usage}{usage.name}_{usage.percentage} ' + return _usage.rstrip() diff --git a/hub/city_model_structure/building_demand/internal_zone.py b/hub/city_model_structure/building_demand/internal_zone.py index 7cad3a15..802c7e20 100644 --- a/hub/city_model_structure/building_demand/internal_zone.py +++ b/hub/city_model_structure/building_demand/internal_zone.py @@ -7,7 +7,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca import uuid from typing import Union, List -from hub.city_model_structure.building_demand.usage_zone import UsageZone +from hub.city_model_structure.building_demand.usage import Usage from hub.city_model_structure.building_demand.thermal_zone import ThermalZone from hub.city_model_structure.attributes.polyhedron import Polyhedron from hub.city_model_structure.energy_systems.hvac_system import HvacSystem @@ -24,7 +24,7 @@ class InternalZone: self._volume = None self._area = area self._thermal_zones = None - self._usage_zones = None + self._usages = None self._hvac_system = None @property @@ -75,20 +75,20 @@ class InternalZone: return self._area @property - def usage_zones(self) -> [UsageZone]: + def usages(self) -> [Usage]: """ Get internal zone usage zones :return: [UsageZone] """ - return self._usage_zones + return self._usages - @usage_zones.setter - def usage_zones(self, value): + @usages.setter + def usages(self, value): """ Set internal zone usage zones :param value: [UsageZone] """ - self._usage_zones = value + self._usages = value @property def hvac_system(self) -> Union[None, HvacSystem]: diff --git a/hub/city_model_structure/building_demand/occupancy.py b/hub/city_model_structure/building_demand/occupancy.py index 99e4ab23..46d34aa1 100644 --- a/hub/city_model_structure/building_demand/occupancy.py +++ b/hub/city_model_structure/building_demand/occupancy.py @@ -6,7 +6,6 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from typing import Union, List from hub.city_model_structure.attributes.schedule import Schedule -from hub.city_model_structure.building_demand.occupant import Occupant class Occupancy: @@ -106,19 +105,3 @@ class Occupancy: :param value: [Schedule] """ self._occupancy_schedules = value - - @property - def occupants(self) -> Union[None, List[Occupant]]: - """ - Get list of occupants - :return: None or List of Occupant - """ - return self._occupants - - @occupants.setter - def occupants(self, value): - """ - Set list of occupants - :param value: [Occupant] - """ - self._occupants = value diff --git a/hub/city_model_structure/building_demand/occupant.py b/hub/city_model_structure/building_demand/occupant.py deleted file mode 100644 index 9ee6f9bd..00000000 --- a/hub/city_model_structure/building_demand/occupant.py +++ /dev/null @@ -1,147 +0,0 @@ -""" -Occupant module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Sanam Dabirian sanam.dabirian@mail.concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - -import calendar as cal - - -class Occupant: - """ - Occupant class - """ - - def __init__(self): - """ - Constructor - """ - - self._heat_dissipation = None - self._occupancy_rate = None - self._occupant_type = None - self._arrival_time = None - self._departure_time = None - self._break_time = None - self._day_of_week = None - self._pd_of_meetings_duration = None - - @property - def heat_dissipation(self): - """ - Get heat dissipation of occupants in W/person - :return: float - """ - return self._heat_dissipation - - @heat_dissipation.setter - def heat_dissipation(self, value): - """ - Set heat dissipation of occupants in W/person - :param value: float - """ - self._heat_dissipation = float(value) - - @property - def occupancy_rate(self): - """ - Get rate of schedules - :return: float - """ - return self._occupancy_rate - - @occupancy_rate.setter - def occupancy_rate(self, value): - """ - Set rate of schedules - :param value: float - """ - self._occupancy_rate = float(value) - - @property - def occupant_type(self): - """ - Get type of schedules - :return: str - """ - return self._occupant_type - - @occupant_type.setter - def occupant_type(self, value): - """ - Set type of schedules - :param value: float - """ - self._occupant_type = float(value) - - @property - def arrival_time(self): - """ - Get the arrival time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss - :return: time - """ - return self._arrival_time - - @arrival_time.setter - def arrival_time(self, value): - """ - Set the arrival time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss - :param value: time - """ - self._arrival_time = value - - @property - def departure_time(self): - """ - Get the departure time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss - :return: time - """ - return self._departure_time - - @departure_time.setter - def departure_time(self, value): - """ - Set the departure time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss - :param value: str - """ - self._departure_time = value - - @property - def break_time(self): - """ - Get the lunch or break time of the occupant (for office building) in UTC with format ???? - :return: break time - """ - # todo @Sanam: define this format, is it the starting time? is it a list with both, starting and ending time? - return self._break_time - - @property - def day_of_week(self): - """ - Get the day of the week (MON, TUE, WED, THU, FRI, SAT, SUN) - :return: str - """ - # todo @Sanam: is this a property or should it be a function - # to get the day of the week of an specific day of the year? - return self._day_of_week - - @property - def pd_of_meetings_duration(self): - """ - Get the probability distribution of the meeting duration - :return: ?? - """ - # todo @Sanam: what format are you expecting here?? - return self._pd_of_meetings_duration - - @pd_of_meetings_duration.setter - def pd_of_meetings_duration(self, value): - """ - Get the probability distribution of the meeting duration - :param value: ?? - :return: - """ - # todo @Sanam: what format are you expecting here?? - self._pd_of_meetings_duration = value diff --git a/hub/city_model_structure/building_demand/surface.py b/hub/city_model_structure/building_demand/surface.py index 1c4e4cac..dc392160 100644 --- a/hub/city_model_structure/building_demand/surface.py +++ b/hub/city_model_structure/building_demand/surface.py @@ -72,14 +72,6 @@ class Surface: if value is not None: self._id = str(value) - # todo: implement share surfaces - @property - def share_surfaces(self): - """ - Raises not implemented error - """ - raise NotImplementedError - def _max_coord(self, axis): if axis == 'x': axis = 0 @@ -163,11 +155,11 @@ class Surface: @property def type(self): """ - Get surface type Ground, Wall or Roof + Get surface type Ground, Ground wall, Wall, Attic floor, Interior slab, Interior wall, Roof or Virtual internal + If the geometrical LoD is lower than 4, + the surfaces' types are not defined in the importer and can only be Ground, Wall or Roof :return: str """ - - # todo: there are more types: internal wall, internal floor... this method must be redefined if self._type is None: grad = np.rad2deg(self.inclination) if grad >= 170: @@ -304,7 +296,7 @@ class Surface: :return: Surface, Surface, Any """ # todo: check return types - # todo: recheck this method for LoD3 (windows) + # recheck this method for LoD3 (windows) origin = Point([0, 0, z]) normal = np.array([0, 0, 1]) plane = Plane(normal=normal, origin=origin) diff --git a/hub/city_model_structure/building_demand/thermal_zone.py b/hub/city_model_structure/building_demand/thermal_zone.py index d4e2e290..19f305d5 100644 --- a/hub/city_model_structure/building_demand/thermal_zone.py +++ b/hub/city_model_structure/building_demand/thermal_zone.py @@ -26,7 +26,8 @@ class ThermalZone: """ ThermalZone class """ - def __init__(self, thermal_boundaries, parent_internal_zone, volume, footprint_area, usage=None): + + def __init__(self, thermal_boundaries, parent_internal_zone, volume, footprint_area, usage_name=None): self._id = None self._parent_internal_zone = parent_internal_zone self._footprint_area = footprint_area @@ -40,9 +41,9 @@ class ThermalZone: self._ordinate_number = None self._view_factors_matrix = None self._total_floor_area = None - self._usage = usage + self._usage_name = usage_name self._usage_from_parent = False - if usage is None: + if usage_name is None: self._usage_from_parent = True self._hours_day = None self._days_year = None @@ -55,21 +56,21 @@ class ThermalZone: self._usages = None @property - def usage_zones(self): + def usages(self): # example 70-office_30-residential if self._usage_from_parent: - self._usages = copy.deepcopy(self._parent_internal_zone.usage_zones) + self._usages = copy.deepcopy(self._parent_internal_zone.usages) else: - values = self._usage.split('_') + values = self._usage_name.split('_') usages = [] for value in values: usages.append(value.split('-')) self._usages = [] - for parent_usage in self._parent_internal_zone.usage_zones: + for parent_usage in self._parent_internal_zone.usages: for value in usages: - if parent_usage.usage == value[1]: + if parent_usage.name == value[1]: new_usage = copy.deepcopy(parent_usage) - new_usage.percentage = float(value[0])/100 + new_usage.percentage = float(value[0]) / 100 self._usages.append(new_usage) return self._usages @@ -224,19 +225,19 @@ class ThermalZone: self._view_factors_matrix = value @property - def usage(self) -> Union[None, str]: + def usage_name(self) -> Union[None, str]: """ - Get thermal zone usage + Get thermal zone usage name :return: None or str """ if self._usage_from_parent: - if self._parent_internal_zone.usage_zones is None: + if self._parent_internal_zone.usages is None: return None - self._usage = '' - for usage_zone in self._parent_internal_zone.usage_zones: - self._usage += str(round(usage_zone.percentage * 100)) + '-' + usage_zone.usage + '_' - self._usage = self._usage[:-1] - return self._usage + self._usage_name = '' + for usage in self._parent_internal_zone.usages: + self._usage_name += str(round(usage.percentage * 100)) + '-' + usage.name + '_' + self._usage_name = self._usage_name[:-1] + return self._usage_name @staticmethod def _get_schedule_of_day(requested_day_type, schedules): @@ -252,12 +253,12 @@ class ThermalZone: Get thermal zone usage hours per day :return: None or float """ - if self.usage_zones is None: + if self.usages is None: return None if self._hours_day is None: self._hours_day = 0 - for usage_zone in self.usage_zones: - self._hours_day += usage_zone.percentage * usage_zone.hours_day + for usage in self.usages: + self._hours_day += usage.percentage * usage.hours_day return self._hours_day @property @@ -266,12 +267,12 @@ class ThermalZone: Get thermal zone usage days per year :return: None or float """ - if self.usage_zones is None: + if self.usages is None: return None if self._days_year is None: self._days_year = 0 - for usage_zone in self.usage_zones: - self._days_year += usage_zone.percentage * usage_zone.days_year + for usage in self.usages: + self._days_year += usage.percentage * usage.days_year return self._days_year @property @@ -280,14 +281,14 @@ class ThermalZone: Get thermal zone mechanical air change in air change per hour (ACH) :return: None or float """ - if self.usage_zones is None: + if self.usages is None: return None if self._mechanical_air_change is None: self._mechanical_air_change = 0 - for usage_zone in self.usage_zones: - if usage_zone.mechanical_air_change is None: + for usage in self.usages: + if usage.mechanical_air_change is None: return None - self._mechanical_air_change += usage_zone.percentage * usage_zone.mechanical_air_change + self._mechanical_air_change += usage.percentage * usage.mechanical_air_change return self._mechanical_air_change @property @@ -296,7 +297,7 @@ class ThermalZone: Get occupancy in the thermal zone :return: None or Occupancy """ - if self.usage_zones is None: + if self.usages is None: return None if self._occupancy is None: @@ -305,29 +306,35 @@ class ThermalZone: _convective_part = 0 _radiative_part = 0 _latent_part = 0 - for usage_zone in self.usage_zones: - if usage_zone.occupancy is None: + for usage in self.usages: + if usage.occupancy is None: return None - _occupancy_density += usage_zone.percentage * usage_zone.occupancy.occupancy_density - if usage_zone.occupancy.sensible_convective_internal_gain is not None: - _convective_part += usage_zone.percentage * usage_zone.occupancy.sensible_convective_internal_gain - _radiative_part += usage_zone.percentage * usage_zone.occupancy.sensible_radiative_internal_gain - _latent_part += usage_zone.percentage * usage_zone.occupancy.latent_internal_gain + _occupancy_density += usage.percentage * usage.occupancy.occupancy_density + if usage.occupancy.sensible_convective_internal_gain is not None: + _convective_part += usage.percentage * usage.occupancy.sensible_convective_internal_gain + _radiative_part += usage.percentage * usage.occupancy.sensible_radiative_internal_gain + _latent_part += usage.percentage * usage.occupancy.latent_internal_gain self._occupancy.occupancy_density = _occupancy_density self._occupancy.sensible_convective_internal_gain = _convective_part self._occupancy.sensible_radiative_internal_gain = _radiative_part self._occupancy.latent_internal_gain = _latent_part - _occupancy_reference = self.usage_zones[0].occupancy + _occupancy_reference = self.usages[0].occupancy if _occupancy_reference.occupancy_schedules is not None: _schedules = [] for i_schedule in range(0, len(_occupancy_reference.occupancy_schedules)): - schedule = copy.deepcopy(_occupancy_reference.occupancy_schedules[i_schedule]) + schedule = Schedule() + schedule.type = _occupancy_reference.occupancy_schedules[i_schedule].type + schedule.day_types = _occupancy_reference.occupancy_schedules[i_schedule].day_types + schedule.data_type = _occupancy_reference.occupancy_schedules[i_schedule].data_type + schedule.time_step = _occupancy_reference.occupancy_schedules[i_schedule].time_step + schedule.time_range = _occupancy_reference.occupancy_schedules[i_schedule].time_range + new_values = [] for i_value in range(0, len(_occupancy_reference.occupancy_schedules[i_schedule].values)): _new_value = 0 - for usage_zone in self.usage_zones: - _new_value += usage_zone.percentage * usage_zone.occupancy.occupancy_schedules[i_schedule].values[i_value] + for usage in self.usages: + _new_value += usage.percentage * usage.occupancy.occupancy_schedules[i_schedule].values[i_value] new_values.append(_new_value) schedule.values = new_values _schedules.append(schedule) @@ -340,7 +347,7 @@ class ThermalZone: Get lighting information :return: None or Lighting """ - if self.usage_zones is None: + if self.usages is None: return None if self._lighting is None: @@ -349,17 +356,17 @@ class ThermalZone: _convective_part = 0 _radiative_part = 0 _latent_part = 0 - for usage_zone in self.usage_zones: - if usage_zone.lighting is None: + for usage in self.usages: + if usage.lighting is None: return None - _lighting_density += usage_zone.percentage * usage_zone.lighting.density - if usage_zone.lighting.convective_fraction is not None: - _convective_part += usage_zone.percentage * usage_zone.lighting.density \ - * usage_zone.lighting.convective_fraction - _radiative_part += usage_zone.percentage * usage_zone.lighting.density \ - * usage_zone.lighting.radiative_fraction - _latent_part += usage_zone.percentage * usage_zone.lighting.density \ - * usage_zone.lighting.latent_fraction + _lighting_density += usage.percentage * usage.lighting.density + if usage.lighting.convective_fraction is not None: + _convective_part += usage.percentage * usage.lighting.density \ + * usage.lighting.convective_fraction + _radiative_part += usage.percentage * usage.lighting.density \ + * usage.lighting.radiative_fraction + _latent_part += usage.percentage * usage.lighting.density \ + * usage.lighting.latent_fraction self._lighting.density = _lighting_density if _lighting_density > 0: self._lighting.convective_fraction = _convective_part / _lighting_density @@ -370,16 +377,22 @@ class ThermalZone: self._lighting.radiative_fraction = 0 self._lighting.latent_fraction = 0 - _lighting_reference = self.usage_zones[0].lighting + _lighting_reference = self.usages[0].lighting if _lighting_reference.schedules is not None: _schedules = [] for i_schedule in range(0, len(_lighting_reference.schedules)): - schedule = copy.deepcopy(_lighting_reference.schedules[i_schedule]) + schedule = Schedule() + schedule.type = _lighting_reference.schedules[i_schedule].type + schedule.day_types = _lighting_reference.schedules[i_schedule].day_types + schedule.data_type = _lighting_reference.schedules[i_schedule].data_type + schedule.time_step = _lighting_reference.schedules[i_schedule].time_step + schedule.time_range = _lighting_reference.schedules[i_schedule].time_range + new_values = [] for i_value in range(0, len(_lighting_reference.schedules[i_schedule].values)): _new_value = 0 - for usage_zone in self.usage_zones: - _new_value += usage_zone.percentage * usage_zone.lighting.schedules[i_schedule].values[i_value] + for usage in self.usages: + _new_value += usage.percentage * usage.lighting.schedules[i_schedule].values[i_value] new_values.append(_new_value) schedule.values = new_values _schedules.append(schedule) @@ -392,7 +405,7 @@ class ThermalZone: Get appliances information :return: None or Appliances """ - if self.usage_zones is None: + if self.usages is None: return None if self._appliances is None: @@ -401,17 +414,17 @@ class ThermalZone: _convective_part = 0 _radiative_part = 0 _latent_part = 0 - for usage_zone in self.usage_zones: - if usage_zone.appliances is None: + for usage in self.usages: + if usage.appliances is None: return None - _appliances_density += usage_zone.percentage * usage_zone.appliances.density - if usage_zone.appliances.convective_fraction is not None: - _convective_part += usage_zone.percentage * usage_zone.appliances.density \ - * usage_zone.appliances.convective_fraction - _radiative_part += usage_zone.percentage * usage_zone.appliances.density \ - * usage_zone.appliances.radiative_fraction - _latent_part += usage_zone.percentage * usage_zone.appliances.density \ - * usage_zone.appliances.latent_fraction + _appliances_density += usage.percentage * usage.appliances.density + if usage.appliances.convective_fraction is not None: + _convective_part += usage.percentage * usage.appliances.density \ + * usage.appliances.convective_fraction + _radiative_part += usage.percentage * usage.appliances.density \ + * usage.appliances.radiative_fraction + _latent_part += usage.percentage * usage.appliances.density \ + * usage.appliances.latent_fraction self._appliances.density = _appliances_density if _appliances_density > 0: self._appliances.convective_fraction = _convective_part / _appliances_density @@ -422,16 +435,22 @@ class ThermalZone: self._appliances.radiative_fraction = 0 self._appliances.latent_fraction = 0 - _appliances_reference = self.usage_zones[0].appliances + _appliances_reference = self.usages[0].appliances if _appliances_reference.schedules is not None: _schedules = [] for i_schedule in range(0, len(_appliances_reference.schedules)): - schedule = copy.deepcopy(_appliances_reference.schedules[i_schedule]) + schedule = Schedule() + schedule.type = _appliances_reference.schedules[i_schedule].type + schedule.day_types = _appliances_reference.schedules[i_schedule].day_types + schedule.data_type = _appliances_reference.schedules[i_schedule].data_type + schedule.time_step = _appliances_reference.schedules[i_schedule].time_step + schedule.time_range = _appliances_reference.schedules[i_schedule].time_range + new_values = [] for i_value in range(0, len(_appliances_reference.schedules[i_schedule].values)): _new_value = 0 - for usage_zone in self.usage_zones: - _new_value += usage_zone.percentage * usage_zone.appliances.schedules[i_schedule].values[i_value] + for usage in self.usages: + _new_value += usage.percentage * usage.appliances.schedules[i_schedule].values[i_value] new_values.append(_new_value) schedule.values = new_values _schedules.append(schedule) @@ -444,7 +463,7 @@ class ThermalZone: Calculates and returns the list of all internal gains defined :return: [InternalGain] """ - if self.usage_zones is None: + if self.usages is None: return None if self._internal_gains is None: @@ -462,17 +481,17 @@ class ThermalZone: _base_schedule.data_type = cte.ANY_NUMBER _schedules_defined = True values = numpy.zeros([24, 8]) - for usage_zone in self.usage_zones: - for internal_gain in usage_zone.internal_gains: - _average_internal_gain += internal_gain.average_internal_gain * usage_zone.percentage - _convective_fraction += internal_gain.average_internal_gain * usage_zone.percentage \ + for usage in self.usages: + for internal_gain in usage.internal_gains: + _average_internal_gain += internal_gain.average_internal_gain * usage.percentage + _convective_fraction += internal_gain.average_internal_gain * usage.percentage \ * internal_gain.convective_fraction - _radiative_fraction += internal_gain.average_internal_gain * usage_zone.percentage \ + _radiative_fraction += internal_gain.average_internal_gain * usage.percentage \ * internal_gain.radiative_fraction - _latent_fraction += internal_gain.average_internal_gain * usage_zone.percentage \ + _latent_fraction += internal_gain.average_internal_gain * usage.percentage \ * internal_gain.latent_fraction - for usage_zone in self.usage_zones: - for internal_gain in usage_zone.internal_gains: + for usage in self.usages: + for internal_gain in usage.internal_gains: if internal_gain.schedules is None: _schedules_defined = False break @@ -481,7 +500,7 @@ class ThermalZone: break for day, _schedule in enumerate(internal_gain.schedules): for v, value in enumerate(_schedule.values): - values[v, day] += value * usage_zone.percentage + values[v, day] += value * usage.percentage if _schedules_defined: _schedules = [] @@ -506,7 +525,7 @@ class ThermalZone: Get thermal control of this thermal zone :return: None or ThermalControl """ - if self.usage_zones is None: + if self.usages is None: return None if self._thermal_control is None: @@ -514,15 +533,15 @@ class ThermalZone: _mean_heating_set_point = 0 _heating_set_back = 0 _mean_cooling_set_point = 0 - for usage_zone in self.usage_zones: - _mean_heating_set_point += usage_zone.percentage * usage_zone.thermal_control.mean_heating_set_point - _heating_set_back += usage_zone.percentage * usage_zone.thermal_control.heating_set_back - _mean_cooling_set_point += usage_zone.percentage * usage_zone.thermal_control.mean_cooling_set_point + for usage in self.usages: + _mean_heating_set_point += usage.percentage * usage.thermal_control.mean_heating_set_point + _heating_set_back += usage.percentage * usage.thermal_control.heating_set_back + _mean_cooling_set_point += usage.percentage * usage.thermal_control.mean_cooling_set_point self._thermal_control.mean_heating_set_point = _mean_heating_set_point self._thermal_control.heating_set_back = _heating_set_back self._thermal_control.mean_cooling_set_point = _mean_cooling_set_point - _thermal_control_reference = self.usage_zones[0].thermal_control + _thermal_control_reference = self.usages[0].thermal_control _types_reference = [] if _thermal_control_reference.hvac_availability_schedules is not None: _types_reference.append([cte.HVAC_AVAILABILITY, _thermal_control_reference.hvac_availability_schedules]) @@ -535,20 +554,26 @@ class ThermalZone: _schedules = [] _schedule_type = _types_reference[i_type][1] for i_schedule in range(0, len(_schedule_type)): - schedule = copy.deepcopy(_schedule_type[i_schedule]) + schedule = Schedule() + schedule.type = _schedule_type[i_schedule].type + schedule.day_types = _schedule_type[i_schedule].day_types + schedule.data_type = _schedule_type[i_schedule].data_type + schedule.time_step = _schedule_type[i_schedule].time_step + schedule.time_range = _schedule_type[i_schedule].time_range + new_values = [] for i_value in range(0, len(_schedule_type[i_schedule].values)): _new_value = 0 - for usage_zone in self.usage_zones: + for usage in self.usages: if _types_reference[i_type][0] == cte.HVAC_AVAILABILITY: - _new_value += usage_zone.percentage * \ - usage_zone.thermal_control.hvac_availability_schedules[i_schedule].values[i_value] + _new_value += usage.percentage * \ + usage.thermal_control.hvac_availability_schedules[i_schedule].values[i_value] elif _types_reference[i_type][0] == cte.HEATING_SET_POINT: - _new_value += usage_zone.percentage * \ - usage_zone.thermal_control.heating_set_point_schedules[i_schedule].values[i_value] + _new_value += usage.percentage * \ + usage.thermal_control.heating_set_point_schedules[i_schedule].values[i_value] elif _types_reference[i_type][0] == cte.COOLING_SET_POINT: - _new_value += usage_zone.percentage * \ - usage_zone.thermal_control.cooling_set_point_schedules[i_schedule].values[i_value] + _new_value += usage.percentage * \ + usage.thermal_control.cooling_set_point_schedules[i_schedule].values[i_value] new_values.append(_new_value) schedule.values = new_values _schedules.append(schedule) diff --git a/hub/city_model_structure/building_demand/usage.py b/hub/city_model_structure/building_demand/usage.py new file mode 100644 index 00000000..f623ee91 --- /dev/null +++ b/hub/city_model_structure/building_demand/usage.py @@ -0,0 +1,246 @@ +""" +Usage module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Code contributors: Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +import uuid +from typing import Union, List +import hub.helpers.constants as cte +from hub.city_model_structure.building_demand.occupancy import Occupancy +from hub.city_model_structure.building_demand.lighting import Lighting +from hub.city_model_structure.building_demand.appliances import Appliances +from hub.city_model_structure.building_demand.thermal_control import ThermalControl +from hub.city_model_structure.building_demand.internal_gain import InternalGain + + +class Usage: + """ + Usage class + """ + def __init__(self): + self._id = None + self._name = None + self._percentage = None + self._internal_gains = None + self._hours_day = None + self._days_year = None + self._mechanical_air_change = None + self._occupancy = None + self._lighting = None + self._appliances = None + self._thermal_control = None + + @property + def id(self): + """ + Get usage zone id, a universally unique identifier randomly generated + :return: str + """ + if self._id is None: + self._id = uuid.uuid4() + return self._id + + @property + def name(self) -> Union[None, str]: + """ + Get usage zone usage + :return: None or str + """ + return self._name + + @name.setter + def name(self, value): + """ + Set usage zone usage + :param value: str + """ + if value is not None: + self._name = str(value) + + @property + def percentage(self): + """ + Get usage zone percentage in range[0,1] + :return: float + """ + return self._percentage + + @percentage.setter + def percentage(self, value): + """ + Set usage zone percentage in range[0,1] + :param value: float + """ + if value is not None: + self._percentage = float(value) + + @property + def internal_gains(self) -> List[InternalGain]: + """ + Calculates and returns the list of all internal gains defined + :return: InternalGains + """ + if self._internal_gains is None: + if self.occupancy is not None: + if self.occupancy.latent_internal_gain is not None: + _internal_gain = InternalGain() + _internal_gain.type = cte.OCCUPANCY + _total_heat_gain = (self.occupancy.sensible_convective_internal_gain + + self.occupancy.sensible_radiative_internal_gain + + self.occupancy.latent_internal_gain) + _internal_gain.average_internal_gain = _total_heat_gain + _internal_gain.latent_fraction = self.occupancy.latent_internal_gain / _total_heat_gain + _internal_gain.radiative_fraction = self.occupancy.sensible_radiative_internal_gain / _total_heat_gain + _internal_gain.convective_fraction = self.occupancy.sensible_convective_internal_gain / _total_heat_gain + _internal_gain.schedules = self.occupancy.occupancy_schedules + self._internal_gains = [_internal_gain] + if self.lighting is not None: + _internal_gain = InternalGain() + _internal_gain.type = cte.LIGHTING + _internal_gain.average_internal_gain = self.lighting.density + _internal_gain.latent_fraction = self.lighting.latent_fraction + _internal_gain.radiative_fraction = self.lighting.radiative_fraction + _internal_gain.convective_fraction = self.lighting.convective_fraction + _internal_gain.schedules = self.lighting.schedules + if self._internal_gains is not None: + self._internal_gains.append(_internal_gain) + else: + self._internal_gains = [_internal_gain] + if self.appliances is not None: + _internal_gain = InternalGain() + _internal_gain.type = cte.APPLIANCES + _internal_gain.average_internal_gain = self.appliances.density + _internal_gain.latent_fraction = self.appliances.latent_fraction + _internal_gain.radiative_fraction = self.appliances.radiative_fraction + _internal_gain.convective_fraction = self.appliances.convective_fraction + _internal_gain.schedules = self.appliances.schedules + if self._internal_gains is not None: + self._internal_gains.append(_internal_gain) + else: + self._internal_gains = [_internal_gain] + return self._internal_gains + + @internal_gains.setter + def internal_gains(self, value): + """ + Set usage zone internal gains + :param value: [InternalGain] + """ + self._internal_gains = value + + @property + def hours_day(self) -> Union[None, float]: + """ + Get usage zone usage hours per day + :return: None or float + """ + return self._hours_day + + @hours_day.setter + def hours_day(self, value): + """ + Set usage zone usage hours per day + :param value: float + """ + if value is not None: + self._hours_day = float(value) + + @property + def days_year(self) -> Union[None, float]: + """ + Get usage zone usage days per year + :return: None or float + """ + return self._days_year + + @days_year.setter + def days_year(self, value): + """ + Set usage zone usage days per year + :param value: float + """ + if value is not None: + self._days_year = float(value) + + @property + def mechanical_air_change(self) -> Union[None, float]: + """ + Get usage zone mechanical air change in air change per hour (ACH) + :return: None or float + """ + return self._mechanical_air_change + + @mechanical_air_change.setter + def mechanical_air_change(self, value): + """ + Set usage zone mechanical air change in air change per hour (ACH) + :param value: float + """ + if value is not None: + self._mechanical_air_change = float(value) + + @property + def occupancy(self) -> Union[None, Occupancy]: + """ + Get occupancy in the usage zone + :return: None or Occupancy + """ + return self._occupancy + + @occupancy.setter + def occupancy(self, value): + """ + Set occupancy in the usage zone + :param value: Occupancy + """ + self._occupancy = value + + @property + def lighting(self) -> Union[None, Lighting]: + """ + Get lighting information + :return: None or Lighting + """ + return self._lighting + + @lighting.setter + def lighting(self, value): + """ + Set lighting information + :param value: Lighting + """ + self._lighting = value + + @property + def appliances(self) -> Union[None, Appliances]: + """ + Get appliances information + :return: None or Appliances + """ + return self._appliances + + @appliances.setter + def appliances(self, value): + """ + Set appliances information + :param value: Appliances + """ + self._appliances = value + + @property + def thermal_control(self) -> Union[None, ThermalControl]: + """ + Get thermal control of this thermal zone + :return: None or ThermalControl + """ + return self._thermal_control + + @thermal_control.setter + def thermal_control(self, value): + """ + Set thermal control for this thermal zone + :param value: ThermalControl + """ + self._thermal_control = value diff --git a/hub/city_model_structure/building_demand/usage_zone.py b/hub/city_model_structure/building_demand/usage_zone.py index 274d9213..43a69c20 100644 --- a/hub/city_model_structure/building_demand/usage_zone.py +++ b/hub/city_model_structure/building_demand/usage_zone.py @@ -26,7 +26,6 @@ class UsageZone: self._internal_gains = None self._hours_day = None self._days_year = None -# self._electrical_app_average_consumption_sqm_year = None self._mechanical_air_change = None self._occupancy = None self._lighting = None diff --git a/hub/city_model_structure/bus_system.py b/hub/city_model_structure/bus_system.py index 66a1bda2..bcb69793 100644 --- a/hub/city_model_structure/bus_system.py +++ b/hub/city_model_structure/bus_system.py @@ -17,8 +17,8 @@ class BusSystem(CityObject): """ BusSystem(CityObject) class """ - def __init__(self, name, surfaces, city_lower_corner): - super().__init__(name, surfaces, city_lower_corner) + def __init__(self, name, surfaces): + super().__init__(name, surfaces) self._bus_routes = None self._bus_network = None self._buses = None diff --git a/hub/city_model_structure/city.py b/hub/city_model_structure/city.py index a6aa9c8a..03764570 100644 --- a/hub/city_model_structure/city.py +++ b/hub/city_model_structure/city.py @@ -6,6 +6,8 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Code contributors: Peter Yefi peteryefi@gmail.com """ from __future__ import annotations + +import bz2 import sys import pickle import math @@ -23,7 +25,6 @@ from hub.city_model_structure.iot.station import Station from hub.city_model_structure.level_of_detail import LevelOfDetail from hub.city_model_structure.machine import Machine from hub.city_model_structure.parts_consisting_building import PartsConsistingBuilding -from hub.city_model_structure.subway_entrance import SubwayEntrance from hub.helpers.geometry_helper import GeometryHelper from hub.helpers.location import Location from hub.city_model_structure.energy_system import EnergySystem @@ -40,10 +41,7 @@ class City: self._lower_corner = lower_corner self._upper_corner = upper_corner self._buildings = None - self._subway_entrances = None self._srs_name = srs_name - self._geometry = GeometryHelper() - # todo: right now extracted at city level, in the future should be extracted also at building level if exist self._location = None self._country_code = None self._climate_reference_city = None @@ -102,13 +100,19 @@ class City: """ return self._get_location().country + @property + def location(self): + return self._get_location().city + @property def name(self): """ Get city name :return: str """ - return self._get_location().city + if self._name is None: + return self._get_location().city + return self._name @property def climate_reference_city(self) -> Union[None, str]: @@ -158,9 +162,6 @@ class City: if self.buildings is not None: for building in self.buildings: self._city_objects.append(building) - if self.subway_entrances is not None: - for subway_entrance in self.subway_entrances: - self._city_objects.append(subway_entrance) if self.energy_systems is not None: for energy_system in self.energy_systems: self._city_objects.append(energy_system) @@ -174,14 +175,6 @@ class City: """ return self._buildings - @property - def subway_entrances(self) -> Union[List[SubwayEntrance], None]: - """ - Get the subway entrances belonging to the city - :return: a list of subway entrances objects or none - """ - return self._subway_entrances - @property def lower_corner(self) -> List[float]: """ @@ -219,10 +212,6 @@ class City: if self._buildings is None: self._buildings = [] self._buildings.append(new_city_object) - elif new_city_object.type == 'subway_entrance': - if self._subway_entrances is None: - self._subway_entrances = [] - self._subway_entrances.append(new_city_object) elif new_city_object.type == 'energy_system': if self._energy_systems is None: self._energy_systems = [] @@ -280,6 +269,15 @@ class City: with open(city_filename, 'wb') as file: pickle.dump(self, file) + def save_compressed(self, city_filename): + """ + Save a city into the given filename + :param city_filename: destination city filename + :return: None + """ + with bz2.BZ2File(city_filename, 'wb') as f: + pickle.dump(self, f) + def region(self, center, radius) -> City: """ Get a region from the city diff --git a/hub/city_model_structure/city_object.py b/hub/city_model_structure/city_object.py index a6c2cad2..70594bbc 100644 --- a/hub/city_model_structure/city_object.py +++ b/hub/city_model_structure/city_object.py @@ -224,5 +224,3 @@ class CityObject: :param value: [Sensor] """ self._sensors = value - - diff --git a/hub/city_model_structure/fuel.py b/hub/city_model_structure/fuel.py index db2925f8..d3122a05 100644 --- a/hub/city_model_structure/fuel.py +++ b/hub/city_model_structure/fuel.py @@ -43,4 +43,3 @@ class Fuel: :return: str """ return self._unit - diff --git a/hub/city_model_structure/iot/sensor_measure.py b/hub/city_model_structure/iot/sensor_measure.py index 7c2d2223..12ab5811 100644 --- a/hub/city_model_structure/iot/sensor_measure.py +++ b/hub/city_model_structure/iot/sensor_measure.py @@ -5,6 +5,7 @@ Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ + class SensorMeasure: def __init__(self, latitude, longitude, utc_timestamp, value): self._latitude = latitude diff --git a/hub/city_model_structure/iot/sensor_type.py b/hub/city_model_structure/iot/sensor_type.py index 49596c50..a9477779 100644 --- a/hub/city_model_structure/iot/sensor_type.py +++ b/hub/city_model_structure/iot/sensor_type.py @@ -7,6 +7,7 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca from enum import Enum + class SensorType(Enum): HUMIDITY = 0 TEMPERATURE = 1 diff --git a/hub/city_model_structure/iot/station.py b/hub/city_model_structure/iot/station.py index bd4e912f..3ce6ee9c 100644 --- a/hub/city_model_structure/iot/station.py +++ b/hub/city_model_structure/iot/station.py @@ -26,7 +26,7 @@ class Station: return self._id @property - def _mobile(self): + def mobile(self): """ Get if the station is mobile or not :return: bool diff --git a/hub/city_model_structure/lca_calculations.py b/hub/city_model_structure/lca_calculations.py deleted file mode 100644 index b6c4b7e4..00000000 --- a/hub/city_model_structure/lca_calculations.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -LifeCycleAssessment retrieve the specific Life Cycle Assessment module for the given region -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Atiya atiya.atiya@mail.concordia.ca -""" -from hub.city_model_structure.machine import Machine - - -class LcaCalculations: - """ - LCA Calculations class - """ - def emission_disposal_machines(self, ): - return Machine.work_efficiency * Machine.energy_consumption_rate * Machine.carbon_emission_factor - - def emission_transportation(self, weight, distance ): - return weight * distance * Machine.energy_consumption_rate * Machine.carbon_emission_factor - - - diff --git a/hub/city_model_structure/level_of_detail.py b/hub/city_model_structure/level_of_detail.py index d673971c..2c8665b9 100644 --- a/hub/city_model_structure/level_of_detail.py +++ b/hub/city_model_structure/level_of_detail.py @@ -14,6 +14,8 @@ class LevelOfDetail: self._geometry = None self._construction = None self._usage = None + self._weather = None + self._surface_radiation = None @property def geometry(self): @@ -59,3 +61,33 @@ class LevelOfDetail: Set the city minimal usage level of detail, 1 or 2 """ self._usage = value + + @property + def weather(self): + """ + Get the city minimal weather level of detail, 0 (yearly), 1 (monthly), 2 (hourly) + :return: int + """ + return self._weather + + @weather.setter + def weather(self, value): + """ + Set the city minimal weather level of detail, 0 (yearly), 1 (monthly), 2 (hourly) + """ + self._usage = value + + @property + def surface_radiation(self): + """ + Get the city minimal surface radiation level of detail, 0 (yearly), 1 (monthly), 2 (hourly) + :return: int + """ + return self._surface_radiation + + @surface_radiation.setter + def surface_radiation(self, value): + """ + Set the city minimal surface radiation level of detail, 0 (yearly), 1 (monthly), 2 (hourly) + """ + self._surface_radiation = value diff --git a/hub/city_model_structure/machine.py b/hub/city_model_structure/machine.py index e06583ca..c285cf59 100644 --- a/hub/city_model_structure/machine.py +++ b/hub/city_model_structure/machine.py @@ -11,8 +11,8 @@ class Machine: Machine class """ - def __init__(self, machine_id, name, work_efficiency, work_efficiency_unit, energy_consumption_rate, energy_consumption_unit, - carbon_emission_factor, carbon_emission_unit): + def __init__(self, machine_id, name, work_efficiency, work_efficiency_unit, energy_consumption_rate, + energy_consumption_unit, carbon_emission_factor, carbon_emission_unit): self._machine_id = machine_id self._name = name self._work_efficiency = work_efficiency @@ -85,4 +85,3 @@ class Machine: :return: str """ return self._carbon_emission_unit - diff --git a/hub/city_model_structure/network.py b/hub/city_model_structure/network.py index 07242aed..83bafeec 100644 --- a/hub/city_model_structure/network.py +++ b/hub/city_model_structure/network.py @@ -18,7 +18,7 @@ class Network(CityObject): Generic network class to be used as base for the network models """ def __init__(self, name, edges=None, nodes=None): - super().__init__(name, 0, [], None) + super().__init__(name, 0) if nodes is None: nodes = [] if edges is None: diff --git a/hub/city_model_structure/subway_entrance.py b/hub/city_model_structure/subway_entrance.py deleted file mode 100644 index 0f555086..00000000 --- a/hub/city_model_structure/subway_entrance.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Subway entrance module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from hub.city_model_structure.city_object import CityObject - - -class SubwayEntrance(CityObject): - """ - SubwayEntrance(CityObject) class - """ - def __init__(self, name, latitude, longitude): - super().__init__(name, 0, []) - self._name = name - self._latitude = latitude - self._longitude = longitude - self._type = 'subway_entrance' - - @property - def latitude(self): - # todo: to be defined the spacial point and the units - """ - Get latitude - :return: float - """ - return self._latitude - - @property - def longitude(self): - # todo: to be defined the spacial point and the units - """ - Get longitude - :return: float - """ - return self._longitude - - @property - def name(self): - """ - Get name - :return: str - """ - return self._name diff --git a/hub/city_model_structure/vehicle.py b/hub/city_model_structure/vehicle.py index ddbdf631..61f36e11 100644 --- a/hub/city_model_structure/vehicle.py +++ b/hub/city_model_structure/vehicle.py @@ -10,7 +10,8 @@ class Vehicle: Vehicle class """ - def __init__(self, vehicle_id, name, fuel_consumption_rate, fuel_consumption_unit, carbon_emission_factor, carbon_emission_factor_unit): + def __init__(self, vehicle_id, name, fuel_consumption_rate, fuel_consumption_unit, carbon_emission_factor, + carbon_emission_factor_unit): self._vehicle_id = vehicle_id self._name = name self._fuel_consumption_rate = fuel_consumption_rate @@ -59,10 +60,9 @@ class Vehicle: return self._carbon_emission_factor @property - def carbon_emission_unit(self) -> str: + def carbon_emission_factor_unit(self) -> str: """ Get carbon emission units :return: str """ - return self._carbon_emission_unit - + return self._carbon_emission_factor_unit diff --git a/hub/data/construction/nrcan.xml b/hub/data/construction/nrcan.xml new file mode 100644 index 00000000..0a366273 --- /dev/null +++ b/hub/data/construction/nrcan.xml @@ -0,0 +1,76 @@ + + + + + BTAPPRE1980/data/surface_thermal_transmittance.json + BTAPPRE1980/data/window_characteristics.json + + + BTAP1980TO2010/data/surface_thermal_transmittance.json + BTAP1980TO2010/data/window_characteristics.json + + + NECB2011/data/surface_thermal_transmittance.json + BTAP1980TO2010/data/window_characteristics.json + + + NECB2017/data/surface_thermal_transmittance.json + BTAP1980TO2010/data/window_characteristics.json + + + NECB2020/data/surface_thermal_transmittance.json + BTAP1980TO2010/data/window_characteristics.json + + + + + FullServiceRestaurant/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/8414706d-3ba9-4d70-ad3c-4db62d865e1b-os-report.html + + + HighriseApartment/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/83ab3764-046e-48a8-85cd-a3c0ac761efa-os-report.html + + + Hospital/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/210dac7e-2d51-40a9-bc78-ad0bc1c57a89-os-report.html + + + LargeHotel/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/d0185276-7fe0-4da9-bb5d-8c8a7c13c405-os-report.html + + + LargeOffice/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/2da33707-50a7-4554-91ed-c5fdbc1ce3b9-os-report.html + + + MediumOffice/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/65d97bf8-8749-410b-b53d-5a9c60e0227c-os-report.html + + + MidriseApartment/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/19518153-9c28-4e40-8bbd-98ef949c1bdb-os-report.html + + + Outpatient/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/deab93c7-d086-432d-bb90-33c8c4e1fab3-os-report.html + + + PrimarySchool/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/87f45397-5ef4-4df9-be41-d33c4b6d2fb7-os-report.html + + + QuickServiceRestaurant/CAN_PQ_Montreal.Intl.AP.716270_CWEC/ 0bc55858-a81b-4d07-9923-1d84e8a23194-os-report.html + + + RetailStandalone/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/a3643bcb-0eea-47d4-b6b9-253ed188ec0c-os-report.html + + + RetailStripmall/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/ebaf5a16-00af-49de-9672-6b373fc825be-os-report.html + + + SecondarySchool/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/3a4f105f-93ed-4b8b-9eb3-c8ca40c5de6e-os-report.html + + + SmallHotel/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/dff0a3fc-d9e5-4866-9e6a-dee9a0da60b2-os-report.html + + + SmallOffice/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/a9a3669d-beb8-4951-aa11-c27dbc61a344-os-report.html + + + Warehouse/CAN_PQ_Montreal.Intl.AP.716270_CWEC/os_report/569ec649-8017-4a3c-bd0a-337eba3ec488-os-report.html + + + diff --git a/hub/data/costs/montreal_costs.xml b/hub/data/costs/montreal_costs.xml new file mode 100644 index 00000000..8cb0f0da --- /dev/null +++ b/hub/data/costs/montreal_costs.xml @@ -0,0 +1,89 @@ + + + + 56 + 9.8 + + + 43.4 + 36 + 50 + + + 78 + 984.5 + 20 + + + + + + 363.5 + 363.5 + 15 + + + 363.5 + 363.5 + 15 + + + 363.5 + 363.5 + 15 + + + + 17 + 17 + 15 + + + 365 + 365 + 15 + + + 365 + 365 + 15 + + + 88 + 2.5 + + + + + 0 + 0 + + 5.6 + + + 40 + 40 + 0.05 + 1 + 4.6 + + 30 + + 6.3 + + + 2 + 1.5 + 3.6 + + + 0 + 0 + + + 2 + + 0 + + + diff --git a/hub/data/usage/ca_archetypes_reduced.xml b/hub/data/usage/ca_archetypes_reduced.xml deleted file mode 100644 index 50e709f4..00000000 --- a/hub/data/usage/ca_archetypes_reduced.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - Building Usage Library Reduced - Library created by Sanam from HOT200 and ecobee - https://www.engineeringtoolbox.com/metabolic-heat-persons-d_706.html - - residential - All residential buildings - - 0.03 - 365 - 14 - - Persons and home appliances - 9.69 - 0.40 - 0.50 - 0.10 - - - 19.5 - - - 19.0 - 18.0 - - - 21.0 - - - 0 - - - - diff --git a/hub/data/usage/de_library.xml b/hub/data/usage/de_library.xml deleted file mode 100644 index a13e323c..00000000 --- a/hub/data/usage/de_library.xml +++ /dev/null @@ -1,2027 +0,0 @@ - - - Building Usage Library - - DIN 18599-10 - - VDI 3807-2 - - VDI 2089.1 - - VDI 4655 - - "Architect Registration Exam - - Review Manual" Section 6, Chapter 27 - - BMVBS: "Bekanntmachung der Regeln für Energieverbrauchskennwerte und der Vergleichswerte im - Nichtwohngebäudebestand" - - "ÜberÖrtlicher BetriebsVergleich Bäderbetriebe (ÜÖBV) Teil II" Deutsche Gesellschaft für das Badewesen e.V. - - EnergieAgentur NRW - - based on UsageLibrary.xlsx, sheet: Germany - false - false - Alkis - - Single family house - mostly a small house inhabited by one family - - - Multi-family house - mostly a bigger house inhabited by more than one family - - - school without shower - a school without shower facilities - - - school with shower - a school that has facilities to shower - - - Retail shop - a shop without frozen food - - - retail shop / refrigerated food - a shop with frozen food - - - Home for the aged or orphanage - this variant describes homes for indiviuals which require nursing - - - industry for washing and showering - this variant describes facilities in industry for washing and showering - - - hotel (Medium-class) - describes hotels which can be described as "medium-class" - - - dormitory - simple accommodation for students or scouts - - - Labor - laboratory and research centers - - - residential - All residential buildings - - 0.033 - 365 - 17 - - Persons and home appliances - 4.2 - 0.3999999999999999 - 0.5000000000000001 - 0.10000000000000009 - - - - - 20.0 - 16.0 - - schedule of daily heating in residential houses - - DIN 18599-10 - 16.0 16.0 16.0 16.0 16.0 16.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - - - - - - 25.0 - - - - 32.37 - 45 - - - 32.0 - - - - Single family house - All residential buildings - - 0.033 - - 0.3999999999999999 - 0.5000000000000001 - 0.10000000000000009 - - - - - - - 16.0 16.0 16.0 16.0 16.0 16.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - - - - - - - - 32.37 - - - - VDI 4655, Climate Zone 5 - - WSB - 0.036 0.036 0.036 0.036 0.036 0.036 0.032 0.028 0.028 0.028 0.028 0.032 0.036 0.036 - 0.036 0.036 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 - 0.02 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.02 0.024 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.02 0.016 0.016 0.02 0.024 0.02 0.02 0.024 0.024 0.024 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.02 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.024 0.024 0.02 0.02 0.02 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.02 0.024 0.024 0.024 0.024 0.028 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.02 - 0.02 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 - 0.02 0.02 0.016 0.016 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.024 0.028 0.028 0.028 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 - 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.028 0.028 0.028 0.024 0.024 0.02 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 - 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.024 - 0.028 0.032 0.032 0.032 0.032 0.032 0.032 0.032 0.032 0.032 0.028 0.028 0.028 0.028 - 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.032 - 0.032 0.036 0.036 0.036 0.036 0.036 0.036 0.032 0.032 0.032 0.036 0.036 0.036 0.036 - 0.036 0.036 0.036 0.032 0.028 0.028 0.028 0.028 0.028 0.028 0.083 0.138 0.138 0.138 - 0.099 0.059 0.059 0.059 0.044 0.028 0.028 0.028 0.028 0.032 0.048 0.052 0.036 0.04 0.04 - 0.04 0.04 0.036 0.052 0.032 0.036 0.036 0.036 0.036 0.052 0.052 0.036 0.032 0.032 0.036 - 0.044 0.044 0.04 0.04 0.04 0.04 0.059 0.059 0.04 0.04 0.04 0.055 0.059 0.044 0.044 0.044 - 0.044 0.044 0.044 0.044 0.044 0.048 0.048 0.048 0.044 0.044 0.044 0.04 0.036 0.036 0.036 - 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 - 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.04 0.063 0.087 0.091 - 0.091 0.087 0.126 0.126 0.122 0.083 0.044 0.044 0.04 0.04 0.044 0.044 0.044 0.044 0.044 - 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.04 0.036 0.036 0.036 0.036 0.04 0.044 0.044 - 0.04 0.04 0.044 0.044 0.044 0.044 0.048 0.052 0.052 0.048 0.048 0.048 0.048 0.048 0.048 - 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 - 0.048 0.048 0.048 0.048 0.048 0.048 0.052 0.052 0.052 0.055 0.055 0.055 0.055 0.055 - 0.055 0.055 0.052 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 - 0.048 0.052 0.052 0.048 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 - 0.044 0.044 0.048 0.052 0.052 0.052 0.052 0.075 0.075 0.048 0.048 0.048 0.048 0.044 - 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.04 0.04 0.04 0.04 0.04 0.04 0.04 0.04 - 0.04 0.04 0.04 0.036 0.04 0.044 0.048 0.048 0.044 0.048 0.044 0.044 0.04 0.04 0.04 0.036 - 0.036 0.036 0.036 0.036 0.04 0.04 0.048 0.059 0.107 0.142 0.134 0.126 0.126 0.165 0.204 - 0.204 0.204 0.208 0.208 0.2 0.216 0.244 0.248 0.248 0.244 0.244 0.244 0.248 0.244 0.248 - 0.248 0.248 0.22 0.22 0.224 0.224 0.244 0.252 0.248 0.248 0.244 0.204 0.126 0.059 0.036 - 0.032 0.028 0.067 0.103 0.059 0.02 0.044 0.067 0.044 0.024 0.024 0.067 0.067 0.028 0.028 - 0.032 0.028 0.024 0.028 0.028 0.028 0.024 0.032 0.036 0.052 0.052 0.036 0.028 0.024 - 0.024 0.028 0.028 0.028 0.028 0.028 0.024 0.044 0.044 0.028 0.032 0.036 0.036 0.028 - 0.028 0.032 0.032 0.032 0.028 0.032 0.032 0.036 0.04 0.04 0.059 0.083 0.079 0.079 0.055 - 0.032 0.048 0.048 0.028 0.032 0.044 0.04 0.032 0.04 0.044 0.04 0.04 0.044 0.036 0.044 - 0.044 0.028 0.028 0.028 0.032 0.032 0.032 0.032 0.032 0.036 0.036 0.036 0.063 0.04 0.044 - 0.087 0.122 0.122 0.122 0.122 0.181 0.24 0.244 0.22 0.197 0.197 0.216 0.236 0.236 0.236 - 0.232 0.24 0.244 0.24 0.236 0.236 0.232 0.236 0.232 0.232 0.228 0.224 0.228 0.228 0.224 - 0.228 0.228 0.185 0.146 0.15 0.146 0.122 0.063 0.071 0.071 0.028 0.024 0.02 0.02 0.016 - 0.016 0.02 0.067 0.063 0.02 0.02 0.02 0.024 0.024 0.02 0.02 0.02 0.02 0.024 0.024 0.024 - 0.024 0.024 0.02 0.02 0.024 0.024 0.02 0.016 0.02 0.024 0.02 0.02 0.02 0.02 0.028 0.024 - 0.024 0.024 0.02 0.016 0.016 0.016 0.016 0.012 0.016 0.012 0.012 0.016 0.016 0.012 0.012 - 0.012 0.012 0.012 0.012 0.016 0.012 0.024 0.024 0.016 0.016 0.016 0.016 0.012 0.012 - 0.016 0.024 0.016 0.016 0.028 0.028 0.016 0.024 0.028 0.028 0.028 0.028 0.02 0.012 0.012 - 0.012 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.016 0.02 - 0.02 0.016 0.016 0.016 0.016 0.012 0.012 0.012 0.012 0.012 0.012 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 0.02 0.016 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.016 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.012 0.012 0.012 0.012 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.02 0.028 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.032 - 0.032 0.083 0.134 0.13 0.158 0.158 0.13 0.161 0.165 0.138 0.138 0.087 0.138 0.138 0.087 - 0.036 0.036 0.036 0.036 0.079 0.126 0.126 0.126 0.122 0.122 0.122 0.122 0.177 0.228 - 0.232 0.232 0.228 0.181 0.134 0.134 0.13 0.083 0.04 0.091 0.142 0.142 0.091 0.036 0.036 - 0.04 0.032 0.036 0.04 0.04 0.04 0.04 0.04 0.04 0.04 0.036 0.036 0.04 0.087 0.134 0.134 - 0.134 0.134 0.134 0.134 0.138 0.142 0.142 0.146 0.146 0.146 0.146 0.146 0.146 0.142 - 0.138 0.095 0.052 0.048 0.048 0.044 0.04 0.04 0.044 0.044 0.048 0.052 0.052 0.052 0.052 - 0.052 0.055 0.055 0.055 0.055 0.052 0.052 0.055 0.055 0.055 0.055 0.055 0.055 0.059 - 0.059 0.059 0.059 0.059 0.059 0.059 0.055 0.055 0.055 0.055 0.055 0.055 0.055 0.055 - 0.055 0.055 0.055 0.059 0.055 0.055 0.055 0.055 0.055 0.055 0.055 0.055 0.059 0.059 - 0.059 0.059 0.059 0.059 0.055 0.055 0.055 0.052 0.048 0.048 0.048 0.036 0.028 0.028 - 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.024 0.024 0.024 - 0.024 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.028 0.028 0.028 0.028 0.028 0.028 0.024 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.028 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 - 0.02 0.016 0.016 0.016 0.016 0.016 0.016 - - - - SSX - 0.07 0.07 0.07 0.07 0.07 0.07 0.089 0.094 0.075 0.07 0.07 0.07 0.07 0.07 0.07 0.079 - 0.079 0.075 0.075 0.075 0.07 0.07 0.07 0.098 0.098 0.07 0.065 0.065 0.061 0.061 0.065 - 0.07 0.07 0.07 0.07 0.07 0.075 0.075 0.065 0.056 0.051 0.047 0.047 0.042 0.028 0.023 - 0.019 0.019 0.023 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.023 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.019 0.023 0.023 0.028 0.033 0.033 0.023 0.023 0.023 - 0.023 0.023 0.023 0.028 0.023 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 - 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 - 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.019 0.023 0.023 0.023 - 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.019 0.019 0.014 0.014 0.014 0.019 - 0.019 0.023 0.023 0.019 0.019 0.019 0.023 0.019 0.019 0.019 0.019 0.023 0.023 0.019 - 0.019 0.019 0.019 0.019 0.019 0.023 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.023 0.023 0.023 0.023 0.023 0.023 0.051 0.028 0.028 0.028 0.028 0.023 0.023 0.023 - 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 - 0.023 0.028 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.014 0.014 - 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 - 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.014 0.014 0.014 0.014 0.014 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.023 0.023 0.019 0.014 0.014 0.014 0.014 0.014 0.014 0.014 - 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.047 0.028 0.023 0.023 - 0.023 0.023 0.023 0.023 0.028 0.028 0.028 0.028 0.023 0.023 0.023 0.023 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.023 0.023 - 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.023 0.028 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 - 0.023 0.019 0.019 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.033 0.056 0.061 0.061 - 0.056 0.056 0.056 0.033 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.037 0.037 0.019 - 0.023 0.075 0.126 0.126 0.122 0.07 0.023 0.023 0.023 0.019 0.019 0.019 0.019 0.028 0.028 - 0.028 0.023 0.023 0.023 0.023 0.019 0.019 0.019 0.019 0.07 0.122 0.122 0.122 0.126 0.126 - 0.122 0.122 0.126 0.15 0.173 0.173 0.173 0.173 0.173 0.178 0.178 0.145 0.122 0.122 0.094 - 0.047 0.028 0.028 0.033 0.037 0.033 0.056 0.061 0.037 0.037 0.037 0.037 0.042 0.042 - 0.037 0.037 0.037 0.042 0.033 0.033 0.033 0.108 0.224 0.248 0.182 0.136 0.136 0.159 - 0.136 0.14 0.14 0.14 0.14 0.14 0.154 0.145 0.192 0.243 0.196 0.089 0.037 0.037 0.037 - 0.037 0.028 0.019 0.019 0.019 0.037 0.033 0.014 0.014 0.014 0.014 0.019 0.019 0.023 - 0.023 0.023 0.028 0.023 0.019 0.023 0.023 0.019 0.019 0.019 0.014 0.014 0.014 0.014 - 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 - 0.014 0.014 0.014 0.014 0.019 0.028 0.033 0.033 0.028 0.028 0.023 0.019 0.019 0.019 - 0.019 0.023 0.023 0.033 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.028 0.028 0.033 - 0.033 0.023 0.019 0.019 0.019 0.019 0.023 0.028 0.028 0.028 0.033 0.028 0.028 0.028 - 0.028 0.023 0.023 0.023 0.028 0.033 0.033 0.033 0.033 0.033 0.033 0.037 0.037 0.037 - 0.037 0.037 0.042 0.042 0.037 0.037 0.037 0.037 0.033 0.037 0.037 0.037 0.037 0.037 - 0.037 0.037 0.033 0.033 0.037 0.037 0.033 0.033 0.033 0.037 0.042 0.042 0.042 0.037 - 0.037 0.037 0.037 0.037 0.037 0.037 0.033 0.033 0.033 0.033 0.033 0.033 0.037 0.042 - 0.042 0.042 0.042 0.042 0.042 0.042 0.042 0.042 0.037 0.037 0.037 0.037 0.037 0.042 - 0.047 0.051 0.051 0.051 0.051 0.051 0.051 0.056 0.042 0.042 0.098 0.15 0.154 0.15 0.15 - 0.15 0.15 0.15 0.15 0.15 0.15 0.15 0.145 0.14 0.145 0.14 0.14 0.145 0.145 0.145 0.145 - 0.145 0.145 0.145 0.145 0.145 0.094 0.047 0.051 0.051 0.047 0.047 0.042 0.042 0.047 - 0.047 0.094 0.089 0.037 0.037 0.042 0.047 0.042 0.047 0.042 0.037 0.033 0.084 0.136 - 0.084 0.028 0.028 0.028 0.028 0.028 0.033 0.028 0.023 0.023 0.023 0.023 0.019 0.033 - 0.037 0.028 0.028 0.033 0.033 0.033 0.037 0.037 0.033 0.037 0.042 0.037 0.033 0.037 - 0.037 0.037 0.042 0.047 0.047 0.047 0.051 0.047 0.037 0.037 0.037 0.037 0.047 0.047 - 0.047 0.042 0.037 0.033 0.033 0.033 0.028 0.037 0.042 0.047 0.042 0.042 0.033 0.023 - 0.023 0.023 0.023 0.028 0.028 0.023 0.023 0.023 0.028 0.028 0.033 0.079 0.122 0.122 0.07 - 0.023 0.079 0.126 0.14 0.112 0.042 0.023 0.028 0.028 0.028 0.028 0.023 0.023 0.028 0.028 - 0.023 0.023 0.019 0.019 0.051 0.07 0.042 0.023 0.023 0.028 0.037 0.028 0.023 0.023 0.023 - 0.023 0.019 0.019 0.019 0.023 0.028 0.023 0.023 0.023 0.023 0.028 0.028 0.023 0.023 - 0.023 0.019 0.023 0.028 0.033 0.084 0.126 0.173 0.224 0.229 0.229 0.173 0.145 0.168 - 0.173 0.154 0.145 0.14 0.145 0.145 0.14 0.145 0.15 0.173 0.145 0.15 0.178 0.182 0.182 - 0.154 0.154 0.154 0.131 0.168 0.173 0.173 0.159 0.15 0.15 0.145 0.15 0.079 0.033 0.033 - 0.033 0.033 0.028 0.033 0.033 0.023 0.023 0.023 0.023 0.028 0.033 0.033 0.033 0.033 - 0.028 0.028 0.033 0.033 0.028 0.028 0.028 0.079 0.131 0.131 0.131 0.126 0.122 0.122 - 0.126 0.126 0.122 0.122 0.126 0.126 0.126 0.079 0.028 0.023 0.023 0.028 0.028 0.028 - 0.028 0.023 0.023 0.023 0.023 0.019 0.019 0.023 0.019 0.019 0.019 0.023 0.023 0.019 - 0.019 0.014 0.014 0.023 0.126 0.122 0.117 0.117 0.122 0.122 0.122 0.122 0.122 0.122 - 0.122 0.126 0.126 0.122 0.122 0.065 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 - 0.023 0.028 0.023 0.023 0.023 0.023 0.028 0.028 0.019 0.019 0.019 0.014 0.014 0.014 - 0.014 0.014 0.014 0.014 0.014 0.019 0.019 0.014 0.014 0.014 0.014 0.019 0.019 0.014 - 0.014 0.014 0.009 0.009 0.014 0.014 0.009 0.009 0.009 0.009 0.009 0.014 0.009 0.009 - 0.009 0.009 0.009 0.009 0.009 0.009 0.009 0.009 0.009 0.019 0.023 0.014 0.014 0.014 - 0.014 0.014 0.014 0.019 0.014 0.014 0.014 0.014 0.014 0.009 0.009 0.009 0.009 0.014 - 0.014 0.009 0.009 0.009 0.014 0.023 0.019 0.009 0.009 0.014 0.019 0.019 0.019 0.023 - 0.019 0.014 0.014 0.014 0.014 0.014 0.014 0.019 0.019 0.014 0.019 0.019 0.019 0.019 - 0.023 0.028 0.023 0.023 0.028 0.028 0.033 0.033 0.033 0.033 0.033 0.033 0.028 0.028 - 0.028 0.028 0.028 0.028 0.023 0.028 0.028 0.023 0.023 0.023 0.023 0.023 0.023 0.023 - 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.033 0.033 0.028 0.028 - 0.028 0.028 0.07 0.117 0.117 0.145 0.178 0.117 0.112 0.098 0.019 0.019 0.023 0.047 0.07 - 0.051 0.061 0.084 0.056 0.056 0.056 0.037 0.042 0.047 0.047 0.042 0.051 0.056 0.051 - 0.051 0.051 0.051 0.051 0.047 0.047 0.047 0.042 0.042 0.042 0.042 0.042 0.042 0.047 - 0.047 0.047 0.042 0.042 0.042 0.042 0.047 0.047 0.051 0.047 0.042 0.042 0.042 0.042 - 0.051 0.051 0.056 0.056 0.056 0.056 0.056 0.061 0.056 0.056 0.098 0.14 0.14 0.14 0.14 - 0.14 0.14 0.14 0.14 0.094 0.051 0.051 0.051 0.047 0.042 0.042 0.037 0.037 0.042 0.047 - 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 - 0.051 0.051 0.051 0.056 0.056 0.056 0.056 0.056 0.056 0.056 0.056 0.056 0.056 0.056 - 0.056 0.051 0.051 0.051 0.051 0.042 0.037 0.037 0.037 0.037 0.042 0.051 0.056 0.051 - 0.051 0.051 0.051 0.051 0.051 0.051 0.051 0.056 0.051 0.051 0.051 0.047 0.047 0.047 - 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 - 0.051 0.047 0.042 0.047 0.047 0.047 0.051 0.051 0.051 0.047 0.047 0.047 0.047 0.047 - 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.047 0.051 0.051 0.051 0.051 - 0.051 0.051 0.051 0.051 0.056 0.056 0.056 0.051 0.047 0.047 0.047 0.042 0.042 0.042 - 0.042 0.042 0.042 0.042 0.042 0.042 0.033 0.023 0.023 0.023 0.023 0.023 0.023 0.023 - 0.023 0.023 0.023 0.023 0.023 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.023 0.023 0.023 0.023 - 0.023 0.023 0.023 0.023 0.023 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 - 0.023 0.023 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 0.019 - 0.019 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.019 0.019 0.019 0.019 - 0.019 0.019 0.019 0.019 0.019 0.019 0.014 0.014 0.014 0.014 0.014 0.014 0.014 0.014 - 0.014 0.014 0.014 0.019 0.019 0.019 0.019 - - - - WWB - 0.045 0.131 0.131 0.131 0.131 0.05 0.05 0.05 0.05 0.05 0.05 0.049 0.05 0.049 0.05 - 0.049 0.051 0.05 0.05 0.05 0.05 0.05 0.044 0.044 0.044 0.05 0.05 0.049 0.05 0.049 0.05 - 0.05 0.072 0.071 0.071 0.05 0.049 0.054 0.053 0.053 0.054 0.053 0.061 0.06 0.06 0.06 - 0.06 0.056 0.049 0.049 0.048 0.048 0.045 0.044 0.044 0.044 0.044 0.045 0.045 0.045 0.045 - 0.045 0.045 0.039 0.039 0.037 0.037 0.036 0.032 0.032 0.032 0.032 0.032 0.052 0.052 - 0.033 0.033 0.033 0.034 0.042 0.042 0.042 0.042 0.035 0.032 0.034 0.042 0.046 0.045 0.04 - 0.036 0.038 0.037 0.03 0.032 0.05 0.05 0.05 0.029 0.039 0.039 0.039 0.039 0.033 0.034 - 0.032 0.033 0.033 0.033 0.034 0.034 0.034 0.034 0.034 0.033 0.032 0.033 0.033 0.033 - 0.033 0.033 0.033 0.034 0.033 0.033 0.033 0.033 0.034 0.033 0.033 0.033 0.033 0.033 - 0.033 0.052 0.054 0.055 0.034 0.041 0.044 0.043 0.042 0.042 0.042 0.042 0.042 0.042 - 0.042 0.041 0.042 0.042 0.042 0.042 0.042 0.038 0.038 0.038 0.038 0.037 0.031 0.029 - 0.039 0.039 0.039 0.039 0.047 0.047 0.039 0.029 0.029 0.029 0.029 0.049 0.049 0.028 - 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.023 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 - 0.028 0.028 0.027 0.023 0.023 0.023 0.044 0.044 0.024 0.024 0.024 0.022 0.022 0.022 - 0.022 0.022 0.021 0.021 0.021 0.021 0.021 0.021 0.021 0.022 0.022 0.022 0.022 0.022 - 0.022 0.022 0.021 0.021 0.021 0.021 0.021 0.021 0.021 0.022 0.022 0.022 0.022 0.022 - 0.022 0.022 0.025 0.025 0.046 0.046 0.032 0.031 0.031 0.031 0.031 0.031 0.031 0.031 - 0.031 0.03 0.027 0.027 0.027 0.027 0.027 0.027 0.027 0.027 0.027 0.027 0.027 0.027 0.026 - 0.026 0.026 0.026 0.026 0.026 0.026 0.027 0.027 0.027 0.027 0.027 0.027 0.026 0.026 - 0.026 0.047 0.047 0.048 0.027 0.027 0.027 0.023 0.023 0.023 0.022 0.022 0.022 0.022 - 0.022 0.022 0.028 0.027 0.027 0.028 0.027 0.032 0.161 0.161 0.161 0.158 0.164 0.163 - 0.034 0.035 0.056 0.042 0.04 0.019 0.019 0.019 0.019 0.019 0.019 0.018 0.02 0.02 0.02 - 0.02 0.041 0.042 0.021 0.021 0.021 0.021 0.021 0.021 0.021 0.021 0.021 0.021 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.021 0.021 0.17 0.169 0.149 0.149 0.15 0.151 0.149 0.149 - 0.149 0.149 0.153 0.16 0.031 0.03 0.03 0.03 0.03 0.03 0.03 0.03 0.029 0.029 0.029 0.029 - 0.023 0.021 0.026 0.026 0.026 0.026 0.023 0.021 0.025 0.153 0.153 0.174 0.173 0.173 - 0.153 0.153 0.025 0.025 0.025 0.025 0.025 0.025 0.025 0.025 0.026 0.026 0.023 0.021 - 0.021 0.021 0.021 0.017 0.017 0.017 0.017 0.017 0.017 0.017 0.017 0.017 0.017 0.017 - 0.017 0.017 0.017 0.017 0.022 0.021 0.021 0.021 0.046 0.042 0.041 0.021 0.021 0.021 - 0.021 0.021 0.021 0.021 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.027 0.066 0.064 - 0.063 0.063 0.063 0.063 0.024 0.024 0.024 0.024 0.032 0.03 0.031 0.032 0.031 0.03 0.031 - 0.031 0.031 0.031 0.031 0.031 0.031 0.051 0.051 0.05 0.029 0.03 0.031 0.031 0.031 0.031 - 0.031 0.038 0.037 0.037 0.037 0.036 0.079 0.047 0.042 0.049 0.049 0.052 0.052 0.051 - 0.051 0.044 0.043 0.042 0.04 0.04 0.04 0.04 0.037 0.04 0.027 0.035 0.035 0.053 0.053 - 0.032 0.032 0.033 0.033 0.033 0.033 0.033 0.033 0.033 0.033 0.032 0.032 0.032 0.032 - 0.032 0.029 0.028 0.028 0.028 0.029 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 - 0.027 0.027 0.027 0.028 0.028 0.027 0.047 0.055 0.035 0.035 0.034 0.034 0.039 0.038 - 0.039 0.039 0.039 0.039 0.039 0.039 0.039 0.039 0.039 0.039 0.038 0.038 0.038 0.035 - 0.035 0.038 0.042 0.042 0.042 0.042 0.035 0.035 0.037 0.035 0.03 0.032 0.052 0.05 0.029 - 0.029 0.029 0.029 0.031 0.03 0.03 0.03 0.03 0.03 0.03 0.029 0.029 0.029 0.029 0.029 - 0.029 0.036 0.036 0.035 0.036 0.035 0.035 0.035 0.035 0.027 0.028 0.116 0.118 0.136 - 0.145 0.142 0.085 0.09 0.084 0.039 0.039 0.038 0.038 0.038 0.096 0.06 0.048 0.048 0.046 - 0.081 0.065 0.065 0.065 0.044 0.044 0.044 0.044 0.08 0.034 0.026 0.062 0.025 0.024 0.027 - 0.035 0.035 0.035 0.035 0.023 0.027 0.024 0.019 0.019 0.017 0.012 0.012 0.17 0.173 0.177 - 0.169 0.16 0.16 0.165 0.165 0.017 0.017 0.017 0.017 0.017 0.017 0.017 0.016 0.016 0.016 - 0.016 0.016 0.016 0.025 0.028 0.028 0.016 0.015 0.022 0.023 0.021 0.044 0.043 0.013 - 0.012 0.011 0.011 0.012 0.012 0.012 0.024 0.015 0.015 0.032 0.035 0.031 0.011 0.011 - 0.011 0.011 0.012 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.01 0.017 0.016 - 0.038 0.037 0.017 0.021 0.021 0.021 0.02 0.02 0.02 0.02 0.02 0.021 0.02 0.02 0.02 0.02 - 0.019 0.019 0.016 0.015 0.015 0.016 0.016 0.016 0.016 0.016 0.015 0.015 0.015 0.015 - 0.015 0.037 0.037 0.036 0.016 0.015 0.015 0.015 0.015 0.016 0.016 0.012 0.011 0.011 - 0.011 0.011 0.01 0.011 0.012 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.01 0.012 0.011 - 0.011 0.011 0.011 0.011 0.015 0.014 0.015 0.016 0.015 0.015 0.015 0.039 0.035 0.035 - 0.014 0.014 0.015 0.014 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.012 0.011 0.011 - 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.012 0.012 0.012 0.011 0.011 - 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.031 0.032 0.038 0.017 0.017 - 0.017 0.016 0.016 0.016 0.016 0.016 0.017 0.017 0.017 0.021 0.021 0.021 0.021 0.021 - 0.021 0.021 0.021 0.021 0.02 0.02 0.02 0.02 0.02 0.017 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.036 0.036 0.031 0.011 0.011 - 0.011 0.011 0.011 0.011 0.011 0.012 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.012 - 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 0.011 - 0.015 0.015 0.015 0.015 0.015 0.015 0.014 0.036 0.035 0.035 0.015 0.015 0.015 0.015 - 0.015 0.011 0.011 0.011 0.011 0.011 0.012 0.012 0.012 0.012 0.012 0.013 0.013 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.013 0.012 0.012 0.014 0.021 0.02 0.02 - 0.026 0.026 0.026 0.027 0.053 0.053 0.053 0.032 0.032 0.032 0.032 0.032 0.036 0.036 - 0.036 0.036 0.036 0.037 0.035 0.035 0.033 0.035 0.035 0.035 0.035 0.035 0.035 0.034 - 0.034 0.034 0.034 0.034 0.035 0.031 0.031 0.031 0.031 0.031 0.032 0.032 0.031 0.027 - 0.027 0.032 0.052 0.052 0.053 0.032 0.032 0.032 0.032 0.032 0.033 0.032 0.032 0.032 - 0.032 0.031 0.031 0.035 0.082 0.082 0.14 0.09 0.034 0.034 0.034 0.034 0.033 0.033 0.032 - 0.032 0.032 0.032 0.033 0.032 0.034 0.025 0.025 0.037 0.042 0.041 0.042 0.026 0.017 - 0.018 0.018 0.018 0.018 0.017 0.017 0.017 0.017 0.017 0.017 0.016 0.016 0.016 0.036 - 0.035 0.036 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.022 0.026 - 0.045 0.046 0.025 0.025 0.026 0.026 0.026 0.026 0.026 0.026 0.027 0.027 0.027 0.027 - 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.023 0.043 0.043 0.023 0.024 0.024 0.023 - 0.023 0.023 0.023 0.023 0.022 0.022 0.022 0.022 0.022 0.022 0.023 0.023 0.022 0.022 - 0.017 0.018 0.018 0.018 0.018 0.018 0.018 0.018 0.018 0.02 0.02 0.02 0.024 0.044 0.044 - 0.023 0.024 0.024 0.024 0.024 0.023 0.023 0.023 0.029 0.036 0.037 0.042 0.042 0.042 - 0.048 0.049 0.129 0.128 0.128 0.128 0.048 0.049 0.049 0.048 0.048 0.049 0.048 0.048 - 0.048 0.048 0.048 0.058 0.058 0.057 0.058 0.055 0.051 0.051 0.077 0.081 0.089 0.068 - 0.069 0.069 0.068 0.068 0.068 0.068 0.068 0.069 0.069 0.065 0.064 0.068 0.075 0.082 - 0.081 0.081 0.07 0.063 0.064 0.067 0.066 0.066 0.066 0.064 0.063 0.063 0.063 0.063 0.063 - 0.063 0.063 0.063 0.062 0.062 0.059 0.062 0.063 0.062 0.062 0.062 0.062 0.063 0.076 - 0.097 0.093 0.071 0.063 0.065 0.065 0.066 0.066 0.065 0.065 0.067 0.066 0.065 0.065 - 0.061 0.061 0.067 0.065 0.065 0.07 0.079 0.081 0.082 0.094 0.082 0.082 0.082 0.081 0.094 - 0.093 0.093 0.092 0.08 0.081 0.081 0.082 0.081 0.081 0.081 0.081 0.081 0.081 0.081 0.084 - 0.067 0.073 0.073 0.068 0.059 0.079 0.079 0.079 0.062 0.055 0.056 0.055 0.058 0.061 - 0.061 0.06 0.06 0.06 0.06 0.061 0.062 0.061 0.057 0.057 0.057 0.057 0.057 0.057 0.057 - 0.061 0.06 0.061 0.061 0.061 0.061 0.061 0.061 0.061 0.06 0.06 0.06 0.06 0.066 0.065 - 0.063 0.063 0.065 0.069 0.074 0.075 0.094 0.095 0.094 0.074 0.074 0.074 0.075 0.074 - 0.074 0.074 0.073 0.073 0.067 0.067 0.067 0.066 0.065 0.066 0.066 0.066 0.065 0.067 - 0.068 0.068 0.067 0.067 0.066 0.066 0.066 0.067 0.066 0.067 0.067 0.067 0.066 0.066 - 0.062 0.061 0.065 0.065 0.065 0.065 0.09 0.087 0.086 0.087 0.065 0.066 0.064 0.064 0.065 - 0.065 0.062 0.062 0.063 0.063 0.063 0.063 0.062 0.062 0.061 0.062 0.063 0.063 0.063 - 0.063 0.063 0.063 0.057 0.067 0.062 0.055 0.055 0.063 0.055 0.055 0.047 0.055 0.055 - 0.055 0.056 0.057 0.056 0.055 0.049 0.057 0.056 0.213 0.208 0.217 0.218 0.219 0.219 - 0.217 0.067 0.076 0.074 0.077 0.074 0.073 0.074 0.074 0.073 0.073 0.072 0.072 0.07 0.076 - 0.076 0.097 0.099 0.094 0.073 0.074 0.069 0.067 0.068 0.07 0.072 0.076 0.069 0.067 0.089 - 0.085 0.065 0.063 0.071 0.071 0.071 0.073 0.082 0.067 0.063 0.063 0.063 0.063 0.063 - 0.064 0.064 0.064 0.062 0.082 0.082 0.061 0.061 0.062 0.068 0.067 0.067 0.068 0.068 - 0.063 - - - - WSB - 0.036 0.036 0.036 0.036 0.036 0.036 0.032 0.028 0.028 0.028 0.028 0.032 0.036 0.036 - 0.036 0.036 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 - 0.02 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.02 0.024 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.02 0.016 0.016 0.02 0.024 0.02 0.02 0.024 0.024 0.024 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.02 0.024 0.024 0.024 0.024 0.024 0.024 - 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.024 0.024 0.02 0.02 0.02 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.02 0.024 0.024 0.024 0.024 0.028 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.02 - 0.02 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 - 0.02 0.02 0.016 0.016 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.024 0.028 0.028 0.028 0.024 0.024 0.024 0.024 - 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 - 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.028 0.028 0.028 0.024 0.024 0.02 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 - 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 0.024 - 0.028 0.032 0.032 0.032 0.032 0.032 0.032 0.032 0.032 0.032 0.028 0.028 0.028 0.028 - 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.028 0.032 - 0.032 0.036 0.036 0.036 0.036 0.036 0.036 0.032 0.032 0.032 0.036 0.036 0.036 0.036 - 0.036 0.036 0.036 0.032 0.028 0.028 0.028 0.028 0.028 0.028 0.083 0.138 0.138 0.138 - 0.099 0.059 0.059 0.059 0.044 0.028 0.028 0.028 0.028 0.032 0.048 0.052 0.036 0.04 0.04 - 0.04 0.04 0.036 0.052 0.032 0.036 0.036 0.036 0.036 0.052 0.052 0.036 0.032 0.032 0.036 - 0.044 0.044 0.04 0.04 0.04 0.04 0.059 0.059 0.04 0.04 0.04 0.055 0.059 0.044 0.044 0.044 - 0.044 0.044 0.044 0.044 0.044 0.048 0.048 0.048 0.044 0.044 0.044 0.04 0.036 0.036 0.036 - 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 - 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.04 0.063 0.087 0.091 - 0.091 0.087 0.126 0.126 0.122 0.083 0.044 0.044 0.04 0.04 0.044 0.044 0.044 0.044 0.044 - 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.04 0.036 0.036 0.036 0.036 0.04 0.044 0.044 - 0.04 0.04 0.044 0.044 0.044 0.044 0.048 0.052 0.052 0.048 0.048 0.048 0.048 0.048 0.048 - 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 - 0.048 0.048 0.048 0.048 0.048 0.048 0.052 0.052 0.052 0.055 0.055 0.055 0.055 0.055 - 0.055 0.055 0.052 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 0.048 - 0.048 0.052 0.052 0.048 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 - 0.044 0.044 0.048 0.052 0.052 0.052 0.052 0.075 0.075 0.048 0.048 0.048 0.048 0.044 - 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.044 0.04 0.04 0.04 0.04 0.04 0.04 0.04 0.04 - 0.04 0.04 0.04 0.036 0.04 0.044 0.048 0.048 0.044 0.048 0.044 0.044 0.04 0.04 0.04 0.036 - 0.036 0.036 0.036 0.036 0.04 0.04 0.048 0.059 0.107 0.142 0.134 0.126 0.126 0.165 0.204 - 0.204 0.204 0.208 0.208 0.2 0.216 0.244 0.248 0.248 0.244 0.244 0.244 0.248 0.244 0.248 - 0.248 0.248 0.22 0.22 0.224 0.224 0.244 0.252 0.248 0.248 0.244 0.204 0.126 0.059 0.036 - 0.032 0.028 0.067 0.103 0.059 0.02 0.044 0.067 0.044 0.024 0.024 0.067 0.067 0.028 0.028 - 0.032 0.028 0.024 0.028 0.028 0.028 0.024 0.032 0.036 0.052 0.052 0.036 0.028 0.024 - 0.024 0.028 0.028 0.028 0.028 0.028 0.024 0.044 0.044 0.028 0.032 0.036 0.036 0.028 - 0.028 0.032 0.032 0.032 0.028 0.032 0.032 0.036 0.04 0.04 0.059 0.083 0.079 0.079 0.055 - 0.032 0.048 0.048 0.028 0.032 0.044 0.04 0.032 0.04 0.044 0.04 0.04 0.044 0.036 0.044 - 0.044 0.028 0.028 0.028 0.032 0.032 0.032 0.032 0.032 0.036 0.036 0.036 0.063 0.04 0.044 - 0.087 0.122 0.122 0.122 0.122 0.181 0.24 0.244 0.22 0.197 0.197 0.216 0.236 0.236 0.236 - 0.232 0.24 0.244 0.24 0.236 0.236 0.232 0.236 0.232 0.232 0.228 0.224 0.228 0.228 0.224 - 0.228 0.228 0.185 0.146 0.15 0.146 0.122 0.063 0.071 0.071 0.028 0.024 0.02 0.02 0.016 - 0.016 0.02 0.067 0.063 0.02 0.02 0.02 0.024 0.024 0.02 0.02 0.02 0.02 0.024 0.024 0.024 - 0.024 0.024 0.02 0.02 0.024 0.024 0.02 0.016 0.02 0.024 0.02 0.02 0.02 0.02 0.028 0.024 - 0.024 0.024 0.02 0.016 0.016 0.016 0.016 0.012 0.016 0.012 0.012 0.016 0.016 0.012 0.012 - 0.012 0.012 0.012 0.012 0.016 0.012 0.024 0.024 0.016 0.016 0.016 0.016 0.012 0.012 - 0.016 0.024 0.016 0.016 0.028 0.028 0.016 0.024 0.028 0.028 0.028 0.028 0.02 0.012 0.012 - 0.012 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.016 0.02 - 0.02 0.016 0.016 0.016 0.016 0.012 0.012 0.012 0.012 0.012 0.012 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.02 0.02 0.02 0.02 0.02 0.016 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.012 0.016 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.016 0.016 0.016 0.012 0.012 0.012 0.012 0.012 0.012 0.012 - 0.012 0.012 0.012 0.012 0.012 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.02 0.028 0.036 0.036 0.036 0.036 0.036 0.036 0.036 0.032 - 0.032 0.083 0.134 0.13 0.158 0.158 0.13 0.161 0.165 0.138 0.138 0.087 0.138 0.138 0.087 - 0.036 0.036 0.036 0.036 0.079 0.126 0.126 0.126 0.122 0.122 0.122 0.122 0.177 0.228 - 0.232 0.232 0.228 0.181 0.134 0.134 0.13 0.083 0.04 0.091 0.142 0.142 0.091 0.036 0.036 - 0.04 0.032 0.036 0.04 0.04 0.04 0.04 0.04 0.04 0.04 0.036 0.036 0.04 0.087 0.134 0.134 - 0.134 0.134 0.134 0.134 0.138 0.142 0.142 0.146 0.146 0.146 0.146 0.146 0.146 0.142 - 0.138 0.095 0.052 0.048 0.048 0.044 0.04 0.04 0.044 0.044 0.048 0.052 0.052 0.052 0.052 - 0.052 0.055 0.055 0.055 0.055 0.052 0.052 0.055 0.055 0.055 0.055 0.055 0.055 0.059 - 0.059 0.059 0.059 0.059 0.059 0.059 0.055 0.055 0.055 0.055 0.055 0.055 0.055 0.055 - 0.055 0.055 0.055 0.059 0.055 0.055 0.055 0.055 0.055 0.055 0.055 0.055 0.059 0.059 - 0.059 0.059 0.059 0.059 0.055 0.055 0.055 0.052 0.048 0.048 0.048 0.036 0.028 0.028 - 0.028 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.024 0.024 0.024 - 0.024 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 0.024 - 0.028 0.028 0.028 0.028 0.028 0.028 0.024 0.02 0.02 0.016 0.016 0.016 0.016 0.016 0.016 - 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.016 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.024 0.024 0.024 0.024 0.024 0.02 0.02 0.02 - 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.024 0.028 0.024 0.02 0.02 0.02 0.02 0.02 0.02 0.02 - 0.02 0.016 0.016 0.016 0.016 0.016 0.016 - - - - - - - - Multi-family house - All residential buildings - - 0.033 - - 0.3999999999999999 - 0.5000000000000001 - 0.10000000000000009 - - - - - - - 16.0 16.0 16.0 16.0 16.0 16.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - 20.0 20.0 20.0 20.0 20.0 20.0 0.0 - - - - - - - - 32.37 - - - - VDI 4655, climate zone 5 - - SWX - 0.0 0.029 0.029 0.027 0.027 0.031 0.028 0.029 0.029 0.024 0.027 0.029 0.024 0.038 - 0.03 0.027 0.028 0.025 0.024 0.026 0.03 0.034 0.032 0.03 0.035 0.04 0.044 0.047 0.036 - 0.034 0.036 0.052 0.042 0.048 0.044 0.047 0.072 0.056 0.042 0.058 0.032 0.034 0.04 0.035 - 0.034 0.032 0.035 0.038 0.042 0.039 0.031 0.029 0.029 0.031 0.041 0.059 0.075 0.058 - 0.057 0.063 0.032 0.036 0.036 0.039 0.039 0.038 0.04 0.05 0.047 0.038 0.041 0.036 0.031 - 0.034 0.038 0.041 0.087 0.059 0.048 0.039 0.042 0.038 0.042 0.048 0.076 0.093 0.103 0.08 - 0.07 0.052 0.052 0.056 0.052 0.044 0.041 0.041 - - - - WWB - 0.0 0.03 0.024 0.024 0.024 0.023 0.025 0.026 0.026 0.026 0.023 0.023 0.022 0.023 - 0.022 0.02 0.025 0.025 0.025 0.021 0.022 0.026 0.025 0.031 0.033 0.036 0.034 0.043 0.049 - 0.052 0.043 0.041 0.032 0.046 0.049 0.042 0.046 0.042 0.038 0.041 0.052 0.051 0.044 - 0.033 0.035 0.039 0.05 0.045 0.033 0.045 0.032 0.029 0.028 0.028 0.025 0.023 0.029 0.033 - 0.043 0.037 0.029 0.033 0.046 0.053 0.048 0.042 0.042 0.043 0.035 0.037 0.037 0.039 - 0.051 0.061 0.072 0.116 0.126 0.134 0.114 0.106 0.113 0.073 0.062 0.045 0.051 0.054 - 0.038 0.056 0.039 0.031 0.036 0.035 0.039 0.035 0.032 0.032 - - - - WSB - 0.0 0.025 0.026 0.024 0.025 0.021 0.024 0.028 0.026 0.023 0.025 0.027 0.029 0.025 - 0.031 0.028 0.031 0.026 0.027 0.024 0.023 0.024 0.031 0.028 0.022 0.018 0.021 0.019 - 0.024 0.029 0.033 0.028 0.035 0.053 0.039 0.038 0.039 0.033 0.036 0.032 0.046 0.048 0.08 - 0.101 0.055 0.036 0.046 0.05 0.057 0.036 0.053 0.08 0.049 0.065 0.054 0.033 0.035 0.033 - 0.033 0.031 0.032 0.035 0.033 0.038 0.044 0.057 0.083 0.08 0.083 0.058 0.074 0.073 0.057 - 0.045 0.059 0.056 0.073 0.046 0.041 0.042 0.042 0.046 0.049 0.107 0.09 0.069 0.081 0.049 - 0.043 0.039 0.034 0.027 0.023 0.023 0.025 0.026 - - - - - - - - - office and administration - All kinds of offices, libraries - - 0.1 - 250 - 11 - - Employees and electronics - 6.2 - 0.3999999999999999 - 0.4 - 0.2 - - average weekDay of Internal gain in an office - - Schedule RWTH - 1.9 1.9 1.9 1.9 1.9 1.9 11.74 15.04 21.04 26.64 32.24 32.24 21.04 26.64 32.24 32.24 - 21.04 15.44 1.9 1.9 1.9 1.9 1.9 1.9 - - - - - - - - 21.0 - 17.0 - - average weekDay of heating an office - - DIN 18599-10 - 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 17.0 17.0 17.0 17.0 17.0 17.0 - - - - - - 24.0 - - average weekDay of cooling an office - - DIN 18599-10 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - - - - - - 2.5 - - - 9.85 - 45 - - - 21.0 - - - - - education - Class rooms. lecture rooms and auditoriums - - 0.17 - 164 - 9 - - Students and electronics - 4.8 - 0.19999999999999996 - 0.5 - 0.3 - - average weekDay of Internal gain in Classroom - - Schedule RWTH - 0.6 0.6 0.6 0.6 0.6 10.85 16.25 22.25 34.25 34.25 22.25 16.25 22.25 34.25 34.25 16.25 - 10.85 0.6 0.6 0.6 0.6 0.6 0.6 0.6 - - - - - - - - 22.0 - 18.0 - - Schedule of an average school day - - DIN 18599-10 - 17.0 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 17.0 - 17.0 17.0 17.0 17.0 17.0 17.0 17.0 - - - - - - 24.0 - - Schedule of an average school day - - DIN 18599-10 - 0.0 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - - - - - - - 9.85 - 45 - - - 9.0 - - - - school without shower - Class rooms. lecture rooms and auditoriums - - 0.17 - - 0.19999999999999996 - 0.5 - 0.3 - - - 0.6 0.6 0.6 0.6 0.6 10.85 16.25 22.25 34.25 34.25 22.25 16.25 22.25 34.25 34.25 - 16.25 10.85 0.6 0.6 0.6 0.6 0.6 0.6 0.6 - - - - - - - - - - 17.0 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 17.0 - 17.0 17.0 17.0 17.0 17.0 17.0 17.0 - - - - - - - - 0.0 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 - - - - - - - 9.85 - - - - - - school with shower - Class rooms. lecture rooms and auditoriums - - 0.17 - - 0.19999999999999996 - 0.5 - 0.3 - - - 0.6 0.6 0.6 0.6 0.6 10.85 16.25 22.25 34.25 34.25 22.25 16.25 22.25 34.25 34.25 - 16.25 10.85 0.6 0.6 0.6 0.6 0.6 0.6 0.6 - - - - - - - - - - 17.0 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 17.0 - 17.0 17.0 17.0 17.0 17.0 17.0 17.0 - - - - - - - - 0.0 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 - - - - - - - 36.92 - - - - - - - event location - Spectator and audience areas - - 0.1 - 250 - 8 - - Spectators - 2.6 - 1.0 - 0.0 - 0.0 - - - - - 21.0 - 17.0 - - - 24.0 - - - 1.6 - - - 14.0 - - - - - hall - Fair/congress buildings. exhibition rooms, - - 0.333 - 150 - 9 - - Visitors - 0.0 - 1.0 - 0.0 - 0.0 - - - - - 21.0 - 17.0 - - - 24.0 - - - - 18.0 - - - - - health care - Hospitals, doctor's practices, examination and treatment rooms, laboratories - - 0.071 - 365 - 24 - - intensive-care medicine - 9.9 - 0.19999999999999996 - 0.5 - 0.3 - - - - - 22.0 - 22.0 - - cooling schedule hospital - - DIN 18599-10 - 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 - 22.0 22.0 22.0 22.0 22.0 22.0 22.0 - - - - - - 24.0 - - heating schedule hospital - - DIN 18599-10 - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - - - - - - - 147.68 - 45 - - - 357.14 - - - - Home for the aged or orphanage - Hospitals, doctor's practices, examination and treatment rooms, laboratories - - 0.066 - - 5.5 - 1.0 - 0.0 - 0.0 - - - - - - - 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 22.0 - 22.0 22.0 22.0 22.0 22.0 22.0 22.0 - - - - - - - - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - - - - - - - 86.15 - - - - - - - hotel - Hotels and other accommodations - - 0.1 - 365 - 11 - - All intern gains - 5.2 - 0.19999999999999996 - 0.5 - 0.3 - - - - - 21.0 - 21.0 - - average heating day of hotels - - DIN 18599-10 - 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - - - - - - 24.0 - - average cooling day of hotels - - DIN 18599-10 - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - - - - - - - 86.15 - 45 - - - 91.0 - - - - dormitory - Hotels and other accommodations - - 0.066 - 24 - - All intern gains - 5.5 - 0.19999999999999996 - 0.5 - 0.3 - - - - - 22.0 - 22.0 - - average heating day of dormitories - - DIN 18599-10 - 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 - 1.0 1.0 1.0 - - - - - - 24.0 - - average cooling day of dormitories - - DIN 18599-10 - 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 - 1.0 1.0 1.0 - - - - - - 1.1 - - - 36.92 - - - - - - hotel (Medium-class) - Hotels and other accommodations - - 0.1 - - All intern gains - 5.2 - 0.19999999999999996 - 0.5 - 0.3 - - - - - - - DIN 18599-10 - 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - - - - - - - - DIN 18599-10 - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - - - - - - - 86.15 - - - - - - - industry - Workshop, assembly, manufactory - - 0.05 - 230 - 9 - - production and installation - 9.9 - 0.19999999999999996 - 0.5 - 0.3 - - - - - 17.0 - 13.0 - - average heating day in production - - DIN 18599-10 - 13.0 13.0 13.0 13.0 13.0 13.0 17.0 17.0 17.0 17.0 17.0 17.0 17.0 17.0 17.0 17.0 13.0 - 13.0 13.0 13.0 13.0 13.0 13.0 13.0 - - - - - - 26.0 - - average cooling day in production - - DIN 18599-10 - 0.0 0.0 0.0 0.0 0.0 0.0 26.0 26.0 26.0 26.0 26.0 26.0 26.0 26.0 26.0 26.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - - - - - - - 44.3 - 45 - - - 322.0 - - - - - restaurant - Restaurants and canteens - - 0.833 - 300 - 14 - - Personal, customers and processes - 13.0 - 0.19999999999999996 - 0.5 - 0.3 - - - - - 21.0 - 17.0 - - average heating day in restaurants - - DIN 18599-10 - 17.0 17.0 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - - - - - - 24.0 - - average cooling day in restaurants - - DIN 18599-117 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 - 1.0 1.0 - - - - - - - 27.07 - 45 - - - 148.0 - - - - - retail - Retail shops - - 0.2 - 300 - 12 - - All intern gains - 6.5 - 1.0 - 0.0 - 0.0 - - average weekDay of Internal gain in shop - - Schedule RWTH - 0.2 0.2 0.2 0.2 0.2 0.2 10.45 15.05 17.85 17.85 17.85 20.65 20.65 20.65 17.85 17.85 - 20.65 23.45 10.4 0.2 0.2 0.2 0.2 0.2 - - - - - - - - 21.0 - 17.0 - - average weekDay of heating a shop - - DIN 18599-10 - 17.0 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 17.0 17.0 17.0 17.0 - - - - - - 24.0 - - average weekDay of cooling a shop - - DIN 18599-10 - 0.0 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 0.0 0.0 0.0 0.0 - - - - - - - 24.61 - 45 - - - 160.0 - - - - Retail shop - Retail shops - - 0.2 - - All intern gains - 6.5 - 1.0 - 0.0 - 0.0 - - - - - - - 17.0 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 17.0 17.0 17.0 17.0 - - - - - - - - 0.0 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 0.0 0.0 0.0 0.0 - - - - - - - 24.61 - - - - - - retail shop / refrigerated food - Retail shops - - 0.2 - - All intern gains - -0.1 - 1.0 - 0.0 - 0.0 - - - - - - - 17.0 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 17.0 17.0 17.0 17.0 - - - - - - - - 0.0 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 0.0 0.0 0.0 0.0 - - - - - - - 24.61 - - - - - - - sport location - gymnasiums and sport facilities with shower - - 0.2 - 365 - 15 - - All intern gains - 15.9 - 0.19999999999999996 - 0.5 - 0.3 - - - - - 21.0 - 17.0 - - average heating day of sport facilities with shower - - DIN 18599-10 - 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 21.0 21.0 21.0 17.0 - - - - - - 24.0 - - average cooling day of sport facilities with shower - - DIN 18599-10 - 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 24.0 24.0 24.0 0.0 - - - - - - - 41.84 - 45 - - - 22.0 - - - - Labor - gymnasiums and sport facilities with shower - - 0.1 - 250 - 11 - - All intern gains - 6.4 - 1.0 - 0.0 - 0.0 - - - - - 22.0 - 18.0 - - average heating day - - 17.0 17.0 17.0 17.0 17.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 21.0 - 21.0 21.0 21.0 21.0 21.0 21.0 17.0 - - - - - - - average cooling day - - 0.0 0.0 0.0 0.0 0.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 24.0 - 24.0 24.0 24.0 24.0 24.0 0.0 - - - - - - - 9.85 - - - 21.0 - - - - - - non-heated - All non-heated buildings (garage, storage hall etc.) - - 0.0 - 0 - 0 - - - - -50.0 - -50.0 - - - 50.0 - - - 0.0 - - - 0.0 - 45 - - - 0.0 - - - - - green house - Description - - 0.0 - 365 - 24 - - plants - 10.0 - 0.19999999999999996 - 0.0 - 0.8 - - - - - 18.0 - 18.0 - - - 18.0 - - - 3.0 - - - 0.0 - 45 - - - 0.0 - - - - - 1010 - Short Label - - residential - - - 1020 - Short Label - - residential - - - 1022 - Short Label - - health care - - - 1036 - Short Label - - - - 1121 - Short Label - - residential - office and administration - - - 1122 - Short Label - - residential - office and administration - - - 1123 - Short Label - - residential - retail - - - 1131 - Short Label - - residential - industry - - - 1220 - Short Label - - industry - residential - - - 1221 - Short Label - - residential - non-heated - - - 1222 - Short Label - - residential - - - 1223 - Short Label - - residential - - - 1312 - Short Label - - residential - - - 1313 - Short Label - - non-heated - - - 1379 - Short Label - - residential - - - 2020 - Short Label - - office and administration - - - 2050 - Short Label - - retail - - - 2055 - Short Label - - non-heated - - - 2060 - Short Label - - hall - - - 2071 - Short Label - - hotel - - - 2072 - Short Label - - hotel - - - 2074 - Short Label - - hotel - - - 2081 - Short Label - - restaurant - - - 2090 - Short Label - - hall - - - 2111 - Short Label - - industry - - - 2112 - Short Label - - industry - - - 2120 - Short Label - - industry - - - 2130 - Short Label - - retail - - - 2140 - Short Label - - hall - - - 2143 - Short Label - - non-heated - - - 2412 - Short Label - - hall - - - 2443 - Short Label - - office and administration - - - 2461 - Short Label - - non-heated - - - 2463 - Short Label - - non-heated - - - 2465 - Short Label - - non-heated - - - 2501 - Short Label - - industry - - - 2510 - Short Label - - non-heated - - - 2513 - Short Label - - non-heated - - - 2523 - Short Label - - industry - - - 2541 - Short Label - - non-heated - - - 2611 - Short Label - - non-heated - - - 2612 - Short Label - - non-heated - - - 2622 - Short Label - - industry - - - 2720 - Short Label - - non-heated - - - 2721 - Short Label - - non-heated - - - 2723 - Short Label - - non-heated - - - 2724 - Short Label - - non-heated - - - 2726 - Short Label - - non-heated - - - 2729 - Short Label - - office and administration - - - 2740 - Short Label - - green house - - - 2849 - Short Label - - health care - - - 2891 - Short Label - - non-heated - - - 2894 - Short Label - - non-heated - - - 3010 - Short Label - - office and administration - - - 3012 - Short Label - - office and administration - - - 3013 - Short Label - - office and administration - - - 3015 - Short Label - - office and administration - - - 3021 - Short Label - - education - - - 3023 - Short Label - - education - - - 3024 - Short Label - - office and administration - - - 3031 - Short Label - - hall - - - 3034 - Short Label - - hall - - - 3036 - Short Label - - event location - - - 3037 - Short Label - - office and administration - - - 3038 - Short Label - - hall - - - 3041 - Short Label - - event location - - - 3043 - Short Label - - event location - - - 3044 - Short Label - - office and administration - - - 3051 - Short Label - - health care - - - 3065 - Short Label - - education - - - 3071 - Short Label - - office and administration - - - 3072 - Short Label - - hall - - - 3074 - Short Label - - non-heated - - - 3075 - Short Label - - hotel - - - 3080 - Short Label - - hall - - - 3090 - Short Label - - hall - - - 3210 - Short Label - - sport location - - - 3211 - Short Label - - sport location - - - 3220 - Short Label - - health care - - - 3221 - Short Label - - sport location - - - 3240 - Short Label - - health care - - - 3242 - Short Label - - health care - - - 3260 - Short Label - - hall - - - 9701 - Short Label - - hall - - diff --git a/hub/data/usage/nrcan.xml b/hub/data/usage/nrcan.xml index 5f160c9a..dfcbf90a 100644 --- a/hub/data/usage/nrcan.xml +++ b/hub/data/usage/nrcan.xml @@ -1,9 +1,5 @@ - - - NECB2020/data/space_types.json - NECB2015/data/schedules.json - - + NECB2020/data/space_types.json + NECB2015/data/schedules.json \ No newline at end of file diff --git a/hub/exports/building_energy/energy_ade.py b/hub/exports/building_energy/energy_ade.py index 419e4f02..53afcb08 100644 --- a/hub/exports/building_energy/energy_ade.py +++ b/hub/exports/building_energy/energy_ade.py @@ -75,7 +75,7 @@ class EnergyAde: 'energy:type': 'grossVolume', 'energy:value': { '@uom': 'm3', - 'energy:value': building.volume + '#text': f'{building.volume}' } } } @@ -169,7 +169,7 @@ class EnergyAde: def _building_geometry(self, building, building_dic, city): building_dic['bldg:Building']['bldg:function'] = building.function - building_dic['bldg:Building']['bldg:usage'] = ', '.join([u.usage for u in building.usage_zones]) + building_dic['bldg:Building']['bldg:usage'] = building.usages_percentage building_dic['bldg:Building']['bldg:yearOfConstruction'] = building.year_of_construction building_dic['bldg:Building']['bldg:roofType'] = building.roof_type building_dic['bldg:Building']['bldg:measuredHeight'] = { @@ -178,16 +178,86 @@ class EnergyAde: } building_dic['bldg:Building']['bldg:storeysAboveGround'] = building.storeys_above_ground - if building.lod == 1: + if city.level_of_detail.geometry == 1: building_dic = self._lod1(building, building_dic, city) - elif building.lod == 2: + elif city.level_of_detail.geometry == 2: building_dic = self._lod2(building, building_dic, city) else: raise NotImplementedError('Only lod 1 and 2 can be exported') return building_dic def _lod1(self, building, building_dic, city): - raise NotImplementedError('Only lod 1 and 2 can be exported') + self._surface_members = [] + boundaries = [{ + 'gml:Envelope': { + '@srsName': city.srs_name, + '@srsDimension': 3, + 'gml:lowerCorner': ' '.join([str(e) for e in city.lower_corner]), + 'gml:upperCorner': ' '.join([str(e) for e in city.upper_corner]) + }}] + for surface in building.surfaces: + surface_member = {'@xlink:href': f'#PolyId{surface.name}'} + self._surface_members.append(surface_member) + if surface.type == 'Wall': + surface_type = 'bldg:WallSurface' + elif surface.type == 'Ground': + surface_type = 'bldg:GroundSurface' + else: + surface_type = 'bldg:RoofSurface' + surface_dic = { + surface_type: { + '@gml:id': f'GML_{uuid.uuid4()}', + 'gml:name': f'{surface.name} ({surface.type})', + 'gml:boundedBy': { + 'gml:Envelope': { + '@srsName': city.srs_name, + 'gml:lowerCorner': f'{surface.lower_corner[0]} {surface.lower_corner[1]}' + f' {surface.lower_corner[2]}', + 'gml:upperCorner': f'{surface.upper_corner[0]} {surface.upper_corner[1]}' + f' {surface.upper_corner[2]}' + } + }, + 'bldg:lod1MultiSurface': { + 'gml:MultiSurface': { + '@srsName': city.srs_name, + '@gml:id': f'GML_{uuid.uuid4()}', + 'surfaceMember': { + 'gml:Polygon': { + '@srsName': city.srs_name, + '@gml:id': f'PolyId{surface.name}', + 'gml:exterior': { + 'gml:LinearRing': { + '@gml:id': f'PolyId{surface.name}_0', + 'gml:posList': { + '@srsDimension': '3', + '@count': len(surface.solid_polygon.coordinates) + 1, + '#text': f'{" ".join(map(str, surface.solid_polygon.points_list))} ' + f'{" ".join(map(str, surface.solid_polygon.coordinates[0]))}' + } + } + } + } + } + } + } + } + } + boundaries.append(surface_dic) + building_dic['bldg:Building']['bldg:lod1Solid'] = { + 'gml:Solid': { + '@gml:id': f'GML_{uuid.uuid4()}', + 'gml:exterior': { + 'gml:CompositeSurface': { + '@srsName': city.srs_name, + '@gml:id': f'GML_{uuid.uuid4()}', + 'gml:surfaceMember': self._surface_members + } + } + } + } + + building_dic['bldg:Building']['gml:boundedBy'] = boundaries + return building_dic def _lod2(self, building, building_dic, city): self._surface_members = [] @@ -264,56 +334,57 @@ class EnergyAde: def _thermal_zones(self, building, city): thermal_zones = [] - for index, thermal_zone in enumerate(building.thermal_zones): - usage_zones = [] - for usage_zone in thermal_zone.usage_zones: - usage_zones.append({'@xlink:href': f'#GML_{usage_zone.id}'}) - thermal_zone_dic = { - 'energy:ThermalZone': { - '@gml:id': f'GML_{thermal_zone.id}', - 'gml:name': f'Thermal zone {index} in {building.name} building', - 'energy:contains': [], - 'energy:floorArea': { - 'energy:FloorArea': { - 'energy:type': 'grossFloorArea', - 'energy:value': { - '@uom': 'm2', - '#text': f'{thermal_zone.footprint_area}' - } - } - }, - 'energy:volume': { - 'energy:VolumeType': { - 'energy:type': 'grossVolume', - 'energy:value': { - '@uom': 'm3', - # todo: for now we have just one thermal zone, therefore is the building volume, this need to be changed - '#text': f'{building.volume}' - } - } - }, - 'energy:isCooled': f'{thermal_zone.is_cooled}', - 'energy:isHeated': f'{thermal_zone.is_heated}', - 'energy:volumeGeometry': { - 'gml:Solid': { - '@gml:id': f'GML_{uuid.uuid4()}', - 'gml:exterior': { - 'gml:CompositeSurface': { - '@srsName': f'{city.srs_name}', - '@gml:id': f'GML_{uuid.uuid4()}', - 'gml:surfaceMember': self._surface_members + for internal_zone in building.internal_zones: + for index, thermal_zone in enumerate(internal_zone.thermal_zones): + usages = [] + for usage in internal_zone.usages: + usages.append({'@xlink:href': f'#GML_{usage.id}'}) + thermal_zone_dic = { + 'energy:ThermalZone': { + '@gml:id': f'GML_{thermal_zone.id}', + 'gml:name': f'Thermal zone {index} in {building.name} building', + 'energy:contains': [], + 'energy:floorArea': { + 'energy:FloorArea': { + 'energy:type': 'grossFloorArea', + 'energy:value': { + '@uom': 'm2', + '#text': f'{thermal_zone.footprint_area}' } } - } - }, - 'energy:boundedBy': self._thermal_boundaries(city, thermal_zone) + }, + 'energy:volume': { + 'energy:VolumeType': { + 'energy:type': 'grossVolume', + 'energy:value': { + '@uom': 'm3', + '#text': f'{thermal_zone.volume}' + } + } + }, + 'energy:isCooled': f'{building.is_conditioned}'.lower(), + 'energy:isHeated': f'{building.is_conditioned}'.lower(), + 'energy:volumeGeometry': { + 'gml:Solid': { + '@gml:id': f'GML_{uuid.uuid4()}', + 'gml:exterior': { + 'gml:CompositeSurface': { + '@srsName': f'{city.srs_name}', + '@gml:id': f'GML_{uuid.uuid4()}', + 'gml:surfaceMember': self._surface_members + } + } + } + }, + 'energy:boundedBy': self._thermal_boundaries(city, thermal_zone) + } } - } - thermal_zone_dic['energy:ThermalZone']['energy:contains'] = usage_zones - thermal_zones.append(thermal_zone_dic) + thermal_zone_dic['energy:ThermalZone']['energy:contains'] = usages + thermal_zones.append(thermal_zone_dic) return thermal_zones - def _thermal_boundaries(self, city, thermal_zone): + @staticmethod + def _thermal_boundaries(city, thermal_zone): thermal_boundaries = [] for thermal_boundary in thermal_zone.thermal_boundaries: thermal_boundary_dic = { @@ -322,11 +393,11 @@ class EnergyAde: 'energy:thermalBoundaryType': thermal_boundary.type, 'energy:azumuth': { '@uom': 'rad', - '#text': f'{thermal_boundary.azimuth}' + '#text': f'{thermal_boundary.parent_surface.azimuth}' }, 'energy:inclination': { '@uom': 'rad', - '#text': f'{thermal_boundary.inclination}' + '#text': f'{thermal_boundary.parent_surface.inclination}' }, 'energy:area': { '@uom': 'm2', @@ -345,9 +416,9 @@ class EnergyAde: '@gml:id': f'GML_{uuid.uuid4()}', 'gml:posList': { '@srsDimension': '3', - '@count': len(thermal_boundary.surface.solid_polygon.coordinates) + 1, - '#text': f'{" ".join(map(str, thermal_boundary.surface.solid_polygon.points_list))} ' - f'{" ".join(map(str, thermal_boundary.surface.solid_polygon.coordinates[0]))}' + '@count': len(thermal_boundary.parent_surface.solid_polygon.coordinates) + 1, + '#text': f'{" ".join(map(str, thermal_boundary.parent_surface.solid_polygon.points_list))} ' + f'{" ".join(map(str, thermal_boundary.parent_surface.solid_polygon.coordinates[0]))}' } } } @@ -364,7 +435,7 @@ class EnergyAde: '@xlink:href': f'#GML_{construction[0]}' } if thermal_boundary.thermal_openings is not None: - for opening in thermal_boundary.thermal_openings: + for _ in thermal_boundary.thermal_openings: opening_construction.append(uuid.uuid4()) thermal_boundary_dic['energy:contains'] = { 'energy:ThermalOpening': { diff --git a/hub/exports/building_energy/idf.py b/hub/exports/building_energy/idf.py index edfb604e..e33a6d34 100644 --- a/hub/exports/building_energy/idf.py +++ b/hub/exports/building_energy/idf.py @@ -206,9 +206,9 @@ class Idf: _schedule.values = _infiltration_values _infiltration_schedules.append(_schedule) for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]: - if schedule.Name == f'Infiltration schedules {thermal_zone.usage}': + if schedule.Name == f'Infiltration schedules {thermal_zone.usage_name}': return - return self._add_standard_compact_hourly_schedule(thermal_zone.usage, 'Infiltration', _infiltration_schedules) + return self._add_standard_compact_hourly_schedule(thermal_zone.usage_name, 'Infiltration', _infiltration_schedules) def _add_people_activity_level_schedules(self, thermal_zone): _occ = thermal_zone.occupancy @@ -218,9 +218,9 @@ class Idf: _total_heat = (_occ.sensible_convective_internal_gain + _occ.sensible_radiative_internal_gain + _occ.latent_internal_gain) / _occ.occupancy_density for schedule in self._idf.idfobjects[self._COMPACT_SCHEDULE]: - if schedule.Name == f'Activity Level schedules {thermal_zone.usage}': + if schedule.Name == f'Activity Level schedules {thermal_zone.usage_name}': return - _kwargs = {'Name': f'Activity Level schedules {thermal_zone.usage}', + _kwargs = {'Name': f'Activity Level schedules {thermal_zone.usage_name}', 'Schedule_Type_Limits_Name': self.idf_type_limits[cte.ANY_NUMBER], 'Field_1': 'Through: 12/31', 'Field_2': 'For AllDays', @@ -299,15 +299,15 @@ class Idf: self._add_heating_system(thermal_zone, name) def _add_thermostat(self, thermal_zone): - thermostat_name = f'Thermostat {thermal_zone.usage}' + thermostat_name = f'Thermostat {thermal_zone.usage_name}' for thermostat in self._idf.idfobjects[self._THERMOSTAT]: if thermostat.Name == thermostat_name: return thermostat # todo: change schedules to schedule name and create schedules using the add_schedule function return self._idf.newidfobject(self._THERMOSTAT, Name=thermostat_name, - Heating_Setpoint_Schedule_Name=f'Heating thermostat schedules {thermal_zone.usage}', - Cooling_Setpoint_Schedule_Name=f'Cooling thermostat schedules {thermal_zone.usage}') + Heating_Setpoint_Schedule_Name=f'Heating thermostat schedules {thermal_zone.usage_name}', + Cooling_Setpoint_Schedule_Name=f'Cooling thermostat schedules {thermal_zone.usage_name}') def _add_heating_system(self, thermal_zone, zone_name): for air_system in self._idf.idfobjects[self._IDEAL_LOAD_AIR_SYSTEM]: @@ -316,9 +316,9 @@ class Idf: thermostat = self._add_thermostat(thermal_zone) self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM, Zone_Name=zone_name, - System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}', - Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}', - Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}', + System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}', + Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}', + Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}', Template_Thermostat_Name=thermostat.Name) def _add_occupancy(self, thermal_zone, zone_name): @@ -329,11 +329,11 @@ class Idf: self._idf.newidfobject(self._PEOPLE, Name=f'{zone_name}_occupancy', Zone_or_ZoneList_Name=zone_name, - Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}', + Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage_name}', Number_of_People_Calculation_Method="People", Number_of_People=number_of_people, Fraction_Radiant=fraction_radiant, - Activity_Level_Schedule_Name=f'Activity Level schedules {thermal_zone.usage}' + Activity_Level_Schedule_Name=f'Activity Level schedules {thermal_zone.usage_name}' ) def _add_infiltration(self, thermal_zone, zone_name): @@ -343,7 +343,7 @@ class Idf: self._idf.newidfobject(self._INFILTRATION, Name=f'{zone_name}_infiltration', Zone_or_ZoneList_Name=zone_name, - Schedule_Name=f'Infiltration schedules {thermal_zone.usage}', + Schedule_Name=f'Infiltration schedules {thermal_zone.usage_name}', Design_Flow_Rate_Calculation_Method='AirChanges/Hour', Air_Changes_per_Hour=thermal_zone.mechanical_air_change ) @@ -386,7 +386,7 @@ class Idf: self._add_vegetation_material(thermal_boundary.parent_surface.vegetation) for thermal_opening in thermal_boundary.thermal_openings: self._add_window_construction_and_material(thermal_opening) - usage = thermal_zone.usage + usage = thermal_zone.usage_name if building.name in self._target_buildings or building.name in self._adjacent_buildings: self._add_infiltration_schedules(thermal_zone) self._add_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules) @@ -461,8 +461,8 @@ class Idf: if surface.Type == self.idf_surfaces[boundary.surface.type]: surface.Construction_Name = boundary.construction_name break - for usage_zone in thermal_zone.usage_zones: - surface.Zone_Name = usage_zone.id + for usage in thermal_zone.usages: + surface.Zone_Name = usage.id break break self._idf.intersect_match() diff --git a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py index 3fb01bef..af8462c0 100644 --- a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py +++ b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py @@ -77,25 +77,25 @@ class InselMonthlyEnergyBalance(Insel): parameters.append('1 % BP(9) Usage type (0=standard, 1=IWU)') # ZONES AND SURFACES - parameters.append(f'{len(internal_zone.usage_zones)} % BP(10) Number of zones') + parameters.append(f'{len(internal_zone.usages)} % BP(10) Number of zones') - for i, usage_zone in enumerate(internal_zone.usage_zones): - percentage_usage = usage_zone.percentage + for i, usage in enumerate(internal_zone.usages): + percentage_usage = usage.percentage parameters.append(f'{float(internal_zone.area) * percentage_usage} % BP(11) #1 Area of zone {i + 1} (m2)') total_internal_gain = 0 - for ig in usage_zone.internal_gains: + for ig in usage.internal_gains: total_internal_gain += float(ig.average_internal_gain) * \ (float(ig.convective_fraction) + float(ig.radiative_fraction)) parameters.append(f'{total_internal_gain} % BP(12) #2 Internal gains of zone {i + 1}') - parameters.append(f'{usage_zone.thermal_control.mean_heating_set_point} % BP(13) #3 Heating setpoint temperature ' + parameters.append(f'{usage.thermal_control.mean_heating_set_point} % BP(13) #3 Heating setpoint temperature ' f'zone {i + 1} (degree Celsius)') - parameters.append(f'{usage_zone.thermal_control.heating_set_back} % BP(14) #4 Heating setback temperature ' + parameters.append(f'{usage.thermal_control.heating_set_back} % BP(14) #4 Heating setback temperature ' f'zone {i + 1} (degree Celsius)') - parameters.append(f'{usage_zone.thermal_control.mean_cooling_set_point} % BP(15) #5 Cooling setpoint temperature ' + parameters.append(f'{usage.thermal_control.mean_cooling_set_point} % BP(15) #5 Cooling setpoint temperature ' f'zone {i + 1} (degree Celsius)') - parameters.append(f'{usage_zone.hours_day} % BP(16) #6 Usage hours per day zone {i + 1}') - parameters.append(f'{usage_zone.days_year} % BP(17) #7 Usage days per year zone {i + 1}') - parameters.append(f'{usage_zone.mechanical_air_change} % BP(18) #8 Minimum air change rate zone {i + 1} (ACH)') + parameters.append(f'{usage.hours_day} % BP(16) #6 Usage hours per day zone {i + 1}') + parameters.append(f'{usage.days_year} % BP(17) #7 Usage days per year zone {i + 1}') + parameters.append(f'{usage.mechanical_air_change} % BP(18) #8 Minimum air change rate zone {i + 1} (ACH)') parameters.append(f'{len(thermal_zone.thermal_boundaries)} % Number of surfaces = BP(11+8z) \n' f'% 1. Surface type (1=wall, 2=ground 3=roof, 4=flat roof)\n' @@ -167,6 +167,8 @@ class InselMonthlyEnergyBalance(Insel): f'inclination {np.rad2deg(surface.inclination)} (degrees)'] if surface.type != 'Ground': + if cte.MONTH not in surface.global_irradiance: + raise ValueError(f'surface: {surface.name} from building {building.name} has no global irradiance!') global_irradiance = surface.global_irradiance[cte.MONTH] for j in range(0, len(global_irradiance)): parameters.append(f'{j + 1} {global_irradiance.at[j, radiation_calculation_method]}') @@ -176,15 +178,17 @@ class InselMonthlyEnergyBalance(Insel): file = Insel._add_block(file, i_block, 'polyg', inputs=inputs, parameters=parameters) - i_block = 300 + i_block = 300 + len(surfaces) inputs = ['4.1', '4.2'] file = Insel._add_block(file, i_block, 'cum', inputs=inputs) - i_block = 303 - inputs = ['300.1', '300.2'] + in_1 = f'{i_block}.1' + in_2 = f'{i_block}.2' + i_block = 303 + len(surfaces) + inputs = [in_1, in_2] file = Insel._add_block(file, i_block, 'atend', inputs=inputs) - i_block = 310 + i_block = 310 + len(surfaces) inputs = ['4.1', '4.2'] parameters = ['1 % Mode', '0 % Suppress FNQ inputs', diff --git a/hub/exports/db_factory.py b/hub/exports/db_factory.py index 482ed481..32906fde 100644 --- a/hub/exports/db_factory.py +++ b/hub/exports/db_factory.py @@ -4,8 +4,10 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project CoderPeter Yefi peteryefi@gmail.com """ -from hub.persistence import CityRepo -from hub.persistence import HeatPumpSimulationRepo +from hub.persistence import City +from hub.persistence import Application +from hub.persistence import User +from hub.persistence import CityObject class DBFactory: @@ -14,40 +16,38 @@ class DBFactory: """ def __init__(self, db_name, app_env, dotenv_path): - self._city_repo = CityRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) - self._hp_simulation_repo = HeatPumpSimulationRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + self._city = City(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + self._application = Application(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + self._user = User(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + self._city_object = CityObject(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) def get_city(self, city_id): """ Retrieve a single city from postgres :param city_id: the id of the city to get """ - return self._city_repo.get_by_id(city_id) + return self._city.get_by_id(city_id) def get_city_by_name(self, city_name): """ Retrieve a single city from postgres :param city_name: the name of the city to get """ - return self._city_repo.get_by_name(city_name) + return self._city.get_by_name(city_name) def get_city_by_user(self, user_id): """ Retrieve cities created by user :param user_id: the id of the user """ - return self._city_repo.get_by_user(user_id) + return self._city.get_by_user(user_id) - def get_hp_simulation(self, hp_sim_id: int): - """ - Retrieve a single heat pump simulation from postgres - :param hp_sim_id: the id of the heat pump to get - """ - return self._hp_simulation_repo.get_by_id(hp_sim_id) + def application_info(self, application_uuid): + return self._application.get_by_uuid(application_uuid) + + def user_info(self, name, password, application_id): + return self._user.get_by_name_application_and_password(name, password, application_id) + + def building_info(self, name, city_id): + return self._city_object.get_by_name_and_city(name, city_id) - def get_hp_simulation_by_city(self, city_id: int): - """ - Retrieve a single city from postgres - :param city_id: the id of the city - """ - return self._hp_simulation_repo.get_by_city(city_id) diff --git a/hub/exports/energy_building_exports_factory.py b/hub/exports/energy_building_exports_factory.py index dd0d9170..0ae9ae55 100644 --- a/hub/exports/energy_building_exports_factory.py +++ b/hub/exports/energy_building_exports_factory.py @@ -9,6 +9,8 @@ from pathlib import Path from hub.exports.building_energy.energy_ade import EnergyAde from hub.exports.building_energy.idf import Idf from hub.exports.building_energy.insel.insel_monthly_energy_balance import InselMonthlyEnergyBalance +from hub.helpers.utils import validate_import_export_type +from hub.hub_logger import logger class EnergyBuildingsExportsFactory: @@ -18,6 +20,11 @@ class EnergyBuildingsExportsFactory: def __init__(self, export_type, city, path, target_buildings=None, adjacent_buildings=None): self._city = city self._export_type = '_' + export_type.lower() + class_funcs = validate_import_export_type(EnergyBuildingsExportsFactory) + if self._export_type not in class_funcs: + err_msg = f"Wrong import type [{self._export_type}]. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) if isinstance(path, str): path = Path(path) self._path = path diff --git a/hub/exports/energy_systems/heat_pump_export.py b/hub/exports/energy_systems/heat_pump_export.py index 6496dc95..b344dd8c 100644 --- a/hub/exports/energy_systems/heat_pump_export.py +++ b/hub/exports/energy_systems/heat_pump_export.py @@ -64,8 +64,8 @@ class HeatPumpExport: # User output return self._get_user_out_put() except IOError as err: - print("I/O exception: {}".format(err)) - logger.error(f'An I/O error occurred while running insel: {err}') + print("I/O exception: {}".format(str(err))) + logger.error(f'An I/O error occurred while running insel: {str(err)}') finally: insel_file_handler.close() insel_template_handler.close() @@ -252,5 +252,3 @@ class HeatPumpExport: s = pd.Series(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "Total"]) df = df.set_index([s]) df.to_csv(self._output_path) - - diff --git a/hub/exports/exports_factory.py b/hub/exports/exports_factory.py index 5ee3a686..678b5a06 100644 --- a/hub/exports/exports_factory.py +++ b/hub/exports/exports_factory.py @@ -9,20 +9,33 @@ from pathlib import Path from hub.exports.formats.obj import Obj from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm from hub.exports.formats.stl import Stl +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type class ExportsFactory: """ Exports factory class """ - def __init__(self, export_type, city, path, target_buildings=None, adjacent_buildings=None): + def __init__(self, export_type, city, path, + target_buildings=None, + adjacent_buildings=None, + weather_file=None, + weather_format=None): self._city = city self._export_type = '_' + export_type.lower() + class_funcs = validate_import_export_type(ExportsFactory) + if self._export_type not in class_funcs: + err_msg = f"Wrong export type [{self._export_type}]. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) if isinstance(path, str): path = Path(path) self._path = path self._target_buildings = target_buildings self._adjacent_buildings = adjacent_buildings + self._weather_file = weather_file + self._weather_format = weather_format @property def _citygml(self): @@ -66,7 +79,10 @@ class ExportsFactory: Export the city to Simplified Radiosity Algorithm xml format :return: None """ - return SimplifiedRadiosityAlgorithm(self._city, (self._path / f'{self._city.name}_sra.xml'), + return SimplifiedRadiosityAlgorithm(self._city, + (self._path / f'{self._city.name}_sra.xml'), + self._weather_file, + self._weather_format, target_buildings=self._target_buildings) def export(self): diff --git a/hub/exports/formats/insel.py b/hub/exports/formats/insel.py index b4ff309e..f1e34e60 100644 --- a/hub/exports/formats/insel.py +++ b/hub/exports/formats/insel.py @@ -27,4 +27,3 @@ class Insel(ABC): def _export(self): raise NotImplementedError - diff --git a/hub/exports/formats/obj.py b/hub/exports/formats/obj.py index 64c856f1..ccdb07d4 100644 --- a/hub/exports/formats/obj.py +++ b/hub/exports/formats/obj.py @@ -26,7 +26,8 @@ class Obj(Triangular): file_name_out = self._city.name + '_ground.' + self._triangular_format file_path_in = (Path(self._path).resolve() / file_name_in).resolve() file_path_out = (Path(self._path).resolve() / file_name_out).resolve() - scene = GeometryFactory('obj', path=file_path_in).scene + obj = GeometryFactory('obj', path=file_path_in) + scene = obj.scene scene.rezero() obj_file = trimesh.exchange.obj.export_obj(scene) with open(file_path_out, 'w') as file: diff --git a/hub/exports/formats/simplified_radiosity_algorithm.py b/hub/exports/formats/simplified_radiosity_algorithm.py index 84e6bd30..8a19cac3 100644 --- a/hub/exports/formats/simplified_radiosity_algorithm.py +++ b/hub/exports/formats/simplified_radiosity_algorithm.py @@ -6,12 +6,25 @@ Project Coder Guillermo.GutierrezMorote@concordia.ca """ import xmltodict +from hub.imports.weather_factory import WeatherFactory +import hub.helpers.constants as cte + class SimplifiedRadiosityAlgorithm: """ Export to SRA format """ - def __init__(self, city, file_name, target_buildings=None, begin_month=1, begin_day=1, end_month=12, end_day=31): + + def __init__(self, + city, + file_name, + weather_file, + weather_format, + target_buildings=None, + begin_month=1, + begin_day=1, + end_month=12, + end_day=31): self._file_name = file_name self._begin_month = begin_month self._begin_day = begin_day @@ -19,6 +32,8 @@ class SimplifiedRadiosityAlgorithm: self._end_day = end_day self._city = city self._target_buildings = target_buildings + self._weather_format = weather_format + self._weather_file = weather_file self._export() def _correct_point(self, point): @@ -29,6 +44,34 @@ class SimplifiedRadiosityAlgorithm: return [x, y, z] def _export(self): + self._export_sra_xml() + self._export_sra_cli() + + def _export_sra_cli(self): + file = self._city.climate_file + days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + WeatherFactory(self._weather_format, self._city, file_name=self._weather_file).enrich() + content = self._city.name + '\n' + content += str(self._city.latitude) + ',' + str(self._city.longitude) + ',0.0,' + str(self._city.time_zone) + '\n' + content += '\ndm m h G_Dh G_Bn\n' + total_days = 0 + for month in range(1, 13): + if month > 1: + total_days += days_in_month[month - 2] + for day in range(1, days_in_month[month - 1] + 1): + for hour in range(1, 25): + if month == 1: + i = 24 * (day - 1) + hour - 1 + else: + i = (total_days + day - 1) * 24 + hour - 1 + representative_building = self._city.buildings[0] + content += str(day) + ' ' + str(month) + ' ' + str(hour) + ' ' \ + + str(representative_building.global_horizontal[cte.HOUR].epw[i]) + ' ' \ + + str(representative_building.beam[cte.HOUR].epw[i]) + '\n' + with open(file, "w") as file: + file.write(content) + + def _export_sra_xml(self): buildings = [] for building_index, building in enumerate(self._city.buildings): if self._target_buildings is None: diff --git a/hub/helpers/auth.py b/hub/helpers/auth.py index 161a3ca2..a9cc34b9 100644 --- a/hub/helpers/auth.py +++ b/hub/helpers/auth.py @@ -1,23 +1,16 @@ +""" +Constant module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Peter Yefi peteryefi@gmail.com +""" + import bcrypt import re class Auth(object): - @staticmethod - def validate_password(password: str) -> bool: - """ - Validates a password - :param password: the password to validate - :return: - """ - pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!#%*?&]{6,20}$" - pattern = re.compile(pattern) - if not re.search(pattern, password): - raise ValueError("Password must be between 6 to 20 characters and must have at least a number, an uppercase " - "letter, a lowercase letter, and a special character") - return True - @staticmethod def hash_password(password: str) -> str: """ diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index 77710265..b75d9d2d 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -66,45 +66,64 @@ DOOR = 'Door' SKYLIGHT = 'Skylight' # functions and usages +RESIDENTIAL = 'residential' SINGLE_FAMILY_HOUSE = 'single family house' MULTI_FAMILY_HOUSE = 'multifamily house' -ROW_HOSE = 'row house' +ROW_HOUSE = 'row house' MID_RISE_APARTMENT = 'mid rise apartment' HIGH_RISE_APARTMENT = 'high rise apartment' +OFFICE_AND_ADMINISTRATION = 'office and administration' SMALL_OFFICE = 'small office' MEDIUM_OFFICE = 'medium office' LARGE_OFFICE = 'large office' +COURTHOUSE = 'courthouse' +FIRE_STATION = 'fire station' +PENITENTIARY = 'penitentiary' +POLICE_STATION = 'police station' +POST_OFFICE = 'post office' +LIBRARY = 'library' +EDUCATION = 'education' PRIMARY_SCHOOL = 'primary school' +PRIMARY_SCHOOL_WITH_SHOWER = 'school with shower' SECONDARY_SCHOOL = 'secondary school' +UNIVERSITY = 'university' +LABORATORY_AND_RESEARCH_CENTER = 'laboratory and research centers' STAND_ALONE_RETAIL = 'stand alone retail' HOSPITAL = 'hospital' OUT_PATIENT_HEALTH_CARE = 'out-patient health care' -STRIP_MALL = 'strip mall' -SUPERMARKET = 'supermarket' -WAREHOUSE = 'warehouse' -QUICK_SERVICE_RESTAURANT = 'quick service restaurant' -FULL_SERVICE_RESTAURANT = 'full service restaurant' -SMALL_HOTEL = 'small hotel' -LARGE_HOTEL = 'large hotel' -RESIDENTIAL = 'residential' -EDUCATION = 'education' -SCHOOL_WITHOUT_SHOWER = 'school without shower' -SCHOOL_WITH_SHOWER = 'school with shower' -RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD = 'retail shop without refrigerated food' -RETAIL_SHOP_WITH_REFRIGERATED_FOOD = 'retail shop with refrigerated food' -HOTEL = 'hotel' -HOTEL_MEDIUM_CLASS = 'hotel medium class' -DORMITORY = 'dormitory' -INDUSTRY = 'industry' -RESTAURANT = 'restaurant' HEALTH_CARE = 'health care' RETIREMENT_HOME_OR_ORPHANAGE = 'retirement home or orphanage' -OFFICE_AND_ADMINISTRATION = 'office and administration' +COMMERCIAL = 'commercial' +STRIP_MALL = 'strip mall' +SUPERMARKET = 'supermarket' +RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD = 'retail shop without refrigerated food' +RETAIL_SHOP_WITH_REFRIGERATED_FOOD = 'retail shop with refrigerated food' +RESTAURANT = 'restaurant' +QUICK_SERVICE_RESTAURANT = 'quick service restaurant' +FULL_SERVICE_RESTAURANT = 'full service restaurant' +HOTEL = 'hotel' +HOTEL_MEDIUM_CLASS = 'hotel medium class' +SMALL_HOTEL = 'small hotel' +LARGE_HOTEL = 'large hotel' +DORMITORY = 'dormitory' EVENT_LOCATION = 'event location' +CONVENTION_CENTER = 'convention center' HALL = 'hall' -SPORTS_LOCATION = 'sports location' -LABOR = 'labor' GREEN_HOUSE = 'green house' +INDUSTRY = 'industry' +WORKSHOP = 'workshop' +WAREHOUSE = 'warehouse' +WAREHOUSE_REFRIGERATED = 'warehouse refrigerated' +SPORTS_LOCATION = 'sports location' +SPORTS_ARENA = 'sports arena' +GYMNASIUM = 'gymnasium' +MOTION_PICTURE_THEATRE = 'motion picture theatre' +MUSEUM = 'museum' +PERFORMING_ARTS_THEATRE = 'performing arts theatre' +TRANSPORTATION = 'transportation' +AUTOMOTIVE_FACILITY = 'automotive facility' +PARKING_GARAGE = 'parking garage' +RELIGIOUS = 'religious' NON_HEATED = 'non-heated' LIGHTING = 'Lights' @@ -133,3 +152,7 @@ FULL_HVAC = 'Heating and cooling and ventilation' # Floats MAX_FLOAT = float('inf') MIN_FLOAT = float('-inf') + +# Tools +SRA = 'sra' +INSEL_MEB = 'insel meb' diff --git a/hub/helpers/data/alkis_function_to_hub_function.py b/hub/helpers/data/alkis_function_to_hub_function.py new file mode 100644 index 00000000..37273db0 --- /dev/null +++ b/hub/helpers/data/alkis_function_to_hub_function.py @@ -0,0 +1,185 @@ +""" +Dictionaries module for Alkis function to hub function +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + + +import hub.helpers.constants as cte + +class AlkisFunctionToHubFunction: + + def __init__(self): + self._dictionary = {"1000": cte.RESIDENTIAL, + "1010": "tenement", + "1020": "hostel", + "1030": "residential- and administration building", + "1040": "residential- and office building", + "1050": "residential- and business building", + "1060": "residential- and plant building", + "1070": "agrarian- and forestry building", + "1080": "residential- and commercial building", + "1090": "forester's lodge", + "1100": "holiday house", + "1110": "summer house", + "1120": "office building", + "1130": "credit institution", + "1140": "insurance", + "1150": "business building", + "1160": "department store", + "1170": "shopping centre", + "1180": "kiosk", + "1190": "pharmacy", + "1200": "pavilion", + "1210": cte.HOTEL, + "1220": "youth hostel", + "1230": "campsite building", + "1240": "restaurant", + "1250": "cantine", + "1260": "recreational site", + "1270": "function room", + "1280": "cinema", + "1290": "bowling alley", + "1300": "casino", + "1310": "industrial building", + "1320": "factory", + "1330": cte.WORKSHOP, + "1340": "petrol / gas station", + "1350": "washing plant", + "1360": "cold store", + "1370": "depot", + "1380": "building for research purposes", + "1390": "quarry", + "1400": "salt works", + "1410": "miscellaneous industrial building", + "1420": "mill", + "1430": "windmill", + "1440": "water mill", + "1450": "bucket elevator", + "1460": "weather station", + "1470": "traffic assets office", + "1480": "street maintenance", + "1490": "waiting hall", + "1500": "signal control box", + "1510": "engine shed", + "1520": "signal box or stop signal", + "1530": "plant building for air traffic", + "1540": "hangar", + "1550": "plant building for shipping", + "1560": "shipyard", + "1570": "dock", + "1580": "plant building for canal lock", + "1590": "boathouse", + "1600": "plant building for cablecar", + "1610": "multi-storey car park", + "1620": "parking level", + "1630": "garage", + "1640": "vehicle hall", + "1650": "underground garage", + "1660": "building for supply", + "1670": "waterworks", + "1680": "pump station", + "1690": "water basin", + "1700": "electric power station", + "1710": "transformer station", + "1720": "converter", + "1730": "reactor", + "1740": "turbine house", + "1750": "boiler house", + "1760": "building for telecommunications", + "1770": "gas works", + "1780": "heat plant", + "1790": "pumping station", + "1800": "building for disposal", + "1810": "building for effluent disposal", + "1820": "building for filter plant", + "1830": "toilet", + "1840": "rubbish bunker", + "1850": "building for rubbish incineration", + "1860": "building for rubbish disposal", + "1870": "building for agrarian and forestry", + "1880": "barn", + "1890": "stall", + "1900": "equestrian hall", + "1910": "alpine cabin", + "1920": "hunting lodge", + "1930": "arboretum", + "1940": "glass house", + "1950": "moveable glass house", + "1960": "public building", + "1970": "administration building", + "1980": "parliament", + "1990": "guildhall", + "2000": "post office", + "2010": "customs office", + "2020": "court", + "2030": "embassy or consulate", + "2040": "district administration", + "2050": "district government", + "2060": "tax office", + "2070": "building for education and research", + "2080": "comprehensive school", + "2090": "vocational school", + "2100": "college or university", + "2110": "research establishment", + "2120": "building for cultural purposes", + "2130": "castle", + "2140": "theatre or opera", + "2150": "concert building", + "2160": cte.MUSEUM, + "2170": "broadcasting building", + "2180": "activity building", + "2190": cte.LIBRARY, + "2200": "fort", + "2210": "religious building", + "2220": "church", + "2230": "synagogue", + "2240": "chapel", + "2250": "community center", + "2260": "place of worship", + "2270": "mosque", + "2280": "temple", + "2290": "convent", + "2300": "building for health care", + "2310": cte.HOSPITAL, + "2320": "healing centre or care home", + "2330": "health centre or outpatients clinic", + "2340": "building for social purposes", + "2350": "youth centre", + "2360": "seniors centre", + "2370": "homeless shelter", + "2380": "kindergarten or nursery", + "2390": "asylum seekers home", + "2400": cte.POLICE_STATION, + "2410": cte.FIRE_STATION, + "2420": "barracks", + "2430": "bunker", + "2440": cte.PENITENTIARY, + "2450": "cemetery building", + "2460": "funeral parlor", + "2470": "crematorium", + "2480": "train station", + "2490": "airport building", + "2500": "building for underground station", + "2510": "building for tramway", + "2520": "building for bus station", + "2530": "shipping terminal", + "2540": "building for recuperation purposes", + "2550": "building for sport purposes", + "2560": "sports hall", + "2570": "building for sports field", + "2580": "swimming baths", + "2590": "indoor swimming pool", + "2600": "sanatorium", + "2610": "zoo building", + "2620": cte.GREEN_HOUSE, + "2630": "botanical show house", + "2640": "bothy", + "2650": "tourist information centre", + "2700": "others", + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/hft_function_to_hub_function.py b/hub/helpers/data/hft_function_to_hub_function.py new file mode 100644 index 00000000..00f63a53 --- /dev/null +++ b/hub/helpers/data/hft_function_to_hub_function.py @@ -0,0 +1,32 @@ +""" +Dictionaries module for Hft function to hub function +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class HftFunctionToHubFunction: + + def __init__(self): + self._dictionary = { + 'residential': cte.RESIDENTIAL, + 'single family house': cte.SINGLE_FAMILY_HOUSE, + 'multifamily house': cte.MULTI_FAMILY_HOUSE, + 'hotel': cte.HOTEL, + 'hospital': cte.HOSPITAL, + 'outpatient': cte.OUT_PATIENT_HEALTH_CARE, + 'commercial': cte.SUPERMARKET, + 'strip mall': cte.STRIP_MALL, + 'warehouse': cte.WAREHOUSE, + 'primary school': cte.PRIMARY_SCHOOL, + 'secondary school': cte.EDUCATION, + 'office': cte.MEDIUM_OFFICE, + 'large office': cte.LARGE_OFFICE + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/hub_function_to_nrcan_construction_function.py b/hub/helpers/data/hub_function_to_nrcan_construction_function.py new file mode 100644 index 00000000..c54382c3 --- /dev/null +++ b/hub/helpers/data/hub_function_to_nrcan_construction_function.py @@ -0,0 +1,78 @@ +""" +Dictionaries module for hub function to nrcan construction function +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class HubFunctionToNrcanConstructionFunction: + + def __init__(self): + self._dictionary = { + cte.RESIDENTIAL: 'MidriseApartment', + cte.SINGLE_FAMILY_HOUSE: 'MidriseApartment', + cte.MULTI_FAMILY_HOUSE: 'HighriseApartment', + cte.ROW_HOUSE: 'MidriseApartment', + cte.MID_RISE_APARTMENT: 'MidriseApartment', + cte.HIGH_RISE_APARTMENT: 'HighriseApartment', + cte.OFFICE_AND_ADMINISTRATION: 'MediumOffice', + cte.SMALL_OFFICE: 'SmallOffice', + cte.MEDIUM_OFFICE: 'MediumOffice', + cte.LARGE_OFFICE: 'LargeOffice', + cte.COURTHOUSE: 'MediumOffice', + cte.FIRE_STATION: 'n/a', + cte.PENITENTIARY: 'LargeHotel', + cte.POLICE_STATION: 'n/a', + cte.POST_OFFICE: 'MediumOffice', + cte.LIBRARY: 'MediumOffice', + cte.EDUCATION: 'SecondarySchool', + cte.PRIMARY_SCHOOL: 'PrimarySchool', + cte.PRIMARY_SCHOOL_WITH_SHOWER: 'PrimarySchool', + cte.SECONDARY_SCHOOL: 'SecondarySchool', + cte.UNIVERSITY: 'SecondarySchool', + cte.LABORATORY_AND_RESEARCH_CENTER: 'SecondarySchool', + cte.STAND_ALONE_RETAIL: 'RetailStandalone', + cte.HOSPITAL: 'Hospital', + cte.OUT_PATIENT_HEALTH_CARE: 'Outpatient', + cte.HEALTH_CARE: 'Outpatient', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'SmallHotel', + cte.COMMERCIAL: 'RetailStripmall', + cte.STRIP_MALL: 'RetailStripmall', + cte.SUPERMARKET: 'RetailStripmall', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'RetailStandalone', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'RetailStandalone', + cte.RESTAURANT: 'FullServiceRestaurant', + cte.QUICK_SERVICE_RESTAURANT: 'QuickServiceRestaurant', + cte.FULL_SERVICE_RESTAURANT: 'FullServiceRestaurant', + cte.HOTEL: 'SmallHotel', + cte.HOTEL_MEDIUM_CLASS: 'SmallHotel', + cte.SMALL_HOTEL: 'SmallHotel', + cte.LARGE_HOTEL: 'LargeHotel', + cte.DORMITORY: 'SmallHotel', + cte.EVENT_LOCATION: 'n/a', + cte.CONVENTION_CENTER: 'n/a', + cte.HALL: 'n/a', + cte.GREEN_HOUSE: 'n/a', + cte.INDUSTRY: 'n/a', + cte.WORKSHOP: 'n/a', + cte.WAREHOUSE: 'Warehouse', + cte.WAREHOUSE_REFRIGERATED: 'Warehouse', + cte.SPORTS_LOCATION: 'n/a', + cte.SPORTS_ARENA: 'n/a', + cte.GYMNASIUM: 'n/a', + cte.MOTION_PICTURE_THEATRE: 'n/a', + cte.MUSEUM: 'n/a', + cte.PERFORMING_ARTS_THEATRE: 'n/a', + cte.TRANSPORTATION: 'n/a', + cte.AUTOMOTIVE_FACILITY: 'n/a', + cte.PARKING_GARAGE: 'n/a', + cte.RELIGIOUS: 'n/a', + cte.NON_HEATED: 'n/a' + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/hub_function_to_nrel_construction_function.py b/hub/helpers/data/hub_function_to_nrel_construction_function.py new file mode 100644 index 00000000..e1f62aec --- /dev/null +++ b/hub/helpers/data/hub_function_to_nrel_construction_function.py @@ -0,0 +1,78 @@ +""" +Dictionaries module for hub function to NREL construction function +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class HubFunctionToNrelConstructionFunction: + + def __init__(self): + self._dictionary = { + cte.RESIDENTIAL: 'residential', + cte.SINGLE_FAMILY_HOUSE: 'residential', + cte.MULTI_FAMILY_HOUSE: 'midrise apartment', + cte.ROW_HOUSE: 'midrise apartment', + cte.MID_RISE_APARTMENT: 'midrise apartment', + cte.HIGH_RISE_APARTMENT: 'high-rise apartment', + cte.OFFICE_AND_ADMINISTRATION: 'medium office', + cte.SMALL_OFFICE: 'small office', + cte.MEDIUM_OFFICE: 'medium office', + cte.LARGE_OFFICE: 'large office', + cte.COURTHOUSE: 'medium office', + cte.FIRE_STATION: 'n/a', + cte.PENITENTIARY: 'large hotel', + cte.POLICE_STATION: 'n/a', + cte.POST_OFFICE: 'medium office', + cte.LIBRARY: 'medium office', + cte.EDUCATION: 'secondary school', + cte.PRIMARY_SCHOOL: 'primary school', + cte.PRIMARY_SCHOOL_WITH_SHOWER: 'primary school', + cte.SECONDARY_SCHOOL: 'secondary school', + cte.UNIVERSITY: 'secondary school', + cte.LABORATORY_AND_RESEARCH_CENTER: 'secondary school', + cte.STAND_ALONE_RETAIL: 'stand-alone retail', + cte.HOSPITAL: 'hospital', + cte.OUT_PATIENT_HEALTH_CARE: 'outpatient healthcare', + cte.HEALTH_CARE: 'outpatient healthcare', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'small hotel', + cte.COMMERCIAL: 'strip mall', + cte.STRIP_MALL: 'strip mall', + cte.SUPERMARKET: 'supermarket', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'stand-alone retail', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'stand-alone retail', + cte.RESTAURANT: 'full service restaurant', + cte.QUICK_SERVICE_RESTAURANT: 'quick service restaurant', + cte.FULL_SERVICE_RESTAURANT: 'full service restaurant', + cte.HOTEL: 'small hotel', + cte.HOTEL_MEDIUM_CLASS: 'small hotel', + cte.SMALL_HOTEL: 'small hotel', + cte.LARGE_HOTEL: 'large hotel', + cte.DORMITORY: 'small hotel', + cte.EVENT_LOCATION: 'n/a', + cte.CONVENTION_CENTER: 'n/a', + cte.HALL: 'n/a', + cte.GREEN_HOUSE: 'n/a', + cte.INDUSTRY: 'n/a', + cte.WORKSHOP: 'n/a', + cte.WAREHOUSE: 'warehouse', + cte.WAREHOUSE_REFRIGERATED: 'warehouse', + cte.SPORTS_LOCATION: 'n/a', + cte.SPORTS_ARENA: 'n/a', + cte.GYMNASIUM: 'n/a', + cte.MOTION_PICTURE_THEATRE: 'n/a', + cte.MUSEUM: 'n/a', + cte.PERFORMING_ARTS_THEATRE: 'n/a', + cte.TRANSPORTATION: 'n/a', + cte.AUTOMOTIVE_FACILITY: 'n/aquebec_to_hub', + cte.PARKING_GARAGE: 'n/a', + cte.RELIGIOUS: 'n/a', + cte.NON_HEATED: 'n/a' + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/hub_usage_to_comnet_usage.py b/hub/helpers/data/hub_usage_to_comnet_usage.py new file mode 100644 index 00000000..8a6cc2c3 --- /dev/null +++ b/hub/helpers/data/hub_usage_to_comnet_usage.py @@ -0,0 +1,78 @@ +""" +Dictionaries module for hub usage to Comnet usage +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class HubUsageToComnetUsage: + + def __init__(self): + self._dictionary = { + cte.RESIDENTIAL: 'BA Multifamily', + cte.SINGLE_FAMILY_HOUSE: 'BA Multifamily', + cte.MULTI_FAMILY_HOUSE: 'BA Multifamily', + cte.ROW_HOUSE: 'BA Multifamily', + cte.MID_RISE_APARTMENT: 'BA Multifamily', + cte.HIGH_RISE_APARTMENT: 'BA Multifamily', + cte.OFFICE_AND_ADMINISTRATION: 'BA Office', + cte.SMALL_OFFICE: 'BA Office', + cte.MEDIUM_OFFICE: 'BA Office', + cte.LARGE_OFFICE: 'BA Office', + cte.COURTHOUSE: 'BA Courthouse', + cte.FIRE_STATION: 'BA Fire Station', + cte.PENITENTIARY: 'BA Penitentiary', + cte.POLICE_STATION: 'BA Police Station', + cte.POST_OFFICE: 'BA Post Office', + cte.LIBRARY: 'BA Library', + cte.EDUCATION: 'BA School/University', + cte.PRIMARY_SCHOOL: 'BA School/University', + cte.PRIMARY_SCHOOL_WITH_SHOWER: 'BA School/University', + cte.SECONDARY_SCHOOL: 'BA School/University', + cte.UNIVERSITY: 'BA School/University', + cte.LABORATORY_AND_RESEARCH_CENTER: 'BA School/University', + cte.STAND_ALONE_RETAIL: 'BA Retail', + cte.HOSPITAL: 'BA Hospital', + cte.OUT_PATIENT_HEALTH_CARE: 'BA Healthcare Clinic', + cte.HEALTH_CARE: 'BA Healthcare Clinic', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'BA Healthcare Clinic', + cte.COMMERCIAL: 'BA Retail', + cte.STRIP_MALL: 'BA Retail', + cte.SUPERMARKET: 'BA Retail', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'BA Retail', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'BA Retail', + cte.RESTAURANT: 'BA Dining: Bar Lounge/Leisure', + cte.QUICK_SERVICE_RESTAURANT: 'BA Dining: Cafeteria/Fast Food', + cte.FULL_SERVICE_RESTAURANT: 'BA Dining: Bar Lounge/Leisure', + cte.HOTEL: 'BA Hotel', + cte.HOTEL_MEDIUM_CLASS: 'BA Motel', + cte.SMALL_HOTEL: 'BA Motel', + cte.LARGE_HOTEL: 'BA Hotel', + cte.DORMITORY: 'BA Dormitory', + cte.EVENT_LOCATION: 'BA Convention Center', + cte.CONVENTION_CENTER: 'BA Convention Center', + cte.HALL: 'BA Town Hall', + cte.GREEN_HOUSE: 'n/a', + cte.INDUSTRY: 'BA Manufacturing Facility', + cte.WORKSHOP: 'BA Workshop', + cte.WAREHOUSE: 'BA Warehouse', + cte.WAREHOUSE_REFRIGERATED: 'BA Warehouse', + cte.SPORTS_LOCATION: 'BA Exercise Center', + cte.SPORTS_ARENA: 'BA Sports Arena', + cte.GYMNASIUM: 'BA Gymnasium', + cte.MOTION_PICTURE_THEATRE: 'BA Motion Picture Theater', + cte.MUSEUM: 'BA Museum', + cte.PERFORMING_ARTS_THEATRE: 'BA Performing Arts Theater', + cte.TRANSPORTATION: 'BA Transportation', + cte.AUTOMOTIVE_FACILITY: 'BA Automotive Facility', + cte.PARKING_GARAGE: 'BA Parking Garage', + cte.RELIGIOUS: 'BA Religious Building', + cte.NON_HEATED: 'n/a' + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/hub_usage_to_hft_usage.py b/hub/helpers/data/hub_usage_to_hft_usage.py new file mode 100644 index 00000000..a05a2ba0 --- /dev/null +++ b/hub/helpers/data/hub_usage_to_hft_usage.py @@ -0,0 +1,78 @@ +""" +Dictionaries module for hub usage to Hft usage +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class HubUsageToHftUsage: + + def __init__(self): + self._dictionary = { + cte.RESIDENTIAL: 'residential', + cte.SINGLE_FAMILY_HOUSE: 'single family house', + cte.MULTI_FAMILY_HOUSE: 'multifamily house', + cte.ROW_HOUSE: 'single family house', + cte.MID_RISE_APARTMENT: 'multifamily house', + cte.HIGH_RISE_APARTMENT: 'multifamily house', + cte.OFFICE_AND_ADMINISTRATION: 'office and administration', + cte.SMALL_OFFICE: 'office and administration', + cte.MEDIUM_OFFICE: 'office and administration', + cte.LARGE_OFFICE: 'office and administration', + cte.COURTHOUSE: 'office and administration', + cte.FIRE_STATION: 'office and administration', + cte.PENITENTIARY: 'school with shower', + cte.POLICE_STATION: 'office and administration', + cte.POST_OFFICE: 'office and administration', + cte.LIBRARY: 'office and administration', + cte.EDUCATION: 'education', + cte.PRIMARY_SCHOOL: 'school without shower', + cte.PRIMARY_SCHOOL_WITH_SHOWER: 'school with shower', + cte.SECONDARY_SCHOOL: 'education', + cte.UNIVERSITY: 'education', + cte.LABORATORY_AND_RESEARCH_CENTER: 'laboratory and research centers', + cte.STAND_ALONE_RETAIL: 'retail', + cte.HOSPITAL: 'health care', + cte.OUT_PATIENT_HEALTH_CARE: 'health care', + cte.HEALTH_CARE: 'health care', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'Home for the aged or orphanage', + cte.COMMERCIAL: 'retail', + cte.STRIP_MALL: 'retail', + cte.SUPERMARKET: 'retail shop / refrigerated food', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'retail', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'retail shop / refrigerated food', + cte.RESTAURANT: 'restaurant', + cte.QUICK_SERVICE_RESTAURANT: 'restaurant', + cte.FULL_SERVICE_RESTAURANT: 'restaurant', + cte.HOTEL: 'hotel', + cte.HOTEL_MEDIUM_CLASS: 'hotel (Medium-class)', + cte.SMALL_HOTEL: 'hotel', + cte.LARGE_HOTEL: 'hotel', + cte.DORMITORY: 'dormitory', + cte.EVENT_LOCATION: 'event location', + cte.CONVENTION_CENTER: 'event location', + cte.HALL: 'hall', + cte.GREEN_HOUSE: 'green house', + cte.INDUSTRY: 'industry', + cte.WORKSHOP: 'industry', + cte.WAREHOUSE: 'industry', + cte.WAREHOUSE_REFRIGERATED: 'industry', + cte.SPORTS_LOCATION: 'sport location', + cte.SPORTS_ARENA: 'sport location', + cte.GYMNASIUM: 'sport location', + cte.MOTION_PICTURE_THEATRE: 'event location', + cte.MUSEUM: 'event location', + cte.PERFORMING_ARTS_THEATRE: 'event location', + cte.TRANSPORTATION: 'n/a', + cte.AUTOMOTIVE_FACILITY: 'n/a', + cte.PARKING_GARAGE: 'n/a', + cte.RELIGIOUS: 'event location', + cte.NON_HEATED: 'non-heated' + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/hub_usage_to_nrcan_usage.py b/hub/helpers/data/hub_usage_to_nrcan_usage.py new file mode 100644 index 00000000..b792d0d2 --- /dev/null +++ b/hub/helpers/data/hub_usage_to_nrcan_usage.py @@ -0,0 +1,78 @@ +""" +Dictionaries module for hub usage to NRCAN usage +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class HubUsageToNrcanUsage: + + def __init__(self): + self._dictionary = { + cte.RESIDENTIAL: 'Multi-unit residential building', + cte.SINGLE_FAMILY_HOUSE: 'Multi-unit residential building', + cte.MULTI_FAMILY_HOUSE: 'Multi-unit residential building', + cte.ROW_HOUSE: 'Multi-unit residential building', + cte.MID_RISE_APARTMENT: 'Multi-unit residential building', + cte.HIGH_RISE_APARTMENT: 'Multi-unit residential building', + cte.OFFICE_AND_ADMINISTRATION: 'Office', + cte.SMALL_OFFICE: 'Office', + cte.MEDIUM_OFFICE: 'Office', + cte.LARGE_OFFICE: 'Office', + cte.COURTHOUSE: 'Courthouse', + cte.FIRE_STATION: 'Fire station', + cte.PENITENTIARY: 'Penitentiary', + cte.POLICE_STATION: 'Police station', + cte.POST_OFFICE: 'Post office', + cte.LIBRARY: 'Library', + cte.EDUCATION: 'School/university', + cte.PRIMARY_SCHOOL: 'School/university', + cte.PRIMARY_SCHOOL_WITH_SHOWER: 'School/university', + cte.SECONDARY_SCHOOL: 'School/university', + cte.UNIVERSITY: 'School/university', + cte.LABORATORY_AND_RESEARCH_CENTER: 'School/university', + cte.STAND_ALONE_RETAIL: 'Retail', + cte.HOSPITAL: 'Hospital', + cte.OUT_PATIENT_HEALTH_CARE: 'Health-care clinic', + cte.HEALTH_CARE: 'Health-care clinic', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'Health-care clinic', + cte.COMMERCIAL: 'Retail', + cte.STRIP_MALL: 'Retail', + cte.SUPERMARKET: 'Retail', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'Retail', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'Retail', + cte.RESTAURANT: 'Dining - bar/lounge', + cte.QUICK_SERVICE_RESTAURANT: 'Dining - cafeteria', + cte.FULL_SERVICE_RESTAURANT: 'Dining - bar/lounge', + cte.HOTEL: 'Hotel', + cte.HOTEL_MEDIUM_CLASS: 'Motel', + cte.SMALL_HOTEL: 'Motel', + cte.LARGE_HOTEL: 'Hotel', + cte.DORMITORY: 'Dormitory', + cte.EVENT_LOCATION: 'Convention centre', + cte.CONVENTION_CENTER: 'Convention centre', + cte.HALL: 'Town hall', + cte.GREEN_HOUSE: 'n/a', + cte.INDUSTRY: 'Manufacturing facility', + cte.WORKSHOP: 'Workshop', + cte.WAREHOUSE: 'Warehouse', + cte.WAREHOUSE_REFRIGERATED: 'Warehouse - refrigerated', + cte.SPORTS_LOCATION: 'Exercise centre', + cte.SPORTS_ARENA: 'Sports arena', + cte.GYMNASIUM: 'Gymnasium', + cte.MOTION_PICTURE_THEATRE: 'Motion picture theatre', + cte.MUSEUM: 'Museum', + cte.PERFORMING_ARTS_THEATRE: 'Performing arts theatre', + cte.TRANSPORTATION: 'Transportation', + cte.AUTOMOTIVE_FACILITY: 'Automotive facility', + cte.PARKING_GARAGE: 'Parking garage', + cte.RELIGIOUS: 'Religious', + cte.NON_HEATED: 'n/a' + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/montreal_function_to_hub_function.py b/hub/helpers/data/montreal_function_to_hub_function.py new file mode 100644 index 00000000..1f8e88ce --- /dev/null +++ b/hub/helpers/data/montreal_function_to_hub_function.py @@ -0,0 +1,557 @@ +""" +Dictionaries module for Montreal function to hub function +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class MontrealFunctionToHubFunction: + + # Todo: "office" "Mausolée" and "hotel/motel" need to be replaced for a constant value. + def __init__(self): + self._dictionary = { + "Administration publique municipale et régionale": "Office", + "Administration publique provinciale": "Office", + "Agence de voyages ou d'expéditions": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Aiguillage et cour de triage de chemins de fer": cte.WAREHOUSE, + "Amphithéâtre et auditorium": cte.EVENT_LOCATION, + "Archives (incluant cinémathèquevidéothèque)": cte.EVENT_LOCATION, + "Aréna et activités connexes (patinage sur glace)": cte.SPORTS_LOCATION, + "Association civiquesociale et fraternelle": cte.OFFICE_AND_ADMINISTRATION, + "Associationunion ou coop d'épargne et de prêt (inclus caisses populaires locales)": cte.OFFICE_AND_ADMINISTRATION, + "Atelier d'artiste": cte.WAREHOUSE, + "Atelier d'artiste ou d'artisan": cte.WAREHOUSE, + "Atelier d'usinage": cte.WAREHOUSE, + "Atelier de mécanicien-dentiste": cte.WAREHOUSE, + "Auberge ou gîte touristique (Hôtel à caractère familiald'au plus 3 étages en hauteur de bâtiment)": cte.SMALL_HOTEL, + "Autoroute": cte.WAREHOUSE, + "Autres activités agricoles": cte.INDUSTRY, + "Autres activités culturelles": cte.EVENT_LOCATION, + "Autres activités d'hébergement": cte.MULTI_FAMILY_HOUSE, + "Autres activités d'impression commerciale": cte.WAREHOUSE, + "Autres activités de la restauration": cte.WAREHOUSE, + "Autres activités de récupération et de triage": cte.WAREHOUSE, + "Autres activités de vente au détail (inclus les kiosques d'autres choses que vêtements et accessoires de vêtements)": cte.STAND_ALONE_RETAIL, + "Autres activités de vente au détail de produits de l'alimentation": cte.STAND_ALONE_RETAIL, + "Autres activités de vente au détail de vêtements comme les accessoires": cte.STAND_ALONE_RETAIL, + "Autres activités de vente au détail reliées aux automobilesaux embarcationsaux avions et à leurs accessoires": cte.STAND_ALONE_RETAIL, + "Autres activités de vente en gros": cte.WAREHOUSE, + "Autres activités minières et extraction de carrières de minerais non métalliques (sauf le pétrole)": cte.INDUSTRY, + "Autres activités nautiques": cte.WAREHOUSE, + "Autres activités religieuses": cte.OFFICE_AND_ADMINISTRATION, + "Autres activités reliées au transport de matériaux par camion": cte.WAREHOUSE, + "Autres activités reliées au transport par autobus": cte.WAREHOUSE, + "Autres activités reliées au transport par chemin de fer": cte.WAREHOUSE, + "Autres activités sportives (inclus centres de tir à l'arc)": cte.SPORTS_LOCATION, + "Autres aménagements d'assemblées publiques": cte.OFFICE_AND_ADMINISTRATION, + "Autres aménagements publics pour différentes activités": cte.OFFICE_AND_ADMINISTRATION, + "Autres aéroports": cte.WAREHOUSE, + "Autres bases et réserves militaires": cte.WAREHOUSE, + "Autres centres de recherche": cte.SECONDARY_SCHOOL, + "Autres centres de services sociaux ou bureaux de travailleurs sociaux": cte.OFFICE_AND_ADMINISTRATION, + "Autres centres et réseaux de télévision et de radiodiffusion (système combiné)": "Office", + "Autres entreposages": cte.WAREHOUSE, + "Autres espaces de plancher inoccupé": cte.WAREHOUSE, + "Autres espaces de terrain et étendues d'eau inexploités": cte.WAREHOUSE, + "Autres expositions d'objets culturels": cte.EVENT_LOCATION, + "Autres immeubles résidentiels": cte.MID_RISE_APARTMENT, + "Autres industries d'appareils d'éclairage": cte.INDUSTRY, + "Autres industries de boissons": cte.INDUSTRY, + "Autres industries de la fabrication d'éléments de charpentes métalliques": cte.INDUSTRY, + "Autres industries de la fonte et de l'affinage de métaux non-ferreux": cte.INDUSTRY, + "Autres industries de la machinerie industrielle et de l'équipement industriel": cte.INDUSTRY, + "Autres industries de pièces et d'accessoires pour véhicules automobiles": cte.INDUSTRY, + "Autres industries de produits alimentaires": cte.INDUSTRY, + "Autres industries de produits alimentaires à base de fruits et de légumes": cte.INDUSTRY, + "Autres industries de produits chimiques": cte.INDUSTRY, + "Autres industries de produits du pétrole et du charbon": cte.INDUSTRY, + "Autres industries de produits en béton": cte.INDUSTRY, + "Autres industries de produits en caoutchouc": cte.INDUSTRY, + "Autres industries de produits en fil métallique": cte.INDUSTRY, + "Autres industries de produits en plastique": cte.INDUSTRY, + "Autres industries de produits manufacturés": cte.INDUSTRY, + "Autres industries de produits métalliques d'ornement et d'architecture": cte.INDUSTRY, + "Autres industries de produits métalliques divers": cte.INDUSTRY, + "Autres industries de produits textiles": cte.INDUSTRY, + "Autres industries de produits électriques.": cte.INDUSTRY, + "Autres industries de vêtements coupés cousus pour femmes et filles": cte.INDUSTRY, + "Autres industries du bois": cte.INDUSTRY, + "Autres industries du laminagedu moulage et de l'extrusion de métaux non-ferreux": cte.INDUSTRY, + "Autres industries du matériel de transport": cte.INDUSTRY, + "Autres industries du matériel scientifique et professionnel": cte.INDUSTRY, + "Autres industries du matériel électrique d'usage industriel": cte.INDUSTRY, + "Autres industries du matériel électronique et de communication": cte.INDUSTRY, + "Autres industries du meuble de bureau": cte.INDUSTRY, + "Autres industries du meuble et d'articles d'ameublement": cte.INDUSTRY, + "Autres industries du meuble résidentiel.": cte.INDUSTRY, + "Autres industries du papier": cte.INDUSTRY, + "Autres industries sidérurgiques": cte.INDUSTRY, + "Autres infrastructures de transport maritime": cte.INDUSTRY, + "Autres installations inhérentes aux ordures": cte.WAREHOUSE, + "Autres installations pour les sports": cte.SPORTS_LOCATION, + "Autres institutions de formation spécialisée (inclus écoles de langues de coutured'arts martiaux de combats et autres)": cte.SECONDARY_SCHOOL, + "Autres lieux d'assemblée pour les loisirs": cte.OFFICE_AND_ADMINISTRATION, + "Autres locaux de groupes": cte.OFFICE_AND_ADMINISTRATION, + "Autres maisons d'institutions religieuses": cte.OFFICE_AND_ADMINISTRATION, + "Autres maisons et locaux fraternels": cte.OFFICE_AND_ADMINISTRATION, + "Autres maisons pour personnes retraitées": cte.OFFICE_AND_ADMINISTRATION, + "Autres parcs": cte.WAREHOUSE, + "Autres routes et voies publiques": "Office", + "Autres résidences d'étudiants": "Office", + "Autres résidences provisoires": "Office", + "Autres services connexes aux valeurs mobilières et aux marchandises": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Autres services d'affaires": "Office", + "Autres services d'aqueduc et d'irrigation": cte.WAREHOUSE, + "Autres services de construction de bâtiments": cte.WAREHOUSE, + "Autres services de génie civil (entrepreneur général)": cte.WAREHOUSE, + "Autres services de l'automobile": cte.WAREHOUSE, + "Autres services de location (sauf entreposage)": cte.WAREHOUSE, + "Autres services de nettoyage": cte.WAREHOUSE, + "Autres services de réparation et d'entretien d'articles personnels et ménagers": cte.STAND_ALONE_RETAIL, + "Autres services de soins thérapeutiques": cte.OUT_PATIENT_HEALTH_CARE, + "Autres services de travaux de construction spécialisés": cte.WAREHOUSE, + "Autres services de travaux de finition de bâtiment (entrepreneur spécialisé)": cte.WAREHOUSE, + "Autres services de télécommunications": cte.STAND_ALONE_RETAIL, + "Autres services divers": cte.WAREHOUSE, + "Autres services du pétrole": cte.WAREHOUSE, + "Autres services gouvernementaux": cte.OFFICE_AND_ADMINISTRATION, + "Autres services immobiliersfinanciers et d'assurance": cte.OFFICE_AND_ADMINISTRATION, + "Autres services médicaux et de santé": cte.OUT_PATIENT_HEALTH_CARE, + "Autres services personnels": cte.OUT_PATIENT_HEALTH_CARE, + "Autres services pour animaux domestiques": cte.OUT_PATIENT_HEALTH_CARE, + "Autres services pour le transport": cte.WAREHOUSE, + "Autres services pour les bâtiments": cte.WAREHOUSE, + "Autres services professionnels": cte.OFFICE_AND_ADMINISTRATION, + "Autres services publics (infrastructure)": cte.WAREHOUSE, + "Autres services reliés à la foresterie": cte.WAREHOUSE, + "Autres terrains de jeux et pistes athlétiques": cte.SPORTS_LOCATION, + "Autres transports par avion (infrastructure)": cte.WAREHOUSE, + "Autres transports par véhicule automobile": cte.WAREHOUSE, + "Autres transportscommunications et services publics (infrastructure)": cte.GREEN_HOUSE, + "Autres types de production végétale": cte.GREEN_HOUSE, + "Autres ventes au détail de marchandises en général": cte.STAND_ALONE_RETAIL, + "Autres établissements avec service complet ou restreint": cte.STAND_ALONE_RETAIL, + "Autres établissements de débits de boissons alcoolisées": cte.STAND_ALONE_RETAIL, + "Aéroport et aérodrome": cte.EVENT_LOCATION, + "Bar à crème glacée": cte.QUICK_SERVICE_RESTAURANT, + "Bar à spectacles": cte.FULL_SERVICE_RESTAURANT, + "Bibliothèque": cte.OFFICE_AND_ADMINISTRATION, + "Bureau de poste": cte.OFFICE_AND_ADMINISTRATION, + "Bâtiment incendié et inutilisable": cte.NON_HEATED, + "C.E.G.E.P. (collège d'enseignement général et professionnel)": cte.SECONDARY_SCHOOL, + "Centre commercial de quartier (15 à 44 magasins)": cte.STRIP_MALL, + "Centre commercial de voisinage (14 magasins et moins)": cte.STAND_ALONE_RETAIL, + "Centre commercial local (45 à 99 magasins)": cte.STRIP_MALL, + "Centre commercial régional (100 à 199 magasins)": cte.STRIP_MALL, + "Centre commercial super régional (200 magasins et plus)": cte.STRIP_MALL, + "Centre communautaire ou de quartier (inclus Centre diocésain)": cte.OFFICE_AND_ADMINISTRATION, + "Centre d'accueil ou établissement curatif (inclus centre de réadaptation pour handicapés physiques et mentaux)": cte.OUT_PATIENT_HEALTH_CARE, + "Centre d'appels téléphoniques": "Office", + "Centre d'entraide et de ressources communautaires (inclus ressources d'hébergement de meubles et d'alimentation)": cte.OUT_PATIENT_HEALTH_CARE, + "Centre d'entreposage de produits pétroliers (pétrole brutgaz pétrole liquéfiémazout domestique et autres produits raffinés)": cte.WAREHOUSE, + "Centre d'entreposage du gaz (avant distrib.aux consommateurs)": cte.WAREHOUSE, + "Centre de distribution ou d'expédition de marchandises diverses": cte.WAREHOUSE, + "Centre de recherche d'activités émergentes (inclus technologies langagières et la photonique)": cte.SECONDARY_SCHOOL, + "Centre de santé (inclus saunas spas et bains thérapeutiques ou turcs)": cte.OUT_PATIENT_HEALTH_CARE, + "Centre de services sociaux (C.S.S. et C.R.S.S.S.)": cte.OUT_PATIENT_HEALTH_CARE, + "Centre de transfert ou d'entreposage de déchets dangereux": cte.WAREHOUSE, + "Centre de tri postal": cte.WAREHOUSE, + "Centre de vérification technique d'automobiles et d'estimation": cte.WAREHOUSE, + "Centre local de services communautaires (C.L.S.C.)": cte.OFFICE_AND_ADMINISTRATION, + "Centre militaire de transport et d'entreposage": cte.WAREHOUSE, + "Centre récréatif en général (activités récréatives diversifiées pour tous groupes d'âge)": cte.EVENT_LOCATION, + "Centre sportif multidisciplinaire (couvert).": cte.SPORTS_LOCATION, + "Chalet ou maison de villégiature": cte.SINGLE_FAMILY_HOUSE, + "Chemin de fer (sauf train touristiqueaiguillage et cour de triage)": cte.WAREHOUSE, + "Cimetière": cte.WAREHOUSE, + "Cinéma": cte.EVENT_LOCATION, + "Clinique médicale (cabinet de médecins généralistes)": cte.OUT_PATIENT_HEALTH_CARE, + "Commission scolaire": cte.OFFICE_AND_ADMINISTRATION, + "Conserveriemarinagesaumurage et séchage de fruits et de légumes": cte.WAREHOUSE, + "Construction d'immeubles pour revente": cte.WAREHOUSE, + "Couvent": cte.EVENT_LOCATION, + "Dépanneur (sans vente d'essence)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Entreposage de tout genre": cte.WAREHOUSE, + "Entreposage du mobilier et d'appareils ménagersincluant les mini-entrepôts": cte.WAREHOUSE, + "Entreposage en vrac à l'extérieur": cte.WAREHOUSE, + "Entreposage frigorifique (sauf les armoires frigorifiques)": cte.WAREHOUSE, + "Entreprise d'excavationde nivellementde défrichage et installation de fosses septiques": cte.WAREHOUSE, + "Entrepôt pour le transport par camion": cte.WAREHOUSE, + "Entretien et équipement de chemins de fer": cte.WAREHOUSE, + "Espace de plancher inoccupé dont l'usage serait commercial autre": cte.NON_HEATED, + "Espace de plancher inoccupé dont l'usage serait industriel": cte.NON_HEATED, + "Espace de plancher inoccupé dont l'usage serait pour des fins culturelles": cte.NON_HEATED, + "Espace de plancher inoccupé dont l'usage serait pour services publics": cte.NON_HEATED, + "Espace de rangement (condo non résidentiel)": cte.NON_HEATED, + "Espace de rangement (condo)": cte.NON_HEATED, + "Espace de terrain non aménagé et non exploité (sauf l'exploitation non commerciale de la forêt)": cte.NON_HEATED, + "Espace pour le séchage des boues provenant de l'usine d'épuration": cte.WAREHOUSE, + "Fabrication de crème glacée et de desserts congelés": cte.INDUSTRY, + "Fondations et organismes de charité": cte.OFFICE_AND_ADMINISTRATION, + "Galerie d'art": cte.EVENT_LOCATION, + "Garage d'autobus et équipement d'entretien": cte.WAREHOUSE, + "Garage de stationnement pour automobiles (infrastructure)": cte.WAREHOUSE, + "Garage de stationnement pour véhicules lourds (Infrastructure)": cte.WAREHOUSE, + "Garage et équipement d'entretien pour le transport par camion (incluant garages municipaux)": cte.WAREHOUSE, + "Gare d'autobus pour passagers": cte.WAREHOUSE, + "Gare de chemins de fer": cte.WAREHOUSE, + "Gymnase et formation athlétique": cte.SPORTS_LOCATION, + "Hangar à avion": cte.WAREHOUSE, + "Hôtel (incluant les hôtels-motels)": "Hotel/Motel", + "Hôtel résidentiel": "Hotel/Motel", + "Immeuble commercial": cte.STAND_ALONE_RETAIL, + "Immeuble non résidentiel en construction": "Office", + "Immeuble résidentiel en construction": cte.RESIDENTIAL, + "Immeuble à bureaux": "Office", + "Immeuble à temps partagé («time share») Propriété ou copropriété ou groupe d'usufruitier ont chacun droit de jouissancepériodique et successif.": "Office", + "Incinérateur": cte.INDUSTRY, + "Industrie d'accessoires vestimentaires et d'autres vêtements": cte.INDUSTRY, + "Industrie d'alcools destinés à la consommation (distillerie)": cte.INDUSTRY, + "Industrie d'appareils d'éclairage (sauf ampoules et tubes)": cte.INDUSTRY, + "Industrie d'appareils orthopédiques et chirurgicaux": cte.INDUSTRY, + "Industrie d'armoires de placards de cuisine et de coiffeuses de salle de bains en bois": cte.INDUSTRY, + "Industrie d'articles de maison en textile et d'articles d'hygiène en textile": cte.INDUSTRY, + "Industrie d'articles de sport et d'athlétisme": cte.INDUSTRY, + "Industrie d'assaisonnements et de vinaigrettes": cte.INDUSTRY, + "Industrie d'autres produits de boulangerie et de pâtisseries": cte.INDUSTRY, + "Industrie d'autres vêtements coupés cousus pour hommes et garçons": cte.INDUSTRY, + "Industrie d'engrais chimique et d'engrais composé": cte.INDUSTRY, + "Industrie d'enseignes au néon (excluant les enseignes en bois) éclairage interne": cte.INDUSTRY, + "Industrie d'équipements de télécommunication": cte.INDUSTRY, + "Industrie de bas et de chaussettes": cte.INDUSTRY, + "Industrie de boissons gazeuses": cte.INDUSTRY, + "Industrie de boîtes en carton ondulé et en carton compact": cte.INDUSTRY, + "Industrie de boîtes pliantes et rigides": cte.INDUSTRY, + "Industrie de carrosseries de véhicules automobiles": cte.INDUSTRY, + "Industrie de chaudièresd'échangeurs de chaleur et de plaques métalliques": cte.INDUSTRY, + "Industrie de contenants en plastique (sauf en mousse)": cte.INDUSTRY, + "Industrie de contreplaqués en bois": cte.INDUSTRY, + "Industrie de fabrication de gaz industriel": cte.INDUSTRY, + "Industrie de fils et de câbles électriques": cte.INDUSTRY, + "Industrie de filés et de tissus tissés (coton)": cte.INDUSTRY, + "Industrie de garnitures et de raccords de plomberie en métal": cte.INDUSTRY, + "Industrie de jouets et de jeux": cte.INDUSTRY, + "Industrie de l'abattage et du conditionnement de la viande (sauf la volaille et le petit gibier)": cte.INDUSTRY, + "Industrie de l'abattage et du conditionnement de la volaille et du petit gibier": cte.INDUSTRY, + "Industrie de l'impression de formulaires commerciaux": cte.INDUSTRY, + "Industrie de l'équipement de manutention": cte.INDUSTRY, + "Industrie de l'étirage de l'extrusion et alliage de l'aluminiumfabriqué à partir d'aluminium acheté": cte.INDUSTRY, + "Industrie de la bijouterie et de l'orfèvrerie (sauf l'affinage secondaire de métaux précieux)": cte.INDUSTRY, + "Industrie de la bière": cte.INDUSTRY, + "Industrie de la chaussure": cte.INDUSTRY, + "Industrie de la confection à forfait de vêtements pour femmes et filles": cte.INDUSTRY, + "Industrie de la construction et de la réparation de navires": cte.INDUSTRY, + "Industrie de la fabrication de supports d'enregistrement de la reproduction du son et des instruments de musique": cte.INDUSTRY, + "Industrie de la glace": cte.INDUSTRY, + "Industrie de la machinerie pour la construction et du matériel d'entretien": cte.INDUSTRY, + "Industrie de la préparation et du conditionnement de poissons et de fruits de mer": cte.INDUSTRY, + "Industrie de la quincaillerie de base": cte.INDUSTRY, + "Industrie de la transformation de la viande et de la fonte des graisses animales": cte.INDUSTRY, + "Industrie de la tôlerie pour ventilation": cte.INDUSTRY, + "Industrie de lampes électriques (ampoules et tubes)": cte.INDUSTRY, + "Industrie de moteurs et de pièces de moteurs de véhicules automobiles": cte.INDUSTRY, + "Industrie de mélange de farine et de pâte": cte.INDUSTRY, + "Industrie de peinturede teinture et de vernis": cte.INDUSTRY, + "Industrie de pellicules et de feuilles non renforcées en plastique": cte.INDUSTRY, + "Industrie de pièces en plastique pour véhicules automobiles": cte.INDUSTRY, + "Industrie de pièces et de composantes électroniques": cte.INDUSTRY, + "Industrie de pneus et de chambres à air": cte.INDUSTRY, + "Industrie de portes et de fenêtres en métal": cte.INDUSTRY, + "Industrie de portes et fenêtres en plastique": cte.INDUSTRY, + "Industrie de produits chimiques inorganiques d'usage industriel": cte.INDUSTRY, + "Industrie de produits d'architecture en plastique": cte.INDUSTRY, + "Industrie de produits de boulangerie commerciale de produits de boulangerie congelés et de pâtisseries": cte.INDUSTRY, + "Industrie de produits de toilette": cte.INDUSTRY, + "Industrie de produits en pierre": cte.INDUSTRY, + "Industrie de produits en plastique stratifié sous pression ou renforcé": cte.INDUSTRY, + "Industrie de produits en verre fabriqué à partir de verre acheté": cte.INDUSTRY, + "Industrie de produits pharmaceutiques et de médicaments": cte.INDUSTRY, + "Industrie de produits pétrochimiques": cte.INDUSTRY, + "Industrie de produits pétroliers raffinés (sauf les huiles de graissage et les graisses lubrifiantes)": cte.INDUSTRY, + "Industrie de pâtes alimentaires sèches": cte.INDUSTRY, + "Industrie de récipients et de boîtes en métal": cte.INDUSTRY, + "Industrie de résines synthétiques et de caoutchouc synthétique": cte.INDUSTRY, + "Industrie de sacs et de poches en matière textile": cte.INDUSTRY, + "Industrie de sacs et de sachets en plastique": cte.INDUSTRY, + "Industrie de savons et de détachants pour le nettoyage": cte.INDUSTRY, + "Industrie de sommiers et de matelas": cte.INDUSTRY, + "Industrie de soupapes en métal": cte.INDUSTRY, + "Industrie de tapis carpettes et moquettes": cte.INDUSTRY, + "Industrie de tous les autres produits divers en bois": cte.INDUSTRY, + "Industrie de tous les autres produits en papier transformé (sauf pour le bureau)": cte.INDUSTRY, + "Industrie de ventilateursde soufflantes et de purificateurs d'air industriels et commerciaux": cte.INDUSTRY, + "Industrie de vêtements de sport pour femmes et filles": cte.INDUSTRY, + "Industrie de vêtements professionnels coupés cousus": cte.INDUSTRY, + "Industrie des pièces et accessoires d'aéronefs (incluant avions et hélicoptères)": cte.INDUSTRY, + "Industrie du béton préparé": cte.INDUSTRY, + "Industrie du cannabis": cte.INDUSTRY, + "Industrie du ciment": cte.INDUSTRY, + "Industrie du clichagede la composition de la reliure et de la lithographie": cte.INDUSTRY, + "Industrie du fromage": cte.INDUSTRY, + "Industrie du lait de consommation": cte.INDUSTRY, + "Industrie du laminagede l'étirage et de l'extrusion du cuivre et de ses alliages": cte.INDUSTRY, + "Industrie du matériel de chauffage et du matériel de réfrigération commerciale": cte.INDUSTRY, + "Industrie du matériel de transport": cte.INDUSTRY, + "Industrie du matériel ferroviaire roulant": cte.INDUSTRY, + "Industrie du matériel électrique de communication et de protection": cte.INDUSTRY, + "Industrie du meuble de maison en bois": cte.INDUSTRY, + "Industrie du meuble et d'articles d'ameublement pour hôtelsrestaurants et institutions": cte.INDUSTRY, + "Industrie du pain": cte.INDUSTRY, + "Industrie du revêtement métallique sur commande": cte.INDUSTRY, + "Industrie du sucre de canne et de betterave à sucre": cte.INDUSTRY, + "Industrie du thé et du café": cte.INDUSTRY, + "Industries des appareils d'aéronefs (incluant avions et hélicoptères)": cte.INDUSTRY, + "Installation d'équipements de réfrigération commerciale": cte.WAREHOUSE, + "Installation portuaire en général": cte.WAREHOUSE, + "Jardin botanique": cte.WAREHOUSE, + "Ligne de l'oléoduc": cte.WAREHOUSE, + "Local pour les associations fraternelles": "Office", + "Logement": cte.RESIDENTIAL, + "Logement vacant dans un bâtiment comportant plusieurs logements ou autres locaux": cte.RESIDENTIAL, + "Loisir et autres activités culturelles": "Office", + "Maison d'agentsde courtiers et de services d'administration des biens-fonds": "Office", + "Maison d'étudiants (collège et université)": "Office", + "Maison de chambres et pension": "Office", + "Maison de chambres pour personnes ayant une déficience intellectuelle": "Office", + "Maison de courtiers et de négociants de marchandises": "Office", + "Maison de réhabilitation": "Office", + "Maison des jeunes": "Office", + "Maison pour personnes en difficulté (séjours périodes limitées)": "Office", + "Maison pour personnes retraitées autonomes": cte.DORMITORY, + "Maison pour personnes retraitées non autonomes (inclus les CHLSD)": cte.DORMITORY, + "Marché public": cte.STRIP_MALL, + "Meunerie et minoterie": "Office", + "Monastère": cte.DORMITORY, + "Monument et site historique": cte.EVENT_LOCATION, + "Motel": "hotel/Motel", + "Musée": cte.EVENT_LOCATION, + "Organisme international et autres organismes extraterritoriaux": "Office", + "Parc d'amusement (extérieur)": cte.NON_HEATED, + "Parc de maisons mobiles (fonds de terre seulement)": cte.NON_HEATED, + "Parc pour la récréation en général": cte.NON_HEATED, + "Parc à caractère récréatif et ornemental": cte.NON_HEATED, + "Passage": cte.NON_HEATED, + "Piscine extérieure et activités connexes": cte.NON_HEATED, + "Piscine intérieure et activités connexes": cte.SPORTS_LOCATION, + "Pose et réparation de parement métalliques et autres (entrepreneur spécialisé)": cte.WAREHOUSE, + "Poste et bureau de douanes": cte.OFFICE_AND_ADMINISTRATION, + "Pouponnière ou garderie de nuit": cte.HOSPITAL, + "Presbytère": cte.OFFICE_AND_ADMINISTRATION, + "Prison provinciale": cte.DORMITORY, + "Protection contre l'incendie et activités connexes": cte.WAREHOUSE, + "Raffinerie de pétrole": cte.INDUSTRY, + "Restaurant et établissement avec service complet (avec terrasse) - Établissements avec permis alcool inclus pub café et brasserie": cte.FULL_SERVICE_RESTAURANT, + "Restaurant et établissement avec service complet (sans terrasse) -Établissements avec permis alcoolinclus pub café et brasserie": cte.FULL_SERVICE_RESTAURANT, + "Restaurant et établissement avec service restreint ( commande au comptoir ou par téléphone)": cte.QUICK_SERVICE_RESTAURANT, + "Restaurant et établissement offrant des repas à libre-service (cafétéria cantine)": cte.QUICK_SERVICE_RESTAURANT, + "Rue et avenue pour l'accès local": cte.NON_HEATED, + "Ruelle": cte.NON_HEATED, + "Récupération et triage de matières polluantes et toxiques": cte.WAREHOUSE, + "Récupération et triage de métaux": cte.WAREHOUSE, + "Réparation et entretien des avions": cte.WAREHOUSE, + "Réserve pour la protection de la faune": cte.NON_HEATED, + "Réservoir d'eau (installation d'emmagasinage de l'eau par retenue et réservoirs)": cte.NON_HEATED, + "Résidence de tourismeappartement maison ou chalet (meublé et équipé pour repas)": "hotel/Motel", + "Salle d'exposition": cte.EVENT_LOCATION, + "Salle et terrain de squash de racquetball et de tennis": cte.SPORTS_LOCATION, + "Salle ou salon de quilles": cte.NON_HEATED, + "Salon de beauté (maquillagemanucureetc..)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Salon de coiffure": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Salon funéraire": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Service bancaire (dépôts et prêtsincluant banque à charte)": cte.OFFICE_AND_ADMINISTRATION, + "Service d'ambulance": cte.WAREHOUSE, + "Service d'architecture": cte.OFFICE_AND_ADMINISTRATION, + "Service d'assainissement de l'environnement": cte.WAREHOUSE, + "Service d'emballage et de protection de marchandises": cte.WAREHOUSE, + "Service d'envoi de marchandises": cte.WAREHOUSE, + "Service d'hébergement des données (sites Web diffusion audio et vidéo en continu services d'application)": cte.WAREHOUSE, + "Service d'hôpital (inclus hôpitaux psychiatriques)": cte.HOSPITAL, + "Service d'optométrie": cte.OUT_PATIENT_HEALTH_CARE, + "Service de buanderie de nettoyage à sec et de teinture (sauf les tapis)": cte.WAREHOUSE, + "Service de comptabilitéde vérification et de tenue de livre": cte.OFFICE_AND_ADMINISTRATION, + "Service de construction de routesde rues et de pontsde trottoirs et de pistes (entrepreneur général)": cte.WAREHOUSE, + "Service de construction non résidentiellecommerciale et institutionnelle (entrepreneur général)": cte.WAREHOUSE, + "Service de construction non résidentielleindustrielle (entrepreneur général)": cte.WAREHOUSE, + "Service de construction résidentielle (entrepreneur)": cte.WAREHOUSE, + "Service de débosselage et de peinture d'automobiles": cte.WAREHOUSE, + "Service de garderie (prématernelle moins de 50 % de poupons)": cte.PRIMARY_SCHOOL, + "Service de génie": cte.OFFICE_AND_ADMINISTRATION, + "Service de holding et d'investissement et de fiducie": cte.OFFICE_AND_ADMINISTRATION, + "Service de laboratoire dentaire": cte.OUT_PATIENT_HEALTH_CARE, + "Service de laboratoire médical": cte.OUT_PATIENT_HEALTH_CARE, + "Service de lavage d'automobiles": cte.WAREHOUSE, + "Service de limousine": cte.WAREHOUSE, + "Service de lingerie et de buanderie industrielle": cte.WAREHOUSE, + "Service de location d'automobiles": cte.WAREHOUSE, + "Service de location d'outils ou d'équipements": cte.WAREHOUSE, + "Service de location d'équipements": cte.WAREHOUSE, + "Service de location de boites postales (sauf le publipostage) et centre de courrier privé": cte.OFFICE_AND_ADMINISTRATION, + "Service de location de camions de remorques utilitaires et de véhicules de plaisance": cte.WAREHOUSE, + "Service de maçonnerie (entrepreneur spécialisé)": cte.INDUSTRY, + "Service de messagers": "Office", + "Service de notaires": "Office", + "Service de paysagement ou de déneigement": cte.WAREHOUSE, + "Service de petite menuiserie et de finition (entrepreneur spécialisé)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Service de plomberie de chauffagede climatisation et de ventilation (entrepreneur spécialisé)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Service de police fédérale et activités connexes": cte.OFFICE_AND_ADMINISTRATION, + "Service de police municipale et activités connexes": cte.OFFICE_AND_ADMINISTRATION, + "Service de pose de portesde fenêtres et de panneaux de verre": cte.WAREHOUSE, + "Service de publicité en général": "Office", + "Service de recherche de développement et d'essais": cte.SECONDARY_SCHOOL, + "Service de remplacement de pièces et d'accessoires d'automobiles (amortisseurs silencieux toits ouvrants glacespare-brises...)": cte.WAREHOUSE, + "Service de revêtement en asphalte et en bitume": cte.WAREHOUSE, + "Service de réparation d'automobiles (garage) sans pompes à essence(5531)": cte.WAREHOUSE, + "Service de réparation d'autres véhicules légers": cte.WAREHOUSE, + "Service de réparation de véhicules légers motorisés (motocyclettemotoneige véhicule tout terrain)": cte.WAREHOUSE, + "Service de réparation et d'entretien de machines et de matériel d'usage commercial et industriel": "Office", + "Service de réparation et d'entretien de matériel informatique": "Office", + "Service de réparation et d'entretien de systèmes de plomberieschauffageventilation et climatisation.(entrepreneur spécialisé)": cte.WAREHOUSE, + "Service de réparation et d'entretien de véhicules lourds": cte.WAREHOUSE, + "Service de réparation et de rembourrage de meubles": cte.WAREHOUSE, + "Service de soudure": cte.WAREHOUSE, + "Service de toilettage pour animaux domestiques": cte.OUT_PATIENT_HEALTH_CARE, + "Service de traitement pour automobiles (antirouilleetc.)": cte.WAREHOUSE, + "Service de travaux d'électricité et installation de câblage (entrepreneur spécialisé)": cte.WAREHOUSE, + "Service de travaux de toiture (entrepreneur spécialisé)": cte.WAREHOUSE, + "Service de télécommunication sans fil (appareil mobile sauf par Internet)": cte.WAREHOUSE, + "Service de vétérinaires (animaux domestiques)": cte.OUT_PATIENT_HEALTH_CARE, + "Service de vétérinaires et d'hôpital pour animaux de ferme": cte.OUT_PATIENT_HEALTH_CARE, + "Service dentaire (inclus chirurgie et hygiène)": cte.OUT_PATIENT_HEALTH_CARE, + "Service en santé mentale (cabinet) (comprend tous services professionnelspsychiatre psychologuepsychanalyste)": cte.OUT_PATIENT_HEALTH_CARE, + "Service en travaux de fondation et de structures en béton (entrepreneur spécialisé)": "Office", + "Service informatique (location ou utilisation partagée services auxiliaires programmation planification et analyse de système)": "Office", + "Service médical (cabinet de médecins et chirurgiens spécialisés)": cte.OUT_PATIENT_HEALTH_CARE, + "Service photographique (incluant les services commerciaux)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Service pour l'entretien ménager": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Service éducationnel et de recherche scientifique": cte.SECONDARY_SCHOOL, + "Services spécialisés reliés à l'activité bancaire": cte.OFFICE_AND_ADMINISTRATION, + "Stade": cte.SPORTS_LOCATION, + "Station de contrôle de la pression de l'eau": cte.WAREHOUSE, + "Station de contrôle de la pression des eaux usées": cte.WAREHOUSE, + "Station de métro": cte.WAREHOUSE, + "Station libre-serviceou avec service et dépanneur sans réparation de véhicules automobiles": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Station libre-serviceou avec service sans réparation de véhicules automobiles": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Station-service avec réparation de véhicules automobiles": cte.WAREHOUSE, + "Stationnement extérieur (condo non résidentiel)": cte.NON_HEATED, + "Stationnement extérieur (condo)": cte.NON_HEATED, + "Stationnement intérieur ( condo non résidentiel)": cte.WAREHOUSE, + "Stationnement intérieur (condo)": cte.WAREHOUSE, + "Studio d'enregistrement du son (disque cassette et disque compact)": "Office", + "Studio de production de filmsde vidéos ou de publicités (ne comprends pas le laboratoire de production)": "Office", + "Studio de télévision (sans public)": "Office", + "Syndicat et organisation similaire": "Office", + "Terminus maritime (passagers) incluant les gares de traversiers": cte.WAREHOUSE, + "Terrain de golf (avec chalet et autres aménagements sportifs)": cte.NON_HEATED, + "Terrain de sport (jeux et pistes pour compétitions et sportgradins)": cte.NON_HEATED, + "Terrain de stationnement pour automobiles": cte.WAREHOUSE, + "Terrains de stationnement pour véhicules lourds": cte.WAREHOUSE, + "Théâtre": cte.EVENT_LOCATION, + "Tour de relais (micro-ondes)": cte.NON_HEATED, + "Tous les autres services d'information": cte.NON_HEATED, + "Transport et gestion d'électricité en bloc": cte.NON_HEATED, + "Transport et gestion du gaz par canalisation": cte.NON_HEATED, + "Université": cte.SECONDARY_SCHOOL, + "Usine de traitement des eaux (filtration)": cte.INDUSTRY, + "Usine de traitement des eaux usées (épuration)": cte.INDUSTRY, + "Vente au détail (fleuriste)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'accessoires pour femmes": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'animaux de maison (animalerie)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'antiquités (sauf le marché aux puces)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'appareils orthopédiques et articles spécialisés de santé": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'articles de sport": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'articles d'accessoires d'aménagement paysager et de jardin": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'instruments et de matériel médical": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'équipements de ferme": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'équipements de plomberie de chauffagede ventilationde climatisation et de foyer": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail d'équipements et d'accessoires de chasse et pêche": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de bicyclettes": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de boissons alcoolisées": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de chaussures": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de fruits et de légumes": cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + "Vente au détail de la viande": cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + "Vente au détail de livres et de papeterie": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de marchandises en général (sauf les marchés aux puces)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de matériaux de construction": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de matériaux de construction (cour à bois)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de matériel électrique": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de meubles": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de motocyclettes de motoneiges et de leurs accessoires": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de médicaments et d'articles divers (pharmacie)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de peinturede verre et de papier tenture": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de piscinesde spas et leurs accessoires": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de pièces de véhicules automobiles et d'accessoires usagés": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de pneus de batteries et d'accessoires": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de produits d'épicerie (avec boucherie)": cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + "Vente au détail de produits d'épicerie (sans boucherie)": cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + "Vente au détail de produits de la boulangerie et de la pâtisserie (manufacturés sur place en totalité ou non)": cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + "Vente au détail de quincaillerie": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de radiosde téléviseurssystèmes de son et appareils électroniques": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de revêtements de planchers et de murs (bois franc plancher flottant carreaux céramiques tapisserie)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de serruresde clés et d'accessoires": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de véhicules automobiles neufs et usagés": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de véhicules automobiles usagés seulement": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de vêtement prêt-à-porter pour femmes": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de vêtements et d'accessoires pour hommes": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de vêtements et d'articles usagésfriperies (sauf le marché aux puces)": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail de vêtements unisexes": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détail du cafédu théd'épices et d'aromates": cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + "Vente au détailclubs de gros et hypermarchés (entrepôt-club)": cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + "Vente au détailfournitures pour la maison et l'auto": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente au détailmagasin à rayons": cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + "Vente en gros d'ameublements de matériels de bureau et de magasin": cte.STRIP_MALL, + "Vente en gros d'appareils et d'équipements de plomberie et de chauffage": cte.STRIP_MALL, + "Vente en gros d'appareils et d'équipements électriques de fils et de matériel électronique de construction": cte.STRIP_MALL, + "Vente en gros d'appareils électriquesde téléviseurs et de radios": cte.STRIP_MALL, + "Vente en gros d'automobiles et autres véhicules automobiles neufs ou d'occasions incluent VR)": cte.STRIP_MALL, + "Vente en gros d'autres appareils ou matériels électriques et électroniques": cte.STRIP_MALL, + "Vente en gros d'autres médicaments de produits chimiques et de produits connexes": cte.STRIP_MALL, + "Vente en gros d'autres pièces d'équipement ou de machinerie (incluant machinerie lourde)": cte.STRIP_MALL, + "Vente en gros d'autres produits reliés à l'épicerie": cte.STRIP_MALL, + "Vente en gros d'équipements et de pièces de machinerie commercialeindustrielle ou agricole (incluant machinerie lourde)": cte.STRIP_MALL, + "Vente en gros d'équipements et de pièces pour la réfrigération ventilation la climatisation et le chauffage (système combiné)": cte.STRIP_MALL, + "Vente en gros d'équipements et de pièces pour les entreprises de services": cte.STRIP_MALL, + "Vente en gros de bois et de matériaux de construction": cte.STRIP_MALL, + "Vente en gros de chaussures": cte.STRIP_MALL, + "Vente en gros de fruits et de légumes frais": cte.STRIP_MALL, + "Vente en gros de médicaments et de produits médicamenteux": cte.STRIP_MALL, + "Vente en gros de pièces et d'accessoires neufs pour véhicules automobiles": cte.STRIP_MALL, + "Vente en gros de pièces et d'équipements électroniques": cte.STRIP_MALL, + "Vente en gros de pneus et de chambres à air": cte.STRIP_MALL, + "Vente en gros de poissons et de fruits de mer": cte.STRIP_MALL, + "Vente en gros de produits de beauté": cte.STRIP_MALL, + "Vente en gros de produits de boulangerie et de pâtisserie": cte.STRIP_MALL, + "Vente en gros de produits laitiers": cte.STRIP_MALL, + "Vente en gros de quincaillerie": cte.STRIP_MALL, + "Vente en gros de tissus et de textiles": cte.STRIP_MALL, + "Vente en gros de viandes et de produits de la viande": cte.STRIP_MALL, + "Vente en gros de vêtements de lingerie de bas et d'accessoires": cte.STRIP_MALL, + "Vente en gros pour l'épicerie en général": cte.STRIP_MALL, + "École commerciale et de secrétariat (non intégrée aux polyvalentes)": cte.SECONDARY_SCHOOL, + "École de beaux-arts et de musique (exclus arts publicitaires arts graphiques et photographie publicitaire)": cte.SECONDARY_SCHOOL, + "École de danse": cte.SECONDARY_SCHOOL, + "École de métiers (non intégrée aux polyvalentes)": cte.SECONDARY_SCHOOL, + "École maternelle": cte.SECONDARY_SCHOOL, + "École polyvalente": cte.SECONDARY_SCHOOL, + "École secondaire": cte.SECONDARY_SCHOOL, + "École à caractère familial (exploité par une personne physique dans sa résidence moins de 15 élèves)": cte.SECONDARY_SCHOOL, + "École élémentaire": cte.SECONDARY_SCHOOL, + "École élémentaire et secondaire": cte.SECONDARY_SCHOOL, + "Église synagogue mosquée et temple": cte.EVENT_LOCATION, + "Établissement avec salle de réception ou de banquet": cte.FULL_SERVICE_RESTAURANT, + "Établissement avec service de boissons alcoolisées (Bar)": cte.QUICK_SERVICE_RESTAURANT, + "Établissement dont l'activité principale est la danse (discothèque avec service alcool boite de nuit) sans alcool code 7397": cte.QUICK_SERVICE_RESTAURANT, + "Mausolée": cte.NON_HEATED, + "Auberge ou gîte touristique (Hôtel à caractère familial, d'au plus 3 étages en hauteur de bâtiment)": cte.HOTEL, + "Service de garderie (prématernelle, moins de 50 % de poupons)": cte.PRIMARY_SCHOOL, + "Église, synagogue, mosquée et temple": cte.CONVENTION_CENTER + + + + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/data/pluto_function_to_hub_function.py b/hub/helpers/data/pluto_function_to_hub_function.py new file mode 100644 index 00000000..c7a640d8 --- /dev/null +++ b/hub/helpers/data/pluto_function_to_hub_function.py @@ -0,0 +1,217 @@ +""" +Dictionaries module for Pluto function to hub function +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import hub.helpers.constants as cte + + +class PlutoFunctionToHubFunction: + + def __init__(self): + self._dictionary = { + 'A0': cte.SINGLE_FAMILY_HOUSE, + 'A1': cte.SINGLE_FAMILY_HOUSE, + 'A2': cte.SINGLE_FAMILY_HOUSE, + 'A3': cte.SINGLE_FAMILY_HOUSE, + 'A4': cte.SINGLE_FAMILY_HOUSE, + 'A5': cte.SINGLE_FAMILY_HOUSE, + 'A6': cte.SINGLE_FAMILY_HOUSE, + 'A7': cte.SINGLE_FAMILY_HOUSE, + 'A8': cte.SINGLE_FAMILY_HOUSE, + 'A9': cte.SINGLE_FAMILY_HOUSE, + 'B1': cte.MULTI_FAMILY_HOUSE, + 'B2': cte.MULTI_FAMILY_HOUSE, + 'B3': cte.MULTI_FAMILY_HOUSE, + 'B9': cte.MULTI_FAMILY_HOUSE, + 'C0': cte.RESIDENTIAL, + 'C1': cte.RESIDENTIAL, + 'C2': cte.RESIDENTIAL, + 'C3': cte.RESIDENTIAL, + 'C4': cte.RESIDENTIAL, + 'C5': cte.RESIDENTIAL, + 'C6': cte.RESIDENTIAL, + 'C7': cte.RESIDENTIAL, + 'C8': cte.RESIDENTIAL, + 'C9': cte.RESIDENTIAL, + 'D0': cte.RESIDENTIAL, + 'D1': cte.RESIDENTIAL, + 'D2': cte.RESIDENTIAL, + 'D3': cte.RESIDENTIAL, + 'D4': cte.RESIDENTIAL, + 'D5': cte.RESIDENTIAL, + 'D6': cte.RESIDENTIAL, + 'D7': cte.RESIDENTIAL, + 'D8': cte.RESIDENTIAL, + 'D9': cte.RESIDENTIAL, + 'E1': cte.WAREHOUSE, + 'E3': cte.WAREHOUSE, + 'E4': cte.WAREHOUSE, + 'E5': cte.WAREHOUSE, + 'E7': cte.WAREHOUSE, + 'E9': cte.WAREHOUSE, + 'F1': cte.WAREHOUSE, + 'F2': cte.WAREHOUSE, + 'F4': cte.WAREHOUSE, + 'F5': cte.WAREHOUSE, + 'F8': cte.WAREHOUSE, + 'F9': cte.WAREHOUSE, + 'G0': cte.SMALL_OFFICE, + 'G1': cte.SMALL_OFFICE, + 'G2': cte.SMALL_OFFICE, + 'G3': cte.SMALL_OFFICE, + 'G4': cte.SMALL_OFFICE, + 'G5': cte.SMALL_OFFICE, + 'G6': cte.SMALL_OFFICE, + 'G7': cte.SMALL_OFFICE, + 'G8': cte.SMALL_OFFICE, + 'G9': cte.SMALL_OFFICE, + 'H1': cte.HOTEL, + 'H2': cte.HOTEL, + 'H3': cte.HOTEL, + 'H4': cte.HOTEL, + 'H5': cte.HOTEL, + 'H6': cte.HOTEL, + 'H7': cte.HOTEL, + 'H8': cte.HOTEL, + 'H9': cte.HOTEL, + 'HB': cte.HOTEL, + 'HH': cte.HOTEL, + 'HR': cte.HOTEL, + 'HS': cte.HOTEL, + 'I1': cte.HOSPITAL, + 'I2': cte.OUT_PATIENT_HEALTH_CARE, + 'I3': cte.OUT_PATIENT_HEALTH_CARE, + 'I4': cte.RESIDENTIAL, + 'I5': cte.OUT_PATIENT_HEALTH_CARE, + 'I6': cte.OUT_PATIENT_HEALTH_CARE, + 'I7': cte.OUT_PATIENT_HEALTH_CARE, + 'I9': cte.OUT_PATIENT_HEALTH_CARE, + 'J1': cte.LARGE_OFFICE, + 'J2': cte.LARGE_OFFICE, + 'J3': cte.LARGE_OFFICE, + 'J4': cte.LARGE_OFFICE, + 'J5': cte.LARGE_OFFICE, + 'J6': cte.LARGE_OFFICE, + 'J7': cte.LARGE_OFFICE, + 'J8': cte.LARGE_OFFICE, + 'J9': cte.LARGE_OFFICE, + 'K1': cte.STRIP_MALL, + 'K2': cte.STRIP_MALL, + 'K3': cte.STRIP_MALL, + 'K4': cte.RESIDENTIAL, + 'K5': cte.RESTAURANT, + 'K6': cte.SUPERMARKET, + 'K7': cte.SUPERMARKET, + 'K8': cte.SUPERMARKET, + 'K9': cte.SUPERMARKET, + 'L1': cte.RESIDENTIAL, + 'L2': cte.RESIDENTIAL, + 'L3': cte.RESIDENTIAL, + 'L8': cte.RESIDENTIAL, + 'L9': cte.RESIDENTIAL, + 'M1': cte.LARGE_OFFICE, + 'M2': cte.LARGE_OFFICE, + 'M3': cte.LARGE_OFFICE, + 'M4': cte.LARGE_OFFICE, + 'M9': cte.LARGE_OFFICE, + 'N1': cte.RESIDENTIAL, + 'N2': cte.RESIDENTIAL, + 'N3': cte.RESIDENTIAL, + 'N4': cte.RESIDENTIAL, + 'N9': cte.RESIDENTIAL, + 'O1': cte.SMALL_OFFICE, + 'O2': cte.SMALL_OFFICE, + 'O3': cte.SMALL_OFFICE, + 'O4': cte.SMALL_OFFICE, + 'O5': cte.SMALL_OFFICE, + 'O6': cte.SMALL_OFFICE, + 'O7': cte.SMALL_OFFICE, + 'O8': cte.SMALL_OFFICE, + 'O9': cte.SMALL_OFFICE, + 'P1': cte.LARGE_OFFICE, + 'P2': cte.HOTEL, + 'P3': cte.SMALL_OFFICE, + 'P4': cte.SMALL_OFFICE, + 'P5': cte.SMALL_OFFICE, + 'P6': cte.SMALL_OFFICE, + 'P7': cte.LARGE_OFFICE, + 'P8': cte.LARGE_OFFICE, + 'P9': cte.SMALL_OFFICE, + 'Q0': cte.SMALL_OFFICE, + 'Q1': cte.SMALL_OFFICE, + 'Q2': cte.SMALL_OFFICE, + 'Q3': cte.SMALL_OFFICE, + 'Q4': cte.SMALL_OFFICE, + 'Q5': cte.SMALL_OFFICE, + 'Q6': cte.SMALL_OFFICE, + 'Q7': cte.SMALL_OFFICE, + 'Q8': cte.SMALL_OFFICE, + 'Q9': cte.SMALL_OFFICE, + 'R0': cte.RESIDENTIAL, + 'R1': cte.RESIDENTIAL, + 'R2': cte.RESIDENTIAL, + 'R3': cte.RESIDENTIAL, + 'R4': cte.RESIDENTIAL, + 'R5': cte.RESIDENTIAL, + 'R6': cte.RESIDENTIAL, + 'R7': cte.RESIDENTIAL, + 'R8': cte.RESIDENTIAL, + 'R9': cte.RESIDENTIAL, + 'RA': cte.RESIDENTIAL, + 'RB': cte.RESIDENTIAL, + 'RC': cte.RESIDENTIAL, + 'RD': cte.RESIDENTIAL, + 'RG': cte.RESIDENTIAL, + 'RH': cte.RESIDENTIAL, + 'RI': cte.RESIDENTIAL, + 'RK': cte.RESIDENTIAL, + 'RM': cte.RESIDENTIAL, + 'RR': cte.RESIDENTIAL, + 'RS': cte.RESIDENTIAL, + 'RW': cte.RESIDENTIAL, + 'RX': cte.RESIDENTIAL, + 'RZ': cte.RESIDENTIAL, + 'S0': cte.RESIDENTIAL, + 'S1': cte.RESIDENTIAL, + 'S2': cte.RESIDENTIAL, + 'S3': cte.RESIDENTIAL, + 'S4': cte.RESIDENTIAL, + 'S5': cte.RESIDENTIAL, + 'S9': cte.RESIDENTIAL, + 'U0': cte.WAREHOUSE, + 'U1': cte.WAREHOUSE, + 'U2': cte.WAREHOUSE, + 'U3': cte.WAREHOUSE, + 'U4': cte.WAREHOUSE, + 'U5': cte.WAREHOUSE, + 'U6': cte.WAREHOUSE, + 'U7': cte.WAREHOUSE, + 'U8': cte.WAREHOUSE, + 'U9': cte.WAREHOUSE, + 'W1': cte.PRIMARY_SCHOOL, + 'W2': cte.PRIMARY_SCHOOL, + 'W3': cte.SECONDARY_SCHOOL, + 'W4': cte.EDUCATION, + 'W5': cte.SECONDARY_SCHOOL, + 'W6': cte.SECONDARY_SCHOOL, + 'W7': cte.SECONDARY_SCHOOL, + 'W8': cte.PRIMARY_SCHOOL, + 'W9': cte.SECONDARY_SCHOOL, + 'Y1': cte.LARGE_OFFICE, + 'Y2': cte.LARGE_OFFICE, + 'Y3': cte.LARGE_OFFICE, + 'Y4': cte.LARGE_OFFICE, + 'Y5': cte.LARGE_OFFICE, + 'Y6': cte.LARGE_OFFICE, + 'Y7': cte.LARGE_OFFICE, + 'Y8': cte.LARGE_OFFICE, + 'Y9': cte.LARGE_OFFICE, + 'Z1': cte.LARGE_OFFICE + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/dictionaries.py b/hub/helpers/dictionaries.py new file mode 100644 index 00000000..3561d682 --- /dev/null +++ b/hub/helpers/dictionaries.py @@ -0,0 +1,92 @@ +""" +Dictionaries module saves all transformations of functions and usages to access the catalogs +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from hub.helpers.data.hft_function_to_hub_function import HftFunctionToHubFunction +from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction +from hub.helpers.data.alkis_function_to_hub_function import AlkisFunctionToHubFunction +from hub.helpers.data.pluto_function_to_hub_function import PlutoFunctionToHubFunction +from hub.helpers.data.hub_function_to_nrel_construction_function import HubFunctionToNrelConstructionFunction +from hub.helpers.data.hub_function_to_nrcan_construction_function import HubFunctionToNrcanConstructionFunction +from hub.helpers.data.hub_usage_to_comnet_usage import HubUsageToComnetUsage +from hub.helpers.data.hub_usage_to_hft_usage import HubUsageToHftUsage +from hub.helpers.data.hub_usage_to_nrcan_usage import HubUsageToNrcanUsage + +class Dictionaries: + """ + Dictionaries class + """ + + @property + def hub_usage_to_hft_usage(self) -> dict: + """ + Hub usage to HfT usage, transformation dictionary + :return: dict + """ + return HubUsageToHftUsage().dictionary + + @property + def hub_usage_to_comnet_usage(self) -> dict: + """ + Hub usage to Comnet usage, transformation dictionary + :return: dict + """ + return HubUsageToComnetUsage().dictionary + + @property + def hub_usage_to_nrcan_usage(self) -> dict: + """ + Get hub usage to NRCAN usage, transformation dictionary + :return: dict + """ + return HubUsageToNrcanUsage().dictionary + + @property + def hub_function_to_nrcan_construction_function(self) -> dict: + """ + Get hub function to NRCAN construction function, transformation dictionary + :return: dict + """ + return HubFunctionToNrcanConstructionFunction().dictionary + + @property + def hub_function_to_nrel_construction_function(self) -> dict: + """ + Get hub function to NREL construction function, transformation dictionary + :return: dict + """ + return HubFunctionToNrelConstructionFunction().dictionary + + @property + def pluto_function_to_hub_function(self) -> dict: + """ + Get Pluto function to hub function, transformation dictionary + :return: dict + """ + return PlutoFunctionToHubFunction().dictionary + + @property + def hft_function_to_hub_function(self) -> dict: + """ + Get Hft function to hub function, transformation dictionary + :return: dict + """ + return HftFunctionToHubFunction().dictionary + + @property + def montreal_function_to_hub_function(self) -> dict: + """ + Get Montreal function to hub function, transformation dictionary + """ + return MontrealFunctionToHubFunction().dictionary + + @property + def alkis_function_to_hub_function(self) -> dict: + """ + Get Alkis function to hub function, transformation dictionary + """ + return AlkisFunctionToHubFunction().dictionary + diff --git a/hub/helpers/geometry_helper.py b/hub/helpers/geometry_helper.py index 1c67bf4a..24c202e3 100644 --- a/hub/helpers/geometry_helper.py +++ b/hub/helpers/geometry_helper.py @@ -39,59 +39,12 @@ class GeometryHelper: max_distance = ConfigurationHelper().max_location_distance_for_shared_walls return GeometryHelper.distance_between_points(location1, location2) < max_distance - def almost_same_area(self, area_1, area_2): - """ - Compare two areas and decides if they are almost equal (absolute error under delta) - :param area_1 - :param area_2 - :return: Boolean - """ - if area_1 == 0 or area_2 == 0: - return False - delta = math.fabs(area_1 - area_2) - return delta <= self._area_delta - - def is_almost_same_surface(self, surface_1, surface_2): - """ - Compare two surfaces and decides if they are almost equal (quadratic error under delta) - :param surface_1: Surface - :param surface_2: Surface - :return: Boolean - """ - - # delta is grads an need to be converted into radians - delta = np.rad2deg(self._delta) - difference = (surface_1.inclination - surface_2.inclination) % math.pi - if abs(difference) > delta: - return False - # s1 and s2 are at least almost parallel surfaces - # calculate distance point to plane using all the vertex - # select surface1 value for the point (X,Y,Z) where two of the values are 0 - minimum_distance = self._delta + 1 - parametric = surface_2.polygon.get_parametric() - normal_2 = surface_2.normal - for point in surface_1.points: - distance = abs( - (point[0] * parametric[0]) + (point[1] * parametric[1]) + (point[2] * parametric[2]) + parametric[3]) - normal_module = math.sqrt(pow(normal_2[0], 2) + pow(normal_2[1], 2) + pow(normal_2[2], 2)) - - if normal_module == 0: - continue - distance = distance / normal_module - if distance < minimum_distance: - minimum_distance = distance - if minimum_distance <= self._delta: - break - - if minimum_distance > self._delta or surface_1.intersect(surface_2) is None: - return False - return True - @staticmethod def segment_list_to_trimesh(lines) -> Trimesh: """ Transform a list of segments into a Trimesh """ + # todo: trimesh has a method for this line_points = [lines[0][0], lines[0][1]] lines.remove(lines[0]) while len(lines) > 1: @@ -106,7 +59,7 @@ class GeometryHelper: line_points.append(line[0]) lines.pop(i - 1) break - polyhedron = Polyhedron(Polygon(line_points).triangulate()) + polyhedron = Polyhedron(Polygon(line_points).triangles) trimesh = Trimesh(polyhedron.vertices, polyhedron.faces) return trimesh diff --git a/hub/helpers/monthly_to_hourly_demand.py b/hub/helpers/monthly_to_hourly_demand.py deleted file mode 100644 index c27682c4..00000000 --- a/hub/helpers/monthly_to_hourly_demand.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -monthly_to_hourly_demand module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import calendar as cal -import pandas as pd -from hub.city_model_structure.building_demand.occupant import Occupant -import hub.helpers.constants as cte - - -class MonthlyToHourlyDemand: - """ - MonthlyToHourlyDemand class - """ - def __init__(self, building, conditioning_seasons): - self._hourly_heating = pd.DataFrame() - self._hourly_cooling = pd.DataFrame() - self._building = building - self._conditioning_seasons = conditioning_seasons - - def hourly_heating(self, key): - """ - hourly distribution of the monthly heating of a building - :param key: string - :return: [hourly_heating] - """ - # todo: this method and the insel model have to be reviewed for more than one thermal zone - external_temp = self._building.external_temperature[cte.HOUR] - # todo: review index depending on how the schedules are defined, either 8760 or 24 hours - for usage_zone in self._building.usage_zones: - temp_set = float(usage_zone.heating_setpoint)-3 - temp_back = float(usage_zone.heating_setback)-3 - # todo: if these are data frames, then they should be called as (Occupancy should be in low case): - # usage_zone.schedules.Occupancy - # self._conditioning_seasons.heating - occupancy = Occupant().get_complete_year_schedule(usage_zone.schedules['Occupancy']) - heating_schedule = self._conditioning_seasons['heating'] - - hourly_heating = [] - i = 0 - j = 0 - temp_grad_day = [] - for month in range(1, 13): - temp_grad_month = 0 - month_range = cal.monthrange(2015, month)[1] - for _ in range(1, month_range+1): - external_temp_med = 0 - for hour in range(0, 24): - external_temp_med += external_temp[key][i]/24 - for hour in range(0, 24): - if external_temp_med < temp_set and heating_schedule[month-1] == 1: - if occupancy[hour] > 0: - hdd = temp_set - external_temp[key][i] - if hdd < 0: - hdd = 0 - temp_grad_day.append(hdd) - else: - hdd = temp_back - external_temp[key][i] - if hdd < 0: - hdd = 0 - temp_grad_day.append(hdd) - else: - temp_grad_day.append(0) - - temp_grad_month += temp_grad_day[i] - i += 1 - - for _ in range(1, month_range + 1): - for hour in range(0, 24): - monthly_demand = self._building.heating[cte.MONTH][month-1] - if monthly_demand == 'NaN': - monthly_demand = 0 - if temp_grad_month == 0: - hourly_demand = 0 - else: - hourly_demand = float(monthly_demand)*float(temp_grad_day[j])/float(temp_grad_month) - hourly_heating.append(hourly_demand) - j += 1 - self._hourly_heating = pd.DataFrame(data=hourly_heating, columns=['monthly to hourly']) - return self._hourly_heating - - def hourly_cooling(self, key): - """ - hourly distribution of the monthly cooling of a building - :param key: string - :return: [hourly_cooling] - """ - # todo: this method and the insel model have to be reviewed for more than one thermal zone - external_temp = self._building.external_temperature[cte.HOUR] - # todo: review index depending on how the schedules are defined, either 8760 or 24 hours - for usage_zone in self._building.usage_zones: - temp_set = float(usage_zone.cooling_setpoint) - temp_back = 100 - occupancy = Occupant().get_complete_year_schedule(usage_zone.schedules['Occupancy']) - cooling_schedule = self._conditioning_seasons['cooling'] - - hourly_cooling = [] - i = 0 - j = 0 - temp_grad_day = [] - for month in range(1, 13): - temp_grad_month = 0 - month_range = cal.monthrange(2015, month)[1] - for _ in range(1, month_range[1] + 1): - for hour in range(0, 24): - if external_temp[key][i] > temp_set and cooling_schedule[month - 1] == 1: - if occupancy[hour] > 0: - cdd = external_temp[key][i] - temp_set - if cdd < 0: - cdd = 0 - temp_grad_day.append(cdd) - else: - cdd = external_temp[key][i] - temp_back - if cdd < 0: - cdd = 0 - temp_grad_day.append(cdd) - else: - temp_grad_day.append(0) - - temp_grad_month += temp_grad_day[i] - i += 1 - - for _ in range(1, month_range[1] + 1): - for hour in range(0, 24): - # monthly_demand = self._building.heating[cte.MONTH]['INSEL'][month-1] - monthly_demand = self._building.cooling[cte.MONTH][month - 1] - if monthly_demand == 'NaN': - monthly_demand = 0 - if temp_grad_month == 0: - hourly_demand = 0 - else: - hourly_demand = float(monthly_demand) * float(temp_grad_day[j]) / float(temp_grad_month) - hourly_cooling.append(hourly_demand) - j += 1 - self._hourly_cooling = pd.DataFrame(data=hourly_cooling, columns=['monthly to hourly']) - return self._hourly_cooling diff --git a/hub/helpers/utils.py b/hub/helpers/utils.py new file mode 100644 index 00000000..ef461f28 --- /dev/null +++ b/hub/helpers/utils.py @@ -0,0 +1,18 @@ +""" +Constant module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Peter Yefi peteryefi@gmail.com +""" + + +def validate_import_export_type(cls_name: type): + """ + Retrieves all the function names in a class which are property types (decoration) + and normal functions + :param cls_name: the class name + :return: [str], a list of functions in the class + """ + return [func for func in dir(cls_name) + if (type(getattr(cls_name, func)) is property or callable(getattr(cls_name, func))) + and func in cls_name.__dict__ and func[0] == '_' and func != '__init__'] diff --git a/hub/imports/construction/data_classes/building_achetype.py b/hub/imports/construction/data_classes/building_achetype.py deleted file mode 100644 index b6477694..00000000 --- a/hub/imports/construction/data_classes/building_achetype.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -BuildingArchetype stores construction information by building archetypes -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from typing import List -from hub.imports.construction.data_classes.thermal_boundary_archetype import ThermalBoundaryArchetype - - -class BuildingArchetype: - """ - BuildingArchetype class - """ - def __init__(self, archetype_keys, average_storey_height, storeys_above_ground, effective_thermal_capacity, - additional_thermal_bridge_u_value, indirectly_heated_area_ratio, infiltration_rate_system_off, - infiltration_rate_system_on, thermal_boundary_archetypes): - self._archetype_keys = archetype_keys - self._average_storey_height = average_storey_height - self._storeys_above_ground = storeys_above_ground - self._effective_thermal_capacity = effective_thermal_capacity - self._additional_thermal_bridge_u_value = additional_thermal_bridge_u_value - self._indirectly_heated_area_ratio = indirectly_heated_area_ratio - self._infiltration_rate_system_off = infiltration_rate_system_off - self._infiltration_rate_system_on = infiltration_rate_system_on - self._thermal_boundary_archetypes = thermal_boundary_archetypes - - @property - def archetype_keys(self) -> {}: - """ - Get keys that define the archetype - :return: dictionary - """ - return self._archetype_keys - - @property - def average_storey_height(self): - """ - Get archetype's building storey height in meters - :return: float - """ - return self._average_storey_height - - @property - def storeys_above_ground(self): - """ - Get archetype's building storey height in meters - :return: float - """ - return self._storeys_above_ground - - @property - def effective_thermal_capacity(self): - """ - Get archetype's effective thermal capacity in J/m2K - :return: float - """ - return self._effective_thermal_capacity - - @property - def additional_thermal_bridge_u_value(self): - """ - Get archetype's additional U value due to thermal bridges per area of shell in W/m2K - :return: float - """ - return self._additional_thermal_bridge_u_value - - @property - def indirectly_heated_area_ratio(self): - """ - Get archetype's indirectly heated area ratio - :return: float - """ - return self._indirectly_heated_area_ratio - - @property - def infiltration_rate_system_off(self): - """ - Get archetype's infiltration rate when conditioning systems OFF in air changes per hour (ACH) - :return: float - """ - return self._infiltration_rate_system_off - - @property - def infiltration_rate_system_on(self): - """ - Get archetype's infiltration rate when conditioning systems ON in air changes per hour (ACH) - :return: float - """ - return self._infiltration_rate_system_on - - @property - def thermal_boundary_archetypes(self) -> List[ThermalBoundaryArchetype]: - """ - Get thermal boundary archetypes associated to the building archetype - :return: list of boundary archetypes - """ - return self._thermal_boundary_archetypes diff --git a/hub/imports/construction/data_classes/layer_archetype.py b/hub/imports/construction/data_classes/layer_archetype.py deleted file mode 100644 index ece0efc5..00000000 --- a/hub/imports/construction/data_classes/layer_archetype.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -LayerArchetype stores layer and materials information, complementing the BuildingArchetype class -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - - -class LayerArchetype: - """ - LayerArchetype class - """ - def __init__(self, name, solar_absorptance, thermal_absorptance, visible_absorptance, thickness=None, - conductivity=None, specific_heat=None, density=None, no_mass=False, thermal_resistance=None): - self._thickness = thickness - self._conductivity = conductivity - self._specific_heat = specific_heat - self._density = density - self._solar_absorptance = solar_absorptance - self._thermal_absorptance = thermal_absorptance - self._visible_absorptance = visible_absorptance - self._no_mass = no_mass - self._name = name - self._thermal_resistance = thermal_resistance - - @property - def thickness(self): - """ - Get thickness in meters - :return: float - """ - return self._thickness - - @property - def conductivity(self): - """ - Get conductivity in W/mK - :return: float - """ - return self._conductivity - - @property - def specific_heat(self): - """ - Get specific heat in J/kgK - :return: float - """ - return self._specific_heat - - @property - def density(self): - """ - Get density in kg/m3 - :return: float - """ - return self._density - - @property - def solar_absorptance(self): - """ - Get solar absorptance - :return: float - """ - return self._solar_absorptance - - @property - def thermal_absorptance(self): - """ - Get thermal absorptance - :return: float - """ - return self._thermal_absorptance - - @property - def visible_absorptance(self): - """ - Get visible absorptance - :return: float - """ - return self._visible_absorptance - - @property - def no_mass(self) -> bool: - """ - Get no mass flag - :return: Boolean - """ - return self._no_mass - - @property - def name(self): - """ - Get name - :return: str - """ - return self._name - - @property - def thermal_resistance(self): - """ - Get thermal resistance in m2K/W - :return: float - """ - return self._thermal_resistance diff --git a/hub/imports/construction/data_classes/thermal_boundary_archetype.py b/hub/imports/construction/data_classes/thermal_boundary_archetype.py deleted file mode 100644 index 4c78e7c6..00000000 --- a/hub/imports/construction/data_classes/thermal_boundary_archetype.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -ThermalBoundaryArchetype stores thermal boundaries information, complementing the BuildingArchetype class -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from typing import List - -from hub.imports.construction.data_classes.layer_archetype import LayerArchetype -from hub.imports.construction.data_classes.thermal_opening_archetype import ThermalOpeningArchetype - - -class ThermalBoundaryArchetype: - """ - ThermalBoundaryArchetype class - """ - def __init__(self, boundary_type, window_ratio, construction_name, layers, thermal_opening, - outside_solar_absorptance=None, outside_thermal_absorptance=None, outside_visible_absorptance=None, - overall_u_value=None, shortwave_reflectance=None, inside_emissivity=None, alpha_coefficient=None, - radiative_coefficient=None): - self._boundary_type = boundary_type - self._outside_solar_absorptance = outside_solar_absorptance - self._outside_thermal_absorptance = outside_thermal_absorptance - self._outside_visible_absorptance = outside_visible_absorptance - self._window_ratio = window_ratio - self._construction_name = construction_name - self._overall_u_value = overall_u_value - self._layers = layers - self._thermal_opening_archetype = thermal_opening - self._shortwave_reflectance = shortwave_reflectance - self._inside_emissivity = inside_emissivity - self._alpha_coefficient = alpha_coefficient - self._radiative_coefficient = radiative_coefficient - - @property - def boundary_type(self): - """ - Get type - :return: str - """ - return self._boundary_type - - @property - def outside_solar_absorptance(self): - """ - Get outside solar absorptance - :return: float - """ - return self._outside_solar_absorptance - - @property - def outside_thermal_absorptance(self): - """ - Get outside thermal absorptance - :return: float - """ - return self._outside_thermal_absorptance - - @property - def outside_visible_absorptance(self): - """ - Get outside visible absorptance - :return: float - """ - return self._outside_visible_absorptance - - @property - def window_ratio(self): - """ - Get window ratio - :return: float - """ - return self._window_ratio - - @property - def construction_name(self): - """ - Get construction name - :return: str - """ - return self._construction_name - - @property - def layers(self) -> List[LayerArchetype]: - """ - Get layers - :return: [NrelLayerArchetype] - """ - return self._layers - - @property - def thermal_opening_archetype(self) -> ThermalOpeningArchetype: - """ - Get thermal opening archetype - :return: ThermalOpeningArchetype - """ - return self._thermal_opening_archetype - - @property - def overall_u_value(self): - """ - Get overall U-value in W/m2K - :return: float - """ - return self._overall_u_value - - @property - def shortwave_reflectance(self): - """ - Get shortwave reflectance - :return: float - """ - return self._shortwave_reflectance - - @property - def inside_emissivity(self): - """ - Get emissivity inside - :return: float - """ - return self._inside_emissivity - - @property - def alpha_coefficient(self): - """ - Get alpha coefficient - :return: float - """ - return self._alpha_coefficient - - @property - def radiative_coefficient(self): - """ - Get radiative coefficient - :return: float - """ - return self._radiative_coefficient diff --git a/hub/imports/construction/data_classes/thermal_opening_archetype.py b/hub/imports/construction/data_classes/thermal_opening_archetype.py deleted file mode 100644 index bfa7a026..00000000 --- a/hub/imports/construction/data_classes/thermal_opening_archetype.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -ThermalOpeningArchetype stores thermal openings information, complementing the BuildingArchetype class -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - - -class ThermalOpeningArchetype: - """ - ThermalOpeningArchetype class - """ - def __init__(self, conductivity=None, frame_ratio=None, g_value=None, thickness=None, - back_side_solar_transmittance_at_normal_incidence=None, - front_side_solar_transmittance_at_normal_incidence=None, overall_u_value=None, - openable_ratio=None, inside_emissivity=None, alpha_coefficient=None, radiative_coefficient=None, - construction_name=None): - self._conductivity = conductivity - self._frame_ratio = frame_ratio - self._g_value = g_value - self._thickness = thickness - self._back_side_solar_transmittance_at_normal_incidence = back_side_solar_transmittance_at_normal_incidence - self._front_side_solar_transmittance_at_normal_incidence = front_side_solar_transmittance_at_normal_incidence - self._overall_u_value = overall_u_value - self._openable_ratio = openable_ratio - self._inside_emissivity = inside_emissivity - self._alpha_coefficient = alpha_coefficient - self._radiative_coefficient = radiative_coefficient - self._construction_name = construction_name - - @property - def conductivity(self): - """ - Get conductivity in W/mK - :return: float - """ - return self._conductivity - - @property - def frame_ratio(self): - """ - Get frame ratio - :return: float - """ - return self._frame_ratio - - @property - def g_value(self): - """ - Get g-value, also called shgc - :return: float - """ - return self._g_value - - @property - def thickness(self): - """ - Get thickness in meters - :return: float - """ - return self._thickness - - @property - def back_side_solar_transmittance_at_normal_incidence(self): - """ - Get back side solar transmittance at normal incidence - :return: float - """ - return self._back_side_solar_transmittance_at_normal_incidence - - @property - def front_side_solar_transmittance_at_normal_incidence(self): - """ - Get front side solar transmittance at normal incidence - :return: float - """ - return self._front_side_solar_transmittance_at_normal_incidence - - @property - def overall_u_value(self): - """ - Get overall U-value in W/m2K - :return: float - """ - return self._overall_u_value - - @property - def openable_ratio(self): - """ - Get openable ratio - :return: float - """ - return self._openable_ratio - - @property - def inside_emissivity(self): - """ - Get emissivity inside - :return: float - """ - return self._inside_emissivity - - @property - def alpha_coefficient(self): - """ - Get alpha coefficient - :return: float - """ - return self._alpha_coefficient - - @property - def radiative_coefficient(self): - """ - Get radiative coefficient - :return: float - """ - return self._radiative_coefficient - - @property - def construction_name(self): - """ - Get thermal opening construction name - """ - return self._construction_name - - @construction_name.setter - def construction_name(self, value): - """ - Set thermal opening construction name - """ - self._construction_name = value diff --git a/hub/imports/construction/helpers/construction_helper.py b/hub/imports/construction/helpers/construction_helper.py index 935e1e13..ed693b5d 100644 --- a/hub/imports/construction/helpers/construction_helper.py +++ b/hub/imports/construction/helpers/construction_helper.py @@ -4,7 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -import sys + from hub.helpers import constants as cte @@ -13,30 +13,6 @@ class ConstructionHelper: Construction helper """ # NREL - _function_to_nrel = { - cte.RESIDENTIAL: 'residential', - cte.SINGLE_FAMILY_HOUSE: 'residential', - cte.MULTI_FAMILY_HOUSE: 'residential', - cte.ROW_HOSE: 'residential', - cte.MID_RISE_APARTMENT: 'midrise apartment', - cte.HIGH_RISE_APARTMENT: 'high-rise apartment', - cte.SMALL_OFFICE: 'small office', - cte.MEDIUM_OFFICE: 'medium office', - cte.LARGE_OFFICE: 'large office', - cte.PRIMARY_SCHOOL: 'primary school', - cte.SECONDARY_SCHOOL: 'secondary school', - cte.STAND_ALONE_RETAIL: 'stand-alone retail', - cte.HOSPITAL: 'hospital', - cte.OUT_PATIENT_HEALTH_CARE: 'outpatient healthcare', - cte.STRIP_MALL: 'strip mall', - cte.SUPERMARKET: 'supermarket', - cte.WAREHOUSE: 'warehouse', - cte.QUICK_SERVICE_RESTAURANT: 'quick service restaurant', - cte.FULL_SERVICE_RESTAURANT: 'full service restaurant', - cte.SMALL_HOTEL: 'small hotel', - cte.LARGE_HOTEL: 'large hotel' - } - _nrel_standards = { 'ASHRAE Std189': 1, 'ASHRAE 90.1_2004': 2 @@ -71,18 +47,6 @@ class ConstructionHelper: cte.ROOF: 'roof' } - @staticmethod - def nrel_from_libs_function(function): - """ - Get NREL function from the given internal function key - :param function: str - :return: str - """ - try: - return ConstructionHelper._function_to_nrel[function] - except KeyError: - sys.stderr.write('Error: keyword not found.\n') - @staticmethod def yoc_to_nrel_standard(year_of_construction): """ @@ -117,4 +81,4 @@ class ConstructionHelper: :return: str """ reference_city = ConstructionHelper.city_to_reference_city(city) - return ConstructionHelper._reference_city_to_nrel_climate_zone[reference_city] \ No newline at end of file + return ConstructionHelper._reference_city_to_nrel_climate_zone[reference_city] diff --git a/hub/imports/construction/helpers/storeys_generation.py b/hub/imports/construction/helpers/storeys_generation.py index 6a1d40a3..179f7431 100644 --- a/hub/imports/construction/helpers/storeys_generation.py +++ b/hub/imports/construction/helpers/storeys_generation.py @@ -49,7 +49,7 @@ class StoreysGeneration: thermal_zones = [storey.thermal_zone] else: # internal thermal boundary -> two thermal zones - grad = np.rad2deg(thermal_boundary.inclination) + grad = np.rad2deg(thermal_boundary.parent_surface.inclination) if grad >= 170: thermal_zones = [storey.thermal_zone, storey.neighbours[0]] else: @@ -116,7 +116,7 @@ class StoreysGeneration: thermal_zones = [storey.thermal_zone] else: # internal thermal boundary -> two thermal zones - grad = np.rad2deg(thermal_boundary.inclination) + grad = np.rad2deg(thermal_boundary.parent_surface.inclination) if grad >= 170: thermal_zones = [storey.thermal_zone, storey.neighbours[0]] else: diff --git a/hub/imports/construction/nrcan_physics_parameters.py b/hub/imports/construction/nrcan_physics_parameters.py new file mode 100644 index 00000000..3904ac69 --- /dev/null +++ b/hub/imports/construction/nrcan_physics_parameters.py @@ -0,0 +1,189 @@ +""" +NrcanPhysicsParameters import the construction and material information defined by NRCAN +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +import sys + +from hub.catalog_factories.construction_catalog_factory import ConstructionCatalogFactory +from hub.city_model_structure.building_demand.layer import Layer +from hub.city_model_structure.building_demand.material import Material +from hub.helpers.dictionaries import Dictionaries +from hub.imports.construction.helpers.construction_helper import ConstructionHelper +from hub.imports.construction.helpers.storeys_generation import StoreysGeneration + + +class NrcanPhysicsParameters: + """ + NrcanPhysicsParameters class + """ + def __init__(self, city, base_path, divide_in_storeys=False): + self._city = city + self._path = base_path + self._divide_in_storeys = divide_in_storeys + self._climate_zone = ConstructionHelper.city_to_nrel_climate_zone(city.name) + + def enrich_buildings(self): + """ + Returns the city with the construction parameters assigned to the buildings + """ + city = self._city + canel_catalog = ConstructionCatalogFactory('nrcan').catalog + for building in city.buildings: + try: + function = Dictionaries().hub_function_to_nrcan_construction_function[building.function] + archetype = self._search_archetype(canel_catalog, function, building.year_of_construction, + self._climate_zone) + except KeyError: + sys.stderr.write(f'Building {building.name} has unknown construction archetype for building function: ' + f'{building.function} and building year of construction: {building.year_of_construction} ' + f'and climate zone reference norm {self._climate_zone}\n') + return + + # if building has no thermal zones defined from geometry, and the building will be divided in storeys, + # one thermal zone per storey is assigned + if len(building.internal_zones) == 1: + if building.internal_zones[0].thermal_zones is None: + self._create_storeys(building, archetype, self._divide_in_storeys) + if self._divide_in_storeys: + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + thermal_zone.total_floor_area = thermal_zone.footprint_area + else: + number_of_storeys = int(float(building.eave_height) / float(building.average_storey_height)) + thermal_zone = building.internal_zones[0].thermal_zones[0] + thermal_zone.total_floor_area = thermal_zone.footprint_area * number_of_storeys + else: + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + thermal_zone.total_floor_area = thermal_zone.footprint_area + + for internal_zone in building.internal_zones: + self._assign_values(internal_zone.thermal_zones, archetype) + for thermal_zone in internal_zone.thermal_zones: + self._calculate_view_factors(thermal_zone) + + @staticmethod + def _search_archetype(nrel_catalog, function, year_of_construction, climate_zone): + nrel_archetypes = nrel_catalog.entries('archetypes') + for building_archetype in nrel_archetypes: + construction_period_limits = building_archetype.construction_period.split(' - ') + if construction_period_limits[1] == 'PRESENT': + construction_period_limits[1] = 3000 + if int(construction_period_limits[0]) <= int(year_of_construction) < int(construction_period_limits[1]): + if (str(function) == str(building_archetype.function)) and \ + (climate_zone == str(building_archetype.climate_zone)): + return building_archetype + raise KeyError('archetype not found') + + @staticmethod + def _search_construction_in_archetype(archetype, construction_type): + construction_archetypes = archetype.constructions + for construction_archetype in construction_archetypes: + if str(construction_type) == str(construction_archetype.type): + return construction_archetype + return None + + def _assign_values(self, thermal_zones, archetype): + for thermal_zone in thermal_zones: + thermal_zone.additional_thermal_bridge_u_value = archetype.extra_loses_due_to_thermal_bridges + thermal_zone.effective_thermal_capacity = archetype.thermal_capacity + thermal_zone.indirectly_heated_area_ratio = archetype.indirect_heated_ratio + thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_for_ventilation_system_on + thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_for_ventilation_system_off + for thermal_boundary in thermal_zone.thermal_boundaries: + construction_archetype = self._search_construction_in_archetype(archetype, thermal_boundary.type) + thermal_boundary.construction_name = construction_archetype.name + try: + thermal_boundary.window_ratio = construction_archetype.window_ratio + except ValueError: + # This is the normal operation way when the windows are defined in the geometry + continue + thermal_boundary.layers = [] + for layer_archetype in construction_archetype.layers: + layer = Layer() + layer.thickness = layer_archetype.thickness + material = Material() + archetype_material = layer_archetype.material + material.name = archetype_material.name + material.id = archetype_material.id + material.no_mass = archetype_material.no_mass + if archetype_material.no_mass: + material.thermal_resistance = archetype_material.thermal_resistance + else: + material.density = archetype_material.density + material.conductivity = archetype_material.conductivity + material.specific_heat = archetype_material.specific_heat + material.solar_absorptance = archetype_material.solar_absorptance + material.thermal_absorptance = archetype_material.thermal_absorptance + material.visible_absorptance = archetype_material.visible_absorptance + layer.material = material + thermal_boundary.layers.append(layer) + # The agreement is that the layers are defined from outside to inside + external_layer = construction_archetype.layers[0] + external_surface = thermal_boundary.parent_surface + external_surface.short_wave_reflectance = 1 - float(external_layer.material.solar_absorptance) + external_surface.long_wave_emittance = 1 - float(external_layer.material.solar_absorptance) + internal_layer = construction_archetype.layers[len(construction_archetype.layers) - 1] + internal_surface = thermal_boundary.internal_surface + internal_surface.short_wave_reflectance = 1 - float(internal_layer.material.solar_absorptance) + internal_surface.long_wave_emittance = 1 - float(internal_layer.material.solar_absorptance) + + for thermal_opening in thermal_boundary.thermal_openings: + if construction_archetype.window is not None: + window_archetype = construction_archetype.window + thermal_opening.construction_name = window_archetype.name + thermal_opening.frame_ratio = window_archetype.frame_ratio + thermal_opening.g_value = window_archetype.g_value + thermal_opening.overall_u_value = window_archetype.overall_u_value + + # todo: verify windows + @staticmethod + def _calculate_view_factors(thermal_zone): + """ + Get thermal zone view factors matrix + :return: [[float]] + """ + total_area = 0 + for thermal_boundary in thermal_zone.thermal_boundaries: + total_area += thermal_boundary.opaque_area + for thermal_opening in thermal_boundary.thermal_openings: + total_area += thermal_opening.area + + view_factors_matrix = [] + for thermal_boundary_1 in thermal_zone.thermal_boundaries: + values = [] + for thermal_boundary_2 in thermal_zone.thermal_boundaries: + value = 0 + if thermal_boundary_1.id != thermal_boundary_2.id: + value = thermal_boundary_2.opaque_area / (total_area - thermal_boundary_1.opaque_area) + values.append(value) + for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_opening in thermal_boundary.thermal_openings: + value = thermal_opening.area / (total_area - thermal_boundary_1.opaque_area) + values.append(value) + view_factors_matrix.append(values) + + for thermal_boundary_1 in thermal_zone.thermal_boundaries: + values = [] + for thermal_opening_1 in thermal_boundary_1.thermal_openings: + for thermal_boundary_2 in thermal_zone.thermal_boundaries: + value = thermal_boundary_2.opaque_area / (total_area - thermal_opening_1.area) + values.append(value) + for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_opening_2 in thermal_boundary.thermal_openings: + value = 0 + if thermal_opening_1.id != thermal_opening_2.id: + value = thermal_opening_2.area / (total_area - thermal_opening_1.area) + values.append(value) + view_factors_matrix.append(values) + thermal_zone.view_factors_matrix = view_factors_matrix + + @staticmethod + def _create_storeys(building, archetype, divide_in_storeys): + building.average_storey_height = archetype.average_storey_height + building.storeys_above_ground = 1 + thermal_zones = StoreysGeneration(building, building.internal_zones[0], + divide_in_storeys=divide_in_storeys).thermal_zones + building.internal_zones[0].thermal_zones = thermal_zones diff --git a/hub/imports/construction/nrel_physics_interface.py b/hub/imports/construction/nrel_physics_interface.py deleted file mode 100644 index 7e802cf6..00000000 --- a/hub/imports/construction/nrel_physics_interface.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Nrel-based interface, it reads format defined within the CERC team based on NREL structure -and enriches the city with archetypes and materials -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - -from hub.imports.construction.helpers.storeys_generation import StoreysGeneration - - -class NrelPhysicsInterface: - """ - NrelPhysicsInterface abstract class - """ - - # todo: verify windows - @staticmethod - def _calculate_view_factors(thermal_zone): - """ - Get thermal zone view factors matrix - :return: [[float]] - """ - total_area = 0 - for thermal_boundary in thermal_zone.thermal_boundaries: - total_area += thermal_boundary.opaque_area - for thermal_opening in thermal_boundary.thermal_openings: - total_area += thermal_opening.area - - view_factors_matrix = [] - for thermal_boundary_1 in thermal_zone.thermal_boundaries: - values = [] - for thermal_boundary_2 in thermal_zone.thermal_boundaries: - value = 0 - if thermal_boundary_1.id != thermal_boundary_2.id: - value = thermal_boundary_2.opaque_area / (total_area - thermal_boundary_1.opaque_area) - values.append(value) - for thermal_boundary in thermal_zone.thermal_boundaries: - for thermal_opening in thermal_boundary.thermal_openings: - value = thermal_opening.area / (total_area - thermal_boundary_1.opaque_area) - values.append(value) - view_factors_matrix.append(values) - - for thermal_boundary_1 in thermal_zone.thermal_boundaries: - values = [] - for thermal_opening_1 in thermal_boundary_1.thermal_openings: - for thermal_boundary_2 in thermal_zone.thermal_boundaries: - value = thermal_boundary_2.opaque_area / (total_area - thermal_opening_1.area) - values.append(value) - for thermal_boundary in thermal_zone.thermal_boundaries: - for thermal_opening_2 in thermal_boundary.thermal_openings: - value = 0 - if thermal_opening_1.id != thermal_opening_2.id: - value = thermal_opening_2.area / (total_area - thermal_opening_1.area) - values.append(value) - view_factors_matrix.append(values) - thermal_zone.view_factors_matrix = view_factors_matrix - - @staticmethod - def _create_storeys(building, archetype, divide_in_storeys): - building.average_storey_height = archetype.average_storey_height - building.storeys_above_ground = 1 - thermal_zones = StoreysGeneration(building, building.internal_zones[0], - divide_in_storeys=divide_in_storeys).thermal_zones - building.internal_zones[0].thermal_zones = thermal_zones - - def enrich_buildings(self): - """ - Raise not implemented error - """ - raise NotImplementedError diff --git a/hub/imports/construction/us_physics_parameters.py b/hub/imports/construction/nrel_physics_parameters.py similarity index 70% rename from hub/imports/construction/us_physics_parameters.py rename to hub/imports/construction/nrel_physics_parameters.py index 68f1c3c2..6de4771a 100644 --- a/hub/imports/construction/us_physics_parameters.py +++ b/hub/imports/construction/nrel_physics_parameters.py @@ -1,5 +1,5 @@ """ -UsPhysicsParameters import the construction and material information for US +NrelPhysicsParameters import the construction and material information defined by NREL SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca @@ -7,38 +7,41 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord """ import sys -from hub.imports.construction.nrel_physics_interface import NrelPhysicsInterface +from hub.hub_logger import logger from hub.catalog_factories.construction_catalog_factory import ConstructionCatalogFactory from hub.city_model_structure.building_demand.layer import Layer from hub.city_model_structure.building_demand.material import Material +from hub.helpers.dictionaries import Dictionaries from hub.imports.construction.helpers.construction_helper import ConstructionHelper -from hub.hub_logger import logger +from hub.imports.construction.helpers.storeys_generation import StoreysGeneration -class UsPhysicsParameters(NrelPhysicsInterface): +class NrelPhysicsParameters: """ - UsPhysicsParameters class + NrelPhysicsParameters class """ + def __init__(self, city, base_path, divide_in_storeys=False): self._city = city self._path = base_path self._divide_in_storeys = divide_in_storeys self._climate_zone = ConstructionHelper.city_to_nrel_climate_zone(city.name) - super().__init__() def enrich_buildings(self): """ Returns the city with the construction parameters assigned to the buildings """ city = self._city + nrel_catalog = ConstructionCatalogFactory('nrel').catalog for building in city.buildings: try: - archetype = self._search_archetype(building.function, building.year_of_construction, self._climate_zone) + function = Dictionaries().hub_function_to_nrel_construction_function[building.function] + archetype = self._search_archetype(nrel_catalog, function, building.year_of_construction, + self._climate_zone) except KeyError: - logger.error(f'Building {building.name} has unknown archetype for building function: {building.function} ' - f'and building year of construction: {building.year_of_construction} ' - f'and climate zone reference norm {self._climate_zone}\n') + f'and building year of construction: {building.year_of_construction} ' + f'and climate zone reference norm {self._climate_zone}\n') sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} ' f'and building year of construction: {building.year_of_construction} ' f'and climate zone reference norm {self._climate_zone}\n') @@ -69,8 +72,7 @@ class UsPhysicsParameters(NrelPhysicsInterface): self._calculate_view_factors(thermal_zone) @staticmethod - def _search_archetype(function, year_of_construction, climate_zone): - nrel_catalog = ConstructionCatalogFactory('nrel').catalog + def _search_archetype(nrel_catalog, function, year_of_construction, climate_zone): nrel_archetypes = nrel_catalog.entries('archetypes') for building_archetype in nrel_archetypes: construction_period_limits = building_archetype.construction_period.split(' - ') @@ -142,3 +144,53 @@ class UsPhysicsParameters(NrelPhysicsInterface): thermal_opening.frame_ratio = window_archetype.frame_ratio thermal_opening.g_value = window_archetype.g_value thermal_opening.overall_u_value = window_archetype.overall_u_value + + # todo: verify windows + @staticmethod + def _calculate_view_factors(thermal_zone): + """ + Get thermal zone view factors matrix + :return: [[float]] + """ + total_area = 0 + for thermal_boundary in thermal_zone.thermal_boundaries: + total_area += thermal_boundary.opaque_area + for thermal_opening in thermal_boundary.thermal_openings: + total_area += thermal_opening.area + + view_factors_matrix = [] + for thermal_boundary_1 in thermal_zone.thermal_boundaries: + values = [] + for thermal_boundary_2 in thermal_zone.thermal_boundaries: + value = 0 + if thermal_boundary_1.id != thermal_boundary_2.id: + value = thermal_boundary_2.opaque_area / (total_area - thermal_boundary_1.opaque_area) + values.append(value) + for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_opening in thermal_boundary.thermal_openings: + value = thermal_opening.area / (total_area - thermal_boundary_1.opaque_area) + values.append(value) + view_factors_matrix.append(values) + + for thermal_boundary_1 in thermal_zone.thermal_boundaries: + values = [] + for thermal_opening_1 in thermal_boundary_1.thermal_openings: + for thermal_boundary_2 in thermal_zone.thermal_boundaries: + value = thermal_boundary_2.opaque_area / (total_area - thermal_opening_1.area) + values.append(value) + for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_opening_2 in thermal_boundary.thermal_openings: + value = 0 + if thermal_opening_1.id != thermal_opening_2.id: + value = thermal_opening_2.area / (total_area - thermal_opening_1.area) + values.append(value) + view_factors_matrix.append(values) + thermal_zone.view_factors_matrix = view_factors_matrix + + @staticmethod + def _create_storeys(building, archetype, divide_in_storeys): + building.average_storey_height = archetype.average_storey_height + building.storeys_above_ground = 1 + thermal_zones = StoreysGeneration(building, building.internal_zones[0], + divide_in_storeys=divide_in_storeys).thermal_zones + building.internal_zones[0].thermal_zones = thermal_zones diff --git a/hub/imports/construction_factory.py b/hub/imports/construction_factory.py index bc77be4f..f9ce905a 100644 --- a/hub/imports/construction_factory.py +++ b/hub/imports/construction_factory.py @@ -3,19 +3,29 @@ ConstructionFactory (before PhysicsFactory) retrieve the specific construction m SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ + from pathlib import Path -from hub.imports.construction.us_physics_parameters import UsPhysicsParameters +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type +from hub.imports.construction.nrel_physics_parameters import NrelPhysicsParameters +from hub.imports.construction.nrcan_physics_parameters import NrcanPhysicsParameters class ConstructionFactory: """ - PhysicsFactor class + ConstructionFactory class """ def __init__(self, handler, city, base_path=None): if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/construction') self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(ConstructionFactory) + if self._handler not in class_funcs: + err_msg = f"Wrong import type [{self._handler}]. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._city = city self._base_path = base_path @@ -23,7 +33,14 @@ class ConstructionFactory: """ Enrich the city by using NREL information """ - UsPhysicsParameters(self._city, self._base_path).enrich_buildings() + NrelPhysicsParameters(self._city, self._base_path).enrich_buildings() + self._city.level_of_detail.construction = 2 + + def _nrcan(self): + """ + Enrich the city by using NRCAN information + """ + NrcanPhysicsParameters(self._city, self._base_path).enrich_buildings() self._city.level_of_detail.construction = 2 def enrich(self): @@ -38,4 +55,4 @@ class ConstructionFactory: Enrich the city given to the class using the class given handler :return: None """ - UsPhysicsParameters(self._city, self._base_path).enrich_buildings() \ No newline at end of file + NrelPhysicsParameters(self._city, self._base_path).enrich_buildings() diff --git a/hub/imports/db_factory.py b/hub/imports/db_factory.py index f4d75ba8..596aa225 100644 --- a/hub/imports/db_factory.py +++ b/hub/imports/db_factory.py @@ -4,10 +4,9 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project CoderPeter Yefi peteryefi@gmail.com """ -from hub.persistence import CityRepo -from hub.persistence import HeatPumpSimulationRepo -from typing import Dict from hub.city_model_structure.city import City +from hub.persistence import City as CityRepository +from hub.persistence import SimulationResults class DBFactory: @@ -16,14 +15,18 @@ class DBFactory: """ def __init__(self, db_name, dotenv_path, app_env): - self._city_repo = CityRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) - self._hp_simulation_repo = HeatPumpSimulationRepo(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) + self._city_repository = CityRepository(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) + self._simulation_results = SimulationResults(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) - def persist_city(self, user_id: int, city: City): + def persist_city(self, city: City, pickle_path, application_id: int, user_id: int): """ Persist city into postgres database + :param city: City to be stored + :param pickle_path: Path to save the pickle file + :param application_id: Application id owning this city + :param user_id: User who create the city """ - return self._city_repo.insert(city, user_id) + return self._city_repository.insert(city, pickle_path, application_id, user_id) def update_city(self, city_id, city): """ @@ -31,27 +34,21 @@ class DBFactory: :param city_id: the id of the city to update :param city: the updated city object """ - return self._city_repo.update(city_id, city) + return self._city_repository.update(city_id, city) def delete_city(self, city_id): """ Deletes a single city from postgres :param city_id: the id of the city to get """ - self._city_repo.delete_city(city_id) + self._city_repository.delete(city_id) - def persist_hp_simulation(self, hp_simulation_data: Dict, city_id: int): + def add_simulation_results(self, name, values, city_id=None, city_object_id=None): """ - Persist heat pump simulation results - :param hp_simulation_data: the simulation results - :param city_id: the city object used in running the simulation - :return: HeatPumpSimulation object + Add simulation results to the city or to the city_object + :param name: simulation and simulation engine name + :param values: simulation values in json format + :param city_id: city id or None + :param city_object_id: city object id or None """ - return self._hp_simulation_repo.insert(hp_simulation_data, city_id) - - def delete_hp_simulation(self, hp_sim_id): - """ - Deletes a single heat pump simulation from postgres - :param hp_sim_id: the id of the heat pump simulation to get - """ - self._hp_simulation_repo.delete_hp_simulation(hp_sim_id) + self._simulation_results.insert(name, values,city_id, city_object_id) diff --git a/hub/imports/energy_systems/air_source_hp_parameters.py b/hub/imports/energy_systems/air_source_hp_parameters.py index e1d81853..7e0e0f1f 100644 --- a/hub/imports/energy_systems/air_source_hp_parameters.py +++ b/hub/imports/energy_systems/air_source_hp_parameters.py @@ -2,7 +2,8 @@ AirSourceHeatPumpParameters import the heat pump information SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group -Project Coder Peter Yefi peteryefi@gmail.comCode contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Project Coder Peter Yefi peteryefi@gmail.comCode +contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import pandas as pd @@ -41,7 +42,7 @@ class AirSourceHeatPumpParameters: for sheet, dataframe in heat_pump_dfs.items(): if 'Summary' in sheet: - continue + continue # Remove nan rows and columns and extract cooling and heating data # for each sheet @@ -69,8 +70,7 @@ class AirSourceHeatPumpParameters: Enriches the city with information from file """ heat_pump_data = self._read_file() - for (k_cool, v_cool), (k_heat, v_heat) in \ - zip(heat_pump_data["cooling"].items(), heat_pump_data["heating"].items()): + for (k_cool, v_cool), (k_heat, v_heat) in zip(heat_pump_data["cooling"].items(), heat_pump_data["heating"].items()): heat_pump = AirSourceHP() heat_pump.model = k_cool h_data = self._extract_heat_pump_data(v_heat) @@ -87,7 +87,8 @@ class AirSourceHeatPumpParameters: self._city.add_city_object(energy_system) return self._city - def _extract_heat_pump_data(self, heat_pump_capacity_data: Dict) -> [List, List]: + @staticmethod + def _extract_heat_pump_data(heat_pump_capacity_data: Dict) -> [List, List]: """ Fetches a list of metric based data for heat pump for various temperature, eg. cooling capacity data for 12 capacity heat pump @@ -123,15 +124,16 @@ class AirSourceHeatPumpParameters: x_values = x_values.tolist() # convert list of lists to one list - hp_data = [i/j for i, j in - zip(list(itertools.chain.from_iterable(heat_pump_data[0])), - list(itertools.chain.from_iterable(heat_pump_data[1])))] + hp_data = [i / j for i, j in + zip(list(itertools.chain.from_iterable(heat_pump_data[0])), + list(itertools.chain.from_iterable(heat_pump_data[1])))] # Compute heat output coefficients popt, _ = curve_fit(self._objective_function, [x_values, out_temp], hp_data) return popt.tolist() - def _objective_function(self, xdata: List, a1: float, a2: float, a3: float, a4: float, a5: float, a6: float) -> float: + @staticmethod + def _objective_function(xdata: List, a1: float, a2: float, a3: float, a4: float, a5: float, a6: float) -> float: """ Objective function for computing coefficients :param xdata: diff --git a/hub/imports/energy_systems/water_to_water_hp_parameters.py b/hub/imports/energy_systems/water_to_water_hp_parameters.py index 923f92e0..738a2470 100644 --- a/hub/imports/energy_systems/water_to_water_hp_parameters.py +++ b/hub/imports/energy_systems/water_to_water_hp_parameters.py @@ -71,7 +71,8 @@ class WaterToWaterHPParameters: # range values for extracting data return data - def _extract_hp_data(self, df, columns, ranges): + @staticmethod + def _extract_hp_data(df, columns, ranges): """ Extract variable specific (LWT, PD or TC) data from water to water hp :param df: the dataframe @@ -88,7 +89,8 @@ class WaterToWaterHPParameters: return data.dropna().values.tolist() - def _extract_flow_and_ewt(self, df, ranges, columns, flow_rates): + @staticmethod + def _extract_flow_and_ewt(df, ranges, columns, flow_rates): """ Create the flow and ewt data based on the length of the various columns for the variables being extracted @@ -148,7 +150,8 @@ class WaterToWaterHPParameters: demand) return popt.tolist() - def _objective_function(self, xdata: List, a1: float, a2: float, a3: float, a4: float, a5: float, a6: float, + @staticmethod + def _objective_function(xdata: List, a1: float, a2: float, a3: float, a4: float, a5: float, a6: float, a7: float, a8: float, a9: float, a10: float, a11: float) -> float: """ Objective function for computing coefficients @@ -169,4 +172,3 @@ class WaterToWaterHPParameters: x, y, t = xdata return (a1 * x ** 2) + (a2 * x) + (a3 * y ** 2) + (a4 * y) + (a5 * t ** 2) + (a6 * t) + (a7 * x * y) + ( a8 * x * t) + (a9 * y * t) + (a10 * x * y * t) + a11 - diff --git a/hub/imports/energy_systems_factory.py b/hub/imports/energy_systems_factory.py index f167b848..b1f44876 100644 --- a/hub/imports/energy_systems_factory.py +++ b/hub/imports/energy_systems_factory.py @@ -8,6 +8,8 @@ Code contributors: Peter Yefi peteryefi@gmail.com from pathlib import Path from hub.imports.energy_systems.air_source_hp_parameters import AirSourceHeatPumpParameters from hub.imports.energy_systems.water_to_water_hp_parameters import WaterToWaterHPParameters +from hub.helpers.utils import validate_import_export_type +from hub.hub_logger import logger class EnergySystemsFactory: @@ -19,6 +21,11 @@ class EnergySystemsFactory: if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/energy_systems') self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(EnergySystemsFactory) + if self._handler not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._city = city self._base_path = base_path diff --git a/hub/imports/geometry/citygml.py b/hub/imports/geometry/citygml.py index f68fdd9d..bcd3dec8 100644 --- a/hub/imports/geometry/citygml.py +++ b/hub/imports/geometry/citygml.py @@ -7,26 +7,35 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca import numpy as np import xmltodict -from hub.city_model_structure.city import City + from hub.city_model_structure.building import Building +from hub.city_model_structure.city import City from hub.city_model_structure.parts_consisting_building import PartsConsistingBuilding -from hub.helpers.geometry_helper import GeometryHelper -from hub.imports.geometry.citygml_classes.citygml_lod2 import CityGmlLod2 from hub.imports.geometry.citygml_classes.citygml_lod1 import CityGmlLod1 +from hub.imports.geometry.citygml_classes.citygml_lod2 import CityGmlLod2 class CityGml: """ CityGml class """ - def __init__(self, path, extrusion_height_field=None, year_of_construction_field=None, function_field=None): + def __init__(self, + path, + extrusion_height_field=None, + year_of_construction_field='yearOfConstruction', + function_field='function', + function_to_hub=None): self._city = None self._lod = None self._lod1_tags = ['lod1Solid', 'lod1MultiSurface'] self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve'] self._extrusion_height_field = extrusion_height_field + self._function_to_hub = function_to_hub self._year_of_construction_field = year_of_construction_field + if function_field is None: + function_field = 'function' self._function_field = function_field + self._lower_corner = None self._upper_corner = None with open(path) as gml: @@ -54,7 +63,6 @@ class CityGml: }, force_list=force_list) self._city_objects = None - self._geometry = GeometryHelper() if 'boundedBy' in self._gml['CityModel']: for bound in self._gml['CityModel']['boundedBy']: envelope = bound['Envelope'] @@ -108,10 +116,16 @@ class CityGml: name = city_object['@id'] function = None year_of_construction = None - if 'yearOfConstruction' in city_object: - year_of_construction = city_object['yearOfConstruction'] - if 'function' in city_object: - function = city_object['function'] + if self._year_of_construction_field in city_object: + year_of_construction = city_object[self._year_of_construction_field] + if self._function_field in city_object: + function = city_object[self._function_field] + if type(function) != str: + function = function['#text'] + if self._function_to_hub is not None: + # use the transformation dictionary to retrieve the proper function + function = self._function_to_hub[function] + if any(key in city_object for key in self._lod1_tags): if self._lod is None or self._lod > 1: self._lod = 1 diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index f7c91082..0f240e2e 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -26,7 +26,12 @@ class Geojson: X = 0 Y = 1 - def __init__(self, path, extrusion_height_field=None, year_of_construction_field=None, function_field=None): + def __init__(self, + path, + extrusion_height_field=None, + year_of_construction_field=None, + function_field=None, + function_to_hub=None): # todo: destination epsg should change according actual the location self._transformer = Transformer.from_crs('epsg:4326', 'epsg:26911') self._min_x = cte.MAX_FLOAT @@ -38,6 +43,7 @@ class Geojson: self._extrusion_height_field = extrusion_height_field self._year_of_construction_field = year_of_construction_field self._function_field = function_field + self._function_to_hub = function_to_hub with open(path) as json_file: self._geojson = json.loads(json_file.read()) @@ -118,6 +124,9 @@ class Geojson: function = None if self._function_field is not None: function = feature['properties'][self._function_field] + if self._function_to_hub is not None: + # use the transformation dictionary to retrieve the proper function + function = self._function_to_hub[function] geometry = feature['geometry'] if 'id' in feature: building_name = feature['id'] @@ -125,6 +134,7 @@ class Geojson: building_name = f'building_{building_id}' building_id += 1 polygons = [] + lod = 1 for part, coordinates in enumerate(geometry['coordinates']): polygons = self._get_polygons(polygons, coordinates) for zone, polygon in enumerate(polygons): @@ -133,6 +143,7 @@ class Geojson: year_of_construction, function, [polygon]) + lod = 0 else: if self._max_z < extrusion_height: self._max_z = extrusion_height @@ -145,4 +156,5 @@ class Geojson: self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911') for building in buildings: self._city.add_city_object(building) + self._city.level_of_detail.geometry = lod return self._city diff --git a/hub/imports/geometry/gpandas.py b/hub/imports/geometry/gpandas.py index 83536137..f1bd2815 100644 --- a/hub/imports/geometry/gpandas.py +++ b/hub/imports/geometry/gpandas.py @@ -57,6 +57,7 @@ class GPandas: """ if self._city is None: self._city = City(self._lower_corner, self._upper_corner, self._srs_name) + lod = 0 for scene_index, bldg in self._scene.iterrows(): polygon = bldg.geometry height = float(bldg['height']) diff --git a/hub/imports/geometry/helpers/geometry_helper.py b/hub/imports/geometry/helpers/geometry_helper.py index 1752ac18..b9384801 100644 --- a/hub/imports/geometry/helpers/geometry_helper.py +++ b/hub/imports/geometry/helpers/geometry_helper.py @@ -5,7 +5,6 @@ Copyright © 2022 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -import hub.helpers.constants as cte import numpy as np @@ -13,276 +12,6 @@ class GeometryHelper: """ Geometry helper """ - # function - _pluto_to_function = { - 'A0': cte.SINGLE_FAMILY_HOUSE, - 'A1': cte.SINGLE_FAMILY_HOUSE, - 'A2': cte.SINGLE_FAMILY_HOUSE, - 'A3': cte.SINGLE_FAMILY_HOUSE, - 'A4': cte.SINGLE_FAMILY_HOUSE, - 'A5': cte.SINGLE_FAMILY_HOUSE, - 'A6': cte.SINGLE_FAMILY_HOUSE, - 'A7': cte.SINGLE_FAMILY_HOUSE, - 'A8': cte.SINGLE_FAMILY_HOUSE, - 'A9': cte.SINGLE_FAMILY_HOUSE, - 'B1': cte.MULTI_FAMILY_HOUSE, - 'B2': cte.MULTI_FAMILY_HOUSE, - 'B3': cte.MULTI_FAMILY_HOUSE, - 'B9': cte.MULTI_FAMILY_HOUSE, - 'C0': cte.RESIDENTIAL, - 'C1': cte.RESIDENTIAL, - 'C2': cte.RESIDENTIAL, - 'C3': cte.RESIDENTIAL, - 'C4': cte.RESIDENTIAL, - 'C5': cte.RESIDENTIAL, - 'C6': cte.RESIDENTIAL, - 'C7': cte.RESIDENTIAL, - 'C8': cte.RESIDENTIAL, - 'C9': cte.RESIDENTIAL, - 'D0': cte.RESIDENTIAL, - 'D1': cte.RESIDENTIAL, - 'D2': cte.RESIDENTIAL, - 'D3': cte.RESIDENTIAL, - 'D4': cte.RESIDENTIAL, - 'D5': cte.RESIDENTIAL, - 'D6': cte.RESIDENTIAL, - 'D7': cte.RESIDENTIAL, - 'D8': cte.RESIDENTIAL, - 'D9': cte.RESIDENTIAL, - 'E1': cte.WAREHOUSE, - 'E3': cte.WAREHOUSE, - 'E4': cte.WAREHOUSE, - 'E5': cte.WAREHOUSE, - 'E7': cte.WAREHOUSE, - 'E9': cte.WAREHOUSE, - 'F1': cte.WAREHOUSE, - 'F2': cte.WAREHOUSE, - 'F4': cte.WAREHOUSE, - 'F5': cte.WAREHOUSE, - 'F8': cte.WAREHOUSE, - 'F9': cte.WAREHOUSE, - 'G0': cte.SMALL_OFFICE, - 'G1': cte.SMALL_OFFICE, - 'G2': cte.SMALL_OFFICE, - 'G3': cte.SMALL_OFFICE, - 'G4': cte.SMALL_OFFICE, - 'G5': cte.SMALL_OFFICE, - 'G6': cte.SMALL_OFFICE, - 'G7': cte.SMALL_OFFICE, - 'G8': cte.SMALL_OFFICE, - 'G9': cte.SMALL_OFFICE, - 'H1': cte.HOTEL, - 'H2': cte.HOTEL, - 'H3': cte.HOTEL, - 'H4': cte.HOTEL, - 'H5': cte.HOTEL, - 'H6': cte.HOTEL, - 'H7': cte.HOTEL, - 'H8': cte.HOTEL, - 'H9': cte.HOTEL, - 'HB': cte.HOTEL, - 'HH': cte.HOTEL, - 'HR': cte.HOTEL, - 'HS': cte.HOTEL, - 'I1': cte.HOSPITAL, - 'I2': cte.OUT_PATIENT_HEALTH_CARE, - 'I3': cte.OUT_PATIENT_HEALTH_CARE, - 'I4': cte.RESIDENTIAL, - 'I5': cte.OUT_PATIENT_HEALTH_CARE, - 'I6': cte.OUT_PATIENT_HEALTH_CARE, - 'I7': cte.OUT_PATIENT_HEALTH_CARE, - 'I9': cte.OUT_PATIENT_HEALTH_CARE, - 'J1': cte.LARGE_OFFICE, - 'J2': cte.LARGE_OFFICE, - 'J3': cte.LARGE_OFFICE, - 'J4': cte.LARGE_OFFICE, - 'J5': cte.LARGE_OFFICE, - 'J6': cte.LARGE_OFFICE, - 'J7': cte.LARGE_OFFICE, - 'J8': cte.LARGE_OFFICE, - 'J9': cte.LARGE_OFFICE, - 'K1': cte.STRIP_MALL, - 'K2': cte.STRIP_MALL, - 'K3': cte.STRIP_MALL, - 'K4': cte.RESIDENTIAL, - 'K5': cte.RESTAURANT, - 'K6': cte.SUPERMARKET, - 'K7': cte.SUPERMARKET, - 'K8': cte.SUPERMARKET, - 'K9': cte.SUPERMARKET, - 'L1': cte.RESIDENTIAL, - 'L2': cte.RESIDENTIAL, - 'L3': cte.RESIDENTIAL, - 'L8': cte.RESIDENTIAL, - 'L9': cte.RESIDENTIAL, - 'M1': cte.LARGE_OFFICE, - 'M2': cte.LARGE_OFFICE, - 'M3': cte.LARGE_OFFICE, - 'M4': cte.LARGE_OFFICE, - 'M9': cte.LARGE_OFFICE, - 'N1': cte.RESIDENTIAL, - 'N2': cte.RESIDENTIAL, - 'N3': cte.RESIDENTIAL, - 'N4': cte.RESIDENTIAL, - 'N9': cte.RESIDENTIAL, - 'O1': cte.SMALL_OFFICE, - 'O2': cte.SMALL_OFFICE, - 'O3': cte.SMALL_OFFICE, - 'O4': cte.SMALL_OFFICE, - 'O5': cte.SMALL_OFFICE, - 'O6': cte.SMALL_OFFICE, - 'O7': cte.SMALL_OFFICE, - 'O8': cte.SMALL_OFFICE, - 'O9': cte.SMALL_OFFICE, - 'P1': cte.LARGE_OFFICE, - 'P2': cte.HOTEL, - 'P3': cte.SMALL_OFFICE, - 'P4': cte.SMALL_OFFICE, - 'P5': cte.SMALL_OFFICE, - 'P6': cte.SMALL_OFFICE, - 'P7': cte.LARGE_OFFICE, - 'P8': cte.LARGE_OFFICE, - 'P9': cte.SMALL_OFFICE, - 'Q0': cte.SMALL_OFFICE, - 'Q1': cte.SMALL_OFFICE, - 'Q2': cte.SMALL_OFFICE, - 'Q3': cte.SMALL_OFFICE, - 'Q4': cte.SMALL_OFFICE, - 'Q5': cte.SMALL_OFFICE, - 'Q6': cte.SMALL_OFFICE, - 'Q7': cte.SMALL_OFFICE, - 'Q8': cte.SMALL_OFFICE, - 'Q9': cte.SMALL_OFFICE, - 'R0': cte.RESIDENTIAL, - 'R1': cte.RESIDENTIAL, - 'R2': cte.RESIDENTIAL, - 'R3': cte.RESIDENTIAL, - 'R4': cte.RESIDENTIAL, - 'R5': cte.RESIDENTIAL, - 'R6': cte.RESIDENTIAL, - 'R7': cte.RESIDENTIAL, - 'R8': cte.RESIDENTIAL, - 'R9': cte.RESIDENTIAL, - 'RA': cte.RESIDENTIAL, - 'RB': cte.RESIDENTIAL, - 'RC': cte.RESIDENTIAL, - 'RD': cte.RESIDENTIAL, - 'RG': cte.RESIDENTIAL, - 'RH': cte.RESIDENTIAL, - 'RI': cte.RESIDENTIAL, - 'RK': cte.RESIDENTIAL, - 'RM': cte.RESIDENTIAL, - 'RR': cte.RESIDENTIAL, - 'RS': cte.RESIDENTIAL, - 'RW': cte.RESIDENTIAL, - 'RX': cte.RESIDENTIAL, - 'RZ': cte.RESIDENTIAL, - 'S0': cte.RESIDENTIAL, - 'S1': cte.RESIDENTIAL, - 'S2': cte.RESIDENTIAL, - 'S3': cte.RESIDENTIAL, - 'S4': cte.RESIDENTIAL, - 'S5': cte.RESIDENTIAL, - 'S9': cte.RESIDENTIAL, - 'U0': cte.WAREHOUSE, - 'U1': cte.WAREHOUSE, - 'U2': cte.WAREHOUSE, - 'U3': cte.WAREHOUSE, - 'U4': cte.WAREHOUSE, - 'U5': cte.WAREHOUSE, - 'U6': cte.WAREHOUSE, - 'U7': cte.WAREHOUSE, - 'U8': cte.WAREHOUSE, - 'U9': cte.WAREHOUSE, - 'W1': cte.PRIMARY_SCHOOL, - 'W2': cte.PRIMARY_SCHOOL, - 'W3': cte.SECONDARY_SCHOOL, - 'W4': cte.SECONDARY_SCHOOL, - 'W5': cte.SECONDARY_SCHOOL, - 'W6': cte.SECONDARY_SCHOOL, - 'W7': cte.SECONDARY_SCHOOL, - 'W8': cte.PRIMARY_SCHOOL, - 'W9': cte.SECONDARY_SCHOOL, - 'Y1': cte.LARGE_OFFICE, - 'Y2': cte.LARGE_OFFICE, - 'Y3': cte.LARGE_OFFICE, - 'Y4': cte.LARGE_OFFICE, - 'Y5': cte.LARGE_OFFICE, - 'Y6': cte.LARGE_OFFICE, - 'Y7': cte.LARGE_OFFICE, - 'Y8': cte.LARGE_OFFICE, - 'Y9': cte.LARGE_OFFICE, - 'Z1': cte.LARGE_OFFICE - } - _hft_to_function = { - 'residential': cte.RESIDENTIAL, - 'single family house': cte.SINGLE_FAMILY_HOUSE, - 'multifamily house': cte.MULTI_FAMILY_HOUSE, - 'hotel': cte.HOTEL, - 'hospital': cte.HOSPITAL, - 'outpatient': cte.OUT_PATIENT_HEALTH_CARE, - 'commercial': cte.SUPERMARKET, - 'strip mall': cte.STRIP_MALL, - 'warehouse': cte.WAREHOUSE, - 'primary school': cte.PRIMARY_SCHOOL, - 'secondary school': cte.SECONDARY_SCHOOL, - 'office': cte.MEDIUM_OFFICE, - 'large office': cte.LARGE_OFFICE - } - - # usage - _function_to_usage = { - cte.RESIDENTIAL: cte.RESIDENTIAL, - cte.SINGLE_FAMILY_HOUSE: cte.SINGLE_FAMILY_HOUSE, - cte.MULTI_FAMILY_HOUSE: cte.MULTI_FAMILY_HOUSE, - cte.ROW_HOSE: cte.RESIDENTIAL, - cte.MID_RISE_APARTMENT: cte.RESIDENTIAL, - cte.HIGH_RISE_APARTMENT: cte.RESIDENTIAL, - cte.SMALL_OFFICE: cte.OFFICE_AND_ADMINISTRATION, - cte.MEDIUM_OFFICE: cte.OFFICE_AND_ADMINISTRATION, - cte.LARGE_OFFICE: cte.OFFICE_AND_ADMINISTRATION, - cte.PRIMARY_SCHOOL: cte.EDUCATION, - cte.SECONDARY_SCHOOL: cte.EDUCATION, - cte.STAND_ALONE_RETAIL: cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, - cte.HOSPITAL: cte.HEALTH_CARE, - cte.OUT_PATIENT_HEALTH_CARE: cte.HEALTH_CARE, - cte.STRIP_MALL: cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, - cte.SUPERMARKET: cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, - cte.WAREHOUSE: cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, - cte.QUICK_SERVICE_RESTAURANT: cte.RESTAURANT, - cte.FULL_SERVICE_RESTAURANT: cte.RESTAURANT, - cte.SMALL_HOTEL: cte.HOTEL, - cte.LARGE_HOTEL: cte.HOTEL, - cte.INDUSTRY:cte.INDUSTRY - } - - @staticmethod - def libs_function_from_hft(building_hft_function): - """ - Get internal function from the given HfT function - :param building_hft_function: str - :return: str - """ - return GeometryHelper._hft_to_function[building_hft_function] - - @staticmethod - def libs_function_from_pluto(building_pluto_function): - """ - Get internal function from the given pluto function - :param building_pluto_function: str - :return: str - """ - return GeometryHelper._pluto_to_function[building_pluto_function] - - @staticmethod - def libs_usage_from_libs_function(building_function): - """ - Get the internal usage for the given internal building function - :param building_function: str - :return: str - """ - return GeometryHelper._function_to_usage[building_function] - @staticmethod def to_points_matrix(points): """ diff --git a/hub/imports/geometry/obj.py b/hub/imports/geometry/obj.py index c1a1cee4..6bf62a33 100644 --- a/hub/imports/geometry/obj.py +++ b/hub/imports/geometry/obj.py @@ -51,6 +51,7 @@ class Obj: """ Get city out of an obj file """ + lod = 0 if self._city is None: # todo: refactor this method to clearly choose the obj type # todo: where do we get this information from? diff --git a/hub/imports/geometry/osm_subway.py b/hub/imports/geometry/osm_subway.py deleted file mode 100644 index 8e0bd27b..00000000 --- a/hub/imports/geometry/osm_subway.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -OsmSubway module parses osm files and import the metro location into the city model structure -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -""" - -import sys -import xmltodict -from pyproj import Transformer -from hub.city_model_structure.city import City -from hub.city_model_structure.subway_entrance import SubwayEntrance - - -class OsmSubway: - """ - Open street map subway - """ - def __init__(self, path): - self._city = None - self._subway_entrances = [] - with open(path) as osm: - self._osm = xmltodict.parse(osm.read(), force_list='tag') - for node in self._osm['osm']['node']: - if 'tag' not in node: - continue - for tag in node['tag']: - if '@v' not in tag: - continue - if tag['@v'] == 'subway_entrance': - subway_entrance = SubwayEntrance(node['@id'], node['@lat'], node['@lon']) - self._subway_entrances.append(subway_entrance) - - @property - def city(self) -> City: - """ - Get a city with subway entrances - """ - transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857") - lower_corner = [sys.float_info.max, sys.float_info.max, 0] - upper_corner = [sys.float_info.min, sys.float_info.min, 0] - x = 0 - y = 1 - for subway_entrance in self._subway_entrances: - coordinate = transformer.transform(subway_entrance.longitude, subway_entrance.latitude) - if coordinate[x] >= upper_corner[x]: - upper_corner[x] = coordinate[x] - if coordinate[y] >= upper_corner[y]: - upper_corner[y] = coordinate[y] - if coordinate[x] < lower_corner[x]: - lower_corner[x] = coordinate[x] - if coordinate[y] < lower_corner[y]: - lower_corner[y] = coordinate[y] - - city = City(lower_corner, upper_corner, 'unknown') - for subway_entrance in self._subway_entrances: - city.add_city_object(subway_entrance) - return city diff --git a/hub/imports/geometry_factory.py b/hub/imports/geometry_factory.py index d63e2e7a..2c870085 100644 --- a/hub/imports/geometry_factory.py +++ b/hub/imports/geometry_factory.py @@ -9,10 +9,11 @@ import geopandas from hub.city_model_structure.city import City from hub.imports.geometry.citygml import CityGml from hub.imports.geometry.obj import Obj -from hub.imports.geometry.osm_subway import OsmSubway from hub.imports.geometry.rhino import Rhino from hub.imports.geometry.gpandas import GPandas from hub.imports.geometry.geojson import Geojson +from hub.helpers.utils import validate_import_export_type +from hub.hub_logger import logger class GeometryFactory: @@ -24,13 +25,20 @@ class GeometryFactory: data_frame=None, height_field=None, year_of_construction_field=None, - function_field=None): + function_field=None, + function_to_hub=None): self._file_type = '_' + file_type.lower() + class_funcs = validate_import_export_type(GeometryFactory) + if self._file_type not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._path = path self._data_frame = data_frame self._height_field = height_field self._year_of_construction_field = year_of_construction_field self._function_field = function_field + self._function_to_hub = function_to_hub @property def _citygml(self) -> City: @@ -38,7 +46,11 @@ class GeometryFactory: Enrich the city by using CityGML information as data source :return: City """ - return CityGml(self._path, self._height_field, self._year_of_construction_field, self._function_field).city + return CityGml(self._path, + self._height_field, + self._year_of_construction_field, + self._function_field, + self._function_to_hub).city @property def _obj(self) -> City: @@ -64,15 +76,11 @@ class GeometryFactory: Enrich the city by using Geojson information as data source :return: City """ - return Geojson(self._path, self._height_field, self._year_of_construction_field, self._function_field).city - - @property - def _osm_subway(self) -> City: - """ - Enrich the city by using OpenStreetMap information as data source - :return: City - """ - return OsmSubway(self._path).city + return Geojson(self._path, + self._height_field, + self._year_of_construction_field, + self._function_field, + self._function_to_hub).city @property def _rhino(self) -> City: diff --git a/hub/imports/life_cycle_assessment/lca_machine.py b/hub/imports/life_cycle_assessment/lca_machine.py index ab3d8d61..0d643f83 100644 --- a/hub/imports/life_cycle_assessment/lca_machine.py +++ b/hub/imports/life_cycle_assessment/lca_machine.py @@ -8,6 +8,7 @@ import xmltodict from pathlib import Path from hub.city_model_structure.machine import Machine + class LcaMachine: def __init__(self, city, base_path): self._city = city @@ -22,6 +23,8 @@ class LcaMachine: self._lca = xmltodict.parse(xml.read()) for machine in self._lca["library"]["machines"]['machine']: self._city.machines.append(Machine(machine['@id'], machine['@name'], machine['work_efficiency']['#text'], - machine['work_efficiency']['@unit'], machine['energy_consumption_rate']['#text'], - machine['energy_consumption_rate']['@unit'], machine['carbon_emission_factor']['#text'], - machine['carbon_emission_factor']['@unit'])) + machine['work_efficiency']['@unit'], + machine['energy_consumption_rate']['#text'], + machine['energy_consumption_rate']['@unit'], + machine['carbon_emission_factor']['#text'], + machine['carbon_emission_factor']['@unit'])) diff --git a/hub/imports/life_cycle_assessment/lca_material.py b/hub/imports/life_cycle_assessment/lca_material.py index f61ce50b..70346da8 100644 --- a/hub/imports/life_cycle_assessment/lca_material.py +++ b/hub/imports/life_cycle_assessment/lca_material.py @@ -8,6 +8,7 @@ import xmltodict from pathlib import Path from hub.city_model_structure.lca_material import LcaMaterial as LMaterial + class LcaMaterial: def __init__(self, city, base_path): self._city = city @@ -26,15 +27,15 @@ class LcaMaterial: _material.type = material['@type'] _material.id = material['@id'] _material.name = material['@name'] - _material.density=material['density']['#text'] - _material.density_unit=material['density']['@unit'] - _material.embodied_carbon=material['embodied_carbon']['#text'] - _material.embodied_carbon_unit=material['embodied_carbon']['@unit'] - _material.recycling_ratio=material['recycling_ratio'] - _material.onsite_recycling_ratio=material['onsite_recycling_ratio'] - _material.company_recycling_ratio=material['company_recycling_ratio'] - _material.landfilling_ratio=material['landfilling_ratio'] - _material.cost=material['cost']['#text'] - _material._cost_unit=material['cost']['@unit'] + _material.density = material['density']['#text'] + _material.density_unit = material['density']['@unit'] + _material.embodied_carbon = material['embodied_carbon']['#text'] + _material.embodied_carbon_unit = material['embodied_carbon']['@unit'] + _material.recycling_ratio = material['recycling_ratio'] + _material.onsite_recycling_ratio = material['onsite_recycling_ratio'] + _material.company_recycling_ratio = material['company_recycling_ratio'] + _material.landfilling_ratio = material['landfilling_ratio'] + _material.cost = material['cost']['#text'] + _material._cost_unit = material['cost']['@unit'] self._city.lca_materials.append(_material) diff --git a/hub/imports/life_cycle_assessment/lca_vehicle.py b/hub/imports/life_cycle_assessment/lca_vehicle.py index 89f95174..d69e1e61 100644 --- a/hub/imports/life_cycle_assessment/lca_vehicle.py +++ b/hub/imports/life_cycle_assessment/lca_vehicle.py @@ -26,4 +26,3 @@ class LcaVehicle: vehicle['fuel_consumption_rate']['@unit'], vehicle['carbon_emission_factor']['#text'], vehicle['carbon_emission_factor']['@unit'])) - diff --git a/hub/imports/life_cycle_assessment_factory.py b/hub/imports/life_cycle_assessment_factory.py index 27add0a0..a46924a7 100644 --- a/hub/imports/life_cycle_assessment_factory.py +++ b/hub/imports/life_cycle_assessment_factory.py @@ -10,16 +10,23 @@ from hub.imports.life_cycle_assessment.lca_fuel import LcaFuel from hub.imports.life_cycle_assessment.lca_vehicle import LcaVehicle from hub.imports.life_cycle_assessment.lca_machine import LcaMachine from hub.imports.life_cycle_assessment.lca_material import LcaMaterial +from hub.helpers.utils import validate_import_export_type +from hub.hub_logger import logger class LifeCycleAssessment: """ - Life cicle analize factory class + Life cycle assessment factory class """ def __init__(self, handler, city, base_path=None): if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/life_cycle_assessment') self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(LifeCycleAssessment) + if self._handler not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._city = city self._base_path = base_path diff --git a/hub/imports/results/insel_monthly_energry_balance.py b/hub/imports/results/insel_monthly_energry_balance.py new file mode 100644 index 00000000..385a8aa8 --- /dev/null +++ b/hub/imports/results/insel_monthly_energry_balance.py @@ -0,0 +1,53 @@ +""" +Insel monthly energy balance +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guillermo.GutierrezMorote@concordia.ca +""" + +from pathlib import Path +import pandas as pd +import csv +import hub.helpers.constants as cte + +class InselMonthlyEnergyBalance: + """ + Import SRA results + """ + def __init__(self, city, base_path): + + self._city = city + self._base_path = base_path + + @staticmethod + def _demand(insel_output_file_path): + heating = [] + cooling = [] + with open(Path(insel_output_file_path).resolve()) as csv_file: + csv_reader = csv.reader(csv_file) + for line in csv_reader: + demand = str(line).replace("['", '').replace("']", '').split() + for i in range(0, 2): + if demand[i] != 'NaN': + aux = float(demand[i]) * 1000 # kWh to Wh + demand[i] = str(aux) + else: + demand[i] = '0' + heating.append(demand[0]) + cooling.append(demand[1]) + monthly_heating = pd.DataFrame(heating, columns=[cte.INSEL_MEB]) + monthly_cooling = pd.DataFrame(cooling, columns=[cte.INSEL_MEB]) + return monthly_heating, monthly_cooling + + def enrich(self): + for building in self._city.buildings: + file_name = building.name + '.out' + insel_output_file_path = Path(self._base_path / file_name).resolve() + if insel_output_file_path.is_file(): + building.heating[cte.MONTH], building.cooling[cte.MONTH] = self._demand(insel_output_file_path) + building.heating[cte.YEAR] = pd.DataFrame( + [building.heating[cte.MONTH][cte.INSEL_MEB].sum()], columns=[cte.INSEL_MEB] + ) + building.cooling[cte.YEAR] = pd.DataFrame( + [building.cooling[cte.MONTH][cte.INSEL_MEB].sum()], columns=[cte.INSEL_MEB] + ) diff --git a/hub/imports/results/simplified_radiosity_algorithm.py b/hub/imports/results/simplified_radiosity_algorithm.py new file mode 100644 index 00000000..334a26b1 --- /dev/null +++ b/hub/imports/results/simplified_radiosity_algorithm.py @@ -0,0 +1,97 @@ +""" +Simplified Radiosity Algorithm +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guillermo.GutierrezMorote@concordia.ca +""" +import pandas as pd +import numpy as np +import calendar as cal +import hub.helpers.constants as cte + + +class SimplifiedRadiosityAlgorithm: + """ + Import SRA results + """ + def __init__(self, city, base_path): + + self._city = city + self._base_path = base_path + self._input_file_path = (self._base_path / f'{self._city.name}_sra_SW.out') + self._month_hour = self._month_hour_data_frame + self._results = self._read_results() + self._radiation_list = [] + + @property + def _month_hour_data_frame(self): + array = [] + for i in range(0, 12): + days_of_month = cal.monthrange(2015, i+1)[1] + total_hours = days_of_month * 24 + array = np.concatenate((array, np.full(total_hours, i + 1))) + return pd.DataFrame(array, columns=[cte.MONTH]) + + def _get_monthly_mean_values(self, values): + out = None + if values is not None: + if cte.MONTH not in values.columns: + values = pd.concat([self._month_hour, pd.DataFrame(values)], axis=1) + out = values.groupby(cte.MONTH, as_index=False).mean() + del out[cte.MONTH] + return out + + def _get_yearly_mean_values(self, values): + return values.mean() + + def _read_results(self): + try: + return pd.read_csv(self._input_file_path, sep='\s+', header=0) + except Exception: + raise Exception('No SRA output file found') + + @property + def _radiation(self) -> []: + if len(self._radiation_list) == 0: + id_building = '' + header_building = [] + for column in self._results.columns.values: + if id_building != column.split(':')[1]: + id_building = column.split(':')[1] + if len(header_building) > 0: + self._radiation_list.append(pd.concat([self._month_hour, self._results[header_building]],axis=1)) + header_building = [column] + else: + header_building.append(column) + self._radiation_list.append(pd.concat([self._month_hour, self._results[header_building]], axis=1)) + return self._radiation_list + + def enrich(self): + """ + saves in building surfaces the correspondent irradiance at different time-scales depending on the mode + if building is None, it saves all buildings' surfaces in file, if building is specified, it saves only that + specific building values + :return: none + """ + for radiation in self._radiation: + city_object_name = radiation.columns.values.tolist()[1].split(':')[1] + building = self._city.city_object(city_object_name) + for column in radiation.columns.values: + if column == cte.MONTH: + continue + header_id = column + surface_id = header_id.split(':')[2] + surface = building.surface_by_id(surface_id) + new_value = pd.DataFrame(radiation[[header_id]].to_numpy(), columns=[cte.SRA]) + month_new_value = self._get_monthly_mean_values(new_value) + if cte.MONTH not in surface.global_irradiance: + surface.global_irradiance[cte.MONTH] = month_new_value + else: + pd.concat([surface.global_irradiance[cte.MONTH], month_new_value], axis=1) + if cte.HOUR not in surface.global_irradiance: + surface.global_irradiance[cte.HOUR] = new_value + else: + pd.concat([surface.global_irradiance[cte.HOUR], new_value], axis=1) + if cte.YEAR not in surface.global_irradiance: + surface.global_irradiance[cte.YEAR] = self._get_yearly_mean_values(new_value) + self._city.level_of_detail.surface_radiation = 2 diff --git a/hub/imports/results_factory.py b/hub/imports/results_factory.py new file mode 100644 index 00000000..afa90fb6 --- /dev/null +++ b/hub/imports/results_factory.py @@ -0,0 +1,49 @@ +""" +Result factory retrieve the specific tool results and store the data in the given city +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from pathlib import Path + +from hub.helpers.utils import validate_import_export_type +from hub.hub_logger import logger +from hub.imports.results.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm +from hub.imports.results.insel_monthly_energry_balance import InselMonthlyEnergyBalance + + +class ResultFactory: + """ + UsageFactory class + """ + def __init__(self, handler, city, base_path=None): + if base_path is None: + base_path = Path(Path(__file__).parent.parent / 'data/results') + self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(ResultFactory) + if self._handler not in class_funcs: + err_msg = f"Wrong import type [{self._handler}]. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) + self._city = city + self._base_path = base_path + + def _sra(self): + """ + Enrich the city with Simplified Radiosity Algorithm results + """ + SimplifiedRadiosityAlgorithm(self._city, self._base_path).enrich() + + def _insel_meb(self): + """ + Enrich the city with insel monthly energy balance results + """ + InselMonthlyEnergyBalance(self._city, self._base_path).enrich() + + def enrich(self): + """ + Enrich the city given to the class using the usage factory given handler + :return: None + """ + getattr(self, self._handler, lambda: None)() diff --git a/hub/imports/sensors/concordia_gas_flow.py b/hub/imports/sensors/concordia_gas_flow.py deleted file mode 100644 index c665a909..00000000 --- a/hub/imports/sensors/concordia_gas_flow.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Concordia gas flow -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import pandas as pd -from hub.imports.sensors.concordia_file_report import ConcordiaFileReport -from hub.city_model_structure.iot.concordia_gas_flow_sensor import ConcordiaGasFlowSensor - - -class ConcordiaGasFlow(ConcordiaFileReport): - """ - Concordia gas flow sensor class - """ - - def __init__(self, city, end_point, base_path): - super().__init__(city, end_point, base_path, 'concordia_gas_flow_db.json') - for city_object in city.city_objects: - self._assign_sensor_to_object(city_object) - - def _assign_sensor_to_object(self, obj): - for i in range(len(self._city_object)): - if self._city_object[i] == obj.name and self._sensors[i] in self._sensor_point: - building_measures = [self._measures["Date time"], self._measures[self._sensor_point[self._sensors[i]]]] - building_headers = ["Date time", "Gas Flow Cumulative Monthly"] - building_energy_consumption = pd.concat(building_measures, keys=building_headers, axis=1) - sensor = ConcordiaGasFlowSensor(self._sensors[i]) - sensor_exist = False - for j in range(len(obj.sensors)): - if obj.sensors[j].name is sensor.name: - obj.sensors[j].add_period(building_energy_consumption) - sensor_exist = True - break - if not sensor_exist: - sensor.add_period(building_energy_consumption) - obj.sensors.append(sensor) diff --git a/hub/imports/sensors/concordia_temperature.py b/hub/imports/sensors/concordia_temperature.py deleted file mode 100644 index c219c24a..00000000 --- a/hub/imports/sensors/concordia_temperature.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Concordia temperature -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import pandas as pd -from hub.imports.sensors.concordia_file_report import ConcordiaFileReport -from hub.city_model_structure.iot.concordia_temperature_sensor import ConcordiaTemperatureSensor - - -class ConcordiaTemperature(ConcordiaFileReport): - """ - Concordia temperature sensor class - """ - def __init__(self, city, end_point, base_path): - super().__init__(city, end_point, base_path, 'concordia_temperature_db.json') - for city_object in city.city_objects: - self._assign_sensor_to_object(city_object) - - def _assign_sensor_to_object(self, obj): - for i in range(len(self._city_object)): - if self._city_object[i] == obj.name and self._sensors[i] in self._sensor_point: - building_measures = [self._measures["Date time"], self._measures[self._sensor_point[self._sensors[i]]]] - building_headers = ["Date time", "Temperature"] - building_energy_consumption = pd.concat(building_measures, keys=building_headers, axis=1) - sensor = ConcordiaTemperatureSensor(self._sensors[i]) - sensor_exist = False - for j in range(len(obj.sensors)): - if obj.sensors[j].name is sensor.name: - obj.sensors[j].add_period(building_energy_consumption) - sensor_exist = True - break - if not sensor_exist: - sensor.add_period(building_energy_consumption) - obj.sensors.append(sensor) diff --git a/hub/imports/sensors_factory.py b/hub/imports/sensors_factory.py index f161b8fc..d4793950 100644 --- a/hub/imports/sensors_factory.py +++ b/hub/imports/sensors_factory.py @@ -6,6 +6,8 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from pathlib import Path +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type class SensorsFactory: @@ -16,6 +18,11 @@ class SensorsFactory: if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/sensors') self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(SensorsFactory) + if self._handler not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._city = city self._end_point = end_point self._base_path = base_path diff --git a/hub/imports/usage/ca_usage_parameters.py b/hub/imports/usage/ca_usage_parameters.py deleted file mode 100644 index 66196416..00000000 --- a/hub/imports/usage/ca_usage_parameters.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -CaUsageParameters model the usage properties -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import sys - -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper -from hub.imports.usage.hft_usage_interface import HftUsageInterface - - -class HftUsageParameters(HftUsageInterface): - """ - CaUsageParameters class - """ - def __init__(self, city, base_path): - super().__init__(base_path, 'ca_archetypes_reduced.xml') - self._city = city - - def enrich_buildings(self): - """ - Returns the city with the usage parameters assigned to the buildings - :return: - """ - city = self._city - for building in city.buildings: - usage = GeometryHelper().libs_usage_from_libs_function(building.function) - try: - archetype = self._search_archetype(usage) - except KeyError: - sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' - f' {building.function}, that assigns building usage as ' - f'{GeometryHelper().libs_usage_from_libs_function(building.function)}\n') - return - - for internal_zone in building.internal_zones: - usage_zone = self._assign_values(building.function, archetype) - usage_zone.percentage = 1 - internal_zone.usage_zones = [usage_zone] diff --git a/hub/imports/usage/comnet_usage_parameters.py b/hub/imports/usage/comnet_usage_parameters.py index 26691173..7c6c137e 100644 --- a/hub/imports/usage/comnet_usage_parameters.py +++ b/hub/imports/usage/comnet_usage_parameters.py @@ -1,27 +1,23 @@ """ -ComnetUsageParameters model the usage properties +ComnetUsageParameters extracts the usage properties from Comnet catalog and assigns to each building SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import copy import sys -from typing import Dict -import pandas as pd import numpy import hub.helpers.constants as cte -from hub.helpers.configuration_helper import ConfigurationHelper as ch -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper -from hub.imports.usage.helpers.usage_helper import UsageHelper -from hub.imports.usage.helpers.schedules_helper import SchedulesHelper -from hub.city_model_structure.building_demand.usage_zone import UsageZone +from hub.helpers.dictionaries import Dictionaries +from hub.city_model_structure.building_demand.usage import Usage from hub.city_model_structure.building_demand.lighting import Lighting from hub.city_model_structure.building_demand.occupancy import Occupancy from hub.city_model_structure.building_demand.appliances import Appliances from hub.city_model_structure.building_demand.thermal_control import ThermalControl from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.building_demand.internal_gain import InternalGain +from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory class ComnetUsageParameters: @@ -30,162 +26,7 @@ class ComnetUsageParameters: """ def __init__(self, city, base_path): self._city = city - self._base_path = str(base_path / 'comnet_archetypes.xlsx') - self._data = self._read_file() - self._comnet_schedules_path = str(base_path / 'comnet_schedules_archetypes.xlsx') - self._xls = pd.ExcelFile(self._comnet_schedules_path) - - def _read_file(self) -> Dict: - """ - reads xlsx files containing usage information into a dictionary - :return : Dict - """ - number_usage_types = 33 - xl_file = pd.ExcelFile(self._base_path) - file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", usecols="A:AB", skiprows=[0, 1, 2], - nrows=number_usage_types) - - lighting_data = {} - plug_loads_data = {} - occupancy_data = {} - ventilation_rate = {} - water_heating = {} - process_data = {} - schedules_key = {} - - for j in range(0, number_usage_types): - usage_parameters = file_data.iloc[j] - usage_type = usage_parameters[0] - lighting_data[usage_type] = usage_parameters[1:6].values.tolist() - plug_loads_data[usage_type] = usage_parameters[8:13].values.tolist() - occupancy_data[usage_type] = usage_parameters[17:20].values.tolist() - ventilation_rate[usage_type] = usage_parameters[20:21].values.tolist() - water_heating[usage_type] = usage_parameters[23:24].values.tolist() - process_data[usage_type] = usage_parameters[24:26].values.tolist() - schedules_key[usage_type] = usage_parameters[27:28].values.tolist() - - return {'lighting': lighting_data, - 'plug loads': plug_loads_data, - 'occupancy': occupancy_data, - 'ventilation rate': ventilation_rate, - 'water heating': water_heating, - 'process': process_data, - 'schedules_key': schedules_key} - - @staticmethod - def _parse_usage_type(comnet_usage, data, schedules_data): - _usage_zone = UsageZone() - - # lighting - _lighting = Lighting() - _lighting.latent_fraction = ch().comnet_lighting_latent - _lighting.convective_fraction = ch().comnet_lighting_convective - _lighting.radiative_fraction = ch().comnet_lighting_radiant - _lighting.density = data['lighting'][comnet_usage][4] - - # plug loads - _appliances = None - if data['plug loads'][comnet_usage][0] != 'n.a.': - _appliances = Appliances() - _appliances.latent_fraction = ch().comnet_plugs_latent - _appliances.convective_fraction = ch().comnet_plugs_convective - _appliances.radiative_fraction = ch().comnet_plugs_radiant - _appliances.density = data['plug loads'][comnet_usage][0] - - # occupancy - _occupancy = Occupancy() - value = data['occupancy'][comnet_usage][0] - _occupancy.occupancy_density = 0 - if value != 0: - _occupancy.occupancy_density = 1 / value - - _occupancy.sensible_convective_internal_gain = data['occupancy'][comnet_usage][1] \ - * ch().comnet_occupancy_sensible_convective - _occupancy.sensible_radiative_internal_gain = data['occupancy'][comnet_usage][1] \ - * ch().comnet_occupancy_sensible_radiant - _occupancy.latent_internal_gain = data['occupancy'][comnet_usage][2] - - _usage_zone.mechanical_air_change = data['ventilation rate'][comnet_usage][0] - - schedules_usage = UsageHelper.schedules_key(data['schedules_key'][comnet_usage][0]) - - _extracted_data = pd.read_excel(schedules_data, sheet_name=schedules_usage, usecols="A:AA", skiprows=[0, 1, 2, 3], - nrows=39) - schedules = [] - number_of_schedule_types = 13 - schedules_per_schedule_type = 3 - day_types = dict({'week_day': 0, 'saturday': 1, 'sunday': 2}) - for schedule_types in range(0, number_of_schedule_types): - name = '' - data_type = '' - for schedule_day in range(0, schedules_per_schedule_type): - _schedule = Schedule() - _schedule.time_step = cte.HOUR - _schedule.time_range = cte.DAY - row_cells = _extracted_data.iloc[schedules_per_schedule_type * schedule_types + schedule_day] - if schedule_day == day_types['week_day']: - name = row_cells[0] - data_type = row_cells[1] - _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] - elif schedule_day == day_types['saturday']: - _schedule.day_types = [cte.SATURDAY] - else: - _schedule.day_types = [cte.SUNDAY, cte.HOLIDAY] - _schedule.type = name - _schedule.data_type = SchedulesHelper.data_type_from_comnet(data_type) - if _schedule.data_type == cte.ANY_NUMBER: - values = [] - for cell in row_cells[schedules_per_schedule_type:].to_numpy(): - values.append((float(cell) - 32.) * 5 / 9) - _schedule.values = values - else: - _schedule.values = row_cells[schedules_per_schedule_type:].to_numpy() - schedules.append(_schedule) - - schedules_types = dict({'Occupancy': 0, 'Lights': 3, 'Receptacle': 6, 'Infiltration': 9, 'HVAC Avail': 12, - 'ClgSetPt': 15, 'HtgSetPt': 18}) - - _schedules = [] - for pointer in range(0, 3): - _schedules.append(schedules[schedules_types['Occupancy']+pointer]) - _occupancy.occupancy_schedules = _schedules - _schedules = [] - for pointer in range(0, 3): - _schedules.append(schedules[schedules_types['Lights']+pointer]) - _lighting.schedules = _schedules - _schedules = [] - for pointer in range(0, 3): - _schedules.append(schedules[schedules_types['Receptacle']+pointer]) - _appliances.schedules = _schedules - - _usage_zone.occupancy = _occupancy - _usage_zone.lighting = _lighting - _usage_zone.appliances = _appliances - - _control = ThermalControl() - _schedules = [] - for pointer in range(0, 3): - _schedules.append(schedules[schedules_types['HtgSetPt']+pointer]) - _control.heating_set_point_schedules = _schedules - _schedules = [] - for pointer in range(0, 3): - _schedules.append(schedules[schedules_types['ClgSetPt']+pointer]) - _control.cooling_set_point_schedules = _schedules - _schedules = [] - for pointer in range(0, 3): - _schedules.append(schedules[schedules_types['HVAC Avail']+pointer]) - _control.hvac_availability_schedules = _schedules - _usage_zone.thermal_control = _control - - return _usage_zone - - def _search_archetypes(self, libs_usage): - for item in self._data['lighting']: - comnet_usage = UsageHelper.comnet_from_libs_usage(libs_usage) - if comnet_usage == item: - usage_archetype = self._parse_usage_type(comnet_usage, self._data, self._xls) - return usage_archetype - return None + self._path = base_path def enrich_buildings(self): """ @@ -193,14 +34,14 @@ class ComnetUsageParameters: :return: """ city = self._city + comnet_catalog = UsageCatalogFactory('comnet').catalog for building in city.buildings: - usage = GeometryHelper.libs_usage_from_libs_function(building.function) + usage_name = Dictionaries().hub_usage_to_comnet_usage[building.function] try: - archetype_usage = self._search_archetypes(usage) + archetype_usage = self._search_archetypes(comnet_catalog, usage_name) except KeyError: - sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' - f' {building.function}, that assigns building usage as ' - f'{GeometryHelper.libs_usage_from_libs_function(building.function)}\n') + sys.stderr.write(f'Building {building.name} has unknown usage archetype for building function:' + f' {building.function}') return for internal_zone in building.internal_zones: @@ -210,59 +51,59 @@ class ComnetUsageParameters: raise Exception('Internal zone volume not defined, ACH cannot be calculated') if internal_zone.area <= 0: raise Exception('Internal zone area is zero, ACH cannot be calculated') - if internal_zone.volume <= 0: - raise Exception('Internal zone volume is zero, ACH cannot be calculated') volume_per_area = internal_zone.volume / internal_zone.area - usage_zone = UsageZone() - usage_zone.usage = usage - self._assign_values_usage_zone(usage_zone, archetype_usage, volume_per_area) - usage_zone.percentage = 1 - self._calculate_reduced_values_from_extended_library(usage_zone, archetype_usage) + usage = Usage() + usage.name = usage_name + self._assign_values(usage, archetype_usage, volume_per_area) + usage.percentage = 1 + self._calculate_reduced_values_from_extended_library(usage, archetype_usage) - internal_zone.usage_zones = [usage_zone] + internal_zone.usages = [usage] @staticmethod - def _assign_values_usage_zone(usage_zone, archetype, volume_per_area): + def _search_archetypes(comnet_catalog, usage_name): + comnet_archetypes = comnet_catalog.entries('archetypes').usages + for building_archetype in comnet_archetypes: + if str(usage_name) == str(building_archetype.name): + return building_archetype + raise KeyError('archetype not found') + + @staticmethod + def _assign_values(usage, archetype, volume_per_area): # Due to the fact that python is not a typed language, the wrong object type is assigned to - # usage_zone.occupancy when writing usage_zone.occupancy = archetype.occupancy. + # usage.occupancy when writing usage.occupancy = archetype.occupancy. # Same happens for lighting and appliances. Therefore, this walk around has been done. - usage_zone.mechanical_air_change = archetype.mechanical_air_change * cte.METERS_TO_FEET ** 2 \ - * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET ** 3 / volume_per_area + usage.mechanical_air_change = archetype.ventilation_rate / volume_per_area \ + * cte.HOUR_TO_MINUTES * cte.MINUTES_TO_SECONDS _occupancy = Occupancy() - _occupancy.occupancy_density = archetype.occupancy.occupancy_density * cte.METERS_TO_FEET**2 - _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain \ - * archetype.occupancy.occupancy_density \ - * cte.METERS_TO_FEET**2 * cte.BTU_H_TO_WATTS - _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain \ - * archetype.occupancy.occupancy_density \ - * cte.METERS_TO_FEET**2 * cte.BTU_H_TO_WATTS - _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain \ - * archetype.occupancy.occupancy_density \ - * cte.METERS_TO_FEET**2 * cte.BTU_H_TO_WATTS - _occupancy.occupancy_schedules = archetype.occupancy.occupancy_schedules - usage_zone.occupancy = _occupancy + _occupancy.occupancy_density = archetype.occupancy.occupancy_density + _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain + _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain + _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain + _occupancy.occupancy_schedules = archetype.occupancy.schedules + usage.occupancy = _occupancy _lighting = Lighting() - _lighting.density = archetype.lighting.density / cte.METERS_TO_FEET ** 2 + _lighting.density = archetype.lighting.density _lighting.convective_fraction = archetype.lighting.convective_fraction _lighting.radiative_fraction = archetype.lighting.radiative_fraction _lighting.latent_fraction = archetype.lighting.latent_fraction _lighting.schedules = archetype.lighting.schedules - usage_zone.lighting = _lighting + usage.lighting = _lighting _appliances = Appliances() - _appliances.density = archetype.appliances.density / cte.METERS_TO_FEET ** 2 + _appliances.density = archetype.appliances.density _appliances.convective_fraction = archetype.appliances.convective_fraction _appliances.radiative_fraction = archetype.appliances.radiative_fraction _appliances.latent_fraction = archetype.appliances.latent_fraction _appliances.schedules = archetype.appliances.schedules - usage_zone.appliances = _appliances + usage.appliances = _appliances _control = ThermalControl() _control.cooling_set_point_schedules = archetype.thermal_control.cooling_set_point_schedules _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules _control.hvac_availability_schedules = archetype.thermal_control.hvac_availability_schedules - usage_zone.thermal_control = _control + usage.thermal_control = _control @staticmethod - def _calculate_reduced_values_from_extended_library(usage_zone, archetype): + def _calculate_reduced_values_from_extended_library(usage, archetype): number_of_days_per_type = {'WD': 251, 'Sat': 52, 'Sun': 62} total = 0 for schedule in archetype.thermal_control.hvac_availability_schedules: @@ -276,8 +117,33 @@ class ComnetUsageParameters: for value in schedule.values: total += value * number_of_days_per_type['WD'] - usage_zone.hours_day = total / 365 - usage_zone.days_year = 365 + usage.hours_day = total / 365 + usage.days_year = 365 + + max_heating_setpoint = cte.MIN_FLOAT + min_heating_setpoint = cte.MAX_FLOAT + + for schedule in archetype.thermal_control.heating_set_point_schedules: + if schedule.values is None: + max_heating_setpoint = None + min_heating_setpoint = None + break + if max(schedule.values) > max_heating_setpoint: + max_heating_setpoint = max(schedule.values) + if min(schedule.values) < min_heating_setpoint: + min_heating_setpoint = min(schedule.values) + + min_cooling_setpoint = cte.MAX_FLOAT + for schedule in archetype.thermal_control.cooling_set_point_schedules: + if schedule.values is None: + min_cooling_setpoint = None + break + if min(schedule.values) < min_cooling_setpoint: + min_cooling_setpoint = min(schedule.values) + + usage.thermal_control.mean_heating_set_point = max_heating_setpoint + usage.thermal_control.heating_set_back = min_heating_setpoint + usage.thermal_control.mean_cooling_set_point = min_cooling_setpoint @staticmethod def _calculate_internal_gains(archetype): diff --git a/hub/imports/usage/data_classes/hft_internal_gains_archetype.py b/hub/imports/usage/data_classes/hft_internal_gains_archetype.py deleted file mode 100644 index 9a39d3f9..00000000 --- a/hub/imports/usage/data_classes/hft_internal_gains_archetype.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -HftInternalGainsArchetype stores internal gains information, complementing the HftUsageZoneArchetype class -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - - -class HftInternalGainsArchetype: - """ - HftInternalGainsArchetype class - """ - def __init__(self, internal_gains_type=None, average_internal_gain=None, convective_fraction=None, \ - radiative_fraction=None, latent_fraction=None): - self._type = internal_gains_type - self._average_internal_gain = average_internal_gain - self._convective_fraction = convective_fraction - self._radiative_fraction = radiative_fraction - self._latent_fraction = latent_fraction - - @property - def type(self): - """ - Get internal gains type - :return: string - """ - return self._type - - @type.setter - def type(self, value): - """ - Set internal gains type - :param value: string - """ - self._type = value - - @property - def average_internal_gain(self): - """ - Get internal gains average internal gain in W/m2 - :return: float - """ - return self._average_internal_gain - - @property - def convective_fraction(self): - """ - Get internal gains convective fraction - :return: float - """ - return self._convective_fraction - - @property - def radiative_fraction(self): - """ - Get internal gains radiative fraction - :return: float - """ - return self._radiative_fraction - - @property - def latent_fraction(self): - """ - Get internal gains latent fraction - :return: float - """ - return self._latent_fraction diff --git a/hub/imports/usage/data_classes/usage_zone_archetype.py b/hub/imports/usage/data_classes/usage_zone_archetype.py deleted file mode 100644 index f4267e48..00000000 --- a/hub/imports/usage/data_classes/usage_zone_archetype.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -UsageZoneArchetype stores usage information by usage type -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - -from typing import List -from hub.imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype - - -class UsageZoneArchetype: - """ - UsageZoneArchetype class - """ - def __init__(self, usage=None, internal_gains=None, hours_day=None, days_year=None, - electrical_app_average_consumption_sqm_year=None, mechanical_air_change=None, occupancy=None, - lighting=None, appliances=None): - self._usage = usage - self._internal_gains = internal_gains - self._hours_day = hours_day - self._days_year = days_year - self._electrical_app_average_consumption_sqm_year = electrical_app_average_consumption_sqm_year - self._mechanical_air_change = mechanical_air_change - self._occupancy = occupancy - self._lighting = lighting - self._appliances = appliances - - @property - def internal_gains(self) -> List[HftInternalGainsArchetype]: - """ - Get usage zone internal gains from not detailed heating source in W/m2 - :return: [InternalGain] - """ - return self._internal_gains - - @property - def hours_day(self): - """ - Get usage zone usage hours per day - :return: float - """ - return self._hours_day - - @property - def days_year(self): - """ - Get usage zone usage days per year - :return: float - """ - return self._days_year - - @property - def mechanical_air_change(self): - """ - Set usage zone mechanical air change in air change per hour (ACH) - :return: float - """ - return self._mechanical_air_change - - @property - def usage(self): - """ - Get usage zone usage - :return: str - """ - return self._usage - - @property - def electrical_app_average_consumption_sqm_year(self): - """ - Get average consumption of electrical appliances in Joules per m2 and year (J/m2yr) - :return: float - """ - return self._electrical_app_average_consumption_sqm_year - - @property - def occupancy(self): - """ - Get occupancy data - :return: Occupancy - """ - return self._occupancy - - @property - def lighting(self): - """ - Get lighting data - :return: Lighting - """ - return self._lighting - - @property - def appliances(self): - """ - Get appliances data - :return: Appliances - """ - return self._appliances diff --git a/hub/imports/usage/helpers/schedules_helper.py b/hub/imports/usage/helpers/schedules_helper.py deleted file mode 100644 index dfe6a68b..00000000 --- a/hub/imports/usage/helpers/schedules_helper.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Schedules helper -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import sys -import hub.helpers.constants as cte - - -class SchedulesHelper: - """ - Schedules helper - """ - _usage_to_comnet = { - cte.RESIDENTIAL: 'C-12 Residential', - cte.INDUSTRY: 'C-10 Warehouse', - cte.OFFICE_AND_ADMINISTRATION: 'C-5 Office', - cte.HOTEL: 'C-3 Hotel', - cte.HEALTH_CARE: 'C-2 Health', - cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'C-8 Retail', - cte.HALL: 'C-8 Retail', - cte.RESTAURANT: 'C-7 Restaurant', - cte.EDUCATION: 'C-9 School' - } - - _comnet_to_data_type = { - 'Fraction': cte.FRACTION, - 'OnOff': cte.ON_OFF, - 'Temperature': cte.ANY_NUMBER - } - - # usage - _function_to_usage = { - 'full service restaurant': cte.RESTAURANT, - 'high-rise apartment': cte.RESIDENTIAL, - 'hospital': cte.HEALTH_CARE, - 'large hotel': cte.HOTEL, - 'large office': cte.OFFICE_AND_ADMINISTRATION, - 'medium office': cte.OFFICE_AND_ADMINISTRATION, - 'midrise apartment': cte.RESIDENTIAL, - 'outpatient healthcare': cte.HEALTH_CARE, - 'primary school': cte.EDUCATION, - 'quick service restaurant': cte.RESTAURANT, - 'secondary school': cte.EDUCATION, - 'small hotel': cte.HOTEL, - 'small office': cte.OFFICE_AND_ADMINISTRATION, - 'stand-alone-retail': cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, - 'strip mall': cte.HALL, - 'supermarket': cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, - 'warehouse': cte.INDUSTRY, - 'residential': cte.RESIDENTIAL - } - - @staticmethod - def comnet_from_usage(usage): - """ - Get Comnet usage from the given internal usage key - :param usage: str - :return: str - """ - try: - return SchedulesHelper._usage_to_comnet[usage] - except KeyError: - sys.stderr.write('Error: keyword not found.\n') - - @staticmethod - def data_type_from_comnet(comnet_data_type): - """ - Get data_type from the Comnet data type definitions - :param comnet_data_type: str - :return: str - """ - try: - return SchedulesHelper._comnet_to_data_type[comnet_data_type] - except KeyError: - raise ValueError(f"Error: comnet data type keyword not found.") - - @staticmethod - def usage_from_function(building_function): - """ - Get the internal usage for the given internal building function - :param building_function: str - :return: str - """ - return SchedulesHelper._function_to_usage[building_function] diff --git a/hub/imports/usage/helpers/usage_helper.py b/hub/imports/usage/helpers/usage_helper.py deleted file mode 100644 index 112a2771..00000000 --- a/hub/imports/usage/helpers/usage_helper.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -Usage helper -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import sys -import hub.helpers.constants as cte - - -class UsageHelper: - """ - Usage helper class - """ - _usage_to_hft = { - cte.RESIDENTIAL: 'residential', - cte.SINGLE_FAMILY_HOUSE: 'Single family house', - cte.MULTI_FAMILY_HOUSE: 'Multi-family house', - cte.EDUCATION: 'education', - cte.SCHOOL_WITHOUT_SHOWER: 'school without shower', - cte.SCHOOL_WITH_SHOWER: 'school with shower', - cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'retail', - cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'retail shop / refrigerated food', - cte.HOTEL: 'hotel', - cte.HOTEL_MEDIUM_CLASS: 'hotel (Medium-class)', - cte.DORMITORY: 'dormitory', - cte.INDUSTRY: 'industry', - cte.RESTAURANT: 'restaurant', - cte.HEALTH_CARE: 'health care', - cte.RETIREMENT_HOME_OR_ORPHANAGE: 'Home for the aged or orphanage', - cte.OFFICE_AND_ADMINISTRATION: 'office and administration', - cte.EVENT_LOCATION: 'event location', - cte.HALL: 'hall', - cte.SPORTS_LOCATION: 'sport location', - cte.LABOR: 'Labor', - cte.GREEN_HOUSE: 'green house', - cte.NON_HEATED: 'non-heated'} - - @staticmethod - def hft_from_libs_usage(usage): - """ - Get HfT usage from the given internal usage key - :param usage: str - :return: str - """ - try: - return UsageHelper._usage_to_hft[usage] - except KeyError: - sys.stderr.write('Error: keyword not found to translate from libs_usage to hft usage.\n') - - _usage_to_comnet = { - cte.RESIDENTIAL: 'BA Multifamily', - cte.SINGLE_FAMILY_HOUSE: 'BA Multifamily', - cte.MULTI_FAMILY_HOUSE: 'BA Multifamily', - cte.EDUCATION: 'BA School/University', - cte.SCHOOL_WITHOUT_SHOWER: 'BA School/University', - cte.SCHOOL_WITH_SHOWER: 'BA School/University', - cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'BA Retail', - cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'BA Retail', - cte.HOTEL: 'BA Hotel', - cte.HOTEL_MEDIUM_CLASS: 'BA Hotel', - cte.DORMITORY: 'BA Dormitory', - cte.INDUSTRY: 'BA Manufacturing Facility', - cte.RESTAURANT: 'BA Dining: Family', - cte.HEALTH_CARE: 'BA Hospital', - cte.RETIREMENT_HOME_OR_ORPHANAGE: 'BA Multifamily', - cte.OFFICE_AND_ADMINISTRATION: 'BA Office', - cte.EVENT_LOCATION: 'BA Convention Center', - cte.HALL: 'BA Convention Center', - cte.SPORTS_LOCATION: 'BA Sports Arena', - cte.LABOR: 'BA Gymnasium', - cte.GREEN_HOUSE: cte.GREEN_HOUSE, - cte.NON_HEATED: cte.NON_HEATED - } - - _comnet_schedules_key_to_comnet_schedules = { - 'C-1 Assembly': 'C-1 Assembly', - 'C-2 Public': 'C-2 Health', - 'C-3 Hotel Motel': 'C-3 Hotel', - 'C-4 Manufacturing': 'C-4 Manufacturing', - 'C-5 Office': 'C-5 Office', - 'C-6 Parking Garage': 'C-6 Parking', - 'C-7 Restaurant': 'C-7 Restaurant', - 'C-8 Retail': 'C-8 Retail', - 'C-9 Schools': 'C-9 School', - 'C-10 Warehouse': 'C-10 Warehouse', - 'C-11 Laboratory': 'C-11 Lab', - 'C-12 Residential': 'C-12 Residential', - 'C-13 Data Center': 'C-13 Data', - 'C-14 Gymnasium': 'C-14 Gymnasium'} - - _comnet_schedules_key_to_usage = { - 'C-1 Assembly': 'C-1 Assembly', - 'C-2 Public': 'C-2 Health', - 'C-3 Hotel Motel': 'C-3 Hotel', - 'C-4 Manufacturing': 'C-4 Manufacturing', - 'C-5 Office': 'C-5 Office', - 'C-6 Parking Garage': 'C-6 Parking', - 'C-7 Restaurant': 'C-7 Restaurant', - 'C-8 Retail': 'C-8 Retail', - 'C-9 Schools': 'C-9 School', - 'C-10 Warehouse': 'C-10 Warehouse', - 'C-11 Laboratory': 'C-11 Lab', - 'C-12 Residential': 'C-12 Residential', - 'C-13 Data Center': 'C-13 Data', - 'C-14 Gymnasium': 'C-14 Gymnasium'} - - @staticmethod - def comnet_from_libs_usage(usage): - """ - Get Comnet usage from the given internal usage key - :param usage: str - :return: str - """ - try: - return UsageHelper._usage_to_comnet[usage] - except KeyError: - sys.stderr.write('Error: keyword not found to translate from libs_usage to comnet usage.\n') - - @staticmethod - def schedules_key(usage): - """ - Get Comnet schedules key from the list found in the Comnet usage file - :param usage: str - :return: str - """ - try: - return UsageHelper._comnet_schedules_key_to_comnet_schedules[usage] - except KeyError: - sys.stderr.write('Error: Comnet keyword not found. An update of the Comnet files might have been ' - 'done changing the keywords.\n') diff --git a/hub/imports/usage/hft_usage_interface.py b/hub/imports/usage/hft_usage_interface.py deleted file mode 100644 index 10953cff..00000000 --- a/hub/imports/usage/hft_usage_interface.py +++ /dev/null @@ -1,280 +0,0 @@ -""" -Hft-based interface, it reads format defined within the CERC team (based on that one used in SimStadt and developed by -the IAF team at hft-Stuttgart) -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import xmltodict -import copy -from hub.city_model_structure.building_demand.usage_zone import UsageZone -from hub.city_model_structure.building_demand.internal_gain import InternalGain -from hub.city_model_structure.building_demand.occupancy import Occupancy -from hub.city_model_structure.building_demand.appliances import Appliances -from hub.city_model_structure.building_demand.thermal_control import ThermalControl -from hub.city_model_structure.attributes.schedule import Schedule -import hub.helpers.constants as cte -from hub.imports.usage.helpers.usage_helper import UsageHelper - - -class HftUsageInterface: - """ - HftUsageInterface abstract class - """ - - def __init__(self, base_path, usage_file='ca_library_reduced.xml'): - path = str(base_path / usage_file) - self._usage_archetypes = [] - with open(path) as xml: - self._archetypes = xmltodict.parse(xml.read(), force_list=('zoneUsageVariant', 'zoneUsageType')) - for zone_usage_type in self._archetypes['buildingUsageLibrary']['zoneUsageType']: - usage = zone_usage_type['id'] - usage_archetype = self._parse_zone_usage_type(usage, zone_usage_type) - self._usage_archetypes.append(usage_archetype) - if 'zoneUsageVariant' in zone_usage_type: - for usage_zone_variant in zone_usage_type['zoneUsageVariant']: - usage = usage_zone_variant['id'] - usage_archetype_variant = self._parse_zone_usage_variant(usage, usage_archetype, usage_zone_variant) - self._usage_archetypes.append(usage_archetype_variant) - - @staticmethod - def _parse_zone_usage_type(usage, zone_usage_type): - usage_zone_archetype = UsageZone() - usage_zone_archetype.usage = usage - - if 'occupancy' in zone_usage_type: - _occupancy = Occupancy() - _occupancy.occupancy_density = zone_usage_type['occupancy']['occupancyDensity'] #todo: check units - - if 'internGains' in zone_usage_type['occupancy']: - _internal_gain = InternalGain() - _internal_gain.latent_fraction = zone_usage_type['occupancy']['internGains']['latentFraction'] - _internal_gain.convective_fraction = zone_usage_type['occupancy']['internGains']['convectiveFraction'] - _internal_gain.average_internal_gain = zone_usage_type['occupancy']['internGains']['averageInternGainPerSqm'] - _internal_gain.radiative_fraction = zone_usage_type['occupancy']['internGains']['radiantFraction'] - if 'load' in zone_usage_type['occupancy']['internGains']: - _schedule = Schedule() - _schedule.type = 'internal gains load' - _schedule.time_range = cte.DAY - _schedule.time_step = cte.HOUR - _schedule.data_type = cte.ANY_NUMBER - _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, - cte.SUNDAY] - _values = zone_usage_type['occupancy']['internGains']['load']['weekDayProfile']['values'] - while ' ' in _values: - _values = _values.replace(' ', ' ') - _values = _values.split() - _values_float = [] - for _value in _values: - _values_float.append(float(_value)) - _schedule.values = _values_float - _internal_gain.schedules = [_schedule] - - usage_zone_archetype.internal_gains = [_internal_gain] - - usage_zone_archetype.hours_day = zone_usage_type['occupancy']['usageHoursPerDay'] - usage_zone_archetype.days_year = zone_usage_type['occupancy']['usageDaysPerYear'] - usage_zone_archetype.occupancy = _occupancy - - if 'endUses' in zone_usage_type: - _thermal_control = ThermalControl() - if 'space_heating' in zone_usage_type['endUses']: - _thermal_control.mean_heating_set_point = \ - zone_usage_type['endUses']['space_heating']['heatingSetPointTemperature'] - _thermal_control.heating_set_back = zone_usage_type['endUses']['space_heating']['heatingSetBackTemperature'] - if 'schedule' in zone_usage_type['endUses']['space_heating']: - _schedule = Schedule() - _schedule.type = 'heating temperature' - _schedule.time_range = cte.DAY - _schedule.time_step = cte.HOUR - _schedule.data_type = cte.ANY_NUMBER - _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, - cte.SUNDAY] - _values = zone_usage_type['endUses']['space_heating']['schedule']['weekDayProfile']['values'] - while ' ' in _values: - _values = _values.replace(' ', ' ') - _values = _values.split() - _values_float = [] - for _value in _values: - _values_float.append(float(_value)) - _schedule.values = _values_float - _thermal_control.heating_set_point_schedules = [_schedule] - - if 'space_cooling' in zone_usage_type['endUses']: - _thermal_control.mean_cooling_set_point = \ - zone_usage_type['endUses']['space_cooling']['coolingSetPointTemperature'] - if 'schedule' in zone_usage_type['endUses']['space_cooling']: - _schedule = Schedule() - _schedule.type = 'cooling temperature' - _schedule.time_range = cte.DAY - _schedule.time_step = cte.HOUR - _schedule.data_type = cte.ANY_NUMBER - _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, - cte.SUNDAY] - _values = zone_usage_type['endUses']['space_cooling']['schedule']['weekDayProfile']['values'] - while ' ' in _values: - _values = _values.replace(' ', ' ') - _values = _values.split() - _values_float = [] - for _value in _values: - _values_float.append(float(_value)) - _schedule.values = _values_float - _thermal_control.cooling_set_point_schedules = [_schedule] - - usage_zone_archetype.thermal_control = _thermal_control - - if 'ventilation' in zone_usage_type['endUses'] and zone_usage_type['endUses']['ventilation'] is not None: - usage_zone_archetype.mechanical_air_change = \ - zone_usage_type['endUses']['ventilation']['mechanicalAirChangeRate'] - - # todo: not used or assigned anywhere - if 'domestic_hot_water' in zone_usage_type['endUses']: - # liters to cubic meters - dhw_average_volume_pers_day = float( - zone_usage_type['endUses']['domestic_hot_water']['averageVolumePerPersAndDay']) / 1000 - dhw_preparation_temperature = zone_usage_type['endUses']['domestic_hot_water']['preparationTemperature'] - - if 'all_electrical_appliances' in zone_usage_type['endUses']: - if 'averageConsumptionPerSqmAndYear' in zone_usage_type['endUses']['all_electrical_appliances']: - # kWh to J - usage_zone_archetype.electrical_app_average_consumption_sqm_year = \ - float(zone_usage_type['endUses']['all_electrical_appliances']['averageConsumptionPerSqmAndYear']) \ - * cte.KILO_WATTS_HOUR_TO_JULES - - if 'appliance' in zone_usage_type: - _appliances = Appliances() - _appliances.density = zone_usage_type['appliance']['#text'] #todo: check units - - usage_zone_archetype.appliances = _appliances - - return usage_zone_archetype - - @staticmethod - def _parse_zone_usage_variant(usage, usage_zone, usage_zone_variant): - # the variants mimic the inheritance concept from OOP - usage_zone_archetype = copy.deepcopy(usage_zone) - usage_zone_archetype.usage = usage - - if 'occupancy' in usage_zone_variant: - _occupancy = Occupancy() - if 'occupancyDensity' in usage_zone_variant['occupancy']: - _occupancy.occupancy_density = usage_zone_variant['occupancy']['occupancyDensity'] # todo: check units - if 'usageHoursPerDay' in usage_zone_variant['occupancy']: - usage_zone_archetype.hours_day = usage_zone_variant['occupancy']['usageHoursPerDay'] - if 'usageDaysPerYear' in usage_zone_variant['occupancy']: - usage_zone_archetype.days_year = usage_zone_variant['occupancy']['usageDaysPerYear'] - usage_zone_archetype.occupancy = _occupancy - - if 'internGains' in usage_zone_variant['occupancy']: - _internal_gain = InternalGain() - if 'latentFraction' in usage_zone_variant['occupancy']['internGains']: - _internal_gain.latent_fraction = usage_zone_variant['occupancy']['internGains']['latentFraction'] - if 'convectiveFraction' in usage_zone_variant['occupancy']['internGains']: - _internal_gain.convective_fraction = usage_zone_variant['occupancy']['internGains']['convectiveFraction'] - if 'averageInternGainPerSqm' in usage_zone_variant['occupancy']['internGains']: - _internal_gain.average_internal_gain = \ - usage_zone_variant['occupancy']['internGains']['averageInternGainPerSqm'] - if 'radiantFraction' in usage_zone_variant['occupancy']['internGains']: - _internal_gain.radiative_fraction = usage_zone_variant['occupancy']['internGains']['radiantFraction'] - if 'load' in usage_zone_variant['occupancy']['internGains']: - _schedule = Schedule() - _schedule.type = 'internal gains load' - _schedule.time_range = cte.DAY - _schedule.time_step = cte.HOUR - _schedule.data_type = cte.ANY_NUMBER - _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, - cte.SUNDAY] - _values = usage_zone_variant['occupancy']['internGains']['load']['weekDayProfile']['values'] - while ' ' in _values: - _values = _values.replace(' ', ' ') - _values = _values.split() - _values_float = [] - for _value in _values: - _values_float.append(float(_value)) - _schedule.values = _values_float - _internal_gain.schedules = [_schedule] - - usage_zone_archetype.internal_gains = [_internal_gain] - - if 'endUses' in usage_zone_variant: - _thermal_control = ThermalControl() - if 'space_heating' in usage_zone_variant['endUses']: - if 'heatingSetPointTemperature' in usage_zone_variant['endUses']['space_heating']: - _thermal_control.mean_heating_set_point = \ - usage_zone_variant['endUses']['space_heating']['heatingSetPointTemperature'] - if 'heatingSetBackTemperature' in usage_zone_variant['endUses']['space_heating']: - _thermal_control.heating_set_back = usage_zone_variant['endUses']['space_heating']['heatingSetBackTemperature'] - if 'schedule' in usage_zone_variant['endUses']['space_heating']: - _schedule = Schedule() - _schedule.type = 'heating temperature' - _schedule.time_range = cte.DAY - _schedule.time_step = cte.HOUR - _schedule.data_type = cte.ANY_NUMBER - _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, - cte.SUNDAY] - _values = usage_zone_variant['endUses']['space_heating']['schedule']['weekDayProfile']['values'] - while ' ' in _values: - _values = _values.replace(' ', ' ') - _values = _values.split() - _values_float = [] - for _value in _values: - _values_float.append(float(_value)) - _schedule.values = _values_float - _thermal_control.heating_set_point_schedules = [_schedule] - - if 'space_cooling' in usage_zone_variant['endUses'] and \ - usage_zone_variant['endUses']['space_cooling'] is not None: - if 'coolingSetPointTemperature' in usage_zone_variant['endUses']['space_cooling']: - _thermal_control.mean_cooling_set_point = \ - usage_zone_variant['endUses']['space_cooling']['coolingSetPointTemperature'] - if 'schedule' in usage_zone_variant['endUses']['space_cooling']: - _schedule = Schedule() - _schedule.type = 'cooling temperature' - _schedule.time_range = cte.DAY - _schedule.time_step = cte.HOUR - _schedule.data_type = cte.ANY_NUMBER - _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, - cte.SUNDAY] - _values = usage_zone_variant['endUses']['space_cooling']['schedule']['weekDayProfile']['values'] - while ' ' in _values: - _values = _values.replace(' ', ' ') - _values = _values.split() - _values_float = [] - for _value in _values: - _values_float.append(float(_value)) - _schedule.values = _values_float - _thermal_control.cooling_set_point_schedules = [_schedule] - - usage_zone_archetype.thermal_control = _thermal_control - - if 'ventilation' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['ventilation'] is not None: - usage_zone_archetype.mechanical_air_change = \ - usage_zone_variant['endUses']['ventilation']['mechanicalAirChangeRate'] - - if 'appliance' in usage_zone_variant: - _appliances = Appliances() - _appliances.density = usage_zone_variant['appliance']['#text'] # todo: check units - - usage_zone_archetype.appliances = _appliances - - return usage_zone_archetype - - def _search_archetype(self, libs_usage): - building_usage = UsageHelper().hft_from_libs_usage(libs_usage) - for building_archetype in self._usage_archetypes: - if building_archetype.usage == building_usage: - return building_archetype - return None - - @staticmethod - def _assign_values(usage, archetype): - usage_zone = UsageZone() - usage_zone.usage = usage - usage_zone.internal_gains = copy.deepcopy(archetype.internal_gains) - usage_zone.mechanical_air_change = archetype.mechanical_air_change - usage_zone.occupancy = copy.deepcopy(archetype.occupancy) - usage_zone.appliances = copy.deepcopy(archetype.appliances) - usage_zone.thermal_control = copy.deepcopy(archetype.thermal_control) - usage_zone.days_year = archetype.days_year - usage_zone.hours_day = archetype.hours_day - return usage_zone diff --git a/hub/imports/usage/hft_usage_parameters.py b/hub/imports/usage/hft_usage_parameters.py deleted file mode 100644 index 0ddd968c..00000000 --- a/hub/imports/usage/hft_usage_parameters.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -HftUsageParameters model the usage properties -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import sys - -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper -from hub.imports.usage.hft_usage_interface import HftUsageInterface -from hub.imports.usage.helpers.usage_helper import UsageHelper - - -class HftUsageParameters(HftUsageInterface): - """ - HftUsageParameters class - """ - def __init__(self, city, base_path): - super().__init__(base_path, 'de_library.xml') - self._city = city - - def enrich_buildings(self): - """ - Returns the city with the usage parameters assigned to the buildings - :return: - """ - city = self._city - for building in city.buildings: - usage = GeometryHelper().libs_usage_from_libs_function(building.function) - try: - archetype = self._search_archetype(usage) - except KeyError: - sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' - f' {building.function}, that assigns building usage as ' - f'{GeometryHelper().libs_usage_from_libs_function(building.function)}\n') - return - - for internal_zone in building.internal_zones: - libs_usage = GeometryHelper().libs_usage_from_libs_function(building.function) - usage_zone = self._assign_values(UsageHelper().hft_from_libs_usage(libs_usage), archetype) - usage_zone.percentage = 1 - internal_zone.usage_zones = [usage_zone] diff --git a/hub/imports/usage/nrcan_usage_parameters.py b/hub/imports/usage/nrcan_usage_parameters.py new file mode 100644 index 00000000..68d8a2bf --- /dev/null +++ b/hub/imports/usage/nrcan_usage_parameters.py @@ -0,0 +1,163 @@ +""" +NrcanUsageParameters extracts the usage properties from NRCAN catalog and assigns to each building +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import sys + +import hub.helpers.constants as cte +from hub.helpers.dictionaries import Dictionaries +from hub.city_model_structure.building_demand.usage import Usage +from hub.city_model_structure.building_demand.lighting import Lighting +from hub.city_model_structure.building_demand.occupancy import Occupancy +from hub.city_model_structure.building_demand.appliances import Appliances +from hub.city_model_structure.building_demand.thermal_control import ThermalControl +from hub.catalog_factories.usage_catalog_factory import UsageCatalogFactory + + +class NrcanUsageParameters: + """ + NrcanUsageParameters class + """ + def __init__(self, city, base_path): + self._city = city + self._path = base_path + + def enrich_buildings(self): + """ + Returns the city with the usage parameters assigned to the buildings + :return: + """ + city = self._city + nrcan_catalog = UsageCatalogFactory('nrcan').catalog + comnet_catalog = UsageCatalogFactory('comnet').catalog + + for building in city.buildings: + usage_name = Dictionaries().hub_usage_to_nrcan_usage[building.function] + try: + archetype_usage = self._search_archetypes(nrcan_catalog, usage_name) + except KeyError: + sys.stderr.write(f'Building {building.name} has unknown usage archetype for building function:' + f' {building.function}') + return + + usage_name = Dictionaries().hub_usage_to_comnet_usage[building.function] + try: + comnet_archetype_usage = self._search_archetypes(comnet_catalog, usage_name) + except KeyError: + sys.stderr.write(f'Building {building.name} has unknown usage archetype for building function:' + f' {building.function}') + return + + for internal_zone in building.internal_zones: + if internal_zone.area is None: + raise Exception('Internal zone area not defined, ACH cannot be calculated') + if internal_zone.volume is None: + raise Exception('Internal zone volume not defined, ACH cannot be calculated') + if internal_zone.area <= 0: + raise Exception('Internal zone area is zero, ACH cannot be calculated') + volume_per_area = internal_zone.volume / internal_zone.area + usage = Usage() + usage.name = usage_name + self._assign_values(usage, archetype_usage, volume_per_area) + self._assign_comnet_extra_values(usage, comnet_archetype_usage) + usage.percentage = 1 + self._calculate_reduced_values_from_extended_library(usage, archetype_usage) + + internal_zone.usages = [usage] + + @staticmethod + def _search_archetypes(catalog, usage_name): + archetypes = catalog.entries('archetypes').usages + for building_archetype in archetypes: + if str(usage_name) == str(building_archetype.name): + return building_archetype + raise KeyError('archetype not found') + + @staticmethod + def _assign_values(usage, archetype, volume_per_area): + if archetype.mechanical_air_change > 0: + usage.mechanical_air_change = archetype.mechanical_air_change + elif archetype.ventilation_rate > 0: + usage.mechanical_air_change = archetype.ventilation_rate / volume_per_area \ + * cte.HOUR_TO_MINUTES * cte.MINUTES_TO_SECONDS + else: + usage.mechanical_air_change = 0 + _occupancy = Occupancy() + _occupancy.occupancy_density = archetype.occupancy.occupancy_density + _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain + _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain + _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain + _occupancy.occupancy_schedules = archetype.occupancy.schedules + usage.occupancy = _occupancy + _lighting = Lighting() + _lighting.density = archetype.lighting.density + _lighting.convective_fraction = archetype.lighting.convective_fraction + _lighting.radiative_fraction = archetype.lighting.radiative_fraction + _lighting.latent_fraction = archetype.lighting.latent_fraction + _lighting.schedules = archetype.lighting.schedules + usage.lighting = _lighting + _appliances = Appliances() + _appliances.density = archetype.appliances.density + _appliances.convective_fraction = archetype.appliances.convective_fraction + _appliances.radiative_fraction = archetype.appliances.radiative_fraction + _appliances.latent_fraction = archetype.appliances.latent_fraction + _appliances.schedules = archetype.appliances.schedules + usage.appliances = _appliances + _control = ThermalControl() + _control.cooling_set_point_schedules = archetype.thermal_control.cooling_set_point_schedules + _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules + _control.hvac_availability_schedules = archetype.thermal_control.hvac_availability_schedules + usage.thermal_control = _control + + @staticmethod + def _assign_comnet_extra_values(usage, archetype): + _occupancy = usage.occupancy + _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain + _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain + _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain + + @staticmethod + def _calculate_reduced_values_from_extended_library(usage, archetype): + number_of_days_per_type = {'WD': 251, 'Sat': 52, 'Sun': 62} + total = 0 + for schedule in archetype.thermal_control.hvac_availability_schedules: + if schedule.day_types[0] == cte.SATURDAY: + for value in schedule.values: + total += value * number_of_days_per_type['Sat'] + elif schedule.day_types[0] == cte.SUNDAY: + for value in schedule.values: + total += value * number_of_days_per_type['Sun'] + else: + for value in schedule.values: + total += value * number_of_days_per_type['WD'] + + usage.hours_day = total / 365 + usage.days_year = 365 + + max_heating_setpoint = cte.MIN_FLOAT + min_heating_setpoint = cte.MAX_FLOAT + + for schedule in archetype.thermal_control.heating_set_point_schedules: + if schedule.values is None: + max_heating_setpoint = None + min_heating_setpoint = None + break + if max(schedule.values) > max_heating_setpoint: + max_heating_setpoint = max(schedule.values) + if min(schedule.values) < min_heating_setpoint: + min_heating_setpoint = min(schedule.values) + + min_cooling_setpoint = cte.MAX_FLOAT + for schedule in archetype.thermal_control.cooling_set_point_schedules: + if schedule.values is None: + min_cooling_setpoint = None + break + if min(schedule.values) < min_cooling_setpoint: + min_cooling_setpoint = min(schedule.values) + + usage.thermal_control.mean_heating_set_point = max_heating_setpoint + usage.thermal_control.heating_set_back = min_heating_setpoint + usage.thermal_control.mean_cooling_set_point = min_cooling_setpoint diff --git a/hub/imports/usage_factory.py b/hub/imports/usage_factory.py index b192c90c..e8962480 100644 --- a/hub/imports/usage_factory.py +++ b/hub/imports/usage_factory.py @@ -7,8 +7,10 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from pathlib import Path -from hub.imports.usage.hft_usage_parameters import HftUsageParameters from hub.imports.usage.comnet_usage_parameters import ComnetUsageParameters +from hub.imports.usage.nrcan_usage_parameters import NrcanUsageParameters +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type class UsageFactory: @@ -19,22 +21,27 @@ class UsageFactory: if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/usage') self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(UsageFactory) + if self._handler not in class_funcs: + err_msg = f"Wrong import type [{self._handler}]. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._city = city self._base_path = base_path - def _hft(self): - """ - Enrich the city with HFT usage library - """ - self._city.level_of_detail.usage = 2 - return HftUsageParameters(self._city, self._base_path).enrich_buildings() - def _comnet(self): """ Enrich the city with COMNET usage library """ self._city.level_of_detail.usage = 2 - return ComnetUsageParameters(self._city, self._base_path).enrich_buildings() + ComnetUsageParameters(self._city, self._base_path).enrich_buildings() + + def _nrcan(self): + """ + Enrich the city with NRCAN usage library + """ + self._city.level_of_detail.usage = 2 + NrcanUsageParameters(self._city, self._base_path).enrich_buildings() def enrich(self): """ diff --git a/hub/imports/weather/epw_weather_parameters.py b/hub/imports/weather/epw_weather_parameters.py index 4456c112..47ddb5fe 100644 --- a/hub/imports/weather/epw_weather_parameters.py +++ b/hub/imports/weather/epw_weather_parameters.py @@ -9,6 +9,7 @@ import sys from pathlib import Path import pandas as pd import hub.helpers.constants as cte +from hub.imports.weather.helpers.weather import Weather as wh class EpwWeatherParameters: @@ -28,7 +29,7 @@ class EpwWeatherParameters: city.longitude = line[7] city.time_zone = line[8] for i in range(0, 2): - line = file.readline().split(',') + _ = file.readline().split(',') line = file.readline().split(',') number_records = int(line[1]) depth_measurement_ground_temperature = [] @@ -110,3 +111,10 @@ class EpwWeatherParameters: building.beam[cte.HOUR] = new_value else: pd.concat([building.beam[cte.HOUR], new_value], axis=1) + # create the monthly and yearly values out of the hourly + for building in self._city.buildings: + if cte.MONTH not in building.external_temperature: + building.external_temperature[cte.MONTH] = wh().get_monthly_mean_values(building.external_temperature[cte.HOUR][['epw']]) + if cte.YEAR not in building.external_temperature: + building.external_temperature[cte.YEAR] = wh(). get_yearly_mean_values(building.external_temperature[cte.HOUR][['epw']]) + self._city.level_of_detail.weather = 2 diff --git a/hub/imports/weather/helpers/weather.py b/hub/imports/weather/helpers/weather.py index 94769078..ae4c9ad2 100644 --- a/hub/imports/weather/helpers/weather.py +++ b/hub/imports/weather/helpers/weather.py @@ -6,6 +6,9 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import math import hub.helpers.constants as cte +import pandas as pd +import calendar as cal +import numpy as np class Weather: @@ -28,3 +31,38 @@ class Weather: + 0.32 * (temperature + cte.KELVIN) - cte.KELVIN values.append(value) return values + + def get_monthly_mean_values(self, values): + out = None + if values is not None: + if 'month' not in values.columns: + values = pd.concat([self.month_hour, pd.DataFrame(values)], axis=1) + out = values.groupby('month', as_index=False).mean() + del out['month'] + return out + + def get_yearly_mean_values(self, values): + return values.mean() + + def get_total_month(self, values): + out = None + if values is not None: + if 'month' not in values.columns: + values = pd.concat([self.month_hour, pd.DataFrame(values)], axis=1) + out = pd.DataFrame(values).groupby('month', as_index=False).sum() + del out['month'] + return out + + @property + def month_hour(self): + """ + returns a DataFrame that has x values of the month number (January = 1, February = 2...), + being x the number of hours of the corresponding month + :return: DataFrame(int) + """ + array = [] + for i in range(0, 12): + days_of_month = cal.monthrange(2015, i+1)[1] + total_hours = days_of_month * 24 + array = np.concatenate((array, np.full(total_hours, i + 1))) + return pd.DataFrame(array, columns=['month']) diff --git a/hub/imports/weather_factory.py b/hub/imports/weather_factory.py index 0b214a79..296755de 100644 --- a/hub/imports/weather_factory.py +++ b/hub/imports/weather_factory.py @@ -7,6 +7,8 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca from pathlib import Path from hub.imports.weather.xls_weather_parameters import XlsWeatherParameters from hub.imports.weather.epw_weather_parameters import EpwWeatherParameters +from hub.hub_logger import logger +from hub.helpers.utils import validate_import_export_type class WeatherFactory: @@ -18,6 +20,11 @@ class WeatherFactory: if base_path is None: base_path = Path(Path(__file__).parent.parent / 'data/weather') self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(WeatherFactory) + if self._handler not in class_funcs: + err_msg = f"Wrong import type. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) self._city = city self._base_path = base_path self._file_name = file_name diff --git a/hub/install_postgresql_linux.sh b/hub/install_postgresql_linux.sh deleted file mode 100755 index 70088df0..00000000 --- a/hub/install_postgresql_linux.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' -wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - -sudo apt-get update -sudo apt-get install postgresql \ No newline at end of file diff --git a/hub/persistence/__init__.py b/hub/persistence/__init__.py index caebc883..19c4e0dc 100644 --- a/hub/persistence/__init__.py +++ b/hub/persistence/__init__.py @@ -1,6 +1,8 @@ -from .base_repo import BaseRepo -from .repositories.city_repo import CityRepo -from .repositories.heat_pump_simulation_repo import HeatPumpSimulationRepo +from .repository import Repository +from .repositories.city import City +from .repositories.application import Application +from .repositories.simulation_results import SimulationResults +from .repositories.city_object import CityObject from .db_setup import DBSetup -from .repositories.user_repo import UserRepo +from .repositories.user import User from .models.user import UserRoles diff --git a/hub/persistence/db_config.py b/hub/persistence/configuration.py similarity index 91% rename from hub/persistence/db_config.py rename to hub/persistence/configuration.py index 4ef2dc14..c3afdc77 100644 --- a/hub/persistence/db_config.py +++ b/hub/persistence/configuration.py @@ -10,13 +10,12 @@ from dotenv import load_dotenv from sqlalchemy.ext.declarative import declarative_base from hub.hub_logger import logger -Base = declarative_base() +Models = declarative_base() - -class BaseConfiguration(object): +class Configuration: + """ + Configuration class to hold common persistence configuration """ - Base configuration class to hold common persistence configuration - """ def __init__(self, db_name: str, dotenv_path: str, app_env='TEST'): """ diff --git a/hub/persistence/db_setup.py b/hub/persistence/db_setup.py index dd591749..cf3786f1 100644 --- a/hub/persistence/db_setup.py +++ b/hub/persistence/db_setup.py @@ -1,36 +1,67 @@ +""" +Database setup +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Peter Yefi peteryefi@gmail.com +""" + +from hub.persistence import Repository +from hub.persistence.models import Application from hub.persistence.models import City -from hub.persistence import BaseRepo -from hub.persistence.models import HeatPumpSimulation +from hub.persistence.models import CityObject from hub.persistence.models import User -from hub.persistence.repositories import UserRepo from hub.persistence.models import UserRoles +from hub.persistence.models import SimulationResults +from hub.persistence.repositories import User as UserRepository +from hub.persistence.repositories import Application as ApplicationRepository from hub.hub_logger import logger class DBSetup: - def __init__(self, db_name, app_env, dotenv_path): + def __init__(self, db_name, app_env, dotenv_path, admin_password, application_uuid): """ - Creates database tables and a default admin user + Creates database tables a default admin user and a default admin app with the given password and uuid :param db_name: :param app_env: :param dotenv_path: """ - repo = BaseRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) - User.__table__.create(bind=repo.engine, checkfirst=True) - City.__table__.create(bind=repo.engine, checkfirst=True) - HeatPumpSimulation.__table__.create(bind=repo.engine, checkfirst=True) - self._user_repo = UserRepo(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) - self._create_admin_user(self._user_repo) + repository = Repository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) - def _create_admin_user(self, user_repo): - email = 'admin@hub.com' - password = 'HubAdmin#!98' + # Create the tables using the models + Application.__table__.create(bind=repository.engine, checkfirst=True) + User.__table__.create(bind=repository.engine, checkfirst=True) + City.__table__.create(bind=repository.engine, checkfirst=True) + CityObject.__table__.create(bind=repository.engine, checkfirst=True) + SimulationResults.__table__.create(bind=repository.engine, checkfirst=True) + + self._user_repo = UserRepository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + self._application_repo = ApplicationRepository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + application_id = self._create_admin_app(self._application_repo, application_uuid) + self._create_admin_user(self._user_repo, admin_password, application_id) + + @staticmethod + def _create_admin_app(application_repo, application_uuid): + name = 'AdminTool' + description = 'Admin tool to control city persistence and to test the API v1.4' + print('Creating default admin tool application...') + application = application_repo.insert(name, description, application_uuid) + + if type(application) is dict: + logger.info(application) + else: + msg = f'Created Admin tool with application_uuid: {application_uuid}' + print(msg) + logger.info(msg) + return application.id + + @staticmethod + def _create_admin_user(user_repo, admin_password, application_id): + password = admin_password print('Creating default admin user...') - user = user_repo.insert('Administrator', email, password, UserRoles.Admin) + user = user_repo.insert('Administrator', password, UserRoles.Admin, application_id) if type(user) is dict: logger.info(user) else: - print(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin.value}') - logger.info(f'Created Admin user with email: {email}, password: {password} and role: {UserRoles.Admin.value}') - print('Remember to change the admin default password and email address with the UserFactory') + print(f'Created Admin user') + logger.info(f'Created Admin user') diff --git a/hub/persistence/models/__init__.py b/hub/persistence/models/__init__.py index d29ea9e3..77f5c105 100644 --- a/hub/persistence/models/__init__.py +++ b/hub/persistence/models/__init__.py @@ -1,5 +1,5 @@ +from .application import Application from .city import City -from .heat_pump_simulation import HeatPumpSimulation -from .heat_pump_simulation import SimulationTypes -from .heat_pump_simulation import HeatPumpTypes +from .city_object import CityObject +from .simulation_results import SimulationResults from .user import User, UserRoles diff --git a/hub/persistence/models/application.py b/hub/persistence/models/application.py new file mode 100644 index 00000000..3da947f4 --- /dev/null +++ b/hub/persistence/models/application.py @@ -0,0 +1,31 @@ +""" +Model representation of an application +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import datetime + +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy import Column, Integer, String, Sequence +from sqlalchemy import DateTime +from hub.persistence.configuration import Models + + +class Application(Models): + """ + A model representation of an application + """ + __tablename__ = 'application' + id = Column(Integer, Sequence('application_id_seq'), primary_key=True) + name = Column(String, nullable=False) + description = Column(String, nullable=False) + application_uuid = Column(UUID(as_uuid=True), nullable=False) + created = Column(DateTime, default=datetime.datetime.utcnow) + updated = Column(DateTime, default=datetime.datetime.utcnow) + + def __init__(self, name, description, application_uuid): + self.name = name + self.description = description + self.application_uuid = application_uuid diff --git a/hub/persistence/models/city.py b/hub/persistence/models/city.py index 86ab2bc1..92821723 100644 --- a/hub/persistence/models/city.py +++ b/hub/persistence/models/city.py @@ -5,46 +5,33 @@ Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ -from sqlalchemy import Column, Integer, String, Sequence, ForeignKey -from sqlalchemy import DateTime, PickleType, Float -from hub.persistence.db_config import Base -from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.orm import relationship import datetime -import numpy as np + +from sqlalchemy import Column, Integer, String, Sequence, ForeignKey +from sqlalchemy import DateTime, PickleType +from hub.persistence.configuration import Models -class City(Base): +class City(Models): """A model representation of a city """ - __tablename__ = "city" + __tablename__ = 'city' id = Column(Integer, Sequence('city_id_seq'), primary_key=True) - city = Column(PickleType, nullable=False) + pickle_path = Column(String, nullable=False) name = Column(String, nullable=False) - srs_name = Column(String, nullable=False) - climate_reference_city = Column(String, nullable=True) - time_zone = Column(String, nullable=True) - country_code = Column(String, nullable=False) - latitude = Column(Float) - longitude = Column(Float) - lower_corner = Column(JSONB, nullable=False) - upper_corner = Column(JSONB, nullable=False) + level_of_detail = Column(Integer, nullable=False) + climate_file = Column(String, nullable=False) + application_id = Column(Integer, ForeignKey('application.id'), nullable=False) + user_id = Column(Integer, ForeignKey('user.id'), nullable=True) hub_release = Column(String, nullable=False) - city_version = Column(Integer, nullable=False) - user_id = Column(Integer, ForeignKey('user.id')) - user = relationship("User", back_populates="cities") created = Column(DateTime, default=datetime.datetime.utcnow) updated = Column(DateTime, default=datetime.datetime.utcnow) - def __init__(self, city, name, srs_name, country_code, l_corner, u_corner, user_id): - self.city = city - self.user_id = user_id + def __init__(self, pickle_path, name, level_of_detail, climate_file, application_id, user_id, hub_release): + self.pickle_path = str(pickle_path) self.name = name - self.srs_name = srs_name - self.country_code = country_code - l_corner = l_corner.tolist() if type(l_corner) == np.ndarray else l_corner - u_corner = u_corner.tolist() if type(u_corner) == np.ndarray else u_corner - self.lower_corner = l_corner - self.upper_corner = u_corner - - + self.level_of_detail = level_of_detail + self.climate_file = climate_file + self.application_id = application_id + self.user_id = user_id + self.hub_release = hub_release diff --git a/hub/persistence/models/city_object.py b/hub/persistence/models/city_object.py new file mode 100644 index 00000000..7229a2ac --- /dev/null +++ b/hub/persistence/models/city_object.py @@ -0,0 +1,41 @@ +""" +Model representation of a city object +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import datetime + +from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float +from sqlalchemy import DateTime +from hub.persistence.configuration import Models + +class CityObject(Models): + """ + A model representation of an application + """ + __tablename__ = 'city_object' + id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True) + city_id = Column(Integer, ForeignKey('city.id'), nullable=False) + name = Column(String, nullable=False) + alias = Column(String, nullable=True) + type = Column(String, nullable=False) + year_of_construction = Column(Integer, nullable=True) + function = Column(String, nullable=True) + usage = Column(String, nullable=True) + volume = Column(Float, nullable=False) + area = Column(Float, nullable=False) + created = Column(DateTime, default=datetime.datetime.utcnow) + updated = Column(DateTime, default=datetime.datetime.utcnow) + + def __init__(self, city_id, name, alias, object_type, year_of_construction, function, usage, volume, area): + self.city_id = city_id + self.name = name + self.alias = alias + self.type = object_type + self.year_of_construction = year_of_construction + self.function = function + self.usage = usage + self.volume = volume + self.area = area diff --git a/hub/persistence/models/heat_pump_simulation.py b/hub/persistence/models/heat_pump_simulation.py deleted file mode 100644 index 05f447d9..00000000 --- a/hub/persistence/models/heat_pump_simulation.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Model representation of the results of heat pump simulation -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Peter Yefi peteryefi@gmail.com -""" - -from sqlalchemy import Column, Integer, String, Sequence -from sqlalchemy import Enum, ForeignKey, Float, DateTime -from sqlalchemy.dialects.postgresql import JSONB -from hub.persistence.db_config import Base -import enum -import datetime - - -class SimulationTypes(enum.Enum): - Parallel = 'PARALLEL' - Series = 'SERIES' - - -class HeatPumpTypes(enum.Enum): - Air = 'Air Source' - Water = 'Water to Water' - - -class HeatPumpSimulation(Base): - """A model representation of a building - - Attributes: - city_id, A reference to the city which was used to run this simulation. - hourly_electricity_demand, A JSON object that has hours and their electricity demand - daily_electricity_demand, A JSON object that has days and their electricity demand - monthly_electricity_demand, A JSON object that has months and their electricity demand - daily_fossil_fuel_consumption, A JSON object that has days and fossil fuel consumption - monthly_fossil_fuel_consumption, A JSON object that has months and fossil fuel consumption - heat_pump_type, Water or air heat pump - simulation_type, The type of heat pump simulation (parallel or series) - heat_pump_model, The model of the heat pump (either water to water or air source) - start year, HP simulation start year - end year, HP simulation end year - max_hp_energy_input, Maximum heat pump energy input - max_demand_storage_hour, Hours of storage at maximum demand - building_supply_temp, building supply temperature - temp_difference, Difference in HP and building supply temperatures - fuel_lhv, The lower heating value of fuel - fuel_price, The price of fuel - fuel_efficiency, the efficiency of fuel - fuel_density, the density of fuel - hp_supply_temp, supply temperature of heat pump - - - """ - __tablename__ = "heat_pump_simulation" - id = Column(Integer, Sequence('hp_simulation_id_seq'), primary_key=True) - city_id = Column(Integer, ForeignKey('city.id'), nullable=False) - daily_electricity_demand = Column(JSONB, nullable=False) - hourly_electricity_demand = Column(JSONB, nullable=False) - daily_fossil_fuel_consumption = Column(JSONB, nullable=False) - monthly_fossil_fuel_consumption = Column(JSONB, nullable=False) - monthly_electricity_demand = Column(JSONB, nullable=False) - heat_pump_type = Column(Enum(HeatPumpTypes), nullable=False) - simulation_type = Column(Enum(SimulationTypes), nullable=False) - heat_pump_model = Column(String, nullable=False) - start_year = Column(Integer, nullable=False) - end_year = Column(Integer, nullable=False) - max_hp_energy_input = Column(Float, nullable=False) - max_demand_storage_hour = Column(Float, nullable=False) - building_supply_temp = Column(Float, nullable=False) - temp_difference = Column(Float, nullable=False) - fuel_lhv = Column(Float, nullable=False) - fuel_price = Column(Float, nullable=False) - fuel_efficiency = Column(Float, nullable=False) - fuel_density = Column(Float, nullable=False) - hp_supply_temp = Column(Float, nullable=False) - created = Column(DateTime, default=datetime.datetime.utcnow) - - def __init__(self, city_id, hourly_elec_demand, daily_elec_demand, monthly_elec_demand, daily_fossil, monthly_fossil): - self.city_id = city_id - self.hourly_electricity_demand = hourly_elec_demand - self.daily_electricity_demand = daily_elec_demand - self.monthly_electricity_demand = monthly_elec_demand - self.daily_fossil_fuel_consumption = daily_fossil - self.monthly_fossil_fuel_consumption = monthly_fossil - - - diff --git a/hub/persistence/models/simulation_results.py b/hub/persistence/models/simulation_results.py new file mode 100644 index 00000000..defc3e7e --- /dev/null +++ b/hub/persistence/models/simulation_results.py @@ -0,0 +1,32 @@ +""" +Model representation of simulation results +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import datetime + +from sqlalchemy import Column, Integer, String, Sequence, ForeignKey +from sqlalchemy import DateTime +from sqlalchemy.dialects.postgresql import JSONB +from hub.persistence.configuration import Models + +class SimulationResults(Models): + """ + A model representation of an application + """ + __tablename__ = 'simulation_results' + id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True) + city_id = Column(Integer, ForeignKey('city.id'), nullable=True) + city_object_id = Column(Integer, ForeignKey('city_object.id'), nullable=True) + name = Column(String, nullable=False) + values = Column(JSONB, nullable=False) + created = Column(DateTime, default=datetime.datetime.utcnow) + updated = Column(DateTime, default=datetime.datetime.utcnow) + + def __init__(self, name, values, city_id=None, city_object_id=None): + self.name = name + self.values = values + self.city_id = city_id + self.city_object_id = city_object_id diff --git a/hub/persistence/models/user.py b/hub/persistence/models/user.py index 91027b27..a40ccece 100644 --- a/hub/persistence/models/user.py +++ b/hub/persistence/models/user.py @@ -5,14 +5,13 @@ Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ +import datetime +import enum + from sqlalchemy import Column, Integer, String, Sequence from sqlalchemy import DateTime, Enum -from hub.persistence.db_config import Base -import datetime -from sqlalchemy.orm import validates -import re -import enum -from sqlalchemy.orm import relationship + +from hub.persistence.configuration import Models class UserRoles(enum.Enum): @@ -20,28 +19,21 @@ class UserRoles(enum.Enum): Hub_Reader = 'Hub_Reader' -class User(Base): - """A model representation of a city +class User(Models): """ - __tablename__ = "user" + A model representation of a city + """ + __tablename__ = 'user' id = Column(Integer, Sequence('user_id_seq'), primary_key=True) name = Column(String, nullable=False) - email = Column(String, nullable=False, unique=True) password = Column(String, nullable=False) role = Column(Enum(UserRoles), nullable=False, default=UserRoles.Hub_Reader) - cities = relationship("City", back_populates="user") + application_id = Column(Integer, nullable=False) created = Column(DateTime, default=datetime.datetime.utcnow) updated = Column(DateTime, default=datetime.datetime.utcnow) - @validates("email") - def validate_email(self, key, address): - pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' - if not re.match(pattern, address): - raise ValueError("failed simple email validation") - return address - - def __init__(self, name, email, password, role): + def __init__(self, name, password, role, application_id): self.name = name - self.email = email self.password = password self.role = role + self.application_id = application_id diff --git a/hub/persistence/repositories/__init__.py b/hub/persistence/repositories/__init__.py index febdaca3..158b8448 100644 --- a/hub/persistence/repositories/__init__.py +++ b/hub/persistence/repositories/__init__.py @@ -1 +1,2 @@ -from .user_repo import UserRepo +from .user import User +from .application import Application diff --git a/hub/persistence/repositories/application.py b/hub/persistence/repositories/application.py new file mode 100644 index 00000000..91d720ec --- /dev/null +++ b/hub/persistence/repositories/application.py @@ -0,0 +1,98 @@ +""" +Application repository with database CRUD operations +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import datetime +from typing import Union, Dict + +from sqlalchemy import select +from sqlalchemy.exc import SQLAlchemyError + +from hub.hub_logger import logger +from hub.persistence import Repository +from hub.persistence.models import Application as Model + + +class Application(Repository): + _instance = None + + def __init__(self, db_name: str, dotenv_path: str, app_env: str): + super().__init__(db_name, dotenv_path, app_env) + + def __new__(cls, db_name, dotenv_path, app_env): + """ + Implemented for a singleton pattern + """ + if cls._instance is None: + cls._instance = super(Application, cls).__new__(cls) + return cls._instance + + def insert(self, name: str, description: str, application_uuid: str) -> Union[Model, Dict]: + """ + Inserts a new application + :param name: Application name + :param description: Application description + :param application_uuid: Unique identifier for the application + :return: application and dictionary + """ + application = self.get_by_uuid(application_uuid) + if application is None: + try: + application = Model(name=name, description=description, application_uuid=application_uuid) + self.session.add(application) + self.session.commit() + return application + except SQLAlchemyError as err: + logger.error(f'An error occurred while creating application: {err}') + else: + return {'message': f'An application with {application_uuid} application uuid, already exists'} + + def update(self, application_uuid: str, name: str, description: str) -> Union[Dict, None]: + """ + Updates an application + :param application_uuid: the application uuid of the application to be updated + :param name: the application name + :param description: the application description + :return: + """ + try: + self.session.query(Model).filter( + Model.application_uuid == application_uuid + ).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()}) + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while updating application: {err}') + return {'message': 'Error occurred while updating application'} + + def delete(self, application_uuid: str): + """ + Deletes an application with the application_uuid + :param application_uuid: The application uuid + :return: None + """ + try: + self.session.query(Model).filter(Model.application_uuid == application_uuid).delete() + self.session.flush() + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while deleting application: {err}') + + def get_by_uuid(self, application_uuid: str) -> Union[Model, None]: + """ + Fetch Application based on the application uuid + :param application_uuid: the application uuid + :return: Application with the provided application_uuid or None + """ + try: + result_set = self.session.execute(select(Model).where( + Model.application_uuid == application_uuid) + ).first() + if result_set is None: + return None + return result_set[0] + except SQLAlchemyError as err: + logger.error(f'Error while fetching application by application_uuid: {err}') + diff --git a/hub/persistence/repositories/city.py b/hub/persistence/repositories/city.py new file mode 100644 index 00000000..a1459c96 --- /dev/null +++ b/hub/persistence/repositories/city.py @@ -0,0 +1,156 @@ +""" +City repository with database CRUD operations +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Peter Yefi peteryefi@gmail.com +""" + +import datetime +import pickle +from typing import Union, Dict + +from sqlalchemy import select +from sqlalchemy.exc import SQLAlchemyError + +from hub.city_model_structure.city import City as CityHub +from hub.hub_logger import logger +from hub.persistence import Repository +from hub.persistence.models import City as Model +from hub.persistence.models import CityObject +from hub.version import __version__ + + +class City(Repository): + _instance = None + + def __init__(self, db_name: str, dotenv_path: str, app_env: str): + super().__init__(db_name, dotenv_path, app_env) + + def __new__(cls, db_name, dotenv_path, app_env): + """ + Implemented for a singleton pattern + """ + if cls._instance is None: + cls._instance = super(City, cls).__new__(cls) + return cls._instance + + def insert(self, city: CityHub, pickle_path, application_id, user_id: int) -> Union[Model, Dict]: + """ + Inserts a city + :param city: The complete city instance + :param pickle_path: Path to the pickle + :param application_id: Application id owning the instance + :param user_id: User id owning the instance + :return: City and Dictionary + """ + city.save_compressed(pickle_path) + try: + db_city = Model( + pickle_path, + city.name, + city.level_of_detail.geometry, + 'None' if city.climate_file is None else str(city.climate_file), + application_id, + user_id, + __version__) + + self.session.add(db_city) + self.session.flush() + self.session.commit() + for building in city.buildings: + object_usage = '' + for internal_zone in building.internal_zones: + for usage in internal_zone.usages: + object_usage = f'{object_usage}{usage.name}_{usage.percentage} ' + object_usage = object_usage.rstrip() + db_city_object = CityObject(db_city.id, + building.name, + building.alias, + building.type, + building.year_of_construction, + building.function, + object_usage, + building.volume, + building.floor_area) + self.session.add(db_city_object) + self.session.flush() + self.session.commit() + return db_city + except SQLAlchemyError as err: + print(f'An error occurred while creating city: {err}') + logger.error(f'An error occurred while creating city: {err}') + + def update(self, city_id: int, city: CityHub): + """ + Updates a city name (other updates makes no sense) + :param city_id: the id of the city to be updated + :param city: the city object + :return: + """ + try: + now = datetime.datetime.utcnow() + self.session.query(Model).filter(Model.id == city_id).update({'name': city.name,'updated': now}) + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while updating city: {err}') + + def delete(self, city_id: int): + """ + Deletes a City with the id + :param city_id: the city id + :return: a city + """ + try: + self.session.query(Model).filter(Model.id == city_id).delete() + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while fetching city: {err}') + + def get_by_id(self, city_id: int) -> Model: + """ + Fetch a City based on the id + :param city_id: the city id + :return: a city + """ + try: + return self.session.execute(select(Model).where(Model.id == city_id)).first()[0] + except SQLAlchemyError as err: + logger.error(f'Error while fetching city: {err}') + + def _get_by_hub_version_and_name(self, hub_release: str, city_name: str) -> Model: + """ + Fetch a City based on the name and hub project + :param hub_release: the hub release + :param city_name: the name of the city + :return: a city + """ + try: + return self.session.execute(select(Model) + .where(Model.hub_release == hub_release, Model.name == city_name) + ).first() + except SQLAlchemyError as err: + logger.error(f'Error while fetching city: {err}') + + def get_by_name(self, city_name: str) -> [Model]: + """ + Fetch city based on the name + :param city_name: the name of the building + :return: [ModelCity] with the provided name + """ + try: + result_set = self.session.execute(select(Model).where(Model.name == city_name)) + return [building[0] for building in result_set] + except SQLAlchemyError as err: + logger.error(f'Error while fetching city by name: {err}') + + def get_by_user(self, user_id: int) -> [Model]: + """ + Fetch city based on the user who created it + :param user_id: the id of the user + :return: [ModelCity] with the provided name + """ + try: + result_set = self.session.execute(select(Model).where(Model.user_id == user_id)) + return [building[0] for building in result_set] + except SQLAlchemyError as err: + logger.error(f'Error while fetching city by name: {err}') diff --git a/hub/persistence/repositories/city_object.py b/hub/persistence/repositories/city_object.py new file mode 100644 index 00000000..ded9a1ad --- /dev/null +++ b/hub/persistence/repositories/city_object.py @@ -0,0 +1,124 @@ +""" +City Object repository with database CRUD operations +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import datetime +from typing import Union, Dict + +from sqlalchemy import select +from sqlalchemy.exc import SQLAlchemyError + +from hub.hub_logger import logger +from hub.persistence import Repository +from hub.persistence.models import CityObject as Model +from hub.city_model_structure.building import Building + + +class CityObject(Repository): + _instance = None + + def __init__(self, db_name: str, dotenv_path: str, app_env: str): + super().__init__(db_name, dotenv_path, app_env) + + def __new__(cls, db_name, dotenv_path, app_env): + """ + Implemented for a singleton pattern + """ + if cls._instance is None: + cls._instance = super(CityObject, cls).__new__(cls) + return cls._instance + + def insert(self, city_id:int, building: Building) -> Union[Model, Dict]: + """ + Inserts a new city object + :param city_id: city id for the city owning this city object + :param building: the city object (only building for now) to be inserted + return CityObject model and dictionary + """ + city_object = self.get_by_name_and_city(building.name, city_id) + if city_object is None: + try: + object_usage = '' + for internal_zone in building.internal_zones: + for usage in internal_zone.usages: + object_usage = f'{object_usage}{usage.name}_{usage.percentage} ' + object_usage = object_usage.rstrip() + city_object = Model(city_id=city_id, + name=building.name, + alias=building.alias, + object_type=building.type, + year_of_construction=building.year_of_construction, + function=building.function, + usage=object_usage, + volume=building.volume, + area=building.floor_area) + self.session.add(city_object) + self.session.flush() + self.session.commit() + return city_object + except SQLAlchemyError as err: + logger.error(f'An error occurred while creating city_object: {err}') + else: + return {'message': f'A city_object named {building.name} already exists in that city'} + + def update(self,city_id: int, building: Building) -> Union[Dict, None]: + """ + Updates an application + :param city_id: the city id of the city owning the city object + :param building: the city object + :return: + """ + try: + object_usage = '' + for internal_zone in building.internal_zones: + for usage in internal_zone.usages: + object_usage = f'{object_usage}{usage.name}_{usage.percentage} ' + object_usage = object_usage.rstrip() + self.session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update( + {'name': building.name, + 'alias': building.alias, + 'object_type': building.type, + 'year_of_construction': building.year_of_construction, + 'function': building.function, + 'usage': object_usage, + 'volume': building.volume, + 'area': building.floor_area, + 'updated': datetime.datetime.utcnow()}) + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while updating city object: {err}') + return {'message': 'Error occurred while updating application'} + + def delete(self, city_id: int, name: str): + """ + Deletes an application with the application_uuid + :param city_id: The id for the city owning the city object + :param name: The city object name + :return: None + """ + try: + self.session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete() + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while deleting application: {err}') + + def get_by_name_and_city(self, name, city_id) -> [Model]: + """ + Fetch a city object based on name and city id + :param name: city object name + :param city_id: a city identifier + :return: [CityObject] with the provided name belonging to the city with id city_id + """ + try: + _city_object = self.session.execute(select(Model).where( + Model.name == name, Model.city_id == city_id + )).first() + if _city_object is None: + return None + return _city_object[0] + except SQLAlchemyError as err: + print(err) + logger.error(f'Error while fetching application by application_uuid: {err}') diff --git a/hub/persistence/repositories/city_repo.py b/hub/persistence/repositories/city_repo.py deleted file mode 100644 index cda51259..00000000 --- a/hub/persistence/repositories/city_repo.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -City repository with database CRUD operations -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Peter Yefi peteryefi@gmail.com -""" - -from hub.city_model_structure.city import City -from hub.persistence import BaseRepo -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy import select -from hub.persistence.models import City as DBCity -import pickle -import requests -from urllib3.exceptions import HTTPError -from typing import Union, Dict -from hub.hub_logger import logger -import datetime - - -class CityRepo(BaseRepo): - _instance = None - - def __init__(self, db_name: str, dotenv_path: str, app_env: str): - super().__init__(db_name, dotenv_path, app_env) - - def __new__(cls, db_name, dotenv_path, app_env): - """ - Implemented for a singleton pattern - """ - if cls._instance is None: - cls._instance = super(CityRepo, cls).__new__(cls) - return cls._instance - - def insert(self, city: City, user_id: int) -> Union[City, Dict]: - db_city = DBCity(pickle.dumps(city), city.name, city.srs_name, city.country_code, city.lower_corner, - city.upper_corner, user_id) - db_city.climate_reference_city = city.climate_reference_city - db_city.longitude = city.longitude - db_city.latitude = city.latitude - db_city.time_zone = city.time_zone - - try: - # Retrieve hub project latest release - response = requests.get("https://rs-loy-gitlab.concordia.ca/api/v4/projects/2/repository/branches/master", - headers={"PRIVATE-TOKEN": self.config.hub_token}) - recent_commit = response.json()["commit"]["id"] - logger.info(f'Current commit of hub is {recent_commit}') - exiting_city = self._get_by_hub_version(recent_commit, city.name) - - # Do not persist the same city for the same version of Hub - if exiting_city is None: - db_city.hub_release = recent_commit - cities = self.get_by_name(city.name) - # update version for the same city but different hub versions - - if len(cities) == 0: - db_city.city_version = 0 - else: - db_city.city_version = cities[-1].city_version + 1 - - # Persist city - self.session.add(db_city) - self.session.flush() - self.session.commit() - return db_city - else: - return {'message': f'Same version of {city.name} exist'} - except SQLAlchemyError as err: - logger.error(f'Error while adding city: {err}') - except HTTPError as err: - logger.error(f'Error retrieving Hub latest release: {err}') - - def get_by_id(self, city_id: int) -> DBCity: - """ - Fetch a City based on the id - :param city_id: the city id - :return: a city - """ - try: - return self.session.execute(select(DBCity).where(DBCity.id == city_id)).first()[0] - except SQLAlchemyError as err: - logger.error(f'Error while fetching city: {err}') - - def _get_by_hub_version(self, hub_commit: str, city_name: str) -> City: - """ - Fetch a City based on the name and hub project recent commit - :param hub_commit: the latest hub commit - :param city_name: the name of the city - :return: a city - """ - try: - return self.session.execute(select(DBCity) - .where(DBCity.hub_release == hub_commit, DBCity.name == city_name)).first() - except SQLAlchemyError as err: - logger.error(f'Error while fetching city: {err}') - - def update(self, city_id: int, city: City): - """ - Updates a city - :param city_id: the id of the city to be updated - :param city: the city object - :return: - """ - try: - self.session.query(DBCity).filter(DBCity.id == city_id) \ - .update({ - 'name': city.name, 'srs_name': city.srs_name, 'country_code': city.country_code, 'longitude': city.longitude, - 'latitude': city.latitude, 'time_zone': city.time_zone, 'lower_corner': city.lower_corner.tolist(), - 'upper_corner': city.upper_corner.tolist(), 'climate_reference_city': city.climate_reference_city, - 'updated': datetime.datetime.utcnow() - }) - - self.session.commit() - except SQLAlchemyError as err: - logger.error(f'Error while updating city: {err}') - - def get_by_name(self, city_name: str) -> [DBCity]: - """ - Fetch city based on the name - :param city_name: the name of the building - :return: [ModelCity] with the provided name - """ - try: - result_set = self.session.execute(select(DBCity).where(DBCity.name == city_name)) - return [building[0] for building in result_set] - except SQLAlchemyError as err: - logger.error(f'Error while fetching city by name: {err}') - - def get_by_user(self, user_id: int) -> [DBCity]: - """ - Fetch city based on the user who created it - :param user_id: the id of the user - :return: [ModelCity] with the provided name - """ - try: - result_set = self.session.execute(select(DBCity).where(DBCity.user_id == user_id)) - return [building[0] for building in result_set] - except SQLAlchemyError as err: - logger.error(f'Error while fetching city by name: {err}') - - def delete_city(self, city_id: int): - """ - Deletes a City with the id - :param city_id: the city id - :return: a city - """ - try: - self.session.query(DBCity).filter(DBCity.id == city_id).delete() - self.session.commit() - except SQLAlchemyError as err: - logger.error(f'Error while fetching city: {err}') diff --git a/hub/persistence/repositories/heat_pump_simulation_repo.py b/hub/persistence/repositories/heat_pump_simulation_repo.py deleted file mode 100644 index 4f0ef60e..00000000 --- a/hub/persistence/repositories/heat_pump_simulation_repo.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -Heat pump simulation repository with database CRUD operations -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Peter Yefi peteryefi@gmail.com -""" - -from hub.persistence import BaseRepo, CityRepo -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy import select -from hub.persistence.models import HeatPumpSimulation -from typing import Union, Dict -from hub.hub_logger import logger - - -class HeatPumpSimulationRepo(BaseRepo): - _instance = None - - def __init__(self, db_name, dotenv_path, app_env): - super().__init__(db_name, dotenv_path, app_env) - self._city_repo = CityRepo(db_name, dotenv_path, app_env) - - def __new__(cls, db_name, dotenv_path, app_env): - """ - Implemented for a singleton pattern - """ - if cls._instance is None: - cls._instance = super(HeatPumpSimulationRepo, cls).__new__(cls) - return cls._instance - - def insert(self, hp_sim_data: Dict, city_id: int) -> Union[HeatPumpSimulation, Dict]: - """ - Inserts the results of heat pump simulation - :param hp_sim_data: dictionary with heatpump the simulation inputs and output - :param city_id: the city that was used in running the simulation - :return: HeatPumpSimulation - """ - - city = self._city_repo.get_by_id(city_id) - if city is None: - return {'message': 'city not found in database'} - - try: - hp_simulation = HeatPumpSimulation(city_id, hp_sim_data["HourlyElectricityDemand"], - hp_sim_data["DailyElectricityDemand"], hp_sim_data["MonthlyElectricityDemand"], - hp_sim_data["DailyFossilFuelConsumption"], - hp_sim_data["MonthlyFossilFuelConsumption"]) - hp_simulation.city_id = city_id - hp_simulation.end_year = hp_sim_data["EndYear"] - hp_simulation.start_year = hp_sim_data["StartYear"] - hp_simulation.max_demand_storage_hour = hp_sim_data["HoursOfStorageAtMaxDemand"] - hp_simulation.max_hp_energy_input = hp_sim_data["MaximumHPEnergyInput"] - hp_simulation.building_supply_temp = hp_sim_data["BuildingSuppTemp"] - hp_simulation.temp_difference = hp_sim_data["TemperatureDifference"] - hp_simulation.fuel_lhv = hp_sim_data["FuelLHV"] - hp_simulation.fuel_price = hp_sim_data["FuelPrice"] - hp_simulation.fuel_efficiency = hp_sim_data["FuelEF"] - hp_simulation.fuel_density = hp_sim_data["FuelDensity"] - hp_simulation.hp_supply_temp = hp_sim_data["HPSupTemp"] - hp_simulation.simulation_type = hp_sim_data["SimulationType"] - hp_simulation.heat_pump_model = hp_sim_data["HeatPumpModel"] - hp_simulation.heat_pump_type = hp_sim_data["HeatPumpType"] - - # Persist heat pump simulation data - self.session.add(hp_simulation) - self.session.flush() - self.session.commit() - return hp_simulation - except SQLAlchemyError as err: - logger.error(f'Error while saving heat pump simulation data: {err}') - except KeyError as err: - logger.error(f'A required field is missing in your heat pump simulation dictionary: {err}') - - def get_by_id(self, hp_simulation_id: int) -> HeatPumpSimulation: - """ - Fetches heat pump simulation data - :param hp_simulation_id: the city id - :return: a HeatPumpSimulation - """ - try: - return self.session.execute(select(HeatPumpSimulation).where(HeatPumpSimulation.id == hp_simulation_id)).first()[ - 0] - except SQLAlchemyError as err: - logger.error(f'Error while fetching city: {err}') - - def get_by_city(self, city_id: int) -> [HeatPumpSimulation]: - """ - Fetch heat pump simulation results by city - :param city_id: the name of the building - :return: [HeatPumpSimulation] with the provided name - """ - try: - result_set = self.session.execute(select(HeatPumpSimulation).where(HeatPumpSimulation.city_id == city_id)) - return [sim_data[0] for sim_data in result_set] - except SQLAlchemyError as err: - logger.error(f'Error while fetching city by name: {err}') - - def delete_hp_simulation(self, hp_simulation_id: int): - """ - Deletes a heat pump simulation results - :param hp_simulation_id: the heat pump simulation results id - :return: - """ - try: - self.session.query(HeatPumpSimulation).filter(HeatPumpSimulation.id == hp_simulation_id).delete() - self.session.commit() - except SQLAlchemyError as err: - logger.error(f'Error while fetching city: {err}') diff --git a/hub/persistence/repositories/simulation_results.py b/hub/persistence/repositories/simulation_results.py new file mode 100644 index 00000000..ce612620 --- /dev/null +++ b/hub/persistence/repositories/simulation_results.py @@ -0,0 +1,137 @@ +""" +Simulation results repository with database CRUD operations +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca +""" + +import datetime +from typing import Union, Dict + +from sqlalchemy import select +from sqlalchemy.exc import SQLAlchemyError + +from hub.hub_logger import logger +from hub.persistence import Repository +from hub.persistence.models import SimulationResults as Model +from hub.persistence.models import City +from hub.persistence.models import CityObject + +from hub.city_model_structure.building import Building + + +class SimulationResults(Repository): + _instance = None + + def __init__(self, db_name: str, dotenv_path: str, app_env: str): + super().__init__(db_name, dotenv_path, app_env) + + def __new__(cls, db_name, dotenv_path, app_env): + """ + Implemented for a singleton pattern + """ + if cls._instance is None: + cls._instance = super(SimulationResults, cls).__new__(cls) + return cls._instance + + def insert(self, name: str, values: str, city_id=None, city_object_id=None) -> Union[Model, Dict]: + """ + Inserts simulations results linked either with a city as a whole or with a city object + :param name: results name + :param values: the simulation results in json format + :param city_id: optional city id + :param city_object_id: optional city object id + :return SimulationResults or Dictionary + """ + if city_id is not None: + city = self.get_city(city_id) + if city is None: + return {'message': f'City does not exists'} + else: + city_object = self.get_city_object(city_object_id) + if city_object is None: + return {'message': f'City object does not exists'} + try: + simulation_result = Model(name=name, + values=values, + city_id=city_id, + city_object_id=city_object_id) + self.session.add(simulation_result) + self.session.flush() + self.session.commit() + return simulation_result + except SQLAlchemyError as err: + logger.error(f'An error occurred while creating city_object: {err}') + + def update(self, name: str, values: str, city_id=None, city_object_id=None) -> Union[Dict, None]: + """ + Updates simulation results for a city or a city object + :param name: The simulation results tool and workflow name + :param values: the simulation results in json format + :param city_id: optional city id + :param city_object_id: optional city object id + :return: None or dictionary + """ + try: + if city_id is not None: + self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update( + { + 'values': values, + 'updated': datetime.datetime.utcnow() + }) + self.session.commit() + elif city_object_id is not None: + self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update( + { + 'values': values, + 'updated': datetime.datetime.utcnow() + }) + self.session.commit() + else: + return {'message': 'Missing either city_id or city_object_id'} + except SQLAlchemyError as err: + logger.error(f'Error while updating city object: {err}') + return {'message': 'Error occurred while updating application'} + + def delete(self, name: str, city_id=None, city_object_id=None): + """ + Deletes an application with the application_uuid + :param name: The simulation results tool and workflow name + :param city_id: The id for the city owning the simulation results + :param city_object_id: the id for the city_object ownning these simulation results + + :return: None + """ + try: + if city_id is not None: + self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete() + self.session.commit() + elif city_object_id is not None: + self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete() + self.session.commit() + else: + return {'message': 'Missing either city_id or city_object_id'} + except SQLAlchemyError as err: + logger.error(f'Error while deleting application: {err}') + + def get_city(self, city_id) -> [City]: + """ + Fetch a city object based city id + :param city_id: a city identifier + :return: [City] with the provided city_id + """ + try: + return self.session.execute(select(City).where(City.id == city_id)).first() + except SQLAlchemyError as err: + logger.error(f'Error while fetching city by city_id: {err}') + + def get_city_object(self, city_object_id) -> [CityObject]: + """ + Fetch a city object based city id + :param city_object_id: a city object identifier + :return: [CityObject] with the provided city_object_id + """ + try: + return self.session.execute(select(CityObject).where(CityObject.id == city_object_id)).first() + except SQLAlchemyError as err: + logger.error(f'Error while fetching city by city_id: {err}') diff --git a/hub/persistence/repositories/user.py b/hub/persistence/repositories/user.py new file mode 100644 index 00000000..dfa797d6 --- /dev/null +++ b/hub/persistence/repositories/user.py @@ -0,0 +1,120 @@ +""" +User repository with database CRUD operations +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Peter Yefi peteryefi@gmail.com +""" + +from hub.persistence import Repository +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy import select +from hub.persistence.models import User as Model +from hub.persistence.models import UserRoles +from hub.helpers.auth import Auth +from typing import Union, Dict +from hub.hub_logger import logger +import datetime + + +class User(Repository): + _instance = None + + def __init__(self, db_name: str, dotenv_path: str, app_env: str): + super().__init__(db_name, dotenv_path, app_env) + + def __new__(cls, db_name, dotenv_path, app_env): + """ + Implemented for a singleton pattern + """ + if cls._instance is None: + cls._instance = super(User, cls).__new__(cls) + return cls._instance + + def insert(self, name: str, password: str, role: UserRoles, application_id: int) -> Union[Model, Dict]: + """ + Inserts a new user + :param name: user name + :param password: user password + :param role: user rol [Admin or Hub_Reader] + :param application_id: user application id + :return: [User, Dictionary] + """ + user = self.get_by_name_and_application(name, application_id) + if user is None: + try: + user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id) + self.session.add(user) + self.session.flush() + self.session.commit() + return user + except SQLAlchemyError as err: + logger.error(f'An error occurred while creating user: {err}') + else: + return {'message': f'user {name} already exists for that application'} + + def update(self, user_id: int, name: str, password: str, role: UserRoles) -> Union[Dict, None]: + """ + Updates a user + :param user_id: the id of the user to be updated + :param name: the name of the user + :param password: the password of the user + :param role: the role of the user + :return: + """ + try: + self.session.query(Model).filter(Model.id == user_id).update({ + 'name': name, + 'password': Auth.hash_password(password), + 'role': role, + 'updated': datetime.datetime.utcnow() + }) + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while updating user: {err}') + return {'err_msg': 'Error occurred while updated user'} + + def delete(self, user_id: int): + """ + Deletes a user with the id + :param user_id: the user id + :return: None + """ + try: + self.session.query(Model).filter(Model.id == user_id).delete() + self.session.commit() + except SQLAlchemyError as err: + logger.error(f'Error while fetching user: {err}') + + def get_by_name_and_application(self, name: str, application_id: int) -> [Model]: + """ + Fetch user based on the email address + :param name: User name + :param application_id: User application name + :return: [User] matching the search criteria + """ + try: + return self.session.execute( + select(Model).where(Model.name == name, Model.application_id == application_id) + ).first() + except SQLAlchemyError as err: + logger.error(f'Error while fetching user by name and application: {err}') + + def get_by_name_application_and_password(self, name: str, password: str, application_id: int) -> [Model]: + """ + Fetch user based on the email and password + :param name: User name + :param password: User password + :param application_id: User password + + :return: [User] with the provided email and password + """ + try: + user = self.session.execute( + select(Model).where(Model.name == name, Model.application_id == application_id) + ).first() + if user: + if Auth.check_password(password, user[0].password): + return user[0] + return {'message': 'invalid login information'} + except SQLAlchemyError as err: + logger.error(f'Error while fetching user by email: {err}') diff --git a/hub/persistence/repositories/user_repo.py b/hub/persistence/repositories/user_repo.py deleted file mode 100644 index cfff2f22..00000000 --- a/hub/persistence/repositories/user_repo.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -City repository with database CRUD operations -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Peter Yefi peteryefi@gmail.com -""" - -from hub.persistence import BaseRepo -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy import select -from hub.persistence.models import User -from hub.persistence.models import UserRoles -from hub.helpers.auth import Auth -from typing import Union, Dict -from hub.hub_logger import logger -import datetime - - -class UserRepo(BaseRepo): - _instance = None - - def __init__(self, db_name: str, dotenv_path: str, app_env: str): - super().__init__(db_name, dotenv_path, app_env) - - def __new__(cls, db_name, dotenv_path, app_env): - """ - Implemented for a singleton pattern - """ - if cls._instance is None: - cls._instance = super(UserRepo, cls).__new__(cls) - return cls._instance - - def insert(self, name: str, email: str, password: str, role: UserRoles) -> Union[User, Dict]: - user = self.get_by_email(email) - if user is None: - try: - if Auth.validate_password(password): - user = User(name=name, email=email, password=Auth.hash_password(password), role=role) - self.session.add(user) - self.session.flush() - self.session.commit() - return user - except SQLAlchemyError as err: - logger.error(f'An error occured while creating user: {err}') - else: - return {'message': f'user with {email} email already exists'} - - def update(self, user_id: int, name: str, email: str, password: str, role: UserRoles) -> Union[Dict, None]: - """ - Updates a user - :param user_id: the id of the user to be updated - :param name: the name of the user - :param email: the email of the user - :param password: the password of the user - :param role: the role of the user - :return: - """ - try: - if Auth.validate_password(password): - self.session.query(User).filter(User.id == user_id) \ - .update({'name': name, 'email': email, 'password': Auth.hash_password(password), 'role': role, - 'updated': datetime.datetime.utcnow()}) - self.session.commit() - except SQLAlchemyError as err: - logger.error(f'Error while updating user: {err}') - return {'err_msg': 'Error occured while updated user'} - - def get_by_email(self, email: str) -> [User]: - """ - Fetch user based on the email address - :param email: the email of the user - :return: [User] with the provided email - """ - try: - return self.session.execute(select(User).where(User.email == email)).first() - except SQLAlchemyError as err: - logger.error(f'Error while fetching user by email: {err}') - - def delete_user(self, user_id: int): - """ - Deletes a user with the id - :param user_id: the user id - :return: None - """ - try: - self.session.query(User).filter(User.id == user_id).delete() - self.session.commit() - except SQLAlchemyError as err: - logger.error(f'Error while fetching user: {err}') - - def get_user_by_email_and_password(self, email: str, password: str) -> [User]: - """ - Fetch user based on the email and password - :param email: the email of the user - :param password: the password of the user - :return: [User] with the provided email and password - """ - try: - user = self.session.execute(select(User).where(User.email == email)).first() - if user: - if Auth.check_password(password, user[0].password): - return user - else: - return {'message': 'Wrong email/password combination'} - return {'message': 'user not found'} - except SQLAlchemyError as err: - logger.error(f'Error while fetching user by email: {err}') diff --git a/hub/persistence/base_repo.py b/hub/persistence/repository.py similarity index 68% rename from hub/persistence/base_repo.py rename to hub/persistence/repository.py index 130f7c5e..d9cea244 100644 --- a/hub/persistence/base_repo.py +++ b/hub/persistence/repository.py @@ -5,23 +5,17 @@ Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ -from hub.persistence.db_config import BaseConfiguration +from hub.persistence.configuration import Configuration from sqlalchemy import create_engine from sqlalchemy.orm import Session -class BaseRepo: +class Repository: def __init__(self, db_name, dotenv_path: str, app_env='TEST'): try: - self.config = BaseConfiguration(db_name, dotenv_path, app_env) - self.engine = create_engine(self.config.conn_string()) + self.configuration = Configuration(db_name, dotenv_path, app_env) + self.engine = create_engine(self.configuration.conn_string()) self.session = Session(self.engine) except ValueError as err: print(f'Missing value for credentials: {err}') - - - - - - diff --git a/hub/requirements.txt b/hub/requirements.txt index f4e7d9e3..aefb96c6 100644 --- a/hub/requirements.txt +++ b/hub/requirements.txt @@ -1,6 +1,6 @@ xmltodict numpy -trimesh +trimesh[all]==3.12.0 pyproj pandas requests @@ -22,3 +22,4 @@ bcrypt==4.0.1 shapely geopandas triangle +psycopg2-binary \ No newline at end of file diff --git a/hub/unittests/test_city_layers.py b/hub/unittests/test_city_layers.py index dee4e26b..a763e895 100644 --- a/hub/unittests/test_city_layers.py +++ b/hub/unittests/test_city_layers.py @@ -69,8 +69,6 @@ class CityLayerTest(TestCase): adjacent_buildings=adjacent_buildings).export_debug() filepath = os.path.join(output_path, city.name + ".idf") newfilepath = filepath[:-4] + "_" + uuid.uuid4().hex[:10] + ".idf" - print(filepath) - print(newfilepath) os.rename(filepath, newfilepath) print(f"It took {round((time.time() - t0), 0)} seconds") return newfilepath diff --git a/hub/unittests/test_construction_factory.py b/hub/unittests/test_construction_factory.py index 0678709a..7e37777a 100644 --- a/hub/unittests/test_construction_factory.py +++ b/hub/unittests/test_construction_factory.py @@ -9,7 +9,7 @@ from unittest import TestCase from hub.imports.geometry_factory import GeometryFactory from hub.imports.construction_factory import ConstructionFactory -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper +from hub.helpers.dictionaries import Dictionaries class TestConstructionFactory(TestCase): @@ -34,9 +34,9 @@ class TestConstructionFactory(TestCase): @staticmethod def _internal_function(function_format, original_function): if function_format == 'hft': - new_function = GeometryHelper.libs_function_from_hft(original_function) + new_function = Dictionaries().hft_function_to_hub_function[original_function] elif function_format == 'pluto': - new_function = GeometryHelper.libs_function_from_pluto(original_function) + new_function = Dictionaries().pluto_function_to_hub_function[original_function] else: raise Exception('Function key not recognized. Implemented only "hft" and "pluto"') return new_function @@ -51,7 +51,7 @@ class TestConstructionFactory(TestCase): city = self._get_citygml(file) for building in city.buildings: building.function = self._internal_function(function_format, building.function) - self.assertEqual(building.function, 'residential', 'format hft') + self.assertEqual('residential', building.function, 'format hft') # case 2: Pluto file = 'pluto_building.gml' @@ -59,7 +59,7 @@ class TestConstructionFactory(TestCase): city = self._get_citygml(file) for building in city.buildings: building.function = self._internal_function(function_format, building.function) - self.assertEqual(building.function, 'secondary school', 'format pluto') + self.assertEqual('education', building.function, 'format pluto') # case 3: Alkis file = 'one_building_in_kelowna_alkis.gml' @@ -104,7 +104,7 @@ class TestConstructionFactory(TestCase): self.assertIsNone(building.households, 'building households is not none') self.assertFalse(building.is_conditioned, 'building is conditioned') self.assertIsNotNone(building.shell, 'building shell is none') - self.assertIsNone(building.human_readable_name, 'building human_readable_name is not none') + self.assertIsNone(building.alias, 'building alias is not none') def _check_thermal_zones(self, internal_zone): for thermal_zone in internal_zone.thermal_zones: @@ -122,7 +122,7 @@ class TestConstructionFactory(TestCase): self.assertIsNone(thermal_zone.ordinate_number, 'thermal_zone ordinate number is not none') self.assertIsNotNone(thermal_zone.view_factors_matrix, 'thermal_zone view factors matrix is none') self.assertIsNotNone(thermal_zone.total_floor_area, 'thermal zone total_floor_area is none') - self.assertIsNone(thermal_zone.usage, 'thermal_zone usage is not none') + self.assertIsNone(thermal_zone.usage_name, 'thermal_zone usage is not none') self.assertIsNone(thermal_zone.hours_day, 'thermal_zone hours a day is not none') self.assertIsNone(thermal_zone.days_year, 'thermal_zone days a year is not none') self.assertIsNone(thermal_zone.mechanical_air_change, 'thermal_zone mechanical air change is not none') @@ -180,7 +180,7 @@ class TestConstructionFactory(TestCase): city = self._get_citygml(file) for building in city.buildings: building.year_of_construction = 2005 - building.function = GeometryHelper.libs_function_from_pluto(building.function) + building.function = self._internal_function('pluto', building.function) ConstructionFactory('nrel', city).enrich() self._check_buildings(city) diff --git a/hub/unittests/test_costs_catalog.py b/hub/unittests/test_costs_catalog.py new file mode 100644 index 00000000..22786f30 --- /dev/null +++ b/hub/unittests/test_costs_catalog.py @@ -0,0 +1,28 @@ +""" +TestMontrealCustomCatalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Atiya atiya.atiya@mail.concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from unittest import TestCase +from hub.catalog_factories.costs_catalog_factory import CostCatalogFactory + + +class TestCostsCatalog(TestCase): + + def test_costs_catalog(self): + catalog = CostCatalogFactory('montreal_custom').catalog + catalog_categories = catalog.names() + self.assertIsNotNone(catalog, 'catalog is none') + content = catalog.entries() + self.assertTrue(len(content.archetypes) == 1) + + # retrieving all the entries should not raise any exceptions + for category in catalog_categories: + for value in catalog_categories[category]: + catalog.get_entry(value) + + with self.assertRaises(IndexError): + catalog.get_entry('unknown') diff --git a/hub/unittests/test_db_factory.py b/hub/unittests/test_db_factory.py index 5e54a50f..2a8c0719 100644 --- a/hub/unittests/test_db_factory.py +++ b/hub/unittests/test_db_factory.py @@ -29,7 +29,7 @@ class TestDBFactory(TestCase): :return: None """ # Create test database - repo = BaseRepo(db_name='test_db', app_env='TEST', dotenv_path='../.env') + repo = BaseRepo(db_name='test_db', app_env='TEST', dotenv_path='/usr/local/etc/hub/.env') eng = create_engine(f'postgresql://{repo.config.get_db_user()}@/{repo.config.get_db_user()}') try: diff --git a/hub/unittests/test_enrichement.py b/hub/unittests/test_enrichement.py index b4129477..be0630cb 100644 --- a/hub/unittests/test_enrichement.py +++ b/hub/unittests/test_enrichement.py @@ -7,7 +7,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca from pathlib import Path from unittest import TestCase from hub.imports.geometry_factory import GeometryFactory -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper +from hub.helpers.dictionaries import Dictionaries from hub.imports.usage_factory import UsageFactory from hub.imports.construction_factory import ConstructionFactory @@ -35,9 +35,9 @@ class TestGeometryFactory(TestCase): self._check_buildings(city) for building in city.buildings: for internal_zone in building.internal_zones: - self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in internal_zone.usage_zones: - self.assertIsNotNone(usage_zone.id, 'usage id is none') + self.assertIsNot(len(internal_zone.usages), 0, 'no building usages defined') + for usage in internal_zone.usages: + self.assertIsNotNone(usage.id, 'usage id is none') for thermal_zone in internal_zone.thermal_zones: self._check_thermal_zone(thermal_zone) @@ -45,7 +45,7 @@ class TestGeometryFactory(TestCase): for building in city.buildings: self.assertIsNotNone(building.internal_zones, 'no internal zones created') for internal_zone in building.internal_zones: - self.assertIsNotNone(internal_zone.usage_zones, 'usage zones are not defined') + self.assertIsNotNone(internal_zone.usages, 'usage zones are not defined') self.assertIsNotNone(internal_zone.thermal_zones, 'thermal zones are not defined') self.assertIsNone(building.basement_heated, 'building basement_heated is not none') self.assertIsNone(building.attic_heated, 'building attic_heated is not none') @@ -55,7 +55,7 @@ class TestGeometryFactory(TestCase): def _check_thermal_zone(self, thermal_zone): self.assertIsNotNone(thermal_zone.id, 'thermal_zone id is none') - self.assertIsNotNone(thermal_zone.usage, 'thermal_zone usage is not none') + self.assertIsNotNone(thermal_zone.usage_name, 'thermal_zone usage is not none') self.assertIsNotNone(thermal_zone.hours_day, 'thermal_zone hours a day is none') self.assertIsNotNone(thermal_zone.days_year, 'thermal_zone days a year is none') self.assertIsNotNone(thermal_zone.occupancy, 'thermal_zone occupancy is none') @@ -71,10 +71,10 @@ class TestGeometryFactory(TestCase): def _prepare_case_usage_first(city, input_key, construction_key, usage_key): if input_key == 'pluto': for building in city.buildings: - building.function = GeometryHelper.libs_function_from_pluto(building.function) + building.function = Dictionaries().pluto_function_to_hub_function[building.function] elif input_key == 'hft': for building in city.buildings: - building.function = GeometryHelper.libs_function_from_hft(building.function) + building.function = Dictionaries().hft_function_to_hub_function[building.function] UsageFactory(usage_key, city).enrich() ConstructionFactory(construction_key, city).enrich() @@ -82,16 +82,17 @@ class TestGeometryFactory(TestCase): def _prepare_case_construction_first(city, input_key, construction_key, usage_key): if input_key == 'pluto': for building in city.buildings: - building.function = GeometryHelper.libs_function_from_pluto(building.function) + building.function = Dictionaries().pluto_function_to_hub_function[building.function] elif input_key == 'hft': for building in city.buildings: - building.function = GeometryHelper.libs_function_from_hft(building.function) + building.function = Dictionaries().hft_function_to_hub_function[building.function] + print(construction_key, usage_key) ConstructionFactory(construction_key, city).enrich() UsageFactory(usage_key, city).enrich() def _test_hft(self, file): _construction_keys = ['nrel'] - _usage_keys = ['comnet', 'hft'] + _usage_keys = ['comnet', 'nrcan'] for construction_key in _construction_keys: for usage_key in _usage_keys: # construction factory called first @@ -121,7 +122,7 @@ class TestGeometryFactory(TestCase): def _test_pluto(self, file): _construction_keys = ['nrel'] - _usage_keys = ['comnet', 'hft'] + _usage_keys = ['comnet', 'nrcan'] for construction_key in _construction_keys: for usage_key in _usage_keys: # construction factory called first diff --git a/hub/unittests/test_exports.py b/hub/unittests/test_exports.py index f216dc08..283c9595 100644 --- a/hub/unittests/test_exports.py +++ b/hub/unittests/test_exports.py @@ -10,7 +10,7 @@ from pathlib import Path from unittest import TestCase import pandas as pd from hub.imports.geometry_factory import GeometryFactory -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper +from hub.helpers.dictionaries import Dictionaries from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory from hub.exports.exports_factory import ExportsFactory @@ -48,10 +48,10 @@ class TestExports(TestCase): file_path = (self._example_path / 'one_building_in_kelowna.gml').resolve() self._complete_city = self._get_citygml(file_path) for building in self._complete_city.buildings: - building.function = GeometryHelper().libs_function_from_hft(building.function) + building.function = Dictionaries().hft_function_to_hub_function[building.function] building.year_of_construction = 2006 ConstructionFactory('nrel', self._complete_city).enrich() - UsageFactory('ca', self._complete_city).enrich() + UsageFactory('nrcan', self._complete_city).enrich() cli = (self._example_path / 'weather' / 'inseldb_Summerland.cli').resolve() self._complete_city.climate_file = Path(cli) self._complete_city.climate_reference_city = 'Summerland' @@ -67,6 +67,12 @@ class TestExports(TestCase): self._complete_city = self._get_complete_city(from_pickle) ExportsFactory(export_type, self._complete_city, self._output_path).export() + def _export_building_energy(self, export_type, from_pickle=False): + self._complete_city = self._get_complete_city(from_pickle) + EnergyBuildingsExportsFactory(export_type, self._complete_city, self._output_path).export() + + + def test_obj_export(self): """ export to obj @@ -83,7 +89,8 @@ class TestExports(TestCase): """ export to energy ADE """ - self._export('energy_ade') + self._export_building_energy('energy_ade') + def test_sra_export(self): """ diff --git a/hub/unittests/test_geometry_factory.py b/hub/unittests/test_geometry_factory.py index 25a2bc44..67018841 100644 --- a/hub/unittests/test_geometry_factory.py +++ b/hub/unittests/test_geometry_factory.py @@ -59,7 +59,7 @@ class TestGeometryFactory(TestCase): self.assertIsNotNone(building.roofs, 'building roofs is none') self.assertIsNotNone(building.internal_zones, 'building internal zones is none') for internal_zone in building.internal_zones: - self.assertIsNone(internal_zone.usage_zones, 'usage zones are defined') + self.assertIsNone(internal_zone.usages, 'usage zones are defined') self.assertIsNone(internal_zone.thermal_zones, 'thermal zones are defined') self.assertIsNone(building.basement_heated, 'building basement_heated is not none') self.assertIsNone(building.attic_heated, 'building attic_heated is not none') diff --git a/hub/unittests/test_insel_exports.py b/hub/unittests/test_insel_exports.py index 473e65d5..2fbc54c1 100644 --- a/hub/unittests/test_insel_exports.py +++ b/hub/unittests/test_insel_exports.py @@ -133,13 +133,13 @@ class TestExports(TestCase): if thermal_boundary.type is not cte.GROUND: self.assertIsNotNone(thermal_boundary.parent_surface.short_wave_reflectance) - for usage_zone in internal_zone.usage_zones: - self.assertIsNotNone(usage_zone.percentage, f'usage zone {usage_zone.usage} percentage is none') - self.assertIsNotNone(usage_zone.internal_gains, f'usage zone {usage_zone.usage} internal_gains is none') - self.assertIsNotNone(usage_zone.thermal_control, f'usage zone {usage_zone.usage} thermal_control is none') - self.assertIsNotNone(usage_zone.hours_day, f'usage zone {usage_zone.usage} hours_day is none') - self.assertIsNotNone(usage_zone.days_year, f'usage zone {usage_zone.usage} days_year is none') - self.assertIsNotNone(usage_zone.mechanical_air_change, f'usage zone {usage_zone.usage} ' + for usage in internal_zone.usages: + self.assertIsNotNone(usage.percentage, f'usage zone {usage.name} percentage is none') + self.assertIsNotNone(usage.internal_gains, f'usage zone {usage.name} internal_gains is none') + self.assertIsNotNone(usage.thermal_control, f'usage zone {usage.name} thermal_control is none') + self.assertIsNotNone(usage.hours_day, f'usage zone {usage.name} hours_day is none') + self.assertIsNotNone(usage.days_year, f'usage zone {usage.name} days_year is none') + self.assertIsNotNone(usage.mechanical_air_change, f'usage zone {usage.name} ' f'mechanical_air_change is none') # export files try: diff --git a/hub/unittests/test_usage_catalog.py b/hub/unittests/test_usage_catalog.py index 257fd9ae..2d927372 100644 --- a/hub/unittests/test_usage_catalog.py +++ b/hub/unittests/test_usage_catalog.py @@ -20,4 +20,4 @@ class TestConstructionCatalog(TestCase): catalog = UsageCatalogFactory('nrcan').catalog self.assertIsNotNone(catalog, 'catalog is none') content = catalog.entries() - self.assertEqual(274, len(content.usages), 'Wrong number of usages') + self.assertEqual(34, len(content.usages), 'Wrong number of usages') diff --git a/hub/unittests/test_usage_factory.py b/hub/unittests/test_usage_factory.py index 767fefc1..e4d7b219 100644 --- a/hub/unittests/test_usage_factory.py +++ b/hub/unittests/test_usage_factory.py @@ -9,7 +9,7 @@ from unittest import TestCase from hub.imports.geometry_factory import GeometryFactory from hub.imports.usage_factory import UsageFactory -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper +from hub.helpers.dictionaries import Dictionaries class TestUsageFactory(TestCase): @@ -51,7 +51,7 @@ class TestUsageFactory(TestCase): self.assertIsNotNone(building.walls, 'building walls is none') self.assertIsNotNone(building.roofs, 'building roofs is none') for internal_zone in building.internal_zones: - self.assertTrue(len(internal_zone.usage_zones) > 0, 'usage zones are not defined') + self.assertTrue(len(internal_zone.usages) > 0, 'usage zones are not defined') self.assertIsNone(internal_zone.thermal_zones, 'thermal zones are defined') self.assertIsNone(building.basement_heated, 'building basement_heated is not none') self.assertIsNone(building.attic_heated, 'building attic_heated is not none') @@ -68,15 +68,15 @@ class TestUsageFactory(TestCase): self.assertIsNone(building.households, 'building households is not none') self.assertTrue(building.is_conditioned, 'building is not conditioned') - def _check_usage_zone(self, usage_zone): - self.assertIsNotNone(usage_zone.usage, 'usage is none') - self.assertIsNotNone(usage_zone.percentage, 'usage percentage is none') - self.assertIsNotNone(usage_zone.hours_day, 'hours per day is none') - self.assertIsNotNone(usage_zone.days_year, 'days per year is none') - self.assertIsNotNone(usage_zone.thermal_control, 'thermal control is none') - self.assertIsNotNone(usage_zone.thermal_control.mean_heating_set_point, 'control heating set point is none') - self.assertIsNotNone(usage_zone.thermal_control.heating_set_back, 'control heating set back is none') - self.assertIsNotNone(usage_zone.thermal_control.mean_cooling_set_point, 'control cooling set point is none') + def _check_usage(self, usage): + self.assertIsNotNone(usage.name, 'usage is none') + self.assertIsNotNone(usage.percentage, 'usage percentage is none') + self.assertIsNotNone(usage.hours_day, 'hours per day is none') + self.assertIsNotNone(usage.days_year, 'days per year is none') + self.assertIsNotNone(usage.thermal_control, 'thermal control is none') + self.assertIsNotNone(usage.thermal_control.mean_heating_set_point, 'control heating set point is none') + self.assertIsNotNone(usage.thermal_control.heating_set_back, 'control heating set back is none') + self.assertIsNotNone(usage.thermal_control.mean_cooling_set_point, 'control cooling set point is none') def test_import_comnet(self): """ @@ -85,22 +85,22 @@ class TestUsageFactory(TestCase): file = 'pluto_building.gml' city = self._get_citygml(file) for building in city.buildings: - building.function = GeometryHelper.libs_function_from_pluto(building.function) + building.function = Dictionaries().pluto_function_to_hub_function[building.function] UsageFactory('comnet', city).enrich() self._check_buildings(city) for building in city.buildings: for internal_zone in building.internal_zones: - self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in internal_zone.usage_zones: - self._check_usage_zone(usage_zone) - self.assertIsNotNone(usage_zone.mechanical_air_change, 'mechanical air change is none') - self.assertIsNotNone(usage_zone.thermal_control.heating_set_point_schedules, + self.assertIsNot(len(internal_zone.usages), 0, 'no building usage defined') + for usage in internal_zone.usages: + self._check_usage(usage) + self.assertIsNotNone(usage.mechanical_air_change, 'mechanical air change is none') + self.assertIsNotNone(usage.thermal_control.heating_set_point_schedules, 'control heating set point schedule is none') - self.assertIsNotNone(usage_zone.thermal_control.cooling_set_point_schedules, + self.assertIsNotNone(usage.thermal_control.cooling_set_point_schedules, 'control cooling set point schedule is none') - self.assertIsNotNone(usage_zone.occupancy, 'occupancy is none') - occupancy = usage_zone.occupancy + self.assertIsNotNone(usage.occupancy, 'occupancy is none') + occupancy = usage.occupancy self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none') self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none') self.assertIsNotNone(occupancy.sensible_convective_internal_gain, @@ -109,53 +109,19 @@ class TestUsageFactory(TestCase): 'occupancy sensible radiant internal gain is none') self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none') self.assertIsNone(occupancy.occupants, 'occupancy density is not none') - self.assertIsNotNone(usage_zone.lighting, 'lighting is none') - lighting = usage_zone.lighting + self.assertIsNotNone(usage.lighting, 'lighting is none') + lighting = usage.lighting self.assertIsNotNone(lighting.density, 'lighting density is none') self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none') self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none') self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none') self.assertIsNotNone(lighting.schedules, 'lighting schedule is none') - self.assertIsNotNone(usage_zone.appliances, 'appliances is none') - appliances = usage_zone.appliances + self.assertIsNotNone(usage.appliances, 'appliances is none') + appliances = usage.appliances self.assertIsNotNone(appliances.density, 'appliances density is none') self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none') self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none') self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none') self.assertIsNotNone(appliances.schedules, 'appliances schedule is none') - self.assertIsNotNone(usage_zone.thermal_control.hvac_availability_schedules, + self.assertIsNotNone(usage.thermal_control.hvac_availability_schedules, 'control hvac availability is none') - - def test_import_hft(self): - """ - Enrich the city with the usage information from hft and verify it - """ - file = 'pluto_building.gml' - city = self._get_citygml(file) - for building in city.buildings: - building.function = GeometryHelper.libs_function_from_pluto(building.function) - - UsageFactory('hft', city).enrich() - self._check_buildings(city) - for building in city.buildings: - for internal_zone in building.internal_zones: - self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in internal_zone.usage_zones: - self._check_usage_zone(usage_zone) - self.assertIsNone(usage_zone.mechanical_air_change, 'mechanical air change is not none') - self.assertIsNotNone(usage_zone.thermal_control.heating_set_point_schedules, - 'control heating set point schedule is none') - self.assertIsNotNone(usage_zone.thermal_control.cooling_set_point_schedules, - 'control cooling set point schedule is none') - self.assertIsNotNone(usage_zone.occupancy, 'occupancy is none') - occupancy = usage_zone.occupancy - self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none') - self.assertIsNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none') - self.assertIsNone(occupancy.sensible_convective_internal_gain, - 'occupancy sensible convective internal gain is not none') - self.assertIsNone(occupancy.sensible_radiative_internal_gain, - 'occupancy sensible radiant internal gain is not none') - self.assertIsNone(occupancy.occupancy_schedules, 'occupancy schedule is not none') - self.assertIsNone(occupancy.occupants, 'occupancy density is not none') - self.assertIsNone(usage_zone.lighting, 'lighting is not none') - self.assertIsNone(usage_zone.appliances, 'appliances is not none') diff --git a/hub/version.py b/hub/version.py new file mode 100644 index 00000000..0676f8b8 --- /dev/null +++ b/hub/version.py @@ -0,0 +1 @@ +__version__ = '0.1.7.6' diff --git a/pyproject.toml b/pyproject.toml index a61cfe8b..8c148403 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,49 +4,5 @@ requires = ["setuptools>=61.0.0", "wheel"] build-backend = "setuptools.build_meta" -[project] -name = "cerc_hub" -version = "0.1.7" -description = "CERC Hub consist in a set of classes (Central data model), importers and exporters to help researchers to create better and sustainable cities" -readme = "README.md" -authors = [{ name = "Guillermo Gutierrez", email = "Guillermo.GutierrezMorote@concordia.ca" }] -license = { file = "LICENSE.md" } -classifiers = [ - "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", - "Programming Language :: Python", - "Programming Language :: Python :: 3", -] -keywords = ["city", "hub", "cerc"] -dependencies = [ - "xmltodict", - "numpy", - "trimesh", - "pyproj", - "pandas", - "requests", - "esoreader", - "geomeppy", - "PyWavefront", - "xlrd", - "openpyxl", - "networkx", - "parseidf==1.0.0", - "ply", - "rhino3dm==7.7.0", - "scipy", - "PyYAML", - "pyecore==0.12.2", - "python-dotenv", - "SQLAlchemy", - "bcrypt==4.0.1", - "shapely", - "geopandas", - "triangle" -] -requires-python = ">=3.9" - -[project.optional-dependencies] -dev = ["pip-tools", "pytest"] - -[project.urls] -Homepage = "https://rs-loy-gitlab.concordia.ca/Guille/hub" +[options.packages.find_namespace] +where = "hub" \ No newline at end of file diff --git a/setup.py b/setup.py index f143896c..ba917de2 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,112 @@ -from setuptools import setup, find_packages -import os.path import glob +import pathlib +from distutils.util import convert_path + +import pkg_resources +from setuptools import setup + +with pathlib.Path('hub/requirements.txt').open() as r: + install_requires = [ + str(requirement) + for requirement + in pkg_resources.parse_requirements(r) + ] + +install_requires.append('setuptools') + +main_ns = {} +version = convert_path('hub/version.py') +with open(version) as f: + exec(f.read(), main_ns) + setup( - name='hub', - version="0.1", - packages=find_packages(exclude="unittests"), - data_files=[ - ('config', [os.path.join('hub/config', 'configuration.ini')]), - ('greenery', glob.glob('hub/catalog_factories/greenery/ecore_greenery/*.ecore')), - ('data', glob.glob('hub/data/construction/*.xml')), - ('data', glob.glob('hub/data/customized_imports/*.xml')), - ('data', glob.glob('hub/data/energy_systems/*.xml')), - ('data', glob.glob('hub/data/energy_systems/*.insel')), - ('data', glob.glob('hub/data/energy_systems/*.xlsx')), - ('data', glob.glob('hub/data/energy_systems/*.txt')), - ('data', glob.glob('hub/data/energy_systems/*.yaml')), - ('data', glob.glob('hub/data/greenery/*.xml')), - ('data', glob.glob('hub/data/life_cycle_assessment/*.xml')), - ('data', glob.glob('hub/data/schedules/*.xml')), - ('data', glob.glob('hub/data/schedules/*.xlsx')), - ('data', glob.glob('hub/data/schedules/idf_files/*.idf')), - ('data', glob.glob('hub/data/sensors/*.json')), - ('data', glob.glob('hub/data/usage/*.xml')), - ('data', glob.glob('hub/data/usage/*.xlsx')), - ('data', glob.glob('hub/data/weather/*.dat')), - ('data', glob.glob('hub/data/weather/epw/*.epw')), - ('data', glob.glob('hub/data/weather/*.dat')) - ], - setup_requires=['setuptools'] + name='cerc-hub', + version=main_ns['__version__'], + description="CERC Hub consist in a set of classes (Central data model), importers and exporters to help researchers " + "to create better and sustainable cities", + long_description="CERC Hub consist in a set of classes (Central data model), importers and exporters to help " + "researchers to create better and sustainable cities.\n\nDevelop at Concordia university in canada " + "as part of the research group from the next generation cities institute our aim among others it's " + "to provide a comprehensive set of tools to help researchers and urban developers to make decisions " + "to improve the livability and efficiency of our cities", + classifiers=[ + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + ], + include_package_data=True, + packages=['hub', + 'hub.catalog_factories', + 'hub.catalog_factories.construction', + 'hub.catalog_factories.data_models', + 'hub.catalog_factories.data_models.construction', + 'hub.catalog_factories.data_models.greenery', + 'hub.catalog_factories.data_models.usages', + 'hub.catalog_factories.greenery', + 'hub.catalog_factories.greenery.ecore_greenery', + 'hub.catalog_factories.usage', + 'hub.city_model_structure', + 'hub.city_model_structure.attributes', + 'hub.city_model_structure.building_demand', + 'hub.city_model_structure.energy_systems', + 'hub.city_model_structure.greenery', + 'hub.city_model_structure.iot', + 'hub.city_model_structure.transport', + 'hub.config', + 'hub.data', + 'hub.exports', + 'hub.exports.building_energy', + 'hub.exports.building_energy.idf_files', + 'hub.exports.building_energy.insel', + 'hub.exports.energy_systems', + 'hub.exports.formats', + 'hub.helpers', + 'hub.helpers.data', + 'hub.hub_logger', + 'hub.imports', + 'hub.imports.construction', + 'hub.imports.construction.helpers', + 'hub.imports.construction.data_classes', + 'hub.imports.energy_systems', + 'hub.imports.geometry', + 'hub.imports.geometry.citygml_classes', + 'hub.imports.geometry.helpers', + 'hub.imports.results', + 'hub.imports.usage', + 'hub.imports.weather', + 'hub.imports.weather.helpers', + 'hub.persistence', + 'hub.persistence.models', + 'hub.persistence.repositories', + 'hub.imports' + ], + setup_requires=install_requires, + data_files=[ + ('hub', glob.glob('hub/requirements.txt')), + ('hub/config', glob.glob('hub/config/*.ini')), + ('hub/catalog_factories/greenery/ecore_greenery', glob.glob('hub/catalog_factories/greenery/ecore_greenery/*.ecore')), + ('hub/data/construction.', glob.glob('hub/data/construction/*.xml')), + ('hub/data/customized_imports/', glob.glob('hub/data/customized_imports/*.xml')), + ('hub/data/energy_systems/', glob.glob('hub/data/energy_systems/*.xml')), + ('hub/data/energy_systems/', glob.glob('hub/data/energy_systems/*.insel')), + ('hub/data/energy_systems/', glob.glob('hub/data/energy_systems/*.xlsx')), + ('hub/data/energy_systems/*', glob.glob('hub/data/energy_systems/*.txt')), + ('hub/data/energy_systems/*', glob.glob('hub/data/energy_systems/*.yaml')), + ('hub/data/greenery/', glob.glob('hub/data/greenery/*.xml')), + ('hub/data/life_cycle_assessment/', glob.glob('hub/data/life_cycle_assessment/*.xml')), + ('hub/data/schedules/', glob.glob('hub/data/schedules/*.xml')), + ('hub/data/schedules/', glob.glob('hub/data/schedules/*.xlsx')), + ('hub/data/schedules/idf_files/', glob.glob('hub/data/schedules/idf_files/*.idf')), + ('hub/data/sensors/', glob.glob('hub/data/sensors/*.json')), + ('hub/data/usage/', glob.glob('hub/data/usage/*.xml')), + ('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')), + ('hub/data/weather/', glob.glob('hub/data/weather/*.dat')), + ('hub/exports/building_energy/idf_files', glob.glob('hub/exports/building_energy/idf_files/*.idf')), + ('hub/exports/building_energy/idf_files', glob.glob('hub/exports/building_energy/idf_files/*.idd')), + ('hub/helpers/data', glob.glob('hub/helpers/data/quebec_to_hub.json')) + ], + )