Skip to content

Commit 3cb48f2

Browse files
authored
Implement a ComponentGraphGenerator from the assets API (#16)
2 parents e5d43f7 + a083fce commit 3cb48f2

File tree

5 files changed

+138
-1
lines changed

5 files changed

+138
-1
lines changed

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
* Expose microgrid config `Metadata`.
1414

15+
* This introduces the `ComponentGraphGenerator`, that uses the assets API to fetch assets for a specified microgrid, and builds a component graph for it, from which various formulas for the microgrid can be generated.
16+
1517
## Bug Fixes
1618

1719
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ requires-python = ">= 3.11, < 4"
3030
dependencies = [
3131
"marshmallow-dataclass>=8.7.1,<9",
3232
"typing-extensions >= 4.14.1, < 5",
33+
"frequenz-microgrid-component-graph >= 0.2.0, < 0.3",
34+
"frequenz-client-assets >= 0.2.0, < 0.3",
3335
]
3436
dynamic = ["version"]
3537

src/frequenz/gridpool/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
"""High-level interface to grid pools for the Frequenz platform."""
55

6+
from ._graph_generator import ComponentGraphGenerator
67
from ._microgrid_config import Metadata, MicrogridConfig
78

8-
__all__ = ["Metadata", "MicrogridConfig"]
9+
__all__ = ["ComponentGraphGenerator", "Metadata", "MicrogridConfig"]
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Formula generation from assets API component/connection configurations."""
5+
6+
from typing import cast
7+
8+
from frequenz.client.assets import AssetsApiClient
9+
from frequenz.client.assets.electrical_component import (
10+
ComponentConnection,
11+
ElectricalComponent,
12+
)
13+
from frequenz.client.common.microgrid import MicrogridId
14+
from frequenz.client.common.microgrid.electrical_components import ElectricalComponentId
15+
from frequenz.microgrid_component_graph import ComponentGraph
16+
17+
18+
class ComponentGraphGenerator:
19+
"""Generates component graphs for microgrids using the Assets API."""
20+
21+
def __init__(
22+
self,
23+
client: AssetsApiClient,
24+
) -> None:
25+
"""Initialize this instance.
26+
27+
Args:
28+
client: The Assets API client to use for fetching components and
29+
connections.
30+
"""
31+
self._client: AssetsApiClient = client
32+
33+
async def get_component_graph(
34+
self, microgrid_id: MicrogridId
35+
) -> ComponentGraph[
36+
ElectricalComponent, ComponentConnection, ElectricalComponentId
37+
]:
38+
"""Generate a component graph for the given microgrid ID.
39+
40+
Args:
41+
microgrid_id: The ID of the microgrid to generate the graph for.
42+
43+
Returns:
44+
The component graph representing the microgrid's electrical
45+
components and their connections.
46+
47+
Raises:
48+
ValueError: If any component connections could not be loaded.
49+
"""
50+
components = await self._client.list_microgrid_electrical_components(
51+
microgrid_id
52+
)
53+
connections = (
54+
await self._client.list_microgrid_electrical_component_connections(
55+
microgrid_id
56+
)
57+
)
58+
59+
if any(c is None for c in connections):
60+
raise ValueError("Failed to load all electrical component connections.")
61+
62+
graph = ComponentGraph[
63+
ElectricalComponent, ComponentConnection, ElectricalComponentId
64+
](components, cast(list[ComponentConnection], connections))
65+
66+
return graph

tests/test_graph_generator.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the component graph generator."""
5+
6+
from unittest.mock import AsyncMock, MagicMock
7+
8+
from frequenz.client.assets import AssetsApiClient
9+
from frequenz.client.assets.electrical_component import (
10+
ComponentConnection,
11+
GridConnectionPoint,
12+
Meter,
13+
SolarInverter,
14+
)
15+
from frequenz.client.common.microgrid import MicrogridId
16+
from frequenz.client.common.microgrid.electrical_components import ElectricalComponentId
17+
18+
from frequenz.gridpool._graph_generator import ComponentGraphGenerator
19+
20+
21+
async def test_formula_generation() -> None:
22+
"""Test formula generation from component graph created from Assets API."""
23+
assets_client_mock = MagicMock(spec=AssetsApiClient)
24+
assets_client_mock.list_microgrid_electrical_components = AsyncMock(
25+
return_value=[
26+
GridConnectionPoint(
27+
id=ElectricalComponentId(1),
28+
microgrid_id=MicrogridId(10),
29+
rated_fuse_current=100,
30+
),
31+
Meter(
32+
id=ElectricalComponentId(2),
33+
microgrid_id=MicrogridId(10),
34+
),
35+
Meter(
36+
id=ElectricalComponentId(3),
37+
microgrid_id=MicrogridId(10),
38+
),
39+
SolarInverter(
40+
id=ElectricalComponentId(4),
41+
microgrid_id=MicrogridId(10),
42+
),
43+
]
44+
)
45+
assets_client_mock.list_microgrid_electrical_component_connections = AsyncMock(
46+
return_value=[
47+
ComponentConnection(
48+
source=ElectricalComponentId(1),
49+
destination=ElectricalComponentId(2),
50+
),
51+
ComponentConnection(
52+
source=ElectricalComponentId(1),
53+
destination=ElectricalComponentId(3),
54+
),
55+
ComponentConnection(
56+
source=ElectricalComponentId(2),
57+
destination=ElectricalComponentId(4),
58+
),
59+
]
60+
)
61+
62+
g = ComponentGraphGenerator(assets_client_mock)
63+
graph = await g.get_component_graph(MicrogridId(10))
64+
65+
assert graph.grid_formula() == "COALESCE(#2, #4, 0.0) + #3"
66+
assert graph.pv_formula(None) == "COALESCE(#4, #2, 0.0)"

0 commit comments

Comments
 (0)