Skip to content

Commit

Permalink
chore: implement new exception and remove redundant validation in dto
Browse files Browse the repository at this point in the history
  • Loading branch information
frodehk committed Dec 20, 2024
1 parent 56c0263 commit 1c3d201
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from libecalc.common.time_utils import Period
from libecalc.common.units import Unit
from libecalc.common.utils.rates import RateType
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)
from libecalc.domain.infrastructure.energy_components.utils import _convert_keys_in_dictionary_from_str_to_periods
from libecalc.dto.fuel_type import FuelType
from libecalc.dto.types import ConsumerUserDefinedCategoryType
Expand Down Expand Up @@ -71,14 +75,27 @@ def id(self) -> str:
return generate_id(self.name)

@classmethod
def check_user_defined_category(cls, user_defined_category, name: str):
def check_user_defined_category(
cls, user_defined_category, name: str
): # TODO: Check if this is needed. Should be handled in yaml validation
"""Provide which value and context to make it easier for user to correct wrt mandatory changes."""

if isinstance(user_defined_category, dict) and len(user_defined_category.values()) > 0:
user_defined_category = _convert_keys_in_dictionary_from_str_to_periods(user_defined_category)
for user_category in user_defined_category.values():
if user_category not in list(ConsumerUserDefinedCategoryType):
raise ValueError(
f"CATEGORY: {user_category} is not allowed for {cls.__name__} with name {name}. Valid categories are: {[(consumer_user_defined_category.value) for consumer_user_defined_category in ConsumerUserDefinedCategoryType]}"
msg = (
f"CATEGORY: {user_category} is not allowed for {cls.__name__}. Valid categories are: "
f"{[(consumer_user_defined_category.value) for consumer_user_defined_category in ConsumerUserDefinedCategoryType]}"
)

raise ComponentValidationException(
errors=[
ModelValidationError(
name=name,
message=str(msg),
)
]
)
return user_defined_category

Expand Down Expand Up @@ -109,8 +126,15 @@ def validate_fuel_exist(cls, name: str, fuel: Optional[dict[Period, FuelType]],
if isinstance(fuel, dict) and len(fuel.values()) > 0:
fuel = _convert_keys_in_dictionary_from_str_to_periods(fuel)
if consumes == ConsumptionType.FUEL and (fuel is None or len(fuel) < 1):
msg = f"Missing fuel for fuel consumer '{name}'"
raise ValueError(msg)
msg = "Missing fuel for fuel consumer"
raise ComponentValidationException(
errors=[
ModelValidationError(
name=name,
message=str(msg),
)
],
)
return fuel


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from dataclasses import dataclass
from typing import Optional

import yaml

from libecalc.presentation.yaml.file_context import FileContext
from libecalc.presentation.yaml.validation_errors import Location


@dataclass
class ModelValidationError:
message: str
name: Optional[str] = None
location: Optional[Location] = None
data: Optional[dict] = None
file_context: Optional[FileContext] = None

@property
def yaml(self) -> Optional[str]:
if self.data is None:
return None

return yaml.dump(self.data, sort_keys=False).strip()

def error_message(self):
msg = ""
if self.file_context is not None:
msg += f"Object starting on line {self.file_context.start.line_number}\n"
yaml = self.yaml
if yaml is not None:
msg += "...\n"
msg += yaml
msg += "\n...\n\n"

if self.location is not None and not self.location.is_empty():
msg += f"Location: {self.location.as_dot_separated()}\n"

if self.name is not None:
msg += f"Name: {self.name}\n"

msg += f"Message: {self.message}\n"
return msg

def __str__(self):
return self.error_message()


class ComponentValidationException(Exception):
def __init__(self, errors: list[ModelValidationError]):
self._errors = errors

def errors(self) -> list[ModelValidationError]:
return self._errors
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
SystemComponentConditions,
SystemStreamConditions,
)
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)
from libecalc.domain.infrastructure.energy_components.compressor import Compressor
from libecalc.domain.infrastructure.energy_components.compressor.component_dto import CompressorComponent
from libecalc.domain.infrastructure.energy_components.consumer_system.consumer_system import (
Expand Down Expand Up @@ -261,7 +265,14 @@ def create_consumer(
model_for_period = energy_usage_model

if model_for_period is None:
raise ValueError(f"Could not find model for consumer {consumer.name} at timestep {period}")
raise ComponentValidationException(
errors=[
ModelValidationError(
name=consumer.name,
message=f"Could not find model at timestep {period}",
)
]
)

if consumer.component_type in {ComponentType.COMPRESSOR, ComponentType.COMPRESSOR_V2}:
return Compressor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
from libecalc.common.variables import ExpressionEvaluator
from libecalc.core.models.generator import GeneratorModelSampled
from libecalc.core.result import GeneratorSetResult
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)


class Genset:
Expand Down Expand Up @@ -48,7 +52,14 @@ def evaluate(
assert power_requirement.unit == Unit.MEGA_WATT

if not len(power_requirement) == len(expression_evaluator.get_periods()):
raise ValueError("length of power_requirement does not match the time vector.")
raise ComponentValidationException(
errors=[
ModelValidationError(
name=self.name,
message="length of power_requirement does not match the time vector.",
)
]
)

# Compute fuel consumption from power rate.
fuel_rate = self.evaluate_fuel_rate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
from libecalc.core.result import EcalcModelResult
from libecalc.core.result.emission import EmissionResult
from libecalc.domain.infrastructure.energy_components.base.component_dto import BaseEquipment
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)
from libecalc.domain.infrastructure.energy_components.consumer_system.consumer_system_dto import ConsumerSystem
from libecalc.domain.infrastructure.energy_components.electricity_consumer.electricity_consumer import (
ElectricityConsumer,
Expand All @@ -29,9 +33,6 @@
validate_temporal_model,
)
from libecalc.expression import Expression
from libecalc.presentation.yaml.ltp_validation import (
validate_generator_set_power_from_shore,
)


class GeneratorSet(BaseEquipment, Emitter, EnergyComponent):
Expand Down Expand Up @@ -61,8 +62,6 @@ def __init__(
self.cable_loss = cable_loss
self.max_usage_from_shore = max_usage_from_shore
self.component_type = component_type
self.check_power_from_shore()
self.check_mandatory_category_for_generator_set(user_defined_category)
self._validate_genset_temporal_models(self.generator_set_model, self.fuel)
self.check_consumers()

Expand Down Expand Up @@ -131,16 +130,6 @@ def evaluate_emissions(
fuel_rate=fuel_usage.values,
)

@staticmethod
def check_mandatory_category_for_generator_set(
user_defined_category: dict[Period, ConsumerUserDefinedCategoryType],
):
"""This could be handled automatically with Pydantic, but I want to inform the users in a better way, in
particular since we introduced a breaking change for this to be mandatory for GeneratorSets in v7.2.
"""
if not isinstance(user_defined_category, dict) or not user_defined_category:
raise ValueError("CATEGORY is mandatory and must be set for GeneratorSet")

@staticmethod
def _validate_genset_temporal_models(
generator_set_model: dict[Period, GeneratorSetSampled], fuel: dict[Period, FuelType]
Expand All @@ -164,18 +153,19 @@ def check_fuel(fuel: dict[Period, FuelType]):
return fuel

def check_consumers(self):
errors: list[ModelValidationError] = []
for consumer in self.consumers:
if isinstance(consumer, FuelConsumer):
raise ValueError(
f"Consumer {consumer.name} is not an electricity consumer. Generators can not have fuel consumers."
errors.append(
ModelValidationError(
name=consumer.name,
message="The consumer is not an electricity consumer. "
"Generators can not have fuel consumers.",
)
)

def check_power_from_shore(self):
validate_generator_set_power_from_shore(
cable_loss=self.cable_loss,
max_usage_from_shore=self.max_usage_from_shore,
category=self.user_defined_category,
)
if errors:
raise ComponentValidationException(errors=errors)

def get_graph(self) -> ComponentGraph:
graph = ComponentGraph()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from libecalc.common.string.string_utils import generate_id
from libecalc.common.time_utils import Period
from libecalc.domain.infrastructure.energy_components.base.component_dto import BaseComponent
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)
from libecalc.domain.infrastructure.energy_components.consumer_system.consumer_system_dto import ConsumerSystem
from libecalc.domain.infrastructure.energy_components.fuel_consumer.fuel_consumer import FuelConsumer
from libecalc.domain.infrastructure.energy_components.generator_set.generator_set_dto import GeneratorSet
Expand Down Expand Up @@ -73,24 +77,20 @@ def convert_expression_installation(self, data):
# Implement the conversion logic here
return convert_expression(data)

def check_user_defined_category(self, user_defined_category):
# Provide which value and context to make it easier for user to correct wrt mandatory changes.
if user_defined_category is not None:
if user_defined_category not in list(InstallationUserDefinedCategoryType):
raise ValueError(
f"CATEGORY: {user_defined_category} is not allowed for Installation with name {self.name}. Valid categories are: {[str(installation_user_defined_category.value) for installation_user_defined_category in InstallationUserDefinedCategoryType]}"
)

return user_defined_category

def check_fuel_consumers_or_venting_emitters_exist(self):
try:
if self.fuel_consumers or self.venting_emitters:
return self
except AttributeError:
raise ValueError(
f"Keywords are missing:\n It is required to specify at least one of the keywords "
f"{EcalcYamlKeywords.fuel_consumers}, {EcalcYamlKeywords.generator_sets} or {EcalcYamlKeywords.installation_venting_emitters} in the model.",
raise ComponentValidationException(
errors=[
ModelValidationError(
name=self.name,
message=f"Keywords are missing:\n It is required to specify at least one of the keywords "
f"{EcalcYamlKeywords.fuel_consumers}, {EcalcYamlKeywords.generator_sets} or "
f"{EcalcYamlKeywords.installation_venting_emitters} in the model.",
)
]
) from None

def get_graph(self) -> ComponentGraph:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from libecalc.common.logger import logger
from libecalc.common.time_utils import Periods
from libecalc.core.models.results.base import EnergyFunctionResult
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)
from libecalc.domain.infrastructure.energy_components.legacy_consumer.consumer_function.types import (
ConsumerFunctionType,
)
Expand Down Expand Up @@ -94,7 +98,14 @@ def extend(self, other) -> ConsumerFunctionResult:
if not isinstance(self, type(other)):
msg = "Mixing CONSUMER_SYSTEM with non-CONSUMER_SYSTEM is no longer supported."
logger.warning(msg)
raise ValueError(msg)
raise ComponentValidationException(
errors=[
ModelValidationError(
name=self.__repr_name__(),
message=msg,
)
]
)

for attribute, values in self.__dict__.items():
other_values = other.__getattribute__(attribute)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
from libecalc.common.time_utils import Periods
from libecalc.core.models.results import CompressorTrainResult, EnergyFunctionResult, PumpModelResult
from libecalc.core.result.results import ConsumerModelResult
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)
from libecalc.domain.infrastructure.energy_components.legacy_consumer.consumer_function.results import (
ConsumerFunctionResultBase,
)
Expand Down Expand Up @@ -147,9 +151,16 @@ def __init__(

def extend(self, other) -> ConsumerSystemConsumerFunctionResult:
if not isinstance(self, type(other)):
msg = f"{self.__repr_name__()} Mixing CONSUMER_SYSTEM with non-CONSUMER_SYSTEM is no longer supported."
msg = "Mixing CONSUMER_SYSTEM with non-CONSUMER_SYSTEM is no longer supported."
logger.warning(msg)
raise ValueError(msg)
raise ComponentValidationException(
errors=[
ModelValidationError(
name=self.__repr_name__(),
message=msg,
)
]
)

for attribute, values in self.__dict__.items():
other_values = other.__getattribute__(attribute)
Expand Down
12 changes: 11 additions & 1 deletion src/libecalc/domain/infrastructure/energy_components/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@

from libecalc.common.energy_usage_type import EnergyUsageType
from libecalc.common.time_utils import Period
from libecalc.domain.infrastructure.energy_components.component_validation_error import (
ComponentValidationException,
ModelValidationError,
)
from libecalc.dto.models import ConsumerFunction


def check_model_energy_usage_type(model_data: dict[Period, ConsumerFunction], energy_type: EnergyUsageType):
for model in model_data.values():
if model.energy_usage_type != energy_type:
raise ValueError(f"Model does not consume {energy_type.value}")
raise ComponentValidationException(
errors=[
ModelValidationError(
message=f"Model does not consume {energy_type.value}",
)
]
)
return model_data


Expand Down
Loading

0 comments on commit 1c3d201

Please sign in to comment.