3
3
4
4
"""Configuration for microgrids."""
5
5
6
+ import re
6
7
import tomllib
7
8
from dataclasses import dataclass
8
9
from typing import Any , Literal , cast , get_args
@@ -30,20 +31,60 @@ class ComponentTypeConfig:
30
31
component : list [int ] | None = None
31
32
"""List of component IDs for this component."""
32
33
33
- formula : str = ""
34
+ formula : dict [ str , str ] | None = None
34
35
"""Formula to calculate the power of this component."""
35
36
36
37
def __post_init__ (self ) -> None :
37
38
"""Set the default formula if none is provided."""
38
- if not self .formula :
39
- self .formula = self ._default_formula ()
40
-
41
- def cids (self ) -> list [int ]:
39
+ self .formula = self .formula or {}
40
+ if "AC_ACTIVE_POWER" not in self .formula :
41
+ self .formula ["AC_ACTIVE_POWER" ] = "+" .join (
42
+ [f"#{ cid } " for cid in self ._default_cids ()]
43
+ )
44
+ if self .component_type == "battery" and "BATTERY_SOC_PCT" not in self .formula :
45
+ if self .component :
46
+ cids = self .component
47
+ form = "+" .join ([f"#{ cid } " for cid in cids ])
48
+ form = f"({ form } )/({ len (cids )} )"
49
+ self .formula ["BATTERY_SOC_PCT" ] = form
50
+
51
+ def cids (self , metric : str = "" ) -> list [int ]:
42
52
"""Get component IDs for this component.
43
53
44
54
By default, the meter IDs are returned if available, otherwise the inverter IDs.
45
55
For components without meters or inverters, the component IDs are returned.
46
56
57
+ If a metric is provided, the component IDs are extracted from the formula.
58
+
59
+ Args:
60
+ metric: Metric name of the formula.
61
+
62
+ Returns:
63
+ List of component IDs for this component.
64
+
65
+ Raises:
66
+ ValueError: If the metric is not supported or improperly set.
67
+ """
68
+ if metric :
69
+ if not isinstance (self .formula , dict ):
70
+ raise ValueError ("Formula must be a dictionary." )
71
+ formula = self .formula .get (metric )
72
+ if not formula :
73
+ raise ValueError (
74
+ f"{ metric } does not have a formula for { self .component_type } "
75
+ )
76
+ # Extract component IDs from the formula which are given as e.g. #123
77
+ pattern = r"#(\d+)"
78
+ return [int (e ) for e in re .findall (pattern , self .formula [metric ])]
79
+
80
+ return self ._default_cids ()
81
+
82
+ def _default_cids (self ) -> list [int ]:
83
+ """Get the default component IDs for this component.
84
+
85
+ If available, the meter IDs are returned, otherwise the inverter IDs.
86
+ For components without meters or inverters, the component IDs are returned.
87
+
47
88
Returns:
48
89
List of component IDs for this component.
49
90
@@ -59,14 +100,6 @@ def cids(self) -> list[int]:
59
100
60
101
raise ValueError (f"No IDs available for { self .component_type } " )
61
102
62
- def _default_formula (self ) -> str :
63
- """Return the default formula for this component."""
64
- return "+" .join ([f"#{ cid } " for cid in self .cids ()])
65
-
66
- def has_formula_for (self , metric : str ) -> bool :
67
- """Return whether this formula is valid for a metric."""
68
- return metric in ["AC_ACTIVE_POWER" , "AC_REACTIVE_POWER" ]
69
-
70
103
@classmethod
71
104
def is_valid_type (cls , ctype : str ) -> bool :
72
105
"""Check if `ctype` is a valid enum value."""
@@ -191,7 +224,10 @@ def component_types(self) -> list[str]:
191
224
return list (self ._component_types_cfg .keys ())
192
225
193
226
def component_type_ids (
194
- self , component_type : str , component_category : str | None = None
227
+ self ,
228
+ component_type : str ,
229
+ component_category : str | None = None ,
230
+ metric : str = "" ,
195
231
) -> list [int ]:
196
232
"""Get a list of all component IDs for a component type.
197
233
@@ -200,6 +236,7 @@ def component_type_ids(
200
236
component_category: Specific category of component IDs to retrieve
201
237
(e.g., "meter", "inverter", or "component"). If not provided,
202
238
the default logic is used.
239
+ metric: Metric name of the formula if CIDs should be extracted from the formula.
203
240
204
241
Returns:
205
242
List of component IDs for this component type.
@@ -222,7 +259,7 @@ def component_type_ids(
222
259
category_ids = cast (list [int ], getattr (cfg , component_category , []))
223
260
return category_ids
224
261
225
- return cfg .cids ()
262
+ return cfg .cids (metric )
226
263
227
264
def formula (self , component_type : str , metric : str ) -> str :
228
265
"""Get the formula for a component type.
@@ -235,16 +272,18 @@ def formula(self, component_type: str, metric: str) -> str:
235
272
Formula to be used for this aggregated component as string.
236
273
237
274
Raises:
238
- ValueError: If the component type is unknown.
275
+ ValueError: If the component type is unknown or formula is missing .
239
276
"""
240
277
cfg = self ._component_types_cfg .get (component_type )
241
278
if not cfg :
242
279
raise ValueError (f"{ component_type } not found in config." )
280
+ if cfg .formula is None :
281
+ raise ValueError (f"No formula set for { component_type } " )
282
+ formula = cfg .formula .get (metric )
283
+ if not formula :
284
+ raise ValueError (f"{ component_type } is missing formula for { metric } " )
243
285
244
- if not cfg .has_formula_for (metric ):
245
- raise ValueError (f"{ metric } not supported for { component_type } " )
246
-
247
- return cfg .formula
286
+ return formula
248
287
249
288
@staticmethod
250
289
def load_configs (* paths : str ) -> dict [str , "MicrogridConfig" ]:
0 commit comments