frequenz-gridpool 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {frequenz_gridpool-0.3.0/src/frequenz_gridpool.egg-info → frequenz_gridpool-0.4.0}/PKG-INFO +58 -10
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/README.md +44 -0
- frequenz_gridpool-0.4.0/RELEASE_NOTES.md +20 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/pyproject.toml +16 -10
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/_graph_generator.py +1 -3
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/_microgrid_config.py +137 -9
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/cli/__main__.py +42 -0
- frequenz_gridpool-0.4.0/src/frequenz/gridpool/cli/_render_graph.py +295 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0/src/frequenz_gridpool.egg-info}/PKG-INFO +58 -10
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/SOURCES.txt +1 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/requires.txt +14 -9
- frequenz_gridpool-0.3.0/RELEASE_NOTES.md +0 -17
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/LICENSE +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/MANIFEST.in +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/setup.cfg +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/__init__.py +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/cli/__init__.py +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/conftest.py +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/py.typed +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/dependency_links.txt +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/entry_points.txt +0 -0
- {frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frequenz-gridpool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: High-level interface to grid pools for the Frequenz platform.
|
|
5
5
|
Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
|
|
6
6
|
License: MIT
|
|
@@ -24,7 +24,10 @@ Requires-Dist: marshmallow-dataclass<9,>=8.7.1
|
|
|
24
24
|
Requires-Dist: asyncclick<9,>=8.3.0.4
|
|
25
25
|
Requires-Dist: typing-extensions<5,>=4.14.1
|
|
26
26
|
Requires-Dist: frequenz-microgrid-component-graph<0.4,>=0.3.4
|
|
27
|
-
Requires-Dist: frequenz-client-assets<0.
|
|
27
|
+
Requires-Dist: frequenz-client-assets<0.4,>=0.3.0
|
|
28
|
+
Provides-Extra: render-graph
|
|
29
|
+
Requires-Dist: matplotlib<4,>=3.7.0; extra == "render-graph"
|
|
30
|
+
Requires-Dist: networkx<4,>=3.0; extra == "render-graph"
|
|
28
31
|
Provides-Extra: dev-flake8
|
|
29
32
|
Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
|
|
30
33
|
Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
|
|
@@ -35,19 +38,20 @@ Provides-Extra: dev-formatting
|
|
|
35
38
|
Requires-Dist: black==25.11.0; extra == "dev-formatting"
|
|
36
39
|
Requires-Dist: isort==7.0.0; extra == "dev-formatting"
|
|
37
40
|
Provides-Extra: dev-mkdocs
|
|
38
|
-
Requires-Dist: Markdown==3.10; extra == "dev-mkdocs"
|
|
41
|
+
Requires-Dist: Markdown==3.10.2; extra == "dev-mkdocs"
|
|
39
42
|
Requires-Dist: black==25.11.0; extra == "dev-mkdocs"
|
|
40
43
|
Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
|
|
41
44
|
Requires-Dist: mkdocs-gen-files==0.5.0; extra == "dev-mkdocs"
|
|
42
45
|
Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
|
|
43
46
|
Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
|
|
44
|
-
Requires-Dist: mkdocs-material==9.7.
|
|
45
|
-
Requires-Dist: mkdocstrings[python]==1.0.
|
|
47
|
+
Requires-Dist: mkdocs-material==9.7.3; extra == "dev-mkdocs"
|
|
48
|
+
Requires-Dist: mkdocstrings[python]==1.0.3; extra == "dev-mkdocs"
|
|
46
49
|
Requires-Dist: mkdocstrings-python==1.19.0; extra == "dev-mkdocs"
|
|
47
50
|
Requires-Dist: frequenz-repo-config[lib]==0.13.7; extra == "dev-mkdocs"
|
|
48
51
|
Provides-Extra: dev-mypy
|
|
49
|
-
Requires-Dist: mypy==1.19.
|
|
50
|
-
Requires-Dist: types-
|
|
52
|
+
Requires-Dist: mypy==1.19.1; extra == "dev-mypy"
|
|
53
|
+
Requires-Dist: types-networkx>=3.6.1.20251220; extra == "dev-mypy"
|
|
54
|
+
Requires-Dist: types-Markdown==3.10.2.20260211; extra == "dev-mypy"
|
|
51
55
|
Requires-Dist: frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
|
|
52
56
|
Provides-Extra: dev-noxfile
|
|
53
57
|
Requires-Dist: nox==2025.11.12; extra == "dev-noxfile"
|
|
@@ -55,14 +59,14 @@ Requires-Dist: frequenz-repo-config[lib]==0.13.7; extra == "dev-noxfile"
|
|
|
55
59
|
Provides-Extra: dev-pylint
|
|
56
60
|
Requires-Dist: frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
|
|
57
61
|
Provides-Extra: dev-pytest
|
|
58
|
-
Requires-Dist: pytest==
|
|
59
|
-
Requires-Dist: pylint==4.0.
|
|
62
|
+
Requires-Dist: pytest==9.0.2; extra == "dev-pytest"
|
|
63
|
+
Requires-Dist: pylint==4.0.5; extra == "dev-pytest"
|
|
60
64
|
Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.13.7; extra == "dev-pytest"
|
|
61
65
|
Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
|
|
62
66
|
Requires-Dist: pytest-asyncio==1.3.0; extra == "dev-pytest"
|
|
63
67
|
Requires-Dist: async-solipsism==0.9; extra == "dev-pytest"
|
|
64
68
|
Provides-Extra: dev
|
|
65
|
-
Requires-Dist: frequenz-gridpool[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
|
|
69
|
+
Requires-Dist: frequenz-gridpool[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest,render-graph]; extra == "dev"
|
|
66
70
|
Dynamic: license-file
|
|
67
71
|
|
|
68
72
|
# Frequenz Gridpool Library
|
|
@@ -85,6 +89,50 @@ The following platforms are officially supported (tested):
|
|
|
85
89
|
- **Operating System:** Ubuntu Linux 20.04
|
|
86
90
|
- **Architectures:** amd64, arm64
|
|
87
91
|
|
|
92
|
+
## CLI
|
|
93
|
+
|
|
94
|
+
This package ships the `gridpool-cli` command with two subcommands.
|
|
95
|
+
|
|
96
|
+
### Setup
|
|
97
|
+
|
|
98
|
+
Set the Assets API credentials before running the CLI:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
export ASSETS_API_URL="grpc://..."
|
|
102
|
+
export ASSETS_API_AUTH_KEY="..."
|
|
103
|
+
export ASSETS_API_SIGN_SECRET="..."
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Print component formulas
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
gridpool-cli print-formulas <microgrid_id>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Optional prefix formatting:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
gridpool-cli print-formulas <microgrid_id> --prefix "{microgrid_id}.{component}"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Render component graph
|
|
119
|
+
|
|
120
|
+
Rendering requires optional dependencies. Install with:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install frequenz-gridpool[render-graph]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
gridpool-cli render-graph <microgrid_id>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
To save without opening a window:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
gridpool-cli render-graph <microgrid_id> --no-show --output component_graph.png
|
|
134
|
+
```
|
|
135
|
+
|
|
88
136
|
## Contributing
|
|
89
137
|
|
|
90
138
|
If you want to know how to build this project and contribute to it, please
|
|
@@ -18,6 +18,50 @@ The following platforms are officially supported (tested):
|
|
|
18
18
|
- **Operating System:** Ubuntu Linux 20.04
|
|
19
19
|
- **Architectures:** amd64, arm64
|
|
20
20
|
|
|
21
|
+
## CLI
|
|
22
|
+
|
|
23
|
+
This package ships the `gridpool-cli` command with two subcommands.
|
|
24
|
+
|
|
25
|
+
### Setup
|
|
26
|
+
|
|
27
|
+
Set the Assets API credentials before running the CLI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export ASSETS_API_URL="grpc://..."
|
|
31
|
+
export ASSETS_API_AUTH_KEY="..."
|
|
32
|
+
export ASSETS_API_SIGN_SECRET="..."
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Print component formulas
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gridpool-cli print-formulas <microgrid_id>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Optional prefix formatting:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
gridpool-cli print-formulas <microgrid_id> --prefix "{microgrid_id}.{component}"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Render component graph
|
|
48
|
+
|
|
49
|
+
Rendering requires optional dependencies. Install with:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install frequenz-gridpool[render-graph]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
gridpool-cli render-graph <microgrid_id>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To save without opening a window:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gridpool-cli render-graph <microgrid_id> --no-show --output component_graph.png
|
|
63
|
+
```
|
|
64
|
+
|
|
21
65
|
## Contributing
|
|
22
66
|
|
|
23
67
|
If you want to know how to build this project and contribute to it, please
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Frequenz Gridpool Library Release Notes
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
This release adds default formulas for AC energy metrics when they are not
|
|
6
|
+
explicitly configured.
|
|
7
|
+
|
|
8
|
+
## Upgrading
|
|
9
|
+
|
|
10
|
+
* Update assets client to v0.3.0.
|
|
11
|
+
|
|
12
|
+
## New Features
|
|
13
|
+
|
|
14
|
+
* Added default summation formulas for `AC_ENERGY_ACTIVE`,
|
|
15
|
+
`AC_ENERGY_ACTIVE_CONSUMED`, and `AC_ENERGY_ACTIVE_DELIVERED` when formulas are
|
|
16
|
+
not provided. The defaults are built from the component IDs configured for the
|
|
17
|
+
component type.
|
|
18
|
+
|
|
19
|
+
## Bug Fixes
|
|
20
|
+
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[build-system]
|
|
5
5
|
requires = [
|
|
6
|
-
"setuptools == 80.
|
|
6
|
+
"setuptools == 80.10.1",
|
|
7
7
|
"setuptools_scm[toml] == 9.2.2",
|
|
8
8
|
"frequenz-repo-config[lib] == 0.13.7",
|
|
9
9
|
]
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
"asyncclick >= 8.3.0.4, < 9",
|
|
31
31
|
"typing-extensions >= 4.14.1, < 5",
|
|
32
32
|
"frequenz-microgrid-component-graph >= 0.3.4, < 0.4",
|
|
33
|
-
"frequenz-client-assets >= 0.
|
|
33
|
+
"frequenz-client-assets >= 0.3.0, < 0.4",
|
|
34
34
|
]
|
|
35
35
|
dynamic = ["version"]
|
|
36
36
|
|
|
@@ -42,6 +42,11 @@ name = "Frequenz Energy-as-a-Service GmbH"
|
|
|
42
42
|
email = "floss@frequenz.com"
|
|
43
43
|
|
|
44
44
|
[project.optional-dependencies]
|
|
45
|
+
render-graph = [
|
|
46
|
+
"matplotlib >= 3.7.0, < 4",
|
|
47
|
+
"networkx >= 3.0, < 4",
|
|
48
|
+
]
|
|
49
|
+
|
|
45
50
|
dev-flake8 = [
|
|
46
51
|
"flake8 == 7.3.0",
|
|
47
52
|
"flake8-docstrings == 1.7.0",
|
|
@@ -51,20 +56,21 @@ dev-flake8 = [
|
|
|
51
56
|
]
|
|
52
57
|
dev-formatting = ["black == 25.11.0", "isort == 7.0.0"]
|
|
53
58
|
dev-mkdocs = [
|
|
54
|
-
"Markdown == 3.10",
|
|
59
|
+
"Markdown == 3.10.2",
|
|
55
60
|
"black == 25.11.0",
|
|
56
61
|
"mike == 2.1.3",
|
|
57
62
|
"mkdocs-gen-files == 0.5.0",
|
|
58
63
|
"mkdocs-literate-nav == 0.6.2",
|
|
59
64
|
"mkdocs-macros-plugin == 1.5.0",
|
|
60
|
-
"mkdocs-material == 9.7.
|
|
61
|
-
"mkdocstrings[python] == 1.0.
|
|
65
|
+
"mkdocs-material == 9.7.3",
|
|
66
|
+
"mkdocstrings[python] == 1.0.3",
|
|
62
67
|
"mkdocstrings-python == 1.19.0",
|
|
63
68
|
"frequenz-repo-config[lib] == 0.13.7",
|
|
64
69
|
]
|
|
65
70
|
dev-mypy = [
|
|
66
|
-
"mypy == 1.19.
|
|
67
|
-
"types-
|
|
71
|
+
"mypy == 1.19.1",
|
|
72
|
+
"types-networkx>=3.6.1.20251220",
|
|
73
|
+
"types-Markdown == 3.10.2.20260211",
|
|
68
74
|
# For checking the noxfile, docs/ script, and tests
|
|
69
75
|
"frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]",
|
|
70
76
|
]
|
|
@@ -78,15 +84,15 @@ dev-pylint = [
|
|
|
78
84
|
"frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]",
|
|
79
85
|
]
|
|
80
86
|
dev-pytest = [
|
|
81
|
-
"pytest ==
|
|
82
|
-
"pylint == 4.0.
|
|
87
|
+
"pytest == 9.0.2",
|
|
88
|
+
"pylint == 4.0.5", # We need this to check for the examples
|
|
83
89
|
"frequenz-repo-config[extra-lint-examples] == 0.13.7",
|
|
84
90
|
"pytest-mock == 3.15.1",
|
|
85
91
|
"pytest-asyncio == 1.3.0",
|
|
86
92
|
"async-solipsism == 0.9",
|
|
87
93
|
]
|
|
88
94
|
dev = [
|
|
89
|
-
"frequenz-gridpool[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
|
|
95
|
+
"frequenz-gridpool[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest,render-graph]",
|
|
90
96
|
]
|
|
91
97
|
|
|
92
98
|
[project.urls]
|
{frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/_graph_generator.py
RENAMED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
"""Formula generation from assets API component/connection configurations."""
|
|
5
5
|
|
|
6
|
-
from typing import cast
|
|
7
|
-
|
|
8
6
|
from frequenz.client.assets import AssetsApiClient
|
|
9
7
|
from frequenz.client.assets.electrical_component import (
|
|
10
8
|
ComponentConnection,
|
|
@@ -61,6 +59,6 @@ class ComponentGraphGenerator:
|
|
|
61
59
|
|
|
62
60
|
graph = ComponentGraph[
|
|
63
61
|
ElectricalComponent, ComponentConnection, ElectricalComponentId
|
|
64
|
-
](components,
|
|
62
|
+
](components, connections)
|
|
65
63
|
|
|
66
64
|
return graph
|
{frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz/gridpool/_microgrid_config.py
RENAMED
|
@@ -10,9 +10,13 @@ from dataclasses import field
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, ClassVar, Literal, Self, Type, cast, get_args
|
|
12
12
|
|
|
13
|
+
from frequenz.client.assets import AssetsApiClient
|
|
14
|
+
from frequenz.client.common.microgrid import MicrogridId
|
|
13
15
|
from marshmallow import Schema
|
|
14
16
|
from marshmallow_dataclass import dataclass
|
|
15
17
|
|
|
18
|
+
from ._graph_generator import ComponentGraphGenerator
|
|
19
|
+
|
|
16
20
|
_logger = logging.getLogger(__name__)
|
|
17
21
|
|
|
18
22
|
ComponentType = Literal["grid", "pv", "battery", "consumption", "chp", "ev"]
|
|
@@ -47,15 +51,16 @@ class ComponentTypeConfig:
|
|
|
47
51
|
"please use 'AC_POWER_ACTIVE' instead."
|
|
48
52
|
)
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
defaults = {
|
|
55
|
+
"AC_POWER_ACTIVE",
|
|
56
|
+
"AC_ENERGY_ACTIVE",
|
|
57
|
+
"AC_ENERGY_ACTIVE_CONSUMED",
|
|
58
|
+
"AC_ENERGY_ACTIVE_DELIVERED",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for metric in defaults:
|
|
62
|
+
if metric not in self.formula:
|
|
63
|
+
self.formula[metric] = "+".join(
|
|
59
64
|
[f"#{cid}" for cid in self._default_cids()]
|
|
60
65
|
)
|
|
61
66
|
|
|
@@ -401,3 +406,126 @@ class MicrogridConfig:
|
|
|
401
406
|
microgrid_configs.update({str(key): value for key, value in mcfgs.items()})
|
|
402
407
|
|
|
403
408
|
return microgrid_configs
|
|
409
|
+
|
|
410
|
+
@staticmethod
|
|
411
|
+
async def load_configs_with_formulas(
|
|
412
|
+
assets_url: str,
|
|
413
|
+
assets_auth_key: str,
|
|
414
|
+
assets_sign_secret: str,
|
|
415
|
+
microgrid_config_files: str | Path | list[str | Path] | None = None,
|
|
416
|
+
microgrid_config_dir: str | Path | None = None,
|
|
417
|
+
) -> dict[str, "MicrogridConfig"]:
|
|
418
|
+
"""Load microgrid configurations and ensure formulas are populated.
|
|
419
|
+
|
|
420
|
+
Loads microgrid configuration files and enriches them with automatically
|
|
421
|
+
generated formulas obtained from the Assets API. Missing formulas are filled
|
|
422
|
+
using the component graph generator while preserving any formulas already
|
|
423
|
+
defined in the configuration.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
assets_url:
|
|
427
|
+
Base URL of the Assets API.
|
|
428
|
+
assets_auth_key:
|
|
429
|
+
Authentication key used to access the Assets API.
|
|
430
|
+
assets_sign_secret:
|
|
431
|
+
Signing secret used for authenticated API requests.
|
|
432
|
+
microgrid_config_files:
|
|
433
|
+
Optional path or list of paths to individual microgrid configuration
|
|
434
|
+
files.
|
|
435
|
+
microgrid_config_dir:
|
|
436
|
+
Optional directory containing microgrid configuration files.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
dict[str, MicrogridConfig]:
|
|
440
|
+
Mapping from microgrid ID (as string) to the corresponding populated
|
|
441
|
+
``MicrogridConfig`` instance.
|
|
442
|
+
|
|
443
|
+
Notes:
|
|
444
|
+
- Configuration files are first loaded via ``MicrogridConfig.load_configs``.
|
|
445
|
+
- Any missing formulas are populated by querying the Assets API and
|
|
446
|
+
generating formulas from the microgrid component graph.
|
|
447
|
+
"""
|
|
448
|
+
microgrid_configs = MicrogridConfig.load_configs(
|
|
449
|
+
microgrid_config_files=microgrid_config_files,
|
|
450
|
+
microgrid_config_dir=microgrid_config_dir,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
async with AssetsApiClient(
|
|
454
|
+
assets_url, auth_key=assets_auth_key, sign_secret=assets_sign_secret
|
|
455
|
+
) as assets_client:
|
|
456
|
+
for microgrid_id, config in microgrid_configs.items():
|
|
457
|
+
await populate_missing_formulas(
|
|
458
|
+
microgrid_id=int(microgrid_id),
|
|
459
|
+
config=config,
|
|
460
|
+
assets_client=assets_client,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
return microgrid_configs
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
async def populate_missing_formulas(
|
|
467
|
+
microgrid_id: int,
|
|
468
|
+
config: "MicrogridConfig",
|
|
469
|
+
assets_client: AssetsApiClient,
|
|
470
|
+
) -> None:
|
|
471
|
+
"""Populate missing component formulas from the assets API graph.
|
|
472
|
+
|
|
473
|
+
Builds a component graph for the given microgrid and derives default formulas
|
|
474
|
+
for common component types such as consumption, generation, grid, PV, battery,
|
|
475
|
+
CHP, and EV charging. Existing formulas already present in the configuration
|
|
476
|
+
are preserved; only missing component-type entries or missing metric formulas
|
|
477
|
+
are filled in.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
microgrid_id:
|
|
481
|
+
Identifier of the microgrid whose component graph should be used to
|
|
482
|
+
derive formulas.
|
|
483
|
+
config:
|
|
484
|
+
Microgrid configuration object to update in place.
|
|
485
|
+
assets_client:
|
|
486
|
+
Assets API client used to fetch the component graph.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
None. The configuration is modified in place.
|
|
490
|
+
|
|
491
|
+
Notes:
|
|
492
|
+
- Existing formulas in ``config`` are never overwritten.
|
|
493
|
+
- For missing component types, a new ``ComponentTypeConfig`` is created.
|
|
494
|
+
- The same derived formula is assigned to all supported metric keys for a
|
|
495
|
+
given component type when missing.
|
|
496
|
+
"""
|
|
497
|
+
cgg = ComponentGraphGenerator(assets_client)
|
|
498
|
+
graph = await cgg.get_component_graph(MicrogridId(microgrid_id))
|
|
499
|
+
|
|
500
|
+
auto_formulas = {
|
|
501
|
+
"consumption": graph.consumer_formula(),
|
|
502
|
+
"generation": graph.producer_formula(),
|
|
503
|
+
"grid": graph.grid_formula(),
|
|
504
|
+
"pv": graph.pv_formula(None),
|
|
505
|
+
"battery": graph.battery_formula(None),
|
|
506
|
+
"chp": graph.chp_formula(None),
|
|
507
|
+
"ev": graph.ev_charger_formula(None),
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
metrics = (
|
|
511
|
+
"AC_POWER_ACTIVE",
|
|
512
|
+
"AC_ACTIVE_POWER",
|
|
513
|
+
"AC_ENERGY_ACTIVE",
|
|
514
|
+
"AC_ENERGY_ACTIVE_CONSUMED",
|
|
515
|
+
"AC_ENERGY_ACTIVE_DELIVERED",
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
for ctype, formula in auto_formulas.items():
|
|
519
|
+
cfg = config.ctype.get(ctype)
|
|
520
|
+
if cfg is None:
|
|
521
|
+
config.ctype[ctype] = ComponentTypeConfig(
|
|
522
|
+
formula={metric: formula for metric in metrics}
|
|
523
|
+
)
|
|
524
|
+
continue
|
|
525
|
+
|
|
526
|
+
if cfg.formula is None:
|
|
527
|
+
cfg.formula = {}
|
|
528
|
+
|
|
529
|
+
for metric in metrics:
|
|
530
|
+
if metric not in cfg.formula:
|
|
531
|
+
cfg.formula[metric] = formula
|
|
@@ -10,6 +10,7 @@ from frequenz.client.assets import AssetsApiClient
|
|
|
10
10
|
from frequenz.client.common.microgrid import MicrogridId
|
|
11
11
|
|
|
12
12
|
from frequenz.gridpool import ComponentGraphGenerator
|
|
13
|
+
from frequenz.gridpool.cli._render_graph import ComponentGraphRenderer, RenderOptions
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@click.group()
|
|
@@ -63,6 +64,47 @@ async def print_formulas(
|
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
@cli.command("render-graph")
|
|
68
|
+
@click.argument("microgrid_id", type=int)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--output",
|
|
71
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
72
|
+
default="component_graph.png",
|
|
73
|
+
show_default=True,
|
|
74
|
+
help="Output image path.",
|
|
75
|
+
)
|
|
76
|
+
@click.option(
|
|
77
|
+
"--show/--no-show",
|
|
78
|
+
default=True,
|
|
79
|
+
show_default=True,
|
|
80
|
+
help="Display the graph interactively.",
|
|
81
|
+
)
|
|
82
|
+
async def render_graph(microgrid_id: int, output: str, show: bool) -> None:
|
|
83
|
+
"""Render and save a component graph visualization for a microgrid."""
|
|
84
|
+
url = os.environ.get("ASSETS_API_URL")
|
|
85
|
+
key = os.environ.get("ASSETS_API_AUTH_KEY")
|
|
86
|
+
secret = os.environ.get("ASSETS_API_SIGN_SECRET")
|
|
87
|
+
if not url or not key or not secret:
|
|
88
|
+
raise click.ClickException(
|
|
89
|
+
"ASSETS_API_URL, ASSETS_API_AUTH_KEY, ASSETS_API_SIGN_SECRET must be set."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
async with AssetsApiClient(
|
|
94
|
+
url,
|
|
95
|
+
auth_key=key,
|
|
96
|
+
sign_secret=secret,
|
|
97
|
+
) as client:
|
|
98
|
+
renderer = ComponentGraphRenderer(client)
|
|
99
|
+
graph = await renderer.build_graph(MicrogridId(microgrid_id))
|
|
100
|
+
if not graph.nodes:
|
|
101
|
+
raise click.ClickException("No components found for this microgrid.")
|
|
102
|
+
pos = renderer.compute_layout(graph)
|
|
103
|
+
renderer.render(graph, pos, RenderOptions(output=output, show=show))
|
|
104
|
+
except RuntimeError as exc:
|
|
105
|
+
raise click.ClickException(str(exc)) from exc
|
|
106
|
+
|
|
107
|
+
|
|
66
108
|
def main() -> None:
|
|
67
109
|
"""Run the CLI tool."""
|
|
68
110
|
cli(_anyio_backend="asyncio")
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# License: MIT
|
|
2
|
+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
|
|
3
|
+
# pylint: disable=import-error
|
|
4
|
+
|
|
5
|
+
"""Render component graphs via the Assets API."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from frequenz.client.assets import AssetsApiClient
|
|
13
|
+
from frequenz.client.common.microgrid import MicrogridId
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _format_category(category: Any) -> str:
|
|
17
|
+
"""Normalize a component category into a short display label."""
|
|
18
|
+
if category is None:
|
|
19
|
+
return "UNKNOWN"
|
|
20
|
+
if hasattr(category, "name"):
|
|
21
|
+
return str(category.name).replace("COMPONENT_CATEGORY_", "")
|
|
22
|
+
return str(category).replace("COMPONENT_CATEGORY_", "")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _require_networkx() -> Any:
|
|
26
|
+
try:
|
|
27
|
+
# pylint: disable=import-outside-toplevel
|
|
28
|
+
import networkx as nx
|
|
29
|
+
except ModuleNotFoundError as exc:
|
|
30
|
+
raise RuntimeError(
|
|
31
|
+
"Rendering requires optional dependencies. Install "
|
|
32
|
+
"`frequenz-gridpool[render-graph]`."
|
|
33
|
+
) from exc
|
|
34
|
+
return nx
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _require_matplotlib() -> Any:
|
|
38
|
+
try:
|
|
39
|
+
# pylint: disable=import-outside-toplevel
|
|
40
|
+
import matplotlib.pyplot as plt
|
|
41
|
+
except ModuleNotFoundError as exc:
|
|
42
|
+
raise RuntimeError(
|
|
43
|
+
"Rendering requires optional dependencies. Install "
|
|
44
|
+
"`frequenz-gridpool[render-graph]`."
|
|
45
|
+
) from exc
|
|
46
|
+
return plt
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class RenderOptions:
|
|
51
|
+
"""Rendering options for component graphs."""
|
|
52
|
+
|
|
53
|
+
output: str
|
|
54
|
+
show: bool = True
|
|
55
|
+
figsize: tuple[int, int] = (15, 10)
|
|
56
|
+
title: str = "Microgrid Component Graph"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ComponentGraphRenderer:
|
|
60
|
+
"""Build and render component graphs for a microgrid."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, client: AssetsApiClient) -> None:
|
|
63
|
+
"""Initialize the renderer.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
client: API client used to fetch microgrid components and their connections.
|
|
67
|
+
"""
|
|
68
|
+
self._client = client
|
|
69
|
+
|
|
70
|
+
async def build_graph(self, microgrid_id: MicrogridId) -> Any:
|
|
71
|
+
"""Build a directed graph representing the microgrid electrical topology.
|
|
72
|
+
|
|
73
|
+
Fetches electrical components and their connections for the given microgrid and
|
|
74
|
+
constructs a directed graph where nodes represent components and edges represent
|
|
75
|
+
connections from source to destination.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
microgrid_id: Identifier of the microgrid to fetch and model.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A directed graph containing component nodes and connection edges.
|
|
82
|
+
"""
|
|
83
|
+
components = await self._client.list_microgrid_electrical_components(
|
|
84
|
+
microgrid_id
|
|
85
|
+
)
|
|
86
|
+
connections = (
|
|
87
|
+
await self._client.list_microgrid_electrical_component_connections(
|
|
88
|
+
microgrid_id
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
nx = _require_networkx()
|
|
93
|
+
graph = nx.DiGraph()
|
|
94
|
+
|
|
95
|
+
for component in components:
|
|
96
|
+
graph.add_node(
|
|
97
|
+
component.id,
|
|
98
|
+
name=component.name or str(component.id),
|
|
99
|
+
category=_format_category(component.category),
|
|
100
|
+
orig_id=component.id,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
for connection in connections:
|
|
104
|
+
if connection is None:
|
|
105
|
+
continue
|
|
106
|
+
graph.add_edge(connection.source, connection.destination)
|
|
107
|
+
|
|
108
|
+
return graph
|
|
109
|
+
|
|
110
|
+
def compute_layout(self, graph: Any) -> dict[Any, tuple[float, float]]:
|
|
111
|
+
"""Compute a layered node layout with the root on the left.
|
|
112
|
+
|
|
113
|
+
Selects a root node, groups nodes by shortest-path distance from the root, and
|
|
114
|
+
assigns (x, y) coordinates such that layers are spaced along the x-axis.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
graph: A graph-like object containing nodes and edges.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A mapping from node identifiers to (x, y) coordinates.
|
|
121
|
+
"""
|
|
122
|
+
if not graph.nodes:
|
|
123
|
+
return {}
|
|
124
|
+
|
|
125
|
+
root_node = self._select_root(graph)
|
|
126
|
+
layered_nodes = self._group_by_level(graph, root_node)
|
|
127
|
+
ordered_layers = self._order_layers(graph, layered_nodes)
|
|
128
|
+
return self._build_positions(ordered_layers)
|
|
129
|
+
|
|
130
|
+
def _select_root(self, graph: Any) -> Any:
|
|
131
|
+
"""Select a root node for layout.
|
|
132
|
+
|
|
133
|
+
Prefers a node with no incoming edges and at least one outgoing edge. If no such
|
|
134
|
+
node exists, falls back to an arbitrary node.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
graph: A graph-like object containing nodes and edges.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The selected root node identifier.
|
|
141
|
+
"""
|
|
142
|
+
roots = [
|
|
143
|
+
node
|
|
144
|
+
for node in graph.nodes
|
|
145
|
+
if graph.in_degree(node) == 0 and graph.out_degree(node) > 0
|
|
146
|
+
]
|
|
147
|
+
return roots[0] if roots else list(graph.nodes)[0]
|
|
148
|
+
|
|
149
|
+
def _group_by_level(self, graph: Any, root_node: Any) -> dict[int, list[Any]]:
|
|
150
|
+
"""Group nodes into layers by shortest-path distance from a root node.
|
|
151
|
+
|
|
152
|
+
Nodes reachable from the root are assigned to layers based on their shortest-path
|
|
153
|
+
distance. Nodes not reachable from the root are placed into a final "orphan"
|
|
154
|
+
layer after the deepest reachable layer.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
graph: A graph-like object containing nodes and edges.
|
|
158
|
+
root_node: The node to treat as the root for distance computation.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
A mapping from layer index to the list of nodes in that layer.
|
|
162
|
+
"""
|
|
163
|
+
nx = _require_networkx()
|
|
164
|
+
levels = nx.single_source_shortest_path_length(graph, root_node)
|
|
165
|
+
layered_nodes: dict[int, list[Any]] = {}
|
|
166
|
+
for node, dist in levels.items():
|
|
167
|
+
layered_nodes.setdefault(dist, []).append(node)
|
|
168
|
+
|
|
169
|
+
orphans = [node for node in graph.nodes if node not in levels]
|
|
170
|
+
if orphans:
|
|
171
|
+
orphan_layer = max(layered_nodes.keys()) + 1 if layered_nodes else 0
|
|
172
|
+
layered_nodes[orphan_layer] = orphans
|
|
173
|
+
return layered_nodes
|
|
174
|
+
|
|
175
|
+
def _order_layers(
|
|
176
|
+
self, graph: Any, layered_nodes: dict[int, list[Any]]
|
|
177
|
+
) -> dict[int, list[Any]]:
|
|
178
|
+
"""Order nodes within each layer based on their parent positions.
|
|
179
|
+
|
|
180
|
+
Reorders the nodes in each layer to improve visual readability when rendering a
|
|
181
|
+
layered graph layout. The first layer is ordered deterministically (by string
|
|
182
|
+
representation). For subsequent layers, nodes are ordered primarily by the
|
|
183
|
+
lowest index of any already-ordered parent (predecessor) from the previous
|
|
184
|
+
layers, with a deterministic string-based tie-breaker.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
graph:
|
|
188
|
+
Graph-like object providing predecessor relationships via
|
|
189
|
+
``graph.predecessors(node)``.
|
|
190
|
+
layered_nodes:
|
|
191
|
+
Mapping from layer index to the list of nodes assigned to that layer.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A new mapping with the same layer keys as ``layered_nodes``, where each
|
|
195
|
+
layer's node list is ordered to align children beneath their parents.
|
|
196
|
+
"""
|
|
197
|
+
ordered_layers: dict[int, list[Any]] = {}
|
|
198
|
+
previous_order: dict[Any, int] = {}
|
|
199
|
+
|
|
200
|
+
for level in sorted(layered_nodes):
|
|
201
|
+
nodes = layered_nodes[level]
|
|
202
|
+
if level == 0:
|
|
203
|
+
ordered_nodes = sorted(nodes, key=str)
|
|
204
|
+
else:
|
|
205
|
+
|
|
206
|
+
def sort_key(node: Any) -> tuple[int, str]:
|
|
207
|
+
parents = [
|
|
208
|
+
parent
|
|
209
|
+
for parent in graph.predecessors(node)
|
|
210
|
+
if parent in previous_order
|
|
211
|
+
]
|
|
212
|
+
if parents:
|
|
213
|
+
parent_index = min(previous_order[parent] for parent in parents)
|
|
214
|
+
else:
|
|
215
|
+
parent_index = len(previous_order)
|
|
216
|
+
return (parent_index, str(node))
|
|
217
|
+
|
|
218
|
+
ordered_nodes = sorted(nodes, key=sort_key)
|
|
219
|
+
|
|
220
|
+
ordered_layers[level] = ordered_nodes
|
|
221
|
+
previous_order = {node: idx for idx, node in enumerate(ordered_nodes)}
|
|
222
|
+
|
|
223
|
+
return ordered_layers
|
|
224
|
+
|
|
225
|
+
def _build_positions(
|
|
226
|
+
self, layered_nodes: dict[int, list[Any]]
|
|
227
|
+
) -> dict[Any, tuple[float, float]]:
|
|
228
|
+
"""Compute node positions from pre-grouped layers.
|
|
229
|
+
|
|
230
|
+
Assigns x-coordinates based on layer index and y-coordinates by distributing
|
|
231
|
+
nodes evenly within each layer.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
layered_nodes: Mapping from layer index to the list of nodes in that layer.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
A mapping from node identifiers to (x, y) coordinates.
|
|
238
|
+
"""
|
|
239
|
+
x_spacing, y_spacing = 2.5, 1.2
|
|
240
|
+
pos: dict[Any, tuple[float, float]] = {}
|
|
241
|
+
for level in sorted(layered_nodes):
|
|
242
|
+
nodes = layered_nodes[level]
|
|
243
|
+
x_pos = level * x_spacing
|
|
244
|
+
y_start = (len(nodes) - 1) * y_spacing / 2
|
|
245
|
+
for i, node in enumerate(nodes):
|
|
246
|
+
pos[node] = (x_pos, y_start - (i * y_spacing))
|
|
247
|
+
return pos
|
|
248
|
+
|
|
249
|
+
def render(
|
|
250
|
+
self, graph: Any, pos: dict[Any, tuple[float, float]], options: RenderOptions
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Render a component graph to an image file and optionally display it.
|
|
253
|
+
|
|
254
|
+
This method uses NetworkX and Matplotlib to draw the given graph using the
|
|
255
|
+
provided node positions, applies basic styling (labels, colors, arrows),
|
|
256
|
+
saves the resulting figure to the path specified in ``options.output``,
|
|
257
|
+
and, if configured, opens an interactive window to show the plot.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
graph: A NetworkX graph-like object (typically a ``DiGraph``) whose
|
|
261
|
+
nodes and edges represent the microgrid components and their
|
|
262
|
+
electrical connections.
|
|
263
|
+
pos: A mapping from node identifiers to ``(x, y)`` coordinates used
|
|
264
|
+
to place each node in the rendered figure, usually obtained from
|
|
265
|
+
:meth:`compute_layout`.
|
|
266
|
+
options: Rendering configuration, including output file path, whether
|
|
267
|
+
to show the figure interactively, figure size, and plot title.
|
|
268
|
+
"""
|
|
269
|
+
nx = _require_networkx()
|
|
270
|
+
plt = _require_matplotlib()
|
|
271
|
+
|
|
272
|
+
node_labels = {
|
|
273
|
+
node: (
|
|
274
|
+
f'{graph.nodes[node].get("name", str(node))}\n'
|
|
275
|
+
f'ID: {graph.nodes[node].get("orig_id", node)}'
|
|
276
|
+
)
|
|
277
|
+
for node in graph.nodes
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
plt.figure(figsize=options.figsize)
|
|
281
|
+
nx.draw(
|
|
282
|
+
graph,
|
|
283
|
+
pos,
|
|
284
|
+
with_labels=True,
|
|
285
|
+
labels=node_labels,
|
|
286
|
+
node_color="lightblue",
|
|
287
|
+
edge_color="gray",
|
|
288
|
+
node_size=800,
|
|
289
|
+
font_size=8,
|
|
290
|
+
)
|
|
291
|
+
plt.title(options.title)
|
|
292
|
+
plt.tight_layout()
|
|
293
|
+
plt.savefig(options.output, dpi=300)
|
|
294
|
+
if options.show:
|
|
295
|
+
plt.show()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frequenz-gridpool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: High-level interface to grid pools for the Frequenz platform.
|
|
5
5
|
Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
|
|
6
6
|
License: MIT
|
|
@@ -24,7 +24,10 @@ Requires-Dist: marshmallow-dataclass<9,>=8.7.1
|
|
|
24
24
|
Requires-Dist: asyncclick<9,>=8.3.0.4
|
|
25
25
|
Requires-Dist: typing-extensions<5,>=4.14.1
|
|
26
26
|
Requires-Dist: frequenz-microgrid-component-graph<0.4,>=0.3.4
|
|
27
|
-
Requires-Dist: frequenz-client-assets<0.
|
|
27
|
+
Requires-Dist: frequenz-client-assets<0.4,>=0.3.0
|
|
28
|
+
Provides-Extra: render-graph
|
|
29
|
+
Requires-Dist: matplotlib<4,>=3.7.0; extra == "render-graph"
|
|
30
|
+
Requires-Dist: networkx<4,>=3.0; extra == "render-graph"
|
|
28
31
|
Provides-Extra: dev-flake8
|
|
29
32
|
Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
|
|
30
33
|
Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
|
|
@@ -35,19 +38,20 @@ Provides-Extra: dev-formatting
|
|
|
35
38
|
Requires-Dist: black==25.11.0; extra == "dev-formatting"
|
|
36
39
|
Requires-Dist: isort==7.0.0; extra == "dev-formatting"
|
|
37
40
|
Provides-Extra: dev-mkdocs
|
|
38
|
-
Requires-Dist: Markdown==3.10; extra == "dev-mkdocs"
|
|
41
|
+
Requires-Dist: Markdown==3.10.2; extra == "dev-mkdocs"
|
|
39
42
|
Requires-Dist: black==25.11.0; extra == "dev-mkdocs"
|
|
40
43
|
Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
|
|
41
44
|
Requires-Dist: mkdocs-gen-files==0.5.0; extra == "dev-mkdocs"
|
|
42
45
|
Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
|
|
43
46
|
Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
|
|
44
|
-
Requires-Dist: mkdocs-material==9.7.
|
|
45
|
-
Requires-Dist: mkdocstrings[python]==1.0.
|
|
47
|
+
Requires-Dist: mkdocs-material==9.7.3; extra == "dev-mkdocs"
|
|
48
|
+
Requires-Dist: mkdocstrings[python]==1.0.3; extra == "dev-mkdocs"
|
|
46
49
|
Requires-Dist: mkdocstrings-python==1.19.0; extra == "dev-mkdocs"
|
|
47
50
|
Requires-Dist: frequenz-repo-config[lib]==0.13.7; extra == "dev-mkdocs"
|
|
48
51
|
Provides-Extra: dev-mypy
|
|
49
|
-
Requires-Dist: mypy==1.19.
|
|
50
|
-
Requires-Dist: types-
|
|
52
|
+
Requires-Dist: mypy==1.19.1; extra == "dev-mypy"
|
|
53
|
+
Requires-Dist: types-networkx>=3.6.1.20251220; extra == "dev-mypy"
|
|
54
|
+
Requires-Dist: types-Markdown==3.10.2.20260211; extra == "dev-mypy"
|
|
51
55
|
Requires-Dist: frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
|
|
52
56
|
Provides-Extra: dev-noxfile
|
|
53
57
|
Requires-Dist: nox==2025.11.12; extra == "dev-noxfile"
|
|
@@ -55,14 +59,14 @@ Requires-Dist: frequenz-repo-config[lib]==0.13.7; extra == "dev-noxfile"
|
|
|
55
59
|
Provides-Extra: dev-pylint
|
|
56
60
|
Requires-Dist: frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
|
|
57
61
|
Provides-Extra: dev-pytest
|
|
58
|
-
Requires-Dist: pytest==
|
|
59
|
-
Requires-Dist: pylint==4.0.
|
|
62
|
+
Requires-Dist: pytest==9.0.2; extra == "dev-pytest"
|
|
63
|
+
Requires-Dist: pylint==4.0.5; extra == "dev-pytest"
|
|
60
64
|
Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.13.7; extra == "dev-pytest"
|
|
61
65
|
Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
|
|
62
66
|
Requires-Dist: pytest-asyncio==1.3.0; extra == "dev-pytest"
|
|
63
67
|
Requires-Dist: async-solipsism==0.9; extra == "dev-pytest"
|
|
64
68
|
Provides-Extra: dev
|
|
65
|
-
Requires-Dist: frequenz-gridpool[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
|
|
69
|
+
Requires-Dist: frequenz-gridpool[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest,render-graph]; extra == "dev"
|
|
66
70
|
Dynamic: license-file
|
|
67
71
|
|
|
68
72
|
# Frequenz Gridpool Library
|
|
@@ -85,6 +89,50 @@ The following platforms are officially supported (tested):
|
|
|
85
89
|
- **Operating System:** Ubuntu Linux 20.04
|
|
86
90
|
- **Architectures:** amd64, arm64
|
|
87
91
|
|
|
92
|
+
## CLI
|
|
93
|
+
|
|
94
|
+
This package ships the `gridpool-cli` command with two subcommands.
|
|
95
|
+
|
|
96
|
+
### Setup
|
|
97
|
+
|
|
98
|
+
Set the Assets API credentials before running the CLI:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
export ASSETS_API_URL="grpc://..."
|
|
102
|
+
export ASSETS_API_AUTH_KEY="..."
|
|
103
|
+
export ASSETS_API_SIGN_SECRET="..."
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Print component formulas
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
gridpool-cli print-formulas <microgrid_id>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Optional prefix formatting:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
gridpool-cli print-formulas <microgrid_id> --prefix "{microgrid_id}.{component}"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Render component graph
|
|
119
|
+
|
|
120
|
+
Rendering requires optional dependencies. Install with:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install frequenz-gridpool[render-graph]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
gridpool-cli render-graph <microgrid_id>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
To save without opening a window:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
gridpool-cli render-graph <microgrid_id> --no-show --output component_graph.png
|
|
134
|
+
```
|
|
135
|
+
|
|
88
136
|
## Contributing
|
|
89
137
|
|
|
90
138
|
If you want to know how to build this project and contribute to it, please
|
{frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/SOURCES.txt
RENAMED
|
@@ -10,6 +10,7 @@ src/frequenz/gridpool/conftest.py
|
|
|
10
10
|
src/frequenz/gridpool/py.typed
|
|
11
11
|
src/frequenz/gridpool/cli/__init__.py
|
|
12
12
|
src/frequenz/gridpool/cli/__main__.py
|
|
13
|
+
src/frequenz/gridpool/cli/_render_graph.py
|
|
13
14
|
src/frequenz_gridpool.egg-info/PKG-INFO
|
|
14
15
|
src/frequenz_gridpool.egg-info/SOURCES.txt
|
|
15
16
|
src/frequenz_gridpool.egg-info/dependency_links.txt
|
{frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/requires.txt
RENAMED
|
@@ -2,10 +2,10 @@ marshmallow-dataclass<9,>=8.7.1
|
|
|
2
2
|
asyncclick<9,>=8.3.0.4
|
|
3
3
|
typing-extensions<5,>=4.14.1
|
|
4
4
|
frequenz-microgrid-component-graph<0.4,>=0.3.4
|
|
5
|
-
frequenz-client-assets<0.
|
|
5
|
+
frequenz-client-assets<0.4,>=0.3.0
|
|
6
6
|
|
|
7
7
|
[dev]
|
|
8
|
-
frequenz-gridpool[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]
|
|
8
|
+
frequenz-gridpool[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest,render-graph]
|
|
9
9
|
|
|
10
10
|
[dev-flake8]
|
|
11
11
|
flake8==7.3.0
|
|
@@ -19,20 +19,21 @@ black==25.11.0
|
|
|
19
19
|
isort==7.0.0
|
|
20
20
|
|
|
21
21
|
[dev-mkdocs]
|
|
22
|
-
Markdown==3.10
|
|
22
|
+
Markdown==3.10.2
|
|
23
23
|
black==25.11.0
|
|
24
24
|
mike==2.1.3
|
|
25
25
|
mkdocs-gen-files==0.5.0
|
|
26
26
|
mkdocs-literate-nav==0.6.2
|
|
27
27
|
mkdocs-macros-plugin==1.5.0
|
|
28
|
-
mkdocs-material==9.7.
|
|
29
|
-
mkdocstrings[python]==1.0.
|
|
28
|
+
mkdocs-material==9.7.3
|
|
29
|
+
mkdocstrings[python]==1.0.3
|
|
30
30
|
mkdocstrings-python==1.19.0
|
|
31
31
|
frequenz-repo-config[lib]==0.13.7
|
|
32
32
|
|
|
33
33
|
[dev-mypy]
|
|
34
|
-
mypy==1.19.
|
|
35
|
-
types-
|
|
34
|
+
mypy==1.19.1
|
|
35
|
+
types-networkx>=3.6.1.20251220
|
|
36
|
+
types-Markdown==3.10.2.20260211
|
|
36
37
|
frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]
|
|
37
38
|
|
|
38
39
|
[dev-noxfile]
|
|
@@ -43,9 +44,13 @@ frequenz-repo-config[lib]==0.13.7
|
|
|
43
44
|
frequenz-gridpool[dev-mkdocs,dev-noxfile,dev-pytest]
|
|
44
45
|
|
|
45
46
|
[dev-pytest]
|
|
46
|
-
pytest==
|
|
47
|
-
pylint==4.0.
|
|
47
|
+
pytest==9.0.2
|
|
48
|
+
pylint==4.0.5
|
|
48
49
|
frequenz-repo-config[extra-lint-examples]==0.13.7
|
|
49
50
|
pytest-mock==3.15.1
|
|
50
51
|
pytest-asyncio==1.3.0
|
|
51
52
|
async-solipsism==0.9
|
|
53
|
+
|
|
54
|
+
[render-graph]
|
|
55
|
+
matplotlib<4,>=3.7.0
|
|
56
|
+
networkx<4,>=3.0
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Frequenz Gridpool Library Release Notes
|
|
2
|
-
|
|
3
|
-
## Summary
|
|
4
|
-
|
|
5
|
-
<!-- Here goes a general summary of what this release is about -->
|
|
6
|
-
|
|
7
|
-
## Upgrading
|
|
8
|
-
|
|
9
|
-
* The minimum required version of `frequenz-microgrid-component-graph` is now `v0.3.4`.
|
|
10
|
-
|
|
11
|
-
## New Features
|
|
12
|
-
|
|
13
|
-
<!-- Here goes the main new features and examples or instructions on how to use them -->
|
|
14
|
-
|
|
15
|
-
## Bug Fixes
|
|
16
|
-
|
|
17
|
-
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{frequenz_gridpool-0.3.0 → frequenz_gridpool-0.4.0}/src/frequenz_gridpool.egg-info/top_level.txt
RENAMED
|
File without changes
|