From a15802ed4a0654242a3b889b04bc176fc9b73419 Mon Sep 17 00:00:00 2001 From: Mohammad Tayyab Date: Tue, 10 Mar 2026 16:03:43 +0100 Subject: [PATCH 1/3] feat: add default formulas for AC energy metrics Extend the default formula handling to also cover AC_ENERGY_ACTIVE, AC_ENERGY_ACTIVE_CONSUMED, and AC_ENERGY_ACTIVE_DELIVERED. Signed-off-by: Mohammad Tayyab --- src/frequenz/gridpool/_microgrid_config.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/frequenz/gridpool/_microgrid_config.py b/src/frequenz/gridpool/_microgrid_config.py index a1a4fcc..f934ec6 100644 --- a/src/frequenz/gridpool/_microgrid_config.py +++ b/src/frequenz/gridpool/_microgrid_config.py @@ -47,15 +47,16 @@ def __post_init__(self) -> None: "please use 'AC_POWER_ACTIVE' instead." ) - if "AC_POWER_ACTIVE" not in self.formula: - if "AC_ACTIVE_POWER" in self.formula: - self.formula["AC_POWER_ACTIVE"] = self.formula["AC_ACTIVE_POWER"] - else: - _logger.warning( - "ComponentTypeConfig: No formula provided for 'AC_POWER_ACTIVE', " - "using default summation formula." - ) - self.formula["AC_POWER_ACTIVE"] = "+".join( + defaults = { + "AC_POWER_ACTIVE", + "AC_ENERGY_ACTIVE", + "AC_ENERGY_ACTIVE_CONSUMED", + "AC_ENERGY_ACTIVE_DELIVERED", + } + + for metric in defaults: + if metric not in self.formula: + self.formula[metric] = "+".join( [f"#{cid}" for cid in self._default_cids()] ) From b75fee08a0a586fd66a8e08e1950b13f907997d3 Mon Sep 17 00:00:00 2001 From: Mohammad Tayyab Date: Thu, 12 Mar 2026 11:17:24 +0100 Subject: [PATCH 2/3] feat: add formulas using component graph Signed-off-by: Mohammad Tayyab --- src/frequenz/gridpool/_microgrid_config.py | 127 +++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/src/frequenz/gridpool/_microgrid_config.py b/src/frequenz/gridpool/_microgrid_config.py index f934ec6..2afae01 100644 --- a/src/frequenz/gridpool/_microgrid_config.py +++ b/src/frequenz/gridpool/_microgrid_config.py @@ -10,9 +10,13 @@ from pathlib import Path from typing import Any, ClassVar, Literal, Self, Type, cast, get_args +from frequenz.client.assets import AssetsApiClient +from frequenz.client.common.microgrid import MicrogridId from marshmallow import Schema from marshmallow_dataclass import dataclass +from ._graph_generator import ComponentGraphGenerator + _logger = logging.getLogger(__name__) ComponentType = Literal["grid", "pv", "battery", "consumption", "chp", "ev"] @@ -402,3 +406,126 @@ def load_configs( microgrid_configs.update({str(key): value for key, value in mcfgs.items()}) return microgrid_configs + + @staticmethod + async def load_configs_with_formulas( + assets_url: str, + assets_auth_key: str, + assets_sign_secret: str, + microgrid_config_files: str | Path | list[str | Path] | None = None, + microgrid_config_dir: str | Path | None = None, + ) -> dict[str, "MicrogridConfig"]: + """Load microgrid configurations and ensure formulas are populated. + + Loads microgrid configuration files and enriches them with automatically + generated formulas obtained from the Assets API. Missing formulas are filled + using the component graph generator while preserving any formulas already + defined in the configuration. + + Args: + assets_url: + Base URL of the Assets API. + assets_auth_key: + Authentication key used to access the Assets API. + assets_sign_secret: + Signing secret used for authenticated API requests. + microgrid_config_files: + Optional path or list of paths to individual microgrid configuration + files. + microgrid_config_dir: + Optional directory containing microgrid configuration files. + + Returns: + dict[str, MicrogridConfig]: + Mapping from microgrid ID (as string) to the corresponding populated + ``MicrogridConfig`` instance. + + Notes: + - Configuration files are first loaded via ``MicrogridConfig.load_configs``. + - Any missing formulas are populated by querying the Assets API and + generating formulas from the microgrid component graph. + """ + microgrid_configs = MicrogridConfig.load_configs( + microgrid_config_files=microgrid_config_files, + microgrid_config_dir=microgrid_config_dir, + ) + + async with AssetsApiClient( + assets_url, auth_key=assets_auth_key, sign_secret=assets_sign_secret + ) as assets_client: + for microgrid_id, config in microgrid_configs.items(): + await populate_missing_formulas( + microgrid_id=int(microgrid_id), + config=config, + assets_client=assets_client, + ) + + return microgrid_configs + + +async def populate_missing_formulas( + microgrid_id: int, + config: "MicrogridConfig", + assets_client: AssetsApiClient, +) -> None: + """Populate missing component formulas from the assets API graph. + + Builds a component graph for the given microgrid and derives default formulas + for common component types such as consumption, generation, grid, PV, battery, + CHP, and EV charging. Existing formulas already present in the configuration + are preserved; only missing component-type entries or missing metric formulas + are filled in. + + Args: + microgrid_id: + Identifier of the microgrid whose component graph should be used to + derive formulas. + config: + Microgrid configuration object to update in place. + assets_client: + Assets API client used to fetch the component graph. + + Returns: + None. The configuration is modified in place. + + Notes: + - Existing formulas in ``config`` are never overwritten. + - For missing component types, a new ``ComponentTypeConfig`` is created. + - The same derived formula is assigned to all supported metric keys for a + given component type when missing. + """ + cgg = ComponentGraphGenerator(assets_client) + graph = await cgg.get_component_graph(MicrogridId(microgrid_id)) + + auto_formulas = { + "consumption": graph.consumer_formula(), + "generation": graph.producer_formula(), + "grid": graph.grid_formula(), + "pv": graph.pv_formula(None), + "battery": graph.battery_formula(None), + "chp": graph.chp_formula(None), + "ev": graph.ev_charger_formula(None), + } + + metrics = ( + "AC_POWER_ACTIVE", + "AC_ACTIVE_POWER", + "AC_ENERGY_ACTIVE", + "AC_ENERGY_ACTIVE_CONSUMED", + "AC_ENERGY_ACTIVE_DELIVERED", + ) + + for ctype, formula in auto_formulas.items(): + cfg = config.ctype.get(ctype) + if cfg is None: + config.ctype[ctype] = ComponentTypeConfig( + formula={metric: formula for metric in metrics} + ) + continue + + if cfg.formula is None: + cfg.formula = {} + + for metric in metrics: + if metric not in cfg.formula: + cfg.formula[metric] = formula From 26de36e3619019841bf4f673e6d4a70a9153c52a Mon Sep 17 00:00:00 2001 From: Mohammad Tayyab Date: Tue, 10 Mar 2026 16:09:24 +0100 Subject: [PATCH 3/3] docs: update release notes Signed-off-by: Mohammad Tayyab --- RELEASE_NOTES.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d4001e7..5364f55 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,18 +2,18 @@ ## Summary - +This release adds default formulas for AC energy metrics when they are not +explicitly configured. ## Upgrading -* The minimum required version of `frequenz-microgrid-component-graph` is now `v0.3.4`. ## New Features -* Added `gridpool-cli render-graph` to visualize microgrid component graphs using the - Assets API credentials (`ASSETS_API_URL`, `ASSETS_API_AUTH_KEY`, and - `ASSETS_API_SIGN_SECRET`). +* Added default summation formulas for `AC_ENERGY_ACTIVE`, + `AC_ENERGY_ACTIVE_CONSUMED`, and `AC_ENERGY_ACTIVE_DELIVERED` when formulas are + not provided. The defaults are built from the component IDs configured for the + component type. ## Bug Fixes -- Fixed component graph rendering so children follow the vertical order of their parents, keeping upper-level branches above lower ones in the layered layout. \ No newline at end of file