diff --git a/hub/persistence/db_control.py b/hub/persistence/db_control.py index 6f2477da..305ccaed 100644 --- a/hub/persistence/db_control.py +++ b/hub/persistence/db_control.py @@ -72,7 +72,7 @@ class DBControl: :param city_id: City ID :return: CityObject """ - return self._city_object.get_by_name_and_city(name, city_id) + return self._city_object.get_by_name_or_alias_and_city(name, city_id) def results(self, user_id, application_id, cities, result_names=None) -> Dict: """ @@ -93,9 +93,9 @@ class DBControl: city_id = result_set.id results[city_name] = [] for building_name in city[city_name]: - if self._city_object.get_by_name_and_city(building_name, city_id) is None: + if self._city_object.get_by_name_or_alias_and_city(building_name, city_id) is None: continue - city_object_id = self._city_object.get_by_name_and_city(building_name, city_id).id + city_object_id = self._city_object.get_by_name_or_alias_and_city(building_name, city_id).id _ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names( city_id, city_object_id, @@ -107,16 +107,17 @@ class DBControl: results[city_name].append(values) return results - def persist_city(self, city: City, pickle_path, application_id: int, user_id: int): + def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int): """ Creates a city into the database :param city: City to be stored :param pickle_path: Path to save the pickle file + :param scenario: Simulation scenario name :param application_id: Application id owning this city :param user_id: User who create the city return identity_id """ - return self._city_repository.insert(city, pickle_path, application_id, user_id) + return self._city_repository.insert(city, pickle_path, scenario, application_id, user_id) def update_city(self, city_id, city): """ diff --git a/hub/persistence/models/city.py b/hub/persistence/models/city.py index 9c36193c..e77e51de 100644 --- a/hub/persistence/models/city.py +++ b/hub/persistence/models/city.py @@ -20,19 +20,17 @@ class City(Models): id = Column(Integer, Sequence('city_id_seq'), primary_key=True) pickle_path = Column(String, nullable=False) name = Column(String, nullable=False) - level_of_detail = Column(Integer, nullable=False) - climate_file = Column(String, nullable=False) + scenario = 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) created = Column(DateTime, default=datetime.datetime.utcnow) updated = Column(DateTime, default=datetime.datetime.utcnow) - def __init__(self, pickle_path, name, level_of_detail, climate_file, application_id, user_id, hub_release): + def __init__(self, pickle_path, name, scenario, application_id, user_id, hub_release): self.pickle_path = str(pickle_path) self.name = name - self.level_of_detail = level_of_detail - self.climate_file = climate_file + self.scenario = scenario self.application_id = application_id self.user_id = user_id self.hub_release = hub_release diff --git a/hub/persistence/repositories/city.py b/hub/persistence/repositories/city.py index afbc5523..1e7d85ae 100644 --- a/hub/persistence/repositories/city.py +++ b/hub/persistence/repositories/city.py @@ -34,11 +34,12 @@ class City(Repository): cls._instance = super(City, cls).__new__(cls) return cls._instance - def insert(self, city: CityHub, pickle_path, application_id, user_id: int): + def insert(self, city: CityHub, pickle_path, scenario, application_id, user_id: int): """ Inserts a city :param city: The complete city instance :param pickle_path: Path to the pickle + :param scenario: Simulation scenario name :param application_id: Application id owning the instance :param user_id: User id owning the instance :return: Identity id @@ -48,8 +49,7 @@ class City(Repository): db_city = Model( pickle_path, city.name, - city.level_of_detail.geometry, - 'None' if city.climate_file is None else str(city.climate_file), + scenario, application_id, user_id, __version__) @@ -98,21 +98,19 @@ class City(Repository): logging.error('Error while fetching city %s', err) raise SQLAlchemyError from err - def get_by_user_id_application_id_and_name(self, user_id, application_id, city_name) -> Model: + def get_by_user_id_application_id_and_scenario(self, user_id, application_id, scenario) -> Model: """ Fetch city based on the user who created it :param user_id: the user id :param application_id: the application id - :param city_name: the city name - :return: ModelCity + :param scenario: simulation scenario name + :return: [ModelCity] """ try: result_set = self.session.execute(select(Model).where(Model.user_id == user_id, Model.application_id == application_id, - Model.name == city_name - )).first() - if result_set is not None: - result_set = result_set[0] + Model.scenario == scenario + )).all() return result_set except SQLAlchemyError as err: logging.error('Error while fetching city by name %s', err) diff --git a/hub/persistence/repositories/city_object.py b/hub/persistence/repositories/city_object.py index 8c5c7e60..04b9a82a 100644 --- a/hub/persistence/repositories/city_object.py +++ b/hub/persistence/repositories/city_object.py @@ -7,7 +7,7 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca import datetime import logging -from sqlalchemy import select +from sqlalchemy import select, or_ from sqlalchemy.exc import SQLAlchemyError from hub.city_model_structure.building import Building @@ -39,7 +39,7 @@ class CityObject(Repository): :param building: the city object (only building for now) to be inserted return Identity id """ - city_object = self.get_by_name_and_city(building.name, city_id) + city_object = self.get_by_name_or_alias_and_city(building.name, city_id) if city_object is not None: raise SQLAlchemyError(f'A city_object named {building.name} already exists in that city') try: @@ -96,7 +96,7 @@ class CityObject(Repository): logging.error('Error while deleting application %s', err) raise SQLAlchemyError from err - def get_by_name_and_city(self, name, city_id) -> Model: + def get_by_name_or_alias_and_city(self, name, city_id) -> Model: """ Fetch a city object based on name and city id :param name: city object name @@ -106,7 +106,7 @@ class CityObject(Repository): _city_object = None try: _city_object = self.session.execute(select(Model).where( - Model.name == name, Model.city_id == city_id + or_(Model.name == name, Model.aliases.contains(f'%{name}%')), Model.city_id == city_id )).first() return _city_object[0] except SQLAlchemyError as err: diff --git a/tests/test_db_factory.py b/tests/test_db_factory.py index c1bc6cc6..5b826433 100644 --- a/tests/test_db_factory.py +++ b/tests/test_db_factory.py @@ -14,6 +14,7 @@ from unittest import TestCase from pathlib import Path import sqlalchemy.exc +from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction from hub.imports.geometry_factory import GeometryFactory from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory @@ -52,7 +53,7 @@ class Control: self._skip_reason = f'.env file missing at {dotenv_path}' return dotenv_path = str(dotenv_path) - repository = Repository(db_name='hub_unittests', app_env='TEST', dotenv_path=dotenv_path) + repository = Repository(db_name='montreal_retrofit_test', app_env='TEST', dotenv_path=dotenv_path) engine = create_engine(repository.configuration.connection_string) try: # delete test database if it exists @@ -71,11 +72,15 @@ class Control: CityObject.__table__.create(bind=repository.engine, checkfirst=True) SimulationResults.__table__.create(bind=repository.engine, checkfirst=True) - city_file = "tests_data/FZK_Haus_LoD_2.gml" + city_file = Path('tests_data/test.geojson').resolve() output_path = Path('tests_outputs/').resolve() - self._city = GeometryFactory('citygml', + self._city = GeometryFactory('geojson', city_file, - function_to_hub=Dictionaries().alkis_function_to_hub_function).city + height_field='citygml_me', + year_of_construction_field='ANNEE_CONS', + aliases_field=['ID_UEV', 'CIVIQUE_DE', 'NOM_RUE'], + function_field='CODE_UTILI', + function_to_hub=MontrealFunctionToHubFunction().dictionary).city ConstructionFactory('nrcan', self._city).enrich() UsageFactory('nrcan', self._city).enrich() WeatherFactory('epw', self._city).enrich() @@ -84,7 +89,6 @@ class Control: subprocess.run([self.sra, sra_file], stdout=subprocess.DEVNULL) ResultFactory('sra', self._city, output_path).enrich() - for building in self._city.buildings: building.energy_systems_archetype_name = 'system 1 gas pv' EnergySystemsFactory('montreal_custom', self._city).enrich() @@ -103,6 +107,7 @@ class Control: self._application_id = self._database.persist_application('test', 'test application', self.application_uuid) self._user_id = self._database.create_user('Admin', self._application_id, 'Admin@123', UserRoles.Admin) self._pickle_path = 'tests_data/pickle_path.bz2' + print('done') @property def database(self): @@ -167,6 +172,7 @@ TestDBFactory city_id = control.database.persist_city( control.city, control.pickle_path, + control.city.name, control.application_id, control.user_id) control.database.delete_city(city_id) @@ -222,36 +228,38 @@ TestDBFactory yearly_cooling_consumption = building.cooling_consumption[cte.YEAR] monthly_domestic_hot_water_consumption = building.domestic_hot_water_consumption[cte.MONTH] yearly_domestic_hot_water_consumption = building._domestic_hot_water_consumption[cte.YEAR] - monthly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[cte.MONTH] - yearly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[cte.YEAR] + monthly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[ + cte.MONTH] + yearly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[ + cte.YEAR] monthly_on_site_electrical_production = building.onsite_electrical_production[cte.MONTH] yearly_on_site_electrical_production = building.onsite_electrical_production[cte.YEAR] results = json.dumps({cte.INSEL_MEB: [ - {'monthly_cooling_peak_load': monthly_cooling_peak_load}, - {'yearly_cooling_peak_load': yearly_cooling_peak_load}, - {'monthly_heating_peak_load': monthly_heating_peak_load}, - {'yearly_heating_peak_load': yearly_heating_peak_load}, - {'monthly_cooling_demand': monthly_cooling_demand.tolist()}, - {'yearly_cooling_demand': yearly_cooling_demand.tolist()}, - {'monthly_heating_demand': monthly_heating_demand.tolist()}, - {'yearly_heating_demand': yearly_heating_demand.tolist()}, - {'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand.tolist()}, - {'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand.tolist()}, - {'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand.tolist()}, - {'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand.tolist()}, - {'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand.tolist()}, - {'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand.tolist()}, - {'monthly_heating_consumption': monthly_heating_consumption}, - {'yearly_heating_consumption': yearly_heating_consumption}, - {'monthly_cooling_consumption': monthly_cooling_consumption}, - {'yearly_cooling_consumption': yearly_cooling_consumption}, - {'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption}, - {'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption}, - {'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption}, - {'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption}, - {'monthly_on_site_electrical_production': monthly_on_site_electrical_production}, - {'yearly_on_site_electrical_production': yearly_on_site_electrical_production} - ]}) + {'monthly_cooling_peak_load': monthly_cooling_peak_load}, + {'yearly_cooling_peak_load': yearly_cooling_peak_load}, + {'monthly_heating_peak_load': monthly_heating_peak_load}, + {'yearly_heating_peak_load': yearly_heating_peak_load}, + {'monthly_cooling_demand': monthly_cooling_demand.tolist()}, + {'yearly_cooling_demand': yearly_cooling_demand.tolist()}, + {'monthly_heating_demand': monthly_heating_demand.tolist()}, + {'yearly_heating_demand': yearly_heating_demand.tolist()}, + {'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand.tolist()}, + {'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand.tolist()}, + {'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand.tolist()}, + {'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand.tolist()}, + {'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand.tolist()}, + {'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand.tolist()}, + {'monthly_heating_consumption': monthly_heating_consumption}, + {'yearly_heating_consumption': yearly_heating_consumption}, + {'monthly_cooling_consumption': monthly_cooling_consumption}, + {'yearly_cooling_consumption': yearly_cooling_consumption}, + {'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption}, + {'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption}, + {'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption}, + {'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption}, + {'monthly_on_site_electrical_production': monthly_on_site_electrical_production}, + {'yearly_on_site_electrical_production': yearly_on_site_electrical_production} + ]}) db_building_id = _building.id city_objects_id.append(db_building_id) @@ -260,12 +268,15 @@ TestDBFactory results, city_object_id=db_building_id) self.assertEqual(1, len(city_objects_id), 'wrong number of results') self.assertIsNotNone(city_objects_id[0], 'city_object_id is None') + """ for _id in city_objects_id: control.database.delete_results_by_name('insel meb', city_object_id=_id) control.database.delete_city(city_id) + @classmethod @unittest.skipIf(control.skip_test, control.skip_reason) def tearDownClass(cls): control.database.delete_application(control.application_uuid) control.database.delete_user(control.user_id) + """ \ No newline at end of file