Skip to content

Commit 5926189

Browse files
Create fallback formulas for the PvPool.power
Change PVPowerFormula to get primary and fallback of the given components. For the fallback comonents sub formulas are created This makes sure that both main and fallback formulas have the same behaviour and less complexity. Signed-off-by: Elzbieta Kotulska <[email protected]>
1 parent a1ff91a commit 5926189

File tree

2 files changed

+79
-11
lines changed

2 files changed

+79
-11
lines changed

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_formula_generator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class FormulaGeneratorConfig:
4949
component_ids: abc.Set[int] | None = None
5050
"""The component IDs to use for generating the formula."""
5151

52+
allow_fallback: bool = True
53+
5254

5355
class FormulaGenerator(ABC, Generic[QuantityT]):
5456
"""A class for generating formulas from the component graph."""

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_pv_power_formula.py

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55

66
import logging
77

8-
from frequenz.client.microgrid import ComponentCategory, ComponentMetricId
8+
from frequenz.client.microgrid import Component, ComponentCategory, ComponentMetricId
99

1010
from ....microgrid import connection_manager
1111
from ..._quantities import Power
1212
from .._formula_engine import FormulaEngine
13-
from ._formula_generator import NON_EXISTING_COMPONENT_ID, FormulaGenerator
13+
from ._fallback_formula_metric_fetcher import FallbackFormulaMetricFetcher
14+
from ._formula_generator import (
15+
NON_EXISTING_COMPONENT_ID,
16+
FormulaGenerator,
17+
FormulaGeneratorConfig,
18+
)
1419

1520
_logger = logging.getLogger(__name__)
1621

@@ -58,18 +63,79 @@ def generate( # noqa: DOC502
5863
# frequency as the other streams. So we subscribe with a non-existing
5964
# component id, just to get a `None` message at the resampling interval.
6065
builder.push_component_metric(
61-
NON_EXISTING_COMPONENT_ID, nones_are_zeros=True
66+
NON_EXISTING_COMPONENT_ID,
67+
nones_are_zeros=True,
6268
)
6369
return builder.build()
6470

65-
for idx, component in enumerate(pv_components):
66-
if idx > 0:
67-
builder.push_oper("+")
71+
if self._config.allow_fallback:
72+
fallbacks = self._get_fallback_formulas(pv_components)
6873

69-
# should only be the case if the component is not a meter
70-
builder.push_component_metric(
71-
component.component_id,
72-
nones_are_zeros=component.category != ComponentCategory.METER,
73-
)
74+
for idx, (primary_component, fallback_formula) in enumerate(
75+
fallbacks.items()
76+
):
77+
if idx > 0:
78+
builder.push_oper("+")
79+
80+
builder.push_component_metric(
81+
primary_component.component_id,
82+
nones_are_zeros=(
83+
primary_component.category != ComponentCategory.METER
84+
),
85+
fallback=fallback_formula,
86+
)
87+
else:
88+
for idx, component in enumerate(pv_components):
89+
if idx > 0:
90+
builder.push_oper("+")
91+
92+
builder.push_component_metric(
93+
component.component_id,
94+
nones_are_zeros=component.category != ComponentCategory.METER,
95+
)
7496

7597
return builder.build()
98+
99+
def _get_fallback_formulas(
100+
self, components: set[Component]
101+
) -> dict[Component, FallbackFormulaMetricFetcher[Power] | None]:
102+
"""Find primary and fallback components and create fallback formulas.
103+
104+
The primary component is the one that will be used to calculate the PV power.
105+
If it is not available, the fallback formula will be used instead.
106+
Fallback formulas calculate the PV power using the fallback components.
107+
Fallback formulas are wrapped in `FallbackFormulaMetricFetcher`.
108+
109+
Args:
110+
components: The PV components.
111+
112+
Returns:
113+
A dictionary mapping primary components to their corresponding
114+
FallbackFormulaMetricFetcher.
115+
"""
116+
fallbacks = self._get_metric_fallback_components(components)
117+
118+
fallback_formulas: dict[
119+
Component, FallbackFormulaMetricFetcher[Power] | None
120+
] = {}
121+
for primary_component, fallback_components in fallbacks.items():
122+
if len(fallback_components) == 0:
123+
fallback_formulas[primary_component] = None
124+
continue
125+
fallback_ids = [c.component_id for c in fallback_components]
126+
127+
generator = PVPowerFormula(
128+
f"{self._namespace}_fallback_{fallback_ids}",
129+
self._channel_registry,
130+
self._resampler_subscription_sender,
131+
FormulaGeneratorConfig(
132+
component_ids=set(fallback_ids),
133+
allow_fallback=False,
134+
),
135+
)
136+
137+
fallback_formulas[primary_component] = FallbackFormulaMetricFetcher(
138+
generator
139+
)
140+
141+
return fallback_formulas

0 commit comments

Comments
 (0)