From 0b5e0ce4fc3adf69e1508e2f768eae67a31f2d9a Mon Sep 17 00:00:00 2001 From: Jostein Solaas Date: Mon, 9 Dec 2024 14:20:25 +0100 Subject: [PATCH] fix: ensure unique names across facility_inputs and models Refs: ECALC-896 --- .../yaml/yaml_types/components/yaml_asset.py | 20 ++++++ src/libecalc/testing/yaml_builder.py | 46 +++++++++++- .../yaml/test_yaml_model_unique_name.py | 71 +++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py b/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py index 022585644..79be05dbd 100644 --- a/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py +++ b/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py @@ -135,3 +135,23 @@ def validate_unique_fuel_names(cls, collection, info: ValidationInfo): f" Duplicated names are: {', '.join(duplicated_names)}" ) return collection + + @model_validator(mode="after") + def validate_facility_models_unique_name(self): + models = [] + + if self.facility_inputs is not None: + models.extend(self.facility_inputs) + + if self.models is not None: + models.extend(self.models) + + names = [model.name for model in models] + duplicated_names = get_duplicates(names) + + if len(duplicated_names) > 0: + raise ValueError( + f"Model names must be unique across {self.model_fields['facility_inputs'].alias} and {self.model_fields['models'].alias}." + f" Duplicated names are: {', '.join(duplicated_names)}" + ) + return self diff --git a/src/libecalc/testing/yaml_builder.py b/src/libecalc/testing/yaml_builder.py index 78e80368a..9992ea037 100644 --- a/src/libecalc/testing/yaml_builder.py +++ b/src/libecalc/testing/yaml_builder.py @@ -45,11 +45,12 @@ ) from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_emission import YamlEmission from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_fuel_type import YamlFuelType -from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel +from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel, YamlTurbine from libecalc.presentation.yaml.yaml_types.models.model_reference_validation import ( CompressorEnergyUsageModelModelReference, GeneratorSetModelReference, ) +from libecalc.presentation.yaml.yaml_types.models.yaml_enums import YamlModelType from libecalc.presentation.yaml.yaml_types.time_series.yaml_time_series import ( YamlDefaultTimeSeriesCollection, YamlTimeSeriesCollection, @@ -688,3 +689,46 @@ def with_start(self, start: str): def with_end(self, end: str): self.end = end return self + + +class YamlTurbineBuilder(Builder[YamlTurbine]): + def __init__(self): + self.name = None + self.type = YamlModelType.TURBINE + self.lower_heating_value = None + self.turbine_loads = [] + self.turbine_efficiencies = [] + self.power_adjustment_constant = 0 + self.power_adjustment_factor = 1.0 + + def with_name(self, name: str) -> Self: + self.name = name + return self + + def with_lower_heating_value(self, lower_heating_value: float) -> Self: + self.lower_heating_value = lower_heating_value + return self + + def with_turbine_loads(self, turbine_loads: list[float]) -> Self: + self.turbine_loads = turbine_loads + return self + + def with_turbine_efficiencies(self, turbine_efficiencies: list[float]) -> Self: + self.turbine_efficiencies = turbine_efficiencies + return self + + def with_power_adjustment_constant(self, power_adjustment_constant: float) -> Self: + self.power_adjustment_constant = power_adjustment_constant + return self + + def with_power_adjustment_factor(self, power_adjustment_factor: float) -> Self: + self.power_adjustment_factor = power_adjustment_factor + return self + + def with_test_data(self) -> Self: + self.name = "compressor_train_turbine" + self.lower_heating_value = 38 + self.turbine_loads = [0, 2.352, 4.589, 6.853, 9.125, 11.399, 13.673, 15.947, 18.223, 20.496, 22.767] + self.turbine_efficiencies = [0, 0.138, 0.210, 0.255, 0.286, 0.310, 0.328, 0.342, 0.353, 0.360, 0.362] + + return self diff --git a/tests/libecalc/presentation/yaml/test_yaml_model_unique_name.py b/tests/libecalc/presentation/yaml/test_yaml_model_unique_name.py index 2e78f72aa..650066bb6 100644 --- a/tests/libecalc/presentation/yaml/test_yaml_model_unique_name.py +++ b/tests/libecalc/presentation/yaml/test_yaml_model_unique_name.py @@ -18,6 +18,7 @@ YamlGeneratorSetBuilder, YamlInstallationBuilder, YamlTimeSeriesBuilder, + YamlTurbineBuilder, YamlVentingEmitterOilTypeBuilder, ) @@ -268,3 +269,73 @@ def test_timeseries_unique_name(yaml_asset_configuration_service_factory, resour errors = exc_info.value.errors() assert len(errors) == 1 assert errors[0].message == snapshot("Value error, TIME_SERIES names must be unique. Duplicated names are: SIM1") + + +@pytest.mark.inlinesnapshot +@pytest.mark.snapshot +@pytest.mark.parametrize( + "facility_inputs, models, expected_error_message", + [ + ( + [ + YamlElectricity2fuelBuilder() + .with_test_data() + .with_name("duplicated_name") + .with_file("el2fuelresource") + .validate(), + YamlElectricity2fuelBuilder() + .with_test_data() + .with_name("duplicated_name") + .with_file("el2fuelresource") + .validate(), + ], + [], + snapshot( + "Value error, Model names must be unique across FACILITY_INPUTS and MODELS. Duplicated names are: duplicated_name" + ), + ), + ( + [ + YamlElectricity2fuelBuilder() + .with_test_data() + .with_name("duplicated_name") + .with_file("el2fuelresource") + .validate(), + ], + [ + YamlTurbineBuilder().with_test_data().with_name("duplicated_name").validate(), + ], + snapshot( + "Value error, Model names must be unique across FACILITY_INPUTS and MODELS. Duplicated names are: duplicated_name" + ), + ), + ( + [], + [ + YamlTurbineBuilder().with_test_data().with_name("duplicated_name").validate(), + YamlTurbineBuilder().with_test_data().with_name("duplicated_name").validate(), + ], + snapshot( + "Value error, Model names must be unique across FACILITY_INPUTS and MODELS. Duplicated names are: duplicated_name" + ), + ), + ], +) +def test_models_unique_name( + facility_inputs, models, expected_error_message, yaml_asset_configuration_service_factory, resource_service_factory +): + """ + TEST SCOPE: Check that duplicate timeseries names are not allowed. + """ + model = YamlAssetBuilder().with_test_data().with_facility_inputs(facility_inputs).with_models(models).construct() + yaml_model = YamlModel( + configuration_service=yaml_asset_configuration_service_factory(model, "non_unique_model_names"), + resource_service=resource_service_factory({"el2fuelresource": el2fuel_factory()}), + output_frequency=Frequency.NONE, + ) + with pytest.raises(ModelValidationException) as exc_info: + yaml_model.validate_for_run() + + errors = exc_info.value.errors() + assert len(errors) == 1 + assert errors[0].message == expected_error_message