Skip to content

Commit

Permalink
refactor: exporter queries
Browse files Browse the repository at this point in the history
The exporter knew too much about the specifics. This commit implements an
interface to get specific sets of results, which basically is the query.
What's left in the queries is alignment of results and aggregation of
attributes.
  • Loading branch information
jsolaas committed Oct 15, 2024
1 parent 2760c05 commit 8d35f85
Show file tree
Hide file tree
Showing 17 changed files with 771 additions and 634 deletions.
3 changes: 2 additions & 1 deletion src/ecalc_cli/io/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from libecalc.presentation.exporter.exporter import Exporter
from libecalc.presentation.exporter.formatters.formatter import CSVFormatter
from libecalc.presentation.exporter.handlers.handler import MultiFileHandler
from libecalc.presentation.exporter.infrastructure import ExportableGraphResult
from libecalc.presentation.flow_diagram.EcalcModelMapper import EcalcModelMapper
from libecalc.presentation.json_result.result import (
EcalcModelResult as EcalcModelResultDTO,
Expand Down Expand Up @@ -152,7 +153,7 @@ def export_tsv(
] # last step is always added as a STOP, and does infer the end of the time vector

prognosis_filter = config.filter(frequency=frequency)
result = prognosis_filter.filter(results, resampled_timevector)
result = prognosis_filter.filter(ExportableGraphResult(results), resampled_timevector)

row_based_data: Dict[str, List[str]] = CSVFormatter(separation_character="\t").format_groups(result)

Expand Down
5 changes: 4 additions & 1 deletion src/libecalc/common/temporal_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, Generic, Iterator, Tuple, TypeVar
from typing import Dict, Generic, Iterable, Iterator, Tuple, TypeVar

from libecalc.common.time_utils import Period

Expand All @@ -26,6 +26,9 @@ def __init__(self, data: Dict[datetime, ModelType]):
for start_time, end_time, model in zip(start_times, end_times, data.values())
]

def get_periods(self) -> Iterable[Period]:
return [model.period for model in self.models]

def items(self) -> Iterator[Tuple[Period, ModelType]]:
return ((model.period, model.model) for model in self.models)

Expand Down
2 changes: 1 addition & 1 deletion src/libecalc/common/time_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class Periods:
def create_periods(cls, times: List[datetime], include_before: bool = True, include_after: bool = True) -> Periods:
"""
Create periods from the provided datetimes
:param times: the datetimes to create periods from
:param times: the sorted times to create periods from
:param include_before: whether to add a period that ends with the first provided datetime, i.e. define a period
before the earliest provided datetime.
:param include_after: whether to add a period that starts with the last provided datetime, i.e. define a period
Expand Down
2 changes: 1 addition & 1 deletion src/libecalc/common/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def evaluate(self, expression: Union[Expression, Dict[datetime, Expression], Tem
if isinstance(expression, Expression):
return expression.evaluate(variables=self.variables, fill_length=len(self.get_time_vector()))
elif isinstance(expression, dict):
return self._evaluate_temporal(temporal_expression=TemporalModel[expression])
return self._evaluate_temporal(temporal_expression=TemporalModel(expression))
elif isinstance(expression, TemporalModel):
return self._evaluate_temporal(temporal_expression=expression)

Expand Down
13 changes: 13 additions & 0 deletions src/libecalc/examples/simple/output/model.csv

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/libecalc/fixtures/cases/ltp_export/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from libecalc.dto import Asset, Installation
from libecalc.presentation.exporter.configs.configs import LTPConfig
from libecalc.presentation.exporter.dto.dtos import FilteredResult
from libecalc.presentation.exporter.infrastructure import ExportableGraphResult


def get_consumption(
Expand All @@ -28,7 +29,7 @@ def get_consumption(
)

ltp_filter = LTPConfig.filter(frequency=Frequency.YEAR)
ltp_result = ltp_filter.filter(graph_result, time_vector)
ltp_result = ltp_filter.filter(ExportableGraphResult(graph_result), time_vector)

return ltp_result

Expand Down
14 changes: 6 additions & 8 deletions src/libecalc/presentation/exporter/aggregators.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import abc
from typing import List

from libecalc.application.graph_result import GraphResult
from libecalc.common.component_type import ComponentType
from libecalc.common.logger import logger
from libecalc.common.time_utils import Frequency
from libecalc.presentation.exporter.appliers import Applier
from libecalc.presentation.exporter.domain.exportable import ExportableSet, ExportableType
from libecalc.presentation.exporter.dto.dtos import GroupedQueryResult, QueryResult


class Aggregator(abc.ABC):
@abc.abstractmethod
def aggregate(
self,
energy_calculator_result: GraphResult,
energy_calculator_result: ExportableSet,
) -> List[GroupedQueryResult]:
"""Each entry in this list will be handled separately
Should ideally only work on one level in the hierarchy, more than one level
Expand Down Expand Up @@ -50,7 +49,7 @@ def __init__(self, frequency: Frequency, appliers: List[Applier]):

