diff --git a/src/libecalc/examples/simple/model_duplicate_emissions_in_fuel.yaml b/src/libecalc/examples/simple/model_duplicate_emissions_in_fuel.yaml deleted file mode 100644 index 2f1b05c4b6..0000000000 --- a/src/libecalc/examples/simple/model_duplicate_emissions_in_fuel.yaml +++ /dev/null @@ -1,42 +0,0 @@ -TIME_SERIES: - - NAME: SIM - FILE: production_data.csv - TYPE: DEFAULT -FACILITY_INPUTS: - - NAME: genset - FILE: genset.csv - TYPE: ELECTRICITY2FUEL - -FUEL_TYPES: - - NAME: fuel_gas1 - EMISSIONS: - - NAME: CO2 - FACTOR: 2.19 # CO2/Sm3 fuel gas burned - - NAME: CO2 - FACTOR: 2.19 # CO2/Sm3 fuel gas burned - - NAME: fuel_gas2 - EMISSIONS: - - NAME: CH4 - FACTOR: 5.19 # CO2/Sm3 fuel gas burned - - NAME: CH4 - FACTOR: 5.19 # CO2/Sm3 fuel gas burned - -VARIABLES: - hydrocarbon_export_sm3_per_day: - VALUE: SIM;OIL_PROD {+} SIM;GAS_SALES {/} 1000 # divide the gas rate by 1000 to get oil equivalent - -INSTALLATIONS: - - NAME: Installation A - HCEXPORT: $var.hydrocarbon_export_sm3_per_day - FUEL: fuel_gas - GENERATORSETS: - - NAME: Generator set A - ELECTRICITY2FUEL: genset - CATEGORY: TURBINE-GENERATOR - CONSUMERS: - - NAME: Base production load - CATEGORY: BASE-LOAD - ENERGY_USAGE_MODEL: - TYPE: DIRECT - LOAD: 11.8 # MW - diff --git a/src/libecalc/examples/simple/model_duplicate_names.yaml b/src/libecalc/examples/simple/model_duplicate_names.yaml deleted file mode 100644 index ce37d8ad9d..0000000000 --- a/src/libecalc/examples/simple/model_duplicate_names.yaml +++ /dev/null @@ -1,38 +0,0 @@ -TIME_SERIES: - - NAME: SIM - FILE: production_data.csv - TYPE: DEFAULT -FACILITY_INPUTS: - - NAME: genset - FILE: genset.csv - TYPE: ELECTRICITY2FUEL - -FUEL_TYPES: - - NAME: fuel_gas - EMISSIONS: - - NAME: CO2 - FACTOR: 2.19 # CO2/Sm3 fuel gas burned - - NAME: fuel_gas - EMISSIONS: - - NAME: CO2 - FACTOR: 5.19 # CO2/Sm3 fuel gas burned - - -VARIABLES: - hydrocarbon_export_sm3_per_day: - VALUE: SIM;OIL_PROD {+} SIM;GAS_SALES {/} 1000 # divide the gas rate by 1000 to get oil equivalent - -INSTALLATIONS: - - NAME: Installation A - HCEXPORT: $var.hydrocarbon_export_sm3_per_day - FUEL: fuel_gas - GENERATORSETS: - - NAME: Generator set A - ELECTRICITY2FUEL: genset - CATEGORY: TURBINE-GENERATOR - CONSUMERS: - - NAME: Base production load - CATEGORY: BASE-LOAD - ENERGY_USAGE_MODEL: - TYPE: DIRECT - LOAD: 11.8 # MW diff --git a/src/libecalc/presentation/yaml/mappers/create_references.py b/src/libecalc/presentation/yaml/mappers/create_references.py index 74da16f6b2..51a016510e 100644 --- a/src/libecalc/presentation/yaml/mappers/create_references.py +++ b/src/libecalc/presentation/yaml/mappers/create_references.py @@ -3,7 +3,6 @@ from libecalc.common.errors.exceptions import EcalcError from libecalc.common.logger import logger -from libecalc.common.string.string_utils import get_duplicates from libecalc.dto import EnergyModel from libecalc.presentation.yaml.domain.reference_service import ReferenceService from libecalc.presentation.yaml.mappers.facility_input import FacilityInputMapper @@ -34,31 +33,6 @@ def create_references(configuration: YamlValidator, resources: Resources) -> Ref resources=resources, ) - duplicated_fuel_names = get_duplicates([fuel_data.name for fuel_data in configuration.fuel_types]) - - if len(duplicated_fuel_names) > 0: - raise EcalcError( - title="Duplicate names", - message="Fuel type names must be unique across installations." - f" Duplicated names are: {', '.join(duplicated_fuel_names)}", - ) - - fuel_types_emissions = [fuel_data.emissions for fuel_data in configuration.fuel_types] - - # Check each fuel for duplicated emissions - duplicated_emissions = [] - for emissions in fuel_types_emissions: - duplicated_emissions.append(get_duplicates([emission.name for emission in emissions])) - - duplicated_emissions_names = ",".join(name for string in duplicated_emissions for name in string if len(string) > 0) - - if len(duplicated_emissions_names) > 0: - raise EcalcError( - title="Duplicate names", - message="Emission names must be unique for each fuel type. " - f"Duplicated names are: {duplicated_emissions_names}", - ) - fuel_types = {fuel_data.name: FuelMapper.from_yaml_to_dto(fuel_data) for fuel_data in configuration.fuel_types} return References( @@ -79,11 +53,6 @@ def create_model_references( for model in sorted_models: model_reference = model.name - if model_reference in models_map: - raise EcalcError( - title="Duplicate reference", - message=f"The model '{model_reference}' is defined multiple times", - ) models_map[model_reference] = model_mapper.from_yaml_to_dto(model, models_map) return models_map diff --git a/src/libecalc/presentation/yaml/yaml_types/fuel_type/yaml_fuel_type.py b/src/libecalc/presentation/yaml/yaml_types/fuel_type/yaml_fuel_type.py index b7adbd6308..fdacd92775 100644 --- a/src/libecalc/presentation/yaml/yaml_types/fuel_type/yaml_fuel_type.py +++ b/src/libecalc/presentation/yaml/yaml_types/fuel_type/yaml_fuel_type.py @@ -1,5 +1,7 @@ -from pydantic import ConfigDict, Field +from pydantic import ConfigDict, Field, field_validator +from pydantic_core.core_schema import ValidationInfo +from libecalc.common.string.string_utils import get_duplicates from libecalc.dto.types import FuelTypeUserDefinedCategoryType from libecalc.presentation.yaml.yaml_types import YamlBase from libecalc.presentation.yaml.yaml_types.components.yaml_category_field import ( @@ -30,3 +32,17 @@ class YamlFuelType(YamlBase): description="Warning! Deprecated. Does not have any effect. Lower heating value [MJ/Sm3] of fuel. " "Lower heating value is also known as net calorific value", ) + + @field_validator("emissions", mode="after") + @classmethod + def ensure_unique_emission_names(cls, emissions, info: ValidationInfo): + names = [emission.name for emission in emissions] + duplicated_names = get_duplicates(names) + + if len(duplicated_names) > 0: + raise ValueError( + f"{cls.model_fields[info.field_name].alias} names must be unique." + f" Duplicated names are: {', '.join(duplicated_names)}" + ) + + return emissions diff --git a/tests/conftest.py b/tests/conftest.py index 4959b1bb58..ff898ab973 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -74,13 +74,9 @@ def rounded_snapshot(data: dict, snapshot_name: str): } invalid_example_cases = { - "simple_duplicate_names": (Path(simple.__file__).parent / "model_duplicate_names.yaml").absolute(), "simple_multiple_energy_models_one_consumer": ( Path(simple.__file__).parent / "model_multiple_energy_models_one_consumer.yaml" ).absolute(), - "simple_duplicate_emissions_in_fuel": ( - Path(simple.__file__).parent / "model_duplicate_emissions_in_fuel.yaml" - ).absolute(), } @@ -94,21 +90,11 @@ def simple_temporal_yaml_path(): return valid_example_cases["simple_temporal"] -@pytest.fixture(scope="session") -def simple_duplicate_names_yaml_path(): - return invalid_example_cases["simple_duplicate_names"] - - @pytest.fixture(scope="session") def simple_multiple_energy_models_yaml_path(): return invalid_example_cases["simple_multiple_energy_models_one_consumer"] -@pytest.fixture(scope="session") -def simple_duplicate_emissions_yaml_path(): - return invalid_example_cases["simple_duplicate_emissions_in_fuel"] - - @pytest.fixture(scope="session") def advanced_yaml_path(): return valid_example_cases["advanced"] diff --git a/tests/ecalc_cli/test_app.py b/tests/ecalc_cli/test_app.py index f481fbb900..f4252f1be8 100644 --- a/tests/ecalc_cli/test_app.py +++ b/tests/ecalc_cli/test_app.py @@ -11,7 +11,6 @@ from typer.testing import CliRunner from ecalc_cli import main -from libecalc.common.errors.exceptions import EcalcError from libecalc.common.run_info import RunInfo from libecalc.dto.utils.validators import COMPONENT_NAME_ALLOWED_CHARS from libecalc.presentation.yaml.model_validation_exception import ModelValidationException @@ -459,33 +458,6 @@ def test_yaml_file_error(self): f"Allowed characters are {COMPONENT_NAME_ALLOWED_CHARS}" in str(ee.value) ) - def test_yaml_duplicate_emissions_in_fuel(self, simple_duplicate_emissions_yaml_path, tmp_path): - """ - TEST SCOPE: Check that duplicate emission names for one fuel type are not allowed in Yaml file. - - Args: - simple model file with duplicate emission names: - - Returns: - - """ - with pytest.raises(EcalcError) as exc_info: - runner.invoke( - main.app, - _get_args( - model_file=simple_duplicate_emissions_yaml_path, - csv=True, - output_folder=tmp_path, - name_prefix="test", - output_frequency="YEAR", - ), - catch_exceptions=False, - ) - - assert "Emission names must be unique for each fuel type. " "Duplicated names are: CO2,CH4" in str( - exc_info.value - ) - def test_yaml_multiple_energy_models_one_consumer(self, simple_multiple_energy_models_yaml_path, tmp_path): """ TEST SCOPE: Check that multiple energy models for one consumer are not allowed in Yaml file. diff --git a/tests/libecalc/presentation/yaml/yaml_types/models/fuel_type/test_duplicate_emission_names.py b/tests/libecalc/presentation/yaml/yaml_types/models/fuel_type/test_duplicate_emission_names.py new file mode 100644 index 0000000000..1a0c56fdce --- /dev/null +++ b/tests/libecalc/presentation/yaml/yaml_types/models/fuel_type/test_duplicate_emission_names.py @@ -0,0 +1,22 @@ +import pytest +from inline_snapshot import snapshot +from pydantic import ValidationError + +from libecalc.testing.yaml_builder import YamlEmissionBuilder, YamlFuelTypeBuilder + + +@pytest.mark.snapshot +@pytest.mark.inlinesnapshot +def test_duplicate_emission_names(): + with pytest.raises(ValidationError) as exc_info: + YamlFuelTypeBuilder().with_test_data().with_emissions( + [ + YamlEmissionBuilder().with_test_data().with_name("co2").validate(), + YamlEmissionBuilder().with_test_data().with_name("co2").validate(), + ] + ).validate() + + errors = exc_info.value.errors() + assert len(errors) == 1 + + assert errors[0]["msg"] == snapshot("Value error, EMISSIONS names must be unique. Duplicated names are: co2")