From 0ee2c87a32236266c128d5448f6667cea947f0d6 Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Tue, 3 Feb 2026 13:57:29 +0100 Subject: [PATCH 1/6] refactor: add yaml process system top level --- .../yaml/yaml_types/components/yaml_asset.py | 16 ++++++++++++++++ .../yaml_types/components/yaml_installation.py | 6 ++++++ 2 files changed, 22 insertions(+) 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 17ad730365..5f9dba1629 100644 --- a/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py +++ b/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py @@ -4,6 +4,11 @@ from libecalc.common.string.string_utils import get_duplicates from libecalc.presentation.yaml.yaml_types import YamlBase from libecalc.presentation.yaml.yaml_types.components.yaml_installation import YamlInstallation +from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import ( + YamlCompressorStageProcessSystem, + YamlParallelProcessSystem, + YamlSerialProcessSystem, +) from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import YamlFacilityModel from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_fuel_type import YamlFuelType from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel, YamlFluidModel @@ -61,6 +66,9 @@ class YamlAsset(YamlBase): description="Defines variables used in an energy usage model by means of expressions or constants." "\n\n$ECALC_DOCS_KEYWORDS_URL/VARIABLES", ) + process_systems: YamlParallelProcessSystem | YamlSerialProcessSystem | YamlCompressorStageProcessSystem | None = ( + Field(None, title="PROCESS_SYSTEMS", description="Defines process systems to use in process simulations.") + ) installations: list[YamlInstallation] = Field( ..., title="INSTALLATIONS", @@ -104,6 +112,10 @@ def validate_unique_component_names(self, info: ValidationInfo): for venting_emitter in installation.venting_emitters or []: names.append(venting_emitter.name) + if installation.process_simulations is not None: + for process_simulation in installation.process_simulations: + names.append(process_simulation.name) + duplicated_names = get_duplicates(names) if len(duplicated_names) > 0: @@ -147,6 +159,10 @@ def validate_unique_references(self): for fuel_type in self.fuel_types: references.append(fuel_type.name) + if self.process_systems is not None: + for process_system in self.process_systems: + references.append(process_system.name) + if self.fluid_models is not None: references.extend(self.fluid_models.keys()) diff --git a/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py b/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py index fd20b2f06c..e88187575b 100644 --- a/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py +++ b/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py @@ -17,6 +17,7 @@ from libecalc.presentation.yaml.yaml_types.components.yaml_generator_set import ( YamlGeneratorSet, ) +from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import YamlProcessSimulation from libecalc.presentation.yaml.yaml_types.emitters.yaml_venting_emitter import ( YamlVentingEmitter, ) @@ -74,6 +75,11 @@ class YamlInstallation(YamlBase): title="VENTING_EMITTERS", description="Covers the direct emissions on the installation that are not consuming energy", ) + process_simulations: list[YamlProcessSimulation] | None = Field( + None, + title="PROCESS_SIMULATIONS", + description="Defines one or more process simulations to be run for the installation.", + ) @model_validator(mode="after") def check_some_consumer_or_emitter(self): From 2542fbe8eca993b29893e5e11602fb47a5cca732 Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:02:35 +0100 Subject: [PATCH 2/6] refactor: update yaml model --- .../presentation/yaml/yaml_keywords.py | 3 +++ .../yaml/yaml_models/pyyaml_yaml_model.py | 26 +++++++++++++++++++ .../yaml/yaml_types/components/yaml_asset.py | 23 +++++++++------- .../components/yaml_process_system.py | 11 ++++++++ 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/libecalc/presentation/yaml/yaml_keywords.py b/src/libecalc/presentation/yaml/yaml_keywords.py index 1981acb1b6..f39ff44757 100644 --- a/src/libecalc/presentation/yaml/yaml_keywords.py +++ b/src/libecalc/presentation/yaml/yaml_keywords.py @@ -188,6 +188,9 @@ class EcalcYamlKeywords: time_series_extrapolate_outside_defined = "EXTRAPOLATION" time_series_interpolation_type = "INTERPOLATION_TYPE" + process_systems = "PROCESS_SYSTEMS" + process_units = "PROCESS_UNITS" + start = "START" end = "END" date = "DATE" diff --git a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py index 749a9794d9..604cb5080d 100644 --- a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py +++ b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py @@ -28,6 +28,7 @@ from libecalc.presentation.yaml.yaml_node import YamlDict, YamlList from libecalc.presentation.yaml.yaml_types.components.yaml_asset import YamlAsset from libecalc.presentation.yaml.yaml_types.components.yaml_installation import YamlInstallation +from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import YamlProcessSystem, YamlProcessUnit from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import YamlFacilityModel from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_fuel_type import YamlFuelType from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel @@ -397,6 +398,31 @@ def installations(self) -> Iterable[YamlInstallation]: pass return installations + @property + def process_units(self) -> dict[str, YamlProcessUnit]: + process_units: dict[str, YamlProcessUnit] = {} + raw = self._get_yaml_dict_or_empty(EcalcYamlKeywords.process_units) + + for name, unit_data in raw.items(): + try: + process_units[name] = TypeAdapter(YamlProcessUnit).validate_python(unit_data) + except PydanticValidationError: + pass + return process_units + + @property + def process_systems(self) -> dict[str, YamlProcessSystem]: + process_systems: dict[str, YamlProcessSystem] = {} + raw = self._get_yaml_dict_or_empty(EcalcYamlKeywords.process_systems) + + for name, system_data in raw.items(): + try: + process_systems[name] = TypeAdapter(YamlProcessSystem).validate_python(system_data) + except PydanticValidationError: + pass + + return process_systems + @property def start(self) -> datetime.datetime | None: start_value = self._internal_datamodel.get(EcalcYamlKeywords.start) 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 5f9dba1629..1ab6a1e179 100644 --- a/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py +++ b/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py @@ -5,9 +5,8 @@ from libecalc.presentation.yaml.yaml_types import YamlBase from libecalc.presentation.yaml.yaml_types.components.yaml_installation import YamlInstallation from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import ( - YamlCompressorStageProcessSystem, - YamlParallelProcessSystem, - YamlSerialProcessSystem, + YamlProcessSystem, + YamlProcessUnit, ) from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import YamlFacilityModel from libecalc.presentation.yaml.yaml_types.fuel_type.yaml_fuel_type import YamlFuelType @@ -66,8 +65,15 @@ class YamlAsset(YamlBase): description="Defines variables used in an energy usage model by means of expressions or constants." "\n\n$ECALC_DOCS_KEYWORDS_URL/VARIABLES", ) - process_systems: YamlParallelProcessSystem | YamlSerialProcessSystem | YamlCompressorStageProcessSystem | None = ( - Field(None, title="PROCESS_SYSTEMS", description="Defines process systems to use in process simulations.") + process_units: dict[str, YamlProcessUnit] = Field( + default_factory=dict, + title="PROCESS_UNITS", + description="Defines process units used in PROCESS_SYSTEMS.", + ) + process_systems: dict[str, YamlProcessSystem] = Field( + default_factory=dict, + title="PROCESS_SYSTEMS", + description="Defines process systems to use in process simulations.", ) installations: list[YamlInstallation] = Field( ..., @@ -113,8 +119,8 @@ def validate_unique_component_names(self, info: ValidationInfo): names.append(venting_emitter.name) if installation.process_simulations is not None: - for process_simulation in installation.process_simulations: - names.append(process_simulation.name) + if installation.process_simulations is not None: + names.extend(installation.process_simulations.keys()) duplicated_names = get_duplicates(names) @@ -160,8 +166,7 @@ def validate_unique_references(self): references.append(fuel_type.name) if self.process_systems is not None: - for process_system in self.process_systems: - references.append(process_system.name) + references.extend(self.process_systems.keys()) if self.fluid_models is not None: references.extend(self.fluid_models.keys()) diff --git a/src/libecalc/presentation/yaml/yaml_types/components/yaml_process_system.py b/src/libecalc/presentation/yaml/yaml_types/components/yaml_process_system.py index 268e7f3c62..10b7a86268 100644 --- a/src/libecalc/presentation/yaml/yaml_types/components/yaml_process_system.py +++ b/src/libecalc/presentation/yaml/yaml_types/components/yaml_process_system.py @@ -119,3 +119,14 @@ class YamlProcessSimulation(YamlBase): name: str target: YamlParallelProcessSystem | YamlSerialProcessSystem | ProcessSystemReference stream_distribution: YamlStreamDistribution + + +YamlProcessUnit = Annotated[ + YamlCompressor, + Field(discriminator="type"), +] + +YamlProcessSystem = Annotated[ + YamlParallelProcessSystem | YamlSerialProcessSystem | YamlCompressorStageProcessSystem, + Field(discriminator="type"), +] From f691dd9d99a5e092caf0613a6014466aff0c1d11 Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:38:28 +0100 Subject: [PATCH 3/6] refactor: process simulations to separate section --- .../yaml/yaml_types/components/yaml_asset.py | 17 +++++++++++++---- .../yaml_types/components/yaml_installation.py | 6 ------ 2 files changed, 13 insertions(+), 10 deletions(-) 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 1ab6a1e179..bb8dd68701 100644 --- a/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py +++ b/src/libecalc/presentation/yaml/yaml_types/components/yaml_asset.py @@ -5,6 +5,7 @@ from libecalc.presentation.yaml.yaml_types import YamlBase from libecalc.presentation.yaml.yaml_types.components.yaml_installation import YamlInstallation from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import ( + YamlProcessSimulation, YamlProcessSystem, YamlProcessUnit, ) @@ -75,6 +76,11 @@ class YamlAsset(YamlBase): title="PROCESS_SYSTEMS", description="Defines process systems to use in process simulations.", ) + process_simulations: list[YamlProcessSimulation] = Field( + default_factory=list, + title="PROCESS_SIMULATIONS", + description="Defines one or more process simulations to be run.", + ) installations: list[YamlInstallation] = Field( ..., title="INSTALLATIONS", @@ -118,10 +124,6 @@ def validate_unique_component_names(self, info: ValidationInfo): for venting_emitter in installation.venting_emitters or []: names.append(venting_emitter.name) - if installation.process_simulations is not None: - if installation.process_simulations is not None: - names.extend(installation.process_simulations.keys()) - duplicated_names = get_duplicates(names) if len(duplicated_names) > 0: @@ -168,6 +170,13 @@ def validate_unique_references(self): if self.process_systems is not None: references.extend(self.process_systems.keys()) + if self.process_units is not None: + references.extend(self.process_units.keys()) + + if self.process_simulations is not None: + for process_simulation in self.process_simulations: + references.append(process_simulation.name) + if self.fluid_models is not None: references.extend(self.fluid_models.keys()) diff --git a/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py b/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py index e88187575b..fd20b2f06c 100644 --- a/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py +++ b/src/libecalc/presentation/yaml/yaml_types/components/yaml_installation.py @@ -17,7 +17,6 @@ from libecalc.presentation.yaml.yaml_types.components.yaml_generator_set import ( YamlGeneratorSet, ) -from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import YamlProcessSimulation from libecalc.presentation.yaml.yaml_types.emitters.yaml_venting_emitter import ( YamlVentingEmitter, ) @@ -75,11 +74,6 @@ class YamlInstallation(YamlBase): title="VENTING_EMITTERS", description="Covers the direct emissions on the installation that are not consuming energy", ) - process_simulations: list[YamlProcessSimulation] | None = Field( - None, - title="PROCESS_SIMULATIONS", - description="Defines one or more process simulations to be run for the installation.", - ) @model_validator(mode="after") def check_some_consumer_or_emitter(self): From 7a3ee378f8700a8091a67778ffe7f9a876ee1c97 Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:18:31 +0100 Subject: [PATCH 4/6] refactor: remove yaml process yaml keywords --- .../presentation/yaml/yaml_keywords.py | 3 - .../yaml/yaml_models/pyyaml_yaml_model.py | 58 +++++++++++++++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/libecalc/presentation/yaml/yaml_keywords.py b/src/libecalc/presentation/yaml/yaml_keywords.py index f39ff44757..1981acb1b6 100644 --- a/src/libecalc/presentation/yaml/yaml_keywords.py +++ b/src/libecalc/presentation/yaml/yaml_keywords.py @@ -188,9 +188,6 @@ class EcalcYamlKeywords: time_series_extrapolate_outside_defined = "EXTRAPOLATION" time_series_interpolation_type = "INTERPOLATION_TYPE" - process_systems = "PROCESS_SYSTEMS" - process_units = "PROCESS_UNITS" - start = "START" end = "END" date = "DATE" diff --git a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py index 604cb5080d..b4f3d0bff2 100644 --- a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py +++ b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py @@ -28,15 +28,27 @@ from libecalc.presentation.yaml.yaml_node import YamlDict, YamlList from libecalc.presentation.yaml.yaml_types.components.yaml_asset import YamlAsset from libecalc.presentation.yaml.yaml_types.components.yaml_installation import YamlInstallation -from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import YamlProcessSystem, YamlProcessUnit +from libecalc.presentation.yaml.yaml_types.components.yaml_process_system import ( + YamlProcessSimulation, + YamlProcessSystem, + YamlProcessUnit, +) from libecalc.presentation.yaml.yaml_types.facility_model.yaml_facility_model import YamlFacilityModel 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, YamlFluidModel +from libecalc.presentation.yaml.yaml_types.streams.yaml_inlet_stream import YamlInletStream from libecalc.presentation.yaml.yaml_types.time_series.yaml_time_series import YamlTimeSeriesCollection from libecalc.presentation.yaml.yaml_types.yaml_default_datetime import YamlDefaultDatetime from libecalc.presentation.yaml.yaml_types.yaml_variable import YamlVariable, YamlVariableReferenceId, YamlVariables from libecalc.presentation.yaml.yaml_validation_context import YamlModelValidationContext +# Top-level YAML keywords used only by experimental/new sections +_PROCESS_UNITS_KEY = "PROCESS_UNITS" +_PROCESS_SYSTEMS_KEY = "PROCESS_SYSTEMS" +_INLET_STREAMS_KEY = "INLET_STREAMS" +_FLUID_MODELS_KEY = "FLUID_MODELS" +_PROCESS_SIMULATIONS_KEY = "PROCESS_SIMULATIONS" + dt_adapter = TypeAdapter(datetime.datetime) @@ -398,10 +410,34 @@ def installations(self) -> Iterable[YamlInstallation]: pass return installations + @property + def inlet_streams(self) -> dict[str, YamlInletStream]: + inlet_streams: dict[str, YamlInletStream] = {} + raw = self._get_yaml_dict_or_empty(_INLET_STREAMS_KEY) + + for name, stream in raw.items(): + try: + inlet_streams[name] = TypeAdapter(YamlInletStream).validate_python(stream) + except PydanticValidationError: + pass + return inlet_streams + + @property + def fluid_models(self) -> dict[str, YamlFluidModel]: + fluid_models: dict[str, YamlFluidModel] = {} + raw = self._get_yaml_dict_or_empty(_FLUID_MODELS_KEY) + + for name, fluid_model in raw.items(): + try: + fluid_models[name] = TypeAdapter(YamlFluidModel).validate_python(fluid_model) + except PydanticValidationError: + pass + return fluid_models + @property def process_units(self) -> dict[str, YamlProcessUnit]: process_units: dict[str, YamlProcessUnit] = {} - raw = self._get_yaml_dict_or_empty(EcalcYamlKeywords.process_units) + raw = self._get_yaml_dict_or_empty(_PROCESS_UNITS_KEY) for name, unit_data in raw.items(): try: @@ -413,16 +449,26 @@ def process_units(self) -> dict[str, YamlProcessUnit]: @property def process_systems(self) -> dict[str, YamlProcessSystem]: process_systems: dict[str, YamlProcessSystem] = {} - raw = self._get_yaml_dict_or_empty(EcalcYamlKeywords.process_systems) + raw = self._get_yaml_dict_or_empty(_PROCESS_SYSTEMS_KEY) - for name, system_data in raw.items(): + for name, process_system in raw.items(): try: - process_systems[name] = TypeAdapter(YamlProcessSystem).validate_python(system_data) + process_systems[name] = TypeAdapter(YamlProcessSystem).validate_python(process_system) except PydanticValidationError: pass return process_systems + @property + def process_simulations(self) -> list[YamlProcessSimulation]: + process_simulations: list[YamlProcessSimulation] = [] + for process_simulation in self._get_yaml_list_or_empty(_PROCESS_SIMULATIONS_KEY): + try: + process_simulations.append(TypeAdapter(YamlProcessSimulation).validate_python(process_simulation)) + except PydanticValidationError: + pass + return process_simulations + @property def start(self) -> datetime.datetime | None: start_value = self._internal_datamodel.get(EcalcYamlKeywords.start) From 4d7adcf7fb9b753be77e78eeb11e0b77a1a8660f Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:20:58 +0100 Subject: [PATCH 5/6] chore: type adapter --- .../presentation/yaml/yaml_models/pyyaml_yaml_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py index b4f3d0bff2..8c3d6dc096 100644 --- a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py +++ b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py @@ -414,10 +414,10 @@ def installations(self) -> Iterable[YamlInstallation]: def inlet_streams(self) -> dict[str, YamlInletStream]: inlet_streams: dict[str, YamlInletStream] = {} raw = self._get_yaml_dict_or_empty(_INLET_STREAMS_KEY) - + adapter = TypeAdapter(YamlInletStream) for name, stream in raw.items(): try: - inlet_streams[name] = TypeAdapter(YamlInletStream).validate_python(stream) + inlet_streams[name] = adapter.validate_python(stream) except PydanticValidationError: pass return inlet_streams From a107376d3474824486cf1344e88030406a6a72df Mon Sep 17 00:00:00 2001 From: Frode Helgetun Krogh <70878501+frodehk@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:23:23 +0100 Subject: [PATCH 6/6] chore: type adapter --- .../presentation/yaml/yaml_models/pyyaml_yaml_model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py index 8c3d6dc096..c2ba675fed 100644 --- a/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py +++ b/src/libecalc/presentation/yaml/yaml_models/pyyaml_yaml_model.py @@ -415,6 +415,7 @@ def inlet_streams(self) -> dict[str, YamlInletStream]: inlet_streams: dict[str, YamlInletStream] = {} raw = self._get_yaml_dict_or_empty(_INLET_STREAMS_KEY) adapter = TypeAdapter(YamlInletStream) + for name, stream in raw.items(): try: inlet_streams[name] = adapter.validate_python(stream) @@ -462,9 +463,10 @@ def process_systems(self) -> dict[str, YamlProcessSystem]: @property def process_simulations(self) -> list[YamlProcessSimulation]: process_simulations: list[YamlProcessSimulation] = [] + adapter = TypeAdapter(YamlProcessSimulation) for process_simulation in self._get_yaml_list_or_empty(_PROCESS_SIMULATIONS_KEY): try: - process_simulations.append(TypeAdapter(YamlProcessSimulation).validate_python(process_simulation)) + process_simulations.append(adapter.validate_python(process_simulation)) except PydanticValidationError: pass return process_simulations