Skip to content

Commit

Permalink
fix: electricity consumer was not included in duplicate names
Browse files Browse the repository at this point in the history
  • Loading branch information
jsolaas committed Dec 6, 2024
1 parent fe2a52e commit dfa09fc
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 168 deletions.
48 changes: 1 addition & 47 deletions src/libecalc/dto/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from libecalc.common.logger import logger
from libecalc.common.priorities import Priorities
from libecalc.common.stream_conditions import TimeSeriesStreamConditions
from libecalc.common.string.string_utils import generate_id, get_duplicates
from libecalc.common.string.string_utils import generate_id
from libecalc.common.time_utils import Period, Periods
from libecalc.common.units import Unit
from libecalc.common.utils.rates import (
Expand Down Expand Up @@ -690,52 +690,6 @@ def get_component_process_type(self) -> ComponentType:
def get_name(self) -> str:
return self.name

@model_validator(mode="after")
def validate_unique_names(self):
"""Ensure unique component names within installation."""
names = [self.name]
fuel_types = [FuelType]
fuel_names = [str]
for installation in self.installations:
names.append(installation.name)
fuel_consumers = installation.fuel_consumers
venting_emitters = installation.venting_emitters

names.extend([venting_emitter.name for venting_emitter in venting_emitters])
for fuel_consumer in fuel_consumers:
names.append(fuel_consumer.name)
if isinstance(fuel_consumer, GeneratorSet):
for electricity_consumer in fuel_consumer.consumers:
if isinstance(electricity_consumer, ConsumerSystem):
for consumer in electricity_consumer.consumers:
names.append(consumer.name)
elif isinstance(fuel_consumer, ConsumerSystem):
for consumer in fuel_consumer.consumers:
names.append(consumer.name)
if fuel_consumer.fuel is not None:
for fuel_type in fuel_consumer.fuel.values():
# Need to verify that it is a different fuel
if fuel_type is not None and fuel_type not in fuel_types:
fuel_types.append(fuel_type)
fuel_names.append(fuel_type.name)

duplicated_names = get_duplicates(names)
duplicated_fuel_names = get_duplicates(fuel_names)

if len(duplicated_names) > 0:
raise ValueError(
"Component names must be unique. Components include the main model, installations,"
" generator sets, electricity consumers, fuel consumers, systems and its consumers and direct emitters."
f" Duplicated names are: {', '.join(duplicated_names)}"
)

if len(duplicated_fuel_names) > 0:
raise ValueError(
"Fuel type names must be unique across installations."
f" Duplicated names are: {', '.join(duplicated_fuel_names)}"
)
return self

def get_graph(self) -> ComponentGraph:
graph = ComponentGraph()
graph.add_node(self)
Expand Down
1 change: 1 addition & 0 deletions src/libecalc/presentation/yaml/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def _get_model_types(yaml_model: YamlValidator) -> dict["ModelName", "ModelConte

def _get_validation_context(self, yaml_model: YamlValidator) -> YamlModelValidationContext:
return {
YamlModelValidationContextNames.model_name: yaml_model.name,
YamlModelValidationContextNames.resource_file_names: [name for name, resource in self.resources.items()],
YamlModelValidationContextNames.expression_tokens: self._get_token_references(yaml_model=yaml_model),
YamlModelValidationContextNames.model_types: self._get_model_types(yaml_model=yaml_model),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from pydantic import ConfigDict, Field
from pydantic import ConfigDict, Field, model_validator
from pydantic_core.core_schema import ValidationInfo

from libecalc.common.string.string_utils import get_duplicates
from libecalc.presentation.yaml.yaml_types import YamlBase
from libecalc.presentation.yaml.yaml_types.components.system.yaml_consumer_system import YamlConsumerSystem
from libecalc.presentation.yaml.yaml_types.components.yaml_installation import (
YamlInstallation,
)
Expand All @@ -16,6 +19,7 @@
YamlDefaultDatetime,
)
from libecalc.presentation.yaml.yaml_types.yaml_variable import YamlVariables
from libecalc.presentation.yaml.yaml_validation_context import YamlModelValidationContextNames


class YamlAsset(YamlBase):
Expand Down Expand Up @@ -71,3 +75,59 @@ class YamlAsset(YamlBase):
title="END",
description="Global end date for eCalc calculations in <YYYY-MM-DD> format." "\n\n$ECALC_DOCS_KEYWORDS_URL/END",
)

@model_validator(mode="after")
def validate_unique_component_names(self, info: ValidationInfo):
"""Ensure unique component names in model."""
context = info.context
if not context:
return self

if not context.get(YamlModelValidationContextNames.model_name):
return self

names = [context.get(YamlModelValidationContextNames.model_name)]

for installation in self.installations:
names.append(installation.name)
for fuel_consumer in installation.fuel_consumers or []:
names.append(fuel_consumer.name)
if isinstance(fuel_consumer, YamlConsumerSystem):
for consumer in fuel_consumer.consumers:
names.append(consumer.name)

for generator_set in installation.generator_sets or []:
names.append(generator_set.name)
for electricity_consumer in generator_set.consumers:
names.append(electricity_consumer.name)
if isinstance(electricity_consumer, YamlConsumerSystem):
for consumer in electricity_consumer.consumers:
names.append(consumer.name)

for venting_emitter in installation.venting_emitters or []:
names.append(venting_emitter.name)

duplicated_names = get_duplicates(names)

if len(duplicated_names) > 0:
raise ValueError(
"Component names must be unique. Components include the main model, installations,"
" generator sets, electricity consumers, fuel consumers, systems and its consumers and direct emitters."
f" Duplicated names are: {', '.join(duplicated_names)}"
)

return self

@model_validator(mode="after")
def validate_unique_fuel_names(self):
fuel_names = []

for fuel_type in self.fuel_types:
fuel_names.append(fuel_type.name)

duplicated_fuel_names = get_duplicates(fuel_names)
if len(duplicated_fuel_names) > 0:
raise ValueError(
"Fuel type names must be unique." f" Duplicated names are: {', '.join(duplicated_fuel_names)}"
)
return self
2 changes: 2 additions & 0 deletions src/libecalc/presentation/yaml/yaml_validation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class YamlModelValidationContextNames:
resource_file_names = "resource_file_names"
expression_tokens = "expression_tokens"
model_types = "model_types"
model_name = "model_name"


YamlModelValidationContext = TypedDict(
Expand All @@ -26,6 +27,7 @@ class YamlModelValidationContextNames:
YamlModelValidationContextNames.resource_file_names: list[str], # type: ignore
YamlModelValidationContextNames.expression_tokens: list[str],
YamlModelValidationContextNames.model_types: dict[ModelName, ModelContext],
YamlModelValidationContextNames.model_name: str,
},
total=True,
)
11 changes: 11 additions & 0 deletions src/libecalc/testing/facility_resource_factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from libecalc.presentation.yaml.yaml_entities import MemoryResource