def aggregate(
self,
energy_calculator_result: GraphResult,
energy_calculator_result: ExportableSet,
) -> List[GroupedQueryResult]:
"""Aggregates data at installation level.
Expand All @@ -71,12 +70,11 @@ def aggregate(
:return:
"""
aggregated_installation_results: List[GroupedQueryResult] = []
for installation_id in energy_calculator_result.graph.get_nodes_of_type(ComponentType.INSTALLATION):
installation_graph = energy_calculator_result.get_subgraph(installation_id)
installation_name = installation_graph.graph.get_node_info(installation_graph.graph.root).name
for installation in energy_calculator_result.get_from_type(ExportableType.INSTALLATION):
installation_name = installation.get_name()
single_results: List[QueryResult] = []
for applier in self.appliers:
applier_result = applier.apply(installation_graph, self.frequency)
applier_result = applier.apply(installation, self.frequency)

if applier_result:
single_results.append(applier_result)
Expand Down
14 changes: 7 additions & 7 deletions src/libecalc/presentation/exporter/appliers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@
from datetime import datetime
from typing import Any, Dict, Optional

from libecalc.application.graph_result import GraphResult
from libecalc.common.math.numbers import Numbers
from libecalc.common.time_utils import Frequency
from libecalc.common.units import Unit
from libecalc.presentation.exporter.configs import configs
from libecalc.presentation.exporter.domain.exportable import Exportable
from libecalc.presentation.exporter.dto.dtos import QueryResult
from libecalc.presentation.exporter.queries import Query


class Modifier(abc.ABC):
def __init__(self, modifier: Optional["Modifier"] = None):
def __init__(self, before_modifier: Optional["Modifier"] = None):
"""Modifiers can be chained, just make sure in/output of the modifiers are compatible.
Modifier is completely private, to not be inherited by children
:param modifier:
:param before_modifier:
"""
self.__modifier = modifier
self.__before_modifier = before_modifier

def modify(self, data: Dict[datetime, float]) -> Dict[datetime, Any]:
"""Public modify() method that is used in order to be able
chain modifiers, if needed.
:param data:
:return:
"""
if self.__modifier is not None:
return self._modify(self.__modifier._modify(data))
if self.__before_modifier is not None:
return self._modify(self.__before_modifier.modify(data))

return self._modify(data)

Expand Down Expand Up @@ -88,7 +88,7 @@ def __init__(

def apply(
self,
installation_graph: GraphResult,
installation_graph: Exportable,
frequency: Frequency,
) -> Optional[QueryResult]:
values = self.query.query(installation_graph, self.unit, frequency)
Expand Down
18 changes: 8 additions & 10 deletions src/libecalc/presentation/exporter/configs/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
from libecalc.presentation.exporter.filters import Filter
from libecalc.presentation.exporter.generators import Generator, TimeIndexGenerator
from libecalc.presentation.exporter.queries import (
ElConsumerPowerConsumptionQuery,
ElectricityGeneratedQuery,
EmissionQuery,
FuelConsumerPowerConsumptionQuery,
FuelQuery,
MaxUsageFromShoreQuery,
PowerSupplyOnshoreQuery,
VolumeQuery,
PowerConsumptionQuery,
StorageVolumeQuery,
)

"""
Expand Down Expand Up @@ -605,7 +603,7 @@ def aggregator(frequency: Frequency) -> Aggregator:
name="loadedAndStoredOil", # TODO: Get correct Centuries name here
title="Total Oil Loaded/Stored",
unit=Unit.STANDARD_CUBIC_METER,
query=VolumeQuery(
query=StorageVolumeQuery(
installation_category="FIXED",
consumer_categories=["LOADING"],
),
Expand All @@ -623,7 +621,7 @@ def aggregator(frequency: Frequency) -> Aggregator:
name="gasTurbineCompressorConsumption",
title="Total Power Consumed By Gas-Turbine Driven Compressors",
unit=Unit.GIGA_WATT_HOURS,
query=FuelConsumerPowerConsumptionQuery(
query=PowerConsumptionQuery(
installation_category="FIXED",
consumer_categories=["GAS-DRIVEN-COMPRESSOR"],
),
Expand All @@ -632,7 +630,7 @@ def aggregator(frequency: Frequency) -> Aggregator:
name="offshoreWindConsumption",
title="Total Electricity Consumed From Offshore Wind",
unit=Unit.GIGA_WATT_HOURS,
query=ElConsumerPowerConsumptionQuery(
query=PowerConsumptionQuery(
installation_category="FIXED",
consumer_categories=["OFFSHORE-WIND"],
),
Expand All @@ -642,15 +640,15 @@ def aggregator(frequency: Frequency) -> Aggregator:
name="fromShoreConsumption",
title="Total Electricity Consumed From Power-From-Shore",
unit=Unit.GIGA_WATT_HOURS,
query=ElectricityGeneratedQuery(
query=PowerConsumptionQuery(
producer_categories=["POWER-FROM-SHORE"],
),
),
Applier(
name="powerSupplyOnshore",
title="Power Supply Onshore",
unit=Unit.GIGA_WATT_HOURS,
query=PowerSupplyOnshoreQuery(
query=ElectricityGeneratedQuery(
producer_categories=["POWER-FROM-SHORE"],
),
),
Expand All @@ -666,7 +664,7 @@ def aggregator(frequency: Frequency) -> Aggregator:
name="steamTurbineGeneratorConsumption",
title="Total Electricity Consumed From Steam Turbine Generators",
unit=Unit.GIGA_WATT_HOURS,
query=ElConsumerPowerConsumptionQuery(
query=PowerConsumptionQuery(
installation_category="FIXED",
consumer_categories=["STEAM-TURBINE-GENERATOR"],
),
Expand Down
74 changes: 74 additions & 0 deletions src/libecalc/presentation/exporter/domain/exportable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Iterable, Iterator, List, Optional, Tuple

from libecalc.common.units import Unit


class ExportableType(str, Enum):
INSTALLATION = "INSTALLATION"


class ConsumptionType(str, Enum):
FUEL = "FUEL"
ELECTRICITY = "ELECTRICITY"


@dataclass
class AttributeMeta:
fuel_category: Optional[str]
consumer_category: Optional[str]
producer_category: Optional[str] = None
emission_type: Optional[str] = None


class Attribute(ABC):
@abstractmethod
def datapoints(self) -> Iterable[Tuple[datetime, float]]: ...

@abstractmethod
def get_meta(self) -> AttributeMeta: ...


@dataclass
class AttributeSet(ABC):
attributes: List[Attribute]

def __iter__(self) -> Iterator[Attribute]:
return self.attributes.__iter__()


class Exportable(ABC):
@abstractmethod
def get_name(self) -> str: ...

@abstractmethod
def get_category(self) -> str: ...

@abstractmethod
def get_timesteps(self) -> List[datetime]: ...

@abstractmethod
def get_fuel_consumption(self) -> AttributeSet: ...

@abstractmethod
def get_power_consumption(self, unit: Unit) -> AttributeSet: ...

@abstractmethod
def get_emissions(self, unit: Unit) -> AttributeSet: ...

@abstractmethod
def get_electricity_production(self, unit: Unit) -> AttributeSet: ...

@abstractmethod
def get_maximum_electricity_production(self, unit: Unit) -> AttributeSet: ...

@abstractmethod
def get_storage_volumes(self, unit: Unit) -> AttributeSet: ...


class ExportableSet(ABC):
@abstractmethod
def get_from_type(self, exportable_type: ExportableType) -> List[Exportable]: ...
4 changes: 2 additions & 2 deletions src/libecalc/presentation/exporter/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from datetime import datetime
from typing import List

from libecalc.application.graph_result import GraphResult
from libecalc.presentation.exporter.aggregators import Aggregator
from libecalc.presentation.exporter.domain.exportable import ExportableSet
from libecalc.presentation.exporter.dto.dtos import FilteredResult
from libecalc.presentation.exporter.generators import Generator

Expand All @@ -27,7 +27,7 @@ class Filter:

def filter(
self,
energy_calculator_result: GraphResult,
energy_calculator_result: ExportableSet,
time_vector: List[datetime],
) -> FilteredResult:
data_series_collection = [generator.generate(time_vector) for generator in self.generators]
Expand Down
Loading

0 comments on commit 8d35f85

Please sign in to comment.