From 60b725dad67087c8f8d52d21c5f0128e0351c592 Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 15 Nov 2022 08:38:41 -0500 Subject: [PATCH] Partial nrcan usage implementation --- catalog_factories/usage/comnet_catalog.py | 1 - catalog_factories/usage/nrcan_catalog.py | 143 +++++++++++++++++++++ catalog_factories/usage/usage_helper.py | 62 ++++++++- catalog_factories/usage_catalog_factory.py | 9 ++ data/usage/nrcan.xml | 9 ++ unittests/test_usage_catalog.py | 4 + 6 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 catalog_factories/usage/nrcan_catalog.py create mode 100644 data/usage/nrcan.xml diff --git a/catalog_factories/usage/comnet_catalog.py b/catalog_factories/usage/comnet_catalog.py index e2fb294d..748d0b36 100644 --- a/catalog_factories/usage/comnet_catalog.py +++ b/catalog_factories/usage/comnet_catalog.py @@ -193,7 +193,6 @@ class ComnetCatalog(Catalog): process_data[usage_type] = usage_parameters[24:26].values.tolist() schedules_key[usage_type] = usage_parameters[27:28].item() - return {'lighting': lighting_data, 'plug loads': plug_loads_data, 'occupancy': occupancy_data, diff --git a/catalog_factories/usage/nrcan_catalog.py b/catalog_factories/usage/nrcan_catalog.py new file mode 100644 index 00000000..3ae172d1 --- /dev/null +++ b/catalog_factories/usage/nrcan_catalog.py @@ -0,0 +1,143 @@ +""" +NRCAN usage catalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +from typing import Dict +import pandas as pd +import json +import urllib.request +import xmltodict + +import helpers.constants as cte +from catalog_factories.catalog import Catalog +from catalog_factories.data_models.usages.appliances import Appliances +from catalog_factories.data_models.usages.content import Content +from catalog_factories.data_models.usages.lighting import Lighting +from catalog_factories.data_models.usages.ocupancy import Occupancy +from catalog_factories.data_models.usages.schedule import Schedule +from catalog_factories.data_models.usages.thermal_control import ThermalControl +from catalog_factories.data_models.usages.usage import Usage +from catalog_factories.usage.usage_helper import UsageHelper +from helpers.configuration_helper import ConfigurationHelper as ch + + +class NrcanCatalog(Catalog): + def __init__(self, path): + path = str(path / 'nrcan.xml') + self._content = None + self._schedules = [] + self._lightings = [] + self._occupancies = [] + self._internal_gains = [] + self._appliances = [] + self._thermal_controls = [] + usages = [] + with open(path) as xml: + self._metadata = xmltodict.parse(xml.read()) + self._base_url = self._metadata['nrcan']['@base_url'] + self._load_schedules() + self._load_archetypes() + + 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 + + @staticmethod + def _extract_schedule(raw): + nrcan_schedule_type = raw['category'] + print(raw) + if 'Heating' in raw['name']: + nrcan_schedule_type = f'{nrcan_schedule_type} Heating' + elif 'Cooling' in raw['name']: + nrcan_schedule_type = f'{nrcan_schedule_type} Cooling' + if nrcan_schedule_type not in UsageHelper().nrcan_schedule_type_to_hub_schedule_type: + return None + hub_type = UsageHelper().nrcan_schedule_type_to_hub_schedule_type[nrcan_schedule_type] + data_type = UsageHelper().nrcan_data_type_to_hub_data_type[raw['units']] + time_step = UsageHelper().nrcan_time_to_hub_time[raw['type']] + # nrcan only uses yearly range for the schedules + time_range = cte.YEAR + day_types = UsageHelper().nrcan_day_type_to_hub_days[raw['day_types']] + return Schedule(hub_type, raw['values'], data_type, time_step, time_range, day_types) + + def _load_schedules(self): + usage = self._metadata['nrcan']['standards']['usage'] + url = f'{self._base_url}{usage["schedules_location"]}' + 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.append({schedule_type['name'], schedule}) + + def _get_schedule(self, name): + if name in self._schedules: + print(f'dictionary found {name}: {self._schedule[name]}') + return self._schedule[name] + + def _load_archetypes(self): + usage = self._metadata['nrcan']['standards']['usage'] + url = f'{self._base_url}{usage["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'] + 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 = 0 # self._calculate_hours_day(usage_type) + days_year = 365 + + occupancy_schedule_name = space_type['occupancy_schedule'] + self._load_schedule(occupancy_schedule_name) + lighting_schedule_name = space_type['lighting_schedule'] + appliances_schedule_name = space_type['electric_equipment_schedule'] + # thermal control + heating_setpoint_schedule_name = space_type['heating_setpoint_schedule'] + cooling_setpoint_schedule_name = space_type['cooling_setpoint_schedule'] + print(usage_type, mechanical_air_change, ventilation_rate, hours_day, days_year, occupancy_schedule_name, lighting_schedule_name, appliances_schedule_name, heating_setpoint_schedule_name, cooling_setpoint_schedule_name) + + def _read_archetype_file(self) -> Dict: + """ + reads xml files containing metadata to access to the json file? + :return : Dict + """ + print(self._metadata) + return + + def names(self, category=None): + """ + Get the catalog elements names + :parm: for usage catalog category filter does nothing as there is only one category (usages) + """ + _names = {'usages': []} + for usage in self._content.usages: + _names['usages'].append(usage.usage) + return _names + + def entries(self, category=None): + """ + Get the catalog elements + :parm: for usage catalog category filter does nothing as there is only one category (usages) + """ + return self._content + + def get_entry(self, name): + """ + Get one catalog element by names + :parm: entry name + """ + for usage in self._content.usages: + if usage.usage.lower() == name.lower(): + return usage + raise IndexError(f"{name} doesn't exists in the catalog") diff --git a/catalog_factories/usage/usage_helper.py b/catalog_factories/usage/usage_helper.py index 4a397566..f3c4bf2b 100644 --- a/catalog_factories/usage/usage_helper.py +++ b/catalog_factories/usage/usage_helper.py @@ -13,6 +13,50 @@ class UsageHelper: """ Usage helper class """ + _nrcan_schedule_type_to_hub_schedule_type = { + 'Lighting': cte.LIGHTING, + 'Occupancy': cte.OCCUPANCY, + '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' + } + _nrcan_data_type_to_hub_data_type = { + 'Fraction': cte.FRACTION, + 'ON_OFF': cte.ON_OFF, + 'TEMPERATURE': cte.ANY_NUMBER + } + + _nrcan_time_to_hub_time = { + 'Hourly': cte.HOUR + } + + _nrcan_day_type_to_hub_days = { + 'Default|Wkdy': [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY], + 'Sun|Hol': [cte.SUNDAY, cte.HOLIDAY], + 'Sat': [cte.SATURDAY], + 'Default|WntrDsn|SmrDsn': [cte.MONDAY, + cte.TUESDAY, + cte.WEDNESDAY, + cte.THURSDAY, + cte.FRIDAY, + cte.SATURDAY, + cte.SUNDAY, + cte.HOLIDAY, + cte.WINTER_DESIGN_DAY, + cte.SUMMER_DESIGN_DAY], + 'Default': [cte.MONDAY, + cte.TUESDAY, + cte.WEDNESDAY, + cte.THURSDAY, + cte.FRIDAY, + cte.SATURDAY, + cte.SUNDAY, + cte.HOLIDAY, + cte.WINTER_DESIGN_DAY, + cte.SUMMER_DESIGN_DAY] + + } + _usage_to_hft = { cte.RESIDENTIAL: 'residential', cte.SINGLE_FAMILY_HOUSE: 'Single family house', @@ -71,7 +115,7 @@ class UsageHelper: 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, @@ -93,6 +137,22 @@ class UsageHelper: 'C-14 Gymnasium': 'C-14 Gymnasium' } + @property + def nrcan_day_type_to_hub_days(self): + return self._nrcan_day_type_to_hub_days + + @property + def nrcan_schedule_type_to_hub_schedule_type(self): + return self._nrcan_schedule_type_to_hub_schedule_type + + @property + def nrcan_data_type_to_hub_data_type(self): + return self._nrcan_data_type_to_hub_data_type + + @property + def nrcan_time_to_hub_time(self): + return self._nrcan_time_to_hub_time + @property def comnet_data_type_to_hub_data_type(self): return self._comnet_data_type_to_hub_data_type diff --git a/catalog_factories/usage_catalog_factory.py b/catalog_factories/usage_catalog_factory.py index 7f56a333..f7c20265 100644 --- a/catalog_factories/usage_catalog_factory.py +++ b/catalog_factories/usage_catalog_factory.py @@ -8,6 +8,7 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca from pathlib import Path from typing import TypeVar from catalog_factories.usage.comnet_catalog import ComnetCatalog +from catalog_factories.usage.nrcan_catalog import NrcanCatalog Catalog = TypeVar('Catalog') @@ -25,6 +26,14 @@ class UsageCatalogFactory: """ return ComnetCatalog(self._path) + @property + def _nrcan(self): + """ + Retrieve NRCAN catalog + """ + # nrcan retrieves the data directly from github + return NrcanCatalog(self._path) + @property def catalog(self) -> Catalog: """ diff --git a/data/usage/nrcan.xml b/data/usage/nrcan.xml new file mode 100644 index 00000000..5f160c9a --- /dev/null +++ b/data/usage/nrcan.xml @@ -0,0 +1,9 @@ + + + + + NECB2020/data/space_types.json + NECB2015/data/schedules.json + + + \ No newline at end of file diff --git a/unittests/test_usage_catalog.py b/unittests/test_usage_catalog.py index 171f0eef..78d26f98 100644 --- a/unittests/test_usage_catalog.py +++ b/unittests/test_usage_catalog.py @@ -15,3 +15,7 @@ class TestConstructionCatalog(TestCase): self.assertIsNotNone(catalog, 'catalog is none') content = catalog.entries() self.assertEqual(len(content.usages), 32, 'Wrong number of usages') + + def test_nrcan_catalog(self): + catalog = UsageCatalogFactory('nrcan').catalog + self.assertIsNotNone(catalog, 'catalog is none') \ No newline at end of file