def el2fuel_factory(electricity: list[float] = None, fuel: list[float] = None) -> MemoryResource:
if electricity is None:
electricity = [1, 2, 3, 4, 5]

if fuel is None:
fuel = [el * 2 for el in electricity]

return MemoryResource(headers=["POWER", "FUEL"], data=[electricity, fuel])
35 changes: 18 additions & 17 deletions src/libecalc/testing/yaml_builder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import abc
from enum import Enum
from typing import List, Self, TypeVar, Generic, get_args, cast, Union, Literal
from typing import Generic, List, Literal, Self, TypeVar, Union, get_args

from typing_extensions import get_original_bases

Expand All @@ -10,12 +10,11 @@
FuelTypeUserDefinedCategoryType,
InstallationUserDefinedCategoryType,
)

from libecalc.presentation.yaml.yaml_types import YamlBase
from libecalc.presentation.yaml.yaml_types.components.legacy.energy_usage_model import (
YamlFuelEnergyUsageModel,
YamlElectricityEnergyUsageModel,
YamlEnergyUsageModelCompressor,
YamlFuelEnergyUsageModel,
)
from libecalc.presentation.yaml.yaml_types.components.legacy.energy_usage_model.yaml_energy_usage_model_direct import (
ConsumptionRateType,
Expand All @@ -29,37 +28,37 @@
from libecalc.presentation.yaml.yaml_types.components.yaml_generator_set import YamlGeneratorSet
from libecalc.presentation.yaml.yaml_types.components.yaml_installation import YamlInstallation
from libecalc.presentation.yaml.yaml_types.emitters.yaml_venting_emitter import (
YamlDirectTypeEmitter,
YamlOilTypeEmitter,
YamlVentingEmission,
YamlVentingEmitter,
YamlVentingType,
YamlVentingVolume,
YamlVentingVolumeEmission,
YamlVentingType,
YamlOilTypeEmitter,
YamlVentingEmission,
YamlDirectTypeEmitter,
)
from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import (
YamlCompressorTabularModel,
YamlFacilityAdjustment,
YamlFacilityModel,
YamlGeneratorSetModel,
YamlFacilityModelType,
YamlFacilityAdjustment,
YamlCompressorTabularModel,
YamlGeneratorSetModel,
)
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.model_reference_validation import (
GeneratorSetModelReference,
CompressorEnergyUsageModelModelReference,
GeneratorSetModelReference,
)
from libecalc.presentation.yaml.yaml_types.time_series.yaml_time_series import (
YamlTimeSeriesCollection,
YamlDefaultTimeSeriesCollection,
YamlTimeSeriesCollection,
)
from libecalc.presentation.yaml.yaml_types.yaml_stream_conditions import (
YamlOilRateUnits,
YamlOilVolumeRate,
YamlEmissionRate,
YamlEmissionRateUnits,
YamlOilRateUnits,
YamlOilVolumeRate,
)
from libecalc.presentation.yaml.yaml_types.yaml_temporal_model import YamlTemporalModel
from libecalc.presentation.yaml.yaml_types.yaml_variable import YamlVariables
Expand Down Expand Up @@ -315,7 +314,9 @@ def __init__(self):
def with_test_data(self) -> Self:
self.name = "base load"
self.category = ConsumerUserDefinedCategoryType.FIXED_PRODUCTION_LOAD.value
self.energy_usage_model = YamlEnergyUsageModelDirectBuilder().with_test_data().validate()
self.energy_usage_model = (
YamlEnergyUsageModelDirectBuilder().with_test_data().with_load("0.5").with_fuel_rate(None).validate()
)
return self

def with_name(self, name: str) -> Self:
Expand Down Expand Up @@ -378,7 +379,7 @@ def with_test_data(self) -> Self:
self.category = ConsumerUserDefinedCategoryType.TURBINE_GENERATOR
self.fuel = YamlFuelTypeBuilder().with_test_data().validate().name
self.electricity2fuel = YamlElectricity2fuelBuilder().with_test_data().validate().name
self.consumers.append(YamlElectricityConsumerBuilder().with_test_data().validate())
self.consumers = [YamlElectricityConsumerBuilder().with_test_data().validate()]

return self

Expand Down Expand Up @@ -539,7 +540,7 @@ def with_test_data(self):
self.name = "DefaultInstallation"
self.hydrocarbon_export = 0
self.regularity = 1
self.fuel_consumers.append(YamlFuelConsumerBuilder().with_test_data().validate())
self.fuel_consumers = [YamlFuelConsumerBuilder().with_test_data().validate()]
return self

def with_name(self, name: str) -> Self:
Expand Down
10 changes: 8 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from pathlib import Path
from typing import Optional, cast


import pytest
import yaml

Expand All @@ -28,6 +27,7 @@
YamlEnergyUsageModelDirectBuilder,
YamlFuelConsumerBuilder,
YamlFuelTypeBuilder,
YamlGeneratorSetBuilder,
YamlInstallationBuilder,
)

Expand Down Expand Up @@ -200,6 +200,11 @@ def yaml_fuel_type_builder_factory():
return lambda: YamlFuelTypeBuilder()


@pytest.fixture
def yaml_generator_set_builder_factory():
return lambda: YamlGeneratorSetBuilder()


@pytest.fixture
def minimal_installation_yaml_factory(yaml_installation_builder_factory):
def minimal_installation_yaml(
Expand Down Expand Up @@ -247,8 +252,9 @@ def minimal_model_yaml(fuel_rate: int | str = 50) -> ConfigurationService:
.with_installations([installation])
.with_start("2020-01-01")
.with_end("2023-01-01")
.validate()
)
return yaml_asset_configuration_service_factory(model.validate(), name="minimal_model")
return yaml_asset_configuration_service_factory(model, name="minimal_model")

return minimal_model_yaml

Expand Down
25 changes: 0 additions & 25 deletions tests/ecalc_cli/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,31 +459,6 @@ def test_yaml_file_error(self):
f"Allowed characters are {COMPONENT_NAME_ALLOWED_CHARS}" in str(ee.value)
)

def test_yaml_duplicate_fuel(self, simple_duplicate_names_yaml_path, tmp_path):
"""
TEST SCOPE: Check that duplicate fuel type names are not allowed in Yaml file.
Args:
simple model file with duplicate fuel names:
Returns:
"""
with pytest.raises(EcalcError) as exc_info:
runner.invoke(
main.app,
_get_args(
model_file=simple_duplicate_names_yaml_path,
csv=True,
output_folder=tmp_path,
name_prefix="test",
output_frequency="YEAR",
),
catch_exceptions=False,
)

assert "Duplicated names are: fuel_gas" in str(exc_info.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.
Expand Down
Loading

0 comments on commit dfa09fc

Please sign in to comment.