frequenz-cs-reporting 0.4.0__tar.gz → 0.4.2__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.
Files changed (67) hide show
  1. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/Dockerfile +5 -1
  2. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/PKG-INFO +7 -7
  3. frequenz_cs_reporting-0.4.2/RELEASE_NOTES.md +13 -0
  4. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/pyproject.toml +8 -8
  5. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/app_pages/reporting.py +7 -1
  6. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/services/client_factory.py +61 -2
  7. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/services/data_service.py +43 -2
  8. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/views/dashboard.py +32 -1
  9. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/views/plot_renderers.py +36 -62
  10. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz_cs_reporting.egg-info/PKG-INFO +7 -7
  11. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz_cs_reporting.egg-info/requires.txt +6 -6
  12. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz_cs_reporting.egg-info/scm_file_list.json +67 -67
  13. frequenz_cs_reporting-0.4.2/src/frequenz_cs_reporting.egg-info/scm_version.json +8 -0
  14. frequenz_cs_reporting-0.4.0/RELEASE_NOTES.md +0 -17
  15. frequenz_cs_reporting-0.4.0/src/frequenz_cs_reporting.egg-info/scm_version.json +0 -8
  16. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/.streamlit/config.toml +0 -0
  17. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/LICENSE +0 -0
  18. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/MANIFEST.in +0 -0
  19. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/README.md +0 -0
  20. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/app.py +0 -0
  21. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/setup.cfg +0 -0
  22. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/__init__.py +0 -0
  23. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/app_pages/__init__.py +0 -0
  24. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/app_pages/home.py +0 -0
  25. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/app_pages/solar.py +0 -0
  26. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/assets/__init__.py +0 -0
  27. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/assets/neustrom_background.png +0 -0
  28. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/assets/neustrom_logo.png +0 -0
  29. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/components/__init__.py +0 -0
  30. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/components/inputs.py +0 -0
  31. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/components/plot_charts.py +0 -0
  32. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/components/sidebar_inputs.py +0 -0
  33. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/components/tables.py +0 -0
  34. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/components/ui.py +0 -0
  35. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/conftest.py +0 -0
  36. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/constants.py +0 -0
  37. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/py.typed +0 -0
  38. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/rep_cs_core/__init__.py +0 -0
  39. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/rep_cs_core/config.py +0 -0
  40. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/rep_cs_core/page_spec.py +0 -0
  41. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/services/__init__.py +0 -0
  42. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/services/solar_workflow.py +0 -0
  43. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/styles/dashboard.css +0 -0
  44. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/styles/home.css +0 -0
  45. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/styles/kpi.css +0 -0
  46. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/styles/plot_card.css +0 -0
  47. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/dashboard_section_divider.html +0 -0
  48. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/home_feature_grid.html +0 -0
  49. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/home_feature_item.html +0 -0
  50. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/home_footer.html +0 -0
  51. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/home_hero.html +0 -0
  52. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/home_logo.html +0 -0
  53. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/home_stat_item.html +0 -0
  54. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/templates/home_stats.html +0 -0
  55. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/ui_resources.py +0 -0
  56. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/utils/__init__.py +0 -0
  57. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/utils/env.py +0 -0
  58. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/utils/time.py +0 -0
  59. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/views/__init__.py +0 -0
  60. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/views/metric_renderers.py +0 -0
  61. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/views/sections.py +0 -0
  62. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/views/solar_results.py +0 -0
  63. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz/cs_reporting/views/table_renderers.py +0 -0
  64. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz_cs_reporting.egg-info/SOURCES.txt +0 -0
  65. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz_cs_reporting.egg-info/dependency_links.txt +0 -0
  66. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/src/frequenz_cs_reporting.egg-info/top_level.txt +0 -0
  67. {frequenz_cs_reporting-0.4.0 → frequenz_cs_reporting-0.4.2}/toml_directory/__init__.py +0 -0
@@ -29,4 +29,8 @@ RUN pip install --no-cache-dir \
29
29
  "marshmallow-dataclass>=8.7.1,<9" \
30
30
  "plotly>=6.0.0,<6.4.0" \
31
31
  "kaleido>=0.2.1,<1.2.0" \
32
- "marshmallow>=4.1.0,<5"
32
+ "marshmallow>=4.1.0,<5" \
33
+ "grpcio>=1.81.1,<2"
34
+
35
+ # Prefer packages installed in Deepnote's venv over Deepnote's toolkit server libs.
36
+ ENV PYTHONPATH="/root/venv/lib/python3.12/site-packages:${PYTHONPATH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frequenz-cs-reporting
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Streamlit Library for reporting and solar monitoring
5
5
  Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
6
  License-Expression: MIT
@@ -38,7 +38,7 @@ Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
38
38
  Requires-Dist: flake8-datetimez==20.10.0; extra == "dev-flake8"
39
39
  Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
40
40
  Requires-Dist: flake8-pyproject==1.2.4; extra == "dev-flake8"
41
- Requires-Dist: pydoclint==0.9.0; extra == "dev-flake8"
41
+ Requires-Dist: pydoclint==0.9.1; extra == "dev-flake8"
42
42
  Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
43
43
  Provides-Extra: dev-formatting
44
44
  Requires-Dist: black==26.5.1; extra == "dev-formatting"
@@ -50,18 +50,18 @@ Requires-Dist: mike==2.2.0; extra == "dev-mkdocs"
50
50
  Requires-Dist: mkdocs-gen-files==0.6.1; extra == "dev-mkdocs"
51
51
  Requires-Dist: mkdocs-literate-nav==0.6.3; extra == "dev-mkdocs"
52
52
  Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
53
- Requires-Dist: mkdocs-material==9.7.6; extra == "dev-mkdocs"
54
- Requires-Dist: mkdocstrings[python]==1.0.4; extra == "dev-mkdocs"
53
+ Requires-Dist: mkdocs-material==9.7.7; extra == "dev-mkdocs"
54
+ Requires-Dist: mkdocstrings[python]==1.0.6; extra == "dev-mkdocs"
55
55
  Requires-Dist: mkdocstrings-python==2.0.5; extra == "dev-mkdocs"
56
56
  Requires-Dist: frequenz-repo-config[lib]==0.18.0; extra == "dev-mkdocs"
57
57
  Provides-Extra: dev-mypy
58
- Requires-Dist: mypy==2.1.0; extra == "dev-mypy"
59
- Requires-Dist: types-Markdown==3.10.2.20260518; extra == "dev-mypy"
58
+ Requires-Dist: mypy==2.3.0; extra == "dev-mypy"
59
+ Requires-Dist: types-Markdown==3.10.2.20260712; extra == "dev-mypy"
60
60
  Requires-Dist: types-pytz; extra == "dev-mypy"
61
61
  Requires-Dist: pandas-stubs; extra == "dev-mypy"
62
62
  Requires-Dist: frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
63
63
  Provides-Extra: dev-noxfile
64
- Requires-Dist: nox==2026.4.10; extra == "dev-noxfile"
64
+ Requires-Dist: nox==2026.7.11; extra == "dev-noxfile"
65
65
  Requires-Dist: frequenz-repo-config[lib]==0.18.0; extra == "dev-noxfile"
66
66
  Provides-Extra: dev-pylint
67
67
  Requires-Dist: frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
@@ -0,0 +1,13 @@
1
+ # Frequenz CS Reporting Library Release Notes
2
+
3
+ ## Summary
4
+
5
+
6
+ ## Upgrading
7
+
8
+ - Update dockerfile to have grpcio.
9
+
10
+ ## New Features
11
+
12
+
13
+ ## Bug Fixes
@@ -3,8 +3,8 @@
3
3
 
4
4
  [build-system]
5
5
  requires = [
6
- "setuptools == 82.0.1",
7
- "setuptools_scm[toml] == 10.2.0",
6
+ "setuptools == 83.0.0",
7
+ "setuptools_scm[toml] == 10.2.1",
8
8
  "frequenz-repo-config[lib] == 0.18.0",
9
9
  ]
10
10
  build-backend = "setuptools.build_meta"
@@ -54,7 +54,7 @@ dev-flake8 = [
54
54
  "flake8-datetimez == 20.10.0",
55
55
  "flake8-docstrings == 1.7.0",
56
56
  "flake8-pyproject == 1.2.4", # For reading the flake8 config from pyproject.toml
57
- "pydoclint == 0.9.0",
57
+ "pydoclint == 0.9.1",
58
58
  "pydocstyle == 6.3.0",
59
59
  ]
60
60
  dev-formatting = ["black == 26.5.1", "isort == 8.0.1"]
@@ -65,21 +65,21 @@ dev-mkdocs = [
65
65
  "mkdocs-gen-files == 0.6.1",
66
66
  "mkdocs-literate-nav == 0.6.3",
67
67
  "mkdocs-macros-plugin == 1.5.0",
68
- "mkdocs-material == 9.7.6",
69
- "mkdocstrings[python] == 1.0.4",
68
+ "mkdocs-material == 9.7.7",
69
+ "mkdocstrings[python] == 1.0.6",
70
70
  "mkdocstrings-python == 2.0.5",
71
71
  "frequenz-repo-config[lib] == 0.18.0",
72
72
  ]
73
73
  dev-mypy = [
74
- "mypy == 2.1.0",
75
- "types-Markdown == 3.10.2.20260518",
74
+ "mypy == 2.3.0",
75
+ "types-Markdown == 3.10.2.20260712",
76
76
  "types-pytz",
77
77
  "pandas-stubs",
78
78
  # For checking the noxfile, docs/ script, and tests
79
79
  "frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]",
80
80
  ]
81
81
  dev-noxfile = [
82
- "nox == 2026.4.10",
82
+ "nox == 2026.7.11",
83
83
  "frequenz-repo-config[lib] == 0.18.0",
84
84
  ]
85
85
  dev-pylint = [
@@ -162,7 +162,13 @@ def render() -> None:
162
162
 
163
163
  # Build and render dashboard
164
164
  mapper = ColumnMapper.from_default()
165
- master_df = build_master_df(df_prepared, component_types, mcfg, mapper)
165
+ master_df = build_master_df(
166
+ df_prepared,
167
+ component_types,
168
+ mcfg,
169
+ mapper,
170
+ timezone=timezone,
171
+ )
166
172
  render_dashboard(
167
173
  master_df,
168
174
  resolution=resolution,
@@ -5,12 +5,15 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import asyncio
8
9
  import os
9
10
  from pathlib import Path
10
11
 
11
12
  import streamlit as st
13
+ from frequenz.client.assets import AssetsApiClient
14
+ from frequenz.client.common.microgrid import MicrogridId
12
15
  from frequenz.data.microgrid import component_data
13
- from frequenz.gridpool import MicrogridConfig
16
+ from frequenz.gridpool import MicrogridConfig, load_configs
14
17
 
15
18
  from frequenz.cs_reporting.utils.env import require_env
16
19
 
@@ -28,7 +31,28 @@ def _load_microgrid_configs() -> dict[str, MicrogridConfig]:
28
31
  config_root = Path(os.getenv("MICROGRID_CONFIG_DIR", "toml_directory/"))
29
32
  if not config_root.is_dir():
30
33
  raise RuntimeError(f"Microgrid config directory not found: {config_root}")
31
- return MicrogridConfig.load_configs(microgrid_config_dir=config_root)
34
+ config_files: list[str | Path] = sorted(config_root.glob("*.toml"))
35
+ if not config_files:
36
+ raise RuntimeError(f"No microgrid config files found in: {config_root}")
37
+ auth_key = require_env("API_KEY")
38
+ sign_secret = require_env("API_SECRET")
39
+ assets_api_url = require_env("ASSETS_API_URL")
40
+
41
+ async def load() -> dict[str, MicrogridConfig]:
42
+ assets_client = AssetsApiClient(
43
+ assets_api_url,
44
+ auth_key=auth_key,
45
+ sign_secret=sign_secret,
46
+ )
47
+ try:
48
+ return await load_configs(
49
+ default_files=config_files,
50
+ assets_client=assets_client,
51
+ )
52
+ finally:
53
+ await assets_client.disconnect()
54
+
55
+ return asyncio.run(load())
32
56
 
33
57
 
34
58
  def get_microgrid_client(microgrid_id: int) -> component_data.MicrogridData:
@@ -95,3 +119,38 @@ def get_microgrid_ids() -> list[int]:
95
119
  List of configured microgrid IDs.
96
120
  """
97
121
  return sorted(int(mid.replace("iot", "")) for mid in _load_microgrid_configs())
122
+
123
+
124
+ @st.cache_data(show_spinner=False)
125
+ def get_meter_display_names(microgrid_id: int) -> dict[str, str]:
126
+ """Return component display names for a microgrid from the Assets API.
127
+
128
+ Args:
129
+ microgrid_id: Identifier for the target microgrid.
130
+
131
+ Returns:
132
+ Mapping of component ID strings to display names.
133
+ """
134
+ auth_key = require_env("API_KEY")
135
+ sign_secret = require_env("API_SECRET")
136
+ assets_api_url = require_env("ASSETS_API_URL")
137
+
138
+ async def load() -> dict[str, str]:
139
+ assets_client = AssetsApiClient(
140
+ assets_api_url,
141
+ auth_key=auth_key,
142
+ sign_secret=sign_secret,
143
+ )
144
+ try:
145
+ components = await assets_client.list_microgrid_electrical_components(
146
+ MicrogridId(microgrid_id)
147
+ )
148
+ return {
149
+ str(component.id): component.name
150
+ for component in components
151
+ if component.name
152
+ }
153
+ finally:
154
+ await assets_client.disconnect()
155
+
156
+ return asyncio.run(load())
@@ -6,10 +6,15 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import asyncio
9
+ import logging
10
+ import warnings
11
+ from collections.abc import Iterator
12
+ from contextlib import contextmanager
9
13
  from datetime import datetime, timedelta
10
14
 
11
15
  import pandas as pd
12
16
  import streamlit as st
17
+ from pandas.errors import PerformanceWarning
13
18
 
14
19
  from frequenz.cs_reporting.services.client_factory import (
15
20
  get_component_types,
@@ -17,6 +22,41 @@ from frequenz.cs_reporting.services.client_factory import (
17
22
  )
18
23
  from frequenz.cs_reporting.utils.time import validate_range
19
24
 
25
+ _COMPONENT_DATA_LOGGER = "frequenz.data.microgrid.component_data"
26
+
27
+
28
+ class _MissingComponentFilter(logging.Filter):
29
+ """Filter expected missing-component warnings from the data client."""
30
+
31
+ def filter(self, record: logging.LogRecord) -> bool:
32
+ """Return whether a log record should be emitted."""
33
+ if record.name != _COMPONENT_DATA_LOGGER:
34
+ return True
35
+ message = record.getMessage()
36
+ return not (
37
+ message.startswith("Component ID ")
38
+ and message.endswith(" not found in data, setting zero")
39
+ )
40
+
41
+
42
+ @contextmanager
43
+ def _quiet_expected_component_data_warnings() -> Iterator[None]:
44
+ """Suppress expected noisy warnings emitted while building component data."""
45
+ logger = logging.getLogger(_COMPONENT_DATA_LOGGER)
46
+ component_filter = _MissingComponentFilter()
47
+ logger.addFilter(component_filter)
48
+ try:
49
+ with warnings.catch_warnings():
50
+ warnings.filterwarnings(
51
+ "ignore",
52
+ category=PerformanceWarning,
53
+ message="DataFrame is highly fragmented.*",
54
+ module=r"frequenz\.data\.microgrid\.component_data",
55
+ )
56
+ yield
57
+ finally:
58
+ logger.removeFilter(component_filter)
59
+
20
60
 
21
61
  async def fetch_microgrid_data(
22
62
  microgrid_id: int,
@@ -51,11 +91,12 @@ async def fetch_microgrid_data(
51
91
  keep_components=True,
52
92
  splits=True,
53
93
  )
54
- df = await asyncio.wait_for(coro, timeout)
94
+ with _quiet_expected_component_data_warnings():
95
+ df = await asyncio.wait_for(coro, timeout)
55
96
 
56
97
  if df is None or (hasattr(df, "empty") and df.empty):
57
98
  return pd.DataFrame()
58
- return df
99
+ return df.copy()
59
100
 
60
101
 
61
102
  # Cached sync wrapper for Streamlit pages
@@ -5,13 +5,18 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import asyncio
8
9
  from datetime import timedelta
9
10
  from typing import Any, Iterable
10
11
 
11
12
  import pandas as pd
12
13
  import streamlit as st
14
+ from frequenz.lib.notebooks.reporting.asset_optimization.data import (
15
+ merge_day_ahead_prices,
16
+ )
13
17
  from frequenz.lib.notebooks.reporting.data_processing import create_energy_report_df
14
18
  from frequenz.lib.notebooks.reporting.utils.column_mapper import ColumnMapper
19
+ from frequenz.lib.notebooks.reporting.utils.helpers import get_meter_display_names
15
20
  from frequenz.lib.notebooks.reporting.utils.reporting_nb_functions import (
16
21
  aggregate_metrics,
17
22
  build_component_analysis,
@@ -100,6 +105,12 @@ def _build_tables(
100
105
  }
101
106
 
102
107
  overview_df = build_overview_df(master_df, component_types)
108
+ if "day_ahead_price" in master_df.columns and "timestamp" in overview_df.columns:
109
+ overview_df = overview_df.merge(
110
+ master_df[["timestamp", "day_ahead_price"]],
111
+ on="timestamp",
112
+ how="left",
113
+ )
103
114
 
104
115
  return {
105
116
  "power_table": power_table,
@@ -118,6 +129,7 @@ def build_master_df(
118
129
  component_types: Iterable[str],
119
130
  mcfg: Any,
120
131
  mapper: ColumnMapper,
132
+ timezone: str = "Europe/Berlin",
121
133
  ) -> pd.DataFrame:
122
134
  """Transform raw microgrid data into master analysis dataframe.
123
135
 
@@ -133,6 +145,7 @@ def build_master_df(
133
145
  mcfg: Microgrid configuration object containing component metadata and
134
146
  site-specific settings.
135
147
  mapper: Column name mapper for converting internal names to display names.
148
+ timezone: Timezone name to use for the report timestamps.
136
149
 
137
150
  Returns:
138
151
  Master dataframe with processed and aggregated component data, ready for
@@ -147,7 +160,25 @@ def build_master_df(
147
160
  for c in component_types
148
161
  if pd.to_numeric(raw_df[c], errors="coerce").fillna(0).sum() != 0
149
162
  ]
150
- master_df = create_energy_report_df(raw_df, component_types, mcfg, mapper)
163
+ component_display_names = asyncio.run(
164
+ get_meter_display_names(mcfg.meta.microgrid_id)
165
+ )
166
+ master_df = create_energy_report_df(
167
+ raw_df,
168
+ component_types,
169
+ mcfg,
170
+ mapper=mapper,
171
+ tz_name=timezone,
172
+ assume_tz="UTC",
173
+ component_display_names=component_display_names,
174
+ )
175
+ try:
176
+ master_df = merge_day_ahead_prices(
177
+ master_df.set_index("timestamp"),
178
+ dayahead_country_code="DE_LU",
179
+ ).reset_index()
180
+ except (ModuleNotFoundError, ValueError) as exc:
181
+ st.warning(f"Day-ahead prices could not be loaded: {exc}")
151
182
  return master_df
152
183
 
153
184
 
@@ -13,41 +13,17 @@ import streamlit as st
13
13
  from frequenz.lib.notebooks.reporting.plotter import (
14
14
  plot_energy_pie_chart,
15
15
  plot_time_series,
16
+ plot_time_series_battery_usecase,
16
17
  )
17
18
  from frequenz.lib.notebooks.reporting.utils.column_mapper import ColumnMapper
18
19
 
19
20
  from frequenz.cs_reporting.components.ui import render_plot_card
20
21
  from frequenz.cs_reporting.constants import (
21
22
  COLOR_DICT,
22
- COLUMN_RENAME_MAP,
23
23
  COMPONENT_CONFIGS,
24
24
  TablesResult,
25
25
  )
26
26
 
27
- _DEFAULT_PLOT_ORDER = [
28
- "Zeitpunkt",
29
- "MID Gesamtverbrauch",
30
- "Netzbezug",
31
- "Netzeinspeisung",
32
- "PV-Erzeugung",
33
- "KWK-Erzeugung",
34
- "Wind-Erzeugung",
35
- "Batterie Leistungsfluss",
36
- ]
37
-
38
- _DEFAULT_DOTTED_COLS = ["Netzbezug"]
39
- _FILL_EXCLUDE = {
40
- "Zeitpunkt",
41
- "MID Gesamtverbrauch",
42
- "Netzbezug",
43
- "Netzeinspeisung",
44
- "Netz Einspeisung",
45
- }
46
- _OVERVIEW_ALIASES = {
47
- "BHKW-Erzeugung": "KWK-Erzeugung",
48
- "CHP": "KWK-Erzeugung",
49
- "Netz Einspeisung": "Netzeinspeisung",
50
- }
51
27
  _COMPONENT_TABS = [
52
28
  ("PV Leistung", "pv"),
53
29
  ("Batterie", "batt"),
@@ -55,6 +31,9 @@ _COMPONENT_TABS = [
55
31
  ("KWK", "chp"),
56
32
  ("EV", "ev"),
57
33
  ]
34
+ _BATTERY_OVERVIEW_HEIGHT = 580
35
+ _BATTERY_OVERVIEW_MARGIN = {"t": 150, "r": 56, "b": 96, "l": 64}
36
+ _BATTERY_OVERVIEW_RANGE_SLIDER_THICKNESS = 0.15
58
37
 
59
38
 
60
39
  # pylint: disable=too-many-arguments
@@ -206,43 +185,27 @@ def _render_component_tab(
206
185
  )
207
186
 
208
187
 
209
- def _prepare_overview_df(
210
- tables: TablesResult, mapper: ColumnMapper
211
- ) -> pd.DataFrame | None:
212
- """Return overview dataframe with display-ready column names.
188
+ def _prepare_battery_usecase_df(tables: TablesResult) -> pd.DataFrame | None:
189
+ """Return the overview dataframe prepared for the battery-usecase plot.
213
190
 
214
191
  Args:
215
192
  tables: Dictionary containing precomputed tables.
216
- mapper: Column mapper for converting internal names to display names.
217
193
 
218
194
  Returns:
219
- Overview dataframe with renamed columns, or ``None``
220
- when unavailable.
195
+ Battery-usecase dataframe, or ``None`` when unavailable.
221
196
  """
222
197
  overview_df = tables.get("overview_df")
223
198
  if overview_df is None or overview_df.empty:
224
199
  return None
225
200
 
226
- overview_df = mapper.to_display(overview_df)
227
- # Compatibility shim until the latest lib notebooks update rolls out
228
- overview_df = overview_df.rename(columns=COLUMN_RENAME_MAP)
229
-
230
- # Normalize known legacy aliases so plot ordering always includes key series.
231
- for source, target in _OVERVIEW_ALIASES.items():
232
- if source in overview_df.columns and target not in overview_df.columns:
233
- overview_df = overview_df.rename(columns={source: target})
201
+ return overview_df.drop(columns={"grid_feed_in"}, errors="ignore")
234
202
 
235
- return overview_df
236
203
 
237
-
238
- def _render_overview_plot(
239
- overview_df: pd.DataFrame | None, color_dict: dict[str, str] | None
240
- ) -> None:
204
+ def _render_overview_plot(overview_df: pd.DataFrame | None) -> None:
241
205
  """Render the main time-series overview plot.
242
206
 
243
207
  Args:
244
- overview_df: Overview dataframe in display naming convention.
245
- color_dict: Optional color mapping for plot series.
208
+ overview_df: Battery-usecase dataframe in canonical naming convention.
246
209
 
247
210
  Returns:
248
211
  Streamlit components are rendered directly.
@@ -251,19 +214,32 @@ def _render_overview_plot(
251
214
  st.info("Keine Übersichtsdaten zum Plotten verfügbar.")
252
215
  return
253
216
 
254
- palette = color_dict or COLOR_DICT
255
- plot_order = [col for col in _DEFAULT_PLOT_ORDER if col in overview_df.columns]
256
- fill_cols = [col for col in overview_df.columns if col not in _FILL_EXCLUDE]
257
- dotted_cols = [col for col in _DEFAULT_DOTTED_COLS if col in overview_df.columns]
258
-
259
- render_time_series(
217
+ helper_cols = {"battery_charge", "battery_discharge"}
218
+ cols_list = [col for col in overview_df.columns if col not in helper_cols]
219
+ secondary_y_cols = (
220
+ ["day_ahead_price"] if "day_ahead_price" in overview_df.columns else None
221
+ )
222
+ fig = plot_time_series_battery_usecase(
260
223
  overview_df,
261
- title="Lastgang Übersicht",
262
- color_dict=palette,
263
- fill_cols=fill_cols,
264
- plot_order=plot_order,
265
- dotted_cols=dotted_cols,
224
+ time_col="timestamp",
225
+ cols=cols_list,
226
+ legend_title=None,
227
+ secondary_y_cols=secondary_y_cols,
228
+ secondary_y_title="EUR/MWh" if secondary_y_cols else None,
229
+ title="",
230
+ dotted_cols=[
231
+ "grid_consumption_without_battery",
232
+ "peak_before_optimization",
233
+ "day_ahead_price",
234
+ ],
235
+ stack_mode="psc",
236
+ )
237
+ fig.update_layout(
238
+ height=_BATTERY_OVERVIEW_HEIGHT,
239
+ margin=_BATTERY_OVERVIEW_MARGIN,
240
+ xaxis_rangeslider_thickness=_BATTERY_OVERVIEW_RANGE_SLIDER_THICKNESS,
266
241
  )
242
+ render_plot_card("Lastgang Übersicht", fig)
267
243
 
268
244
 
269
245
  def _get_active_tabs(
@@ -273,11 +249,9 @@ def _get_active_tabs(
273
249
  tabs = []
274
250
 
275
251
  # 1. Overview Tab
276
- overview_df = _prepare_overview_df(tables, mapper)
252
+ overview_df = _prepare_battery_usecase_df(tables)
277
253
  if overview_df is not None and not overview_df.empty:
278
- tabs.append(
279
- ("Zeitreihen-Plot", lambda: _render_overview_plot(overview_df, palette))
280
- )
254
+ tabs.append(("Zeitreihen-Plot", lambda: _render_overview_plot(overview_df)))
281
255
 
282
256
  # 2. Energy Mix Tab
283
257
  power_table = tables.get("power_table")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frequenz-cs-reporting
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Streamlit Library for reporting and solar monitoring
5
5
  Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
6
  License-Expression: MIT
@@ -38,7 +38,7 @@ Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
38
38
  Requires-Dist: flake8-datetimez==20.10.0; extra == "dev-flake8"
39
39
  Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
40
40
  Requires-Dist: flake8-pyproject==1.2.4; extra == "dev-flake8"
41
- Requires-Dist: pydoclint==0.9.0; extra == "dev-flake8"
41
+ Requires-Dist: pydoclint==0.9.1; extra == "dev-flake8"
42
42
  Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
43
43
  Provides-Extra: dev-formatting
44
44
  Requires-Dist: black==26.5.1; extra == "dev-formatting"
@@ -50,18 +50,18 @@ Requires-Dist: mike==2.2.0; extra == "dev-mkdocs"
50
50
  Requires-Dist: mkdocs-gen-files==0.6.1; extra == "dev-mkdocs"
51
51
  Requires-Dist: mkdocs-literate-nav==0.6.3; extra == "dev-mkdocs"
52
52
  Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
53
- Requires-Dist: mkdocs-material==9.7.6; extra == "dev-mkdocs"
54
- Requires-Dist: mkdocstrings[python]==1.0.4; extra == "dev-mkdocs"
53
+ Requires-Dist: mkdocs-material==9.7.7; extra == "dev-mkdocs"
54
+ Requires-Dist: mkdocstrings[python]==1.0.6; extra == "dev-mkdocs"
55
55
  Requires-Dist: mkdocstrings-python==2.0.5; extra == "dev-mkdocs"
56
56
  Requires-Dist: frequenz-repo-config[lib]==0.18.0; extra == "dev-mkdocs"
57
57
  Provides-Extra: dev-mypy
58
- Requires-Dist: mypy==2.1.0; extra == "dev-mypy"
59
- Requires-Dist: types-Markdown==3.10.2.20260518; extra == "dev-mypy"
58
+ Requires-Dist: mypy==2.3.0; extra == "dev-mypy"
59
+ Requires-Dist: types-Markdown==3.10.2.20260712; extra == "dev-mypy"
60
60
  Requires-Dist: types-pytz; extra == "dev-mypy"
61
61
  Requires-Dist: pandas-stubs; extra == "dev-mypy"
62
62
  Requires-Dist: frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
63
63
  Provides-Extra: dev-noxfile
64
- Requires-Dist: nox==2026.4.10; extra == "dev-noxfile"
64
+ Requires-Dist: nox==2026.7.11; extra == "dev-noxfile"
65
65
  Requires-Dist: frequenz-repo-config[lib]==0.18.0; extra == "dev-noxfile"
66
66
  Provides-Extra: dev-pylint
67
67
  Requires-Dist: frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
@@ -21,7 +21,7 @@ flake8==7.3.0
21
21
  flake8-datetimez==20.10.0
22
22
  flake8-docstrings==1.7.0
23
23
  flake8-pyproject==1.2.4
24
- pydoclint==0.9.0
24
+ pydoclint==0.9.1
25
25
  pydocstyle==6.3.0
26
26
 
27
27
  [dev-formatting]
@@ -35,20 +35,20 @@ mike==2.2.0
35
35
  mkdocs-gen-files==0.6.1
36
36
  mkdocs-literate-nav==0.6.3
37
37
  mkdocs-macros-plugin==1.5.0
38
- mkdocs-material==9.7.6
39
- mkdocstrings[python]==1.0.4
38
+ mkdocs-material==9.7.7
39
+ mkdocstrings[python]==1.0.6
40
40
  mkdocstrings-python==2.0.5
41
41
  frequenz-repo-config[lib]==0.18.0
42
42
 
43
43
  [dev-mypy]
44
- mypy==2.1.0
45
- types-Markdown==3.10.2.20260518
44
+ mypy==2.3.0
45
+ types-Markdown==3.10.2.20260712
46
46
  types-pytz
47
47
  pandas-stubs
48
48
  frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]
49
49
 
50
50
  [dev-noxfile]
51
- nox==2026.4.10
51
+ nox==2026.7.11
52
52
  frequenz-repo-config[lib]==0.18.0
53
53
 
54
54
  [dev-pylint]
@@ -1,93 +1,93 @@
1
1
  {
2
2
  "files": [
3
- ".gitignore",
4
- "README.md",
3
+ "mkdocs.yml",
4
+ "app.py",
5
+ "MANIFEST.in",
5
6
  "CONTRIBUTING.md",
6
- ".editorconfig",
7
- ".cookiecutter-replay.json",
8
7
  "Dockerfile",
8
+ ".editorconfig",
9
9
  "pyproject.toml",
10
- "MANIFEST.in",
11
- "app.py",
12
- "RELEASE_NOTES.md",
13
- "LICENSE",
14
- "mkdocs.yml",
15
- "CODEOWNERS",
10
+ ".cookiecutter-replay.json",
16
11
  "noxfile.py",
12
+ "CODEOWNERS",
13
+ "LICENSE",
14
+ "RELEASE_NOTES.md",
15
+ "README.md",
16
+ ".gitignore",
17
+ ".github/RELEASE_NOTES.template.md",
18
+ ".github/labeler.yml",
19
+ ".github/dependabot.yml",
20
+ ".github/keylabeler.yml",
21
+ ".github/ISSUE_TEMPLATE/config.yml",
22
+ ".github/ISSUE_TEMPLATE/bug.yml",
23
+ ".github/ISSUE_TEMPLATE/feature.yml",
24
+ ".github/workflows/dco-merge-queue.yml",
25
+ ".github/workflows/ci.yaml",
26
+ ".github/workflows/isort-migration.yaml",
27
+ ".github/workflows/release.yaml",
28
+ ".github/workflows/labeler.yml",
29
+ ".github/workflows/repo-config-migration.yaml",
30
+ ".github/workflows/auto-dependabot.yaml",
31
+ ".github/workflows/black-migration.yaml",
32
+ ".github/workflows/release-notes-check.yml",
33
+ ".github/workflows/ci-pr.yaml",
34
+ "tests/test_frequenz_cs_reporting.py",
17
35
  "toml_directory/__init__.py",
18
- "src/frequenz/cs_reporting/conftest.py",
19
- "src/frequenz/cs_reporting/ui_resources.py",
20
- "src/frequenz/cs_reporting/constants.py",
36
+ ".streamlit/config.toml",
21
37
  "src/frequenz/cs_reporting/py.typed",
22
38
  "src/frequenz/cs_reporting/__init__.py",
23
- "src/frequenz/cs_reporting/services/solar_workflow.py",
24
- "src/frequenz/cs_reporting/services/client_factory.py",
25
- "src/frequenz/cs_reporting/services/data_service.py",
26
- "src/frequenz/cs_reporting/services/__init__.py",
39
+ "src/frequenz/cs_reporting/ui_resources.py",
40
+ "src/frequenz/cs_reporting/conftest.py",
41
+ "src/frequenz/cs_reporting/constants.py",
42
+ "src/frequenz/cs_reporting/utils/__init__.py",
27
43
  "src/frequenz/cs_reporting/utils/env.py",
28
44
  "src/frequenz/cs_reporting/utils/time.py",
29
- "src/frequenz/cs_reporting/utils/__init__.py",
30
- "src/frequenz/cs_reporting/templates/home_footer.html",
45
+ "src/frequenz/cs_reporting/app_pages/__init__.py",
46
+ "src/frequenz/cs_reporting/app_pages/solar.py",
47
+ "src/frequenz/cs_reporting/app_pages/reporting.py",
48
+ "src/frequenz/cs_reporting/app_pages/home.py",
49
+ "src/frequenz/cs_reporting/services/__init__.py",
50
+ "src/frequenz/cs_reporting/services/client_factory.py",
51
+ "src/frequenz/cs_reporting/services/solar_workflow.py",
52
+ "src/frequenz/cs_reporting/services/data_service.py",
53
+ "src/frequenz/cs_reporting/styles/plot_card.css",
54
+ "src/frequenz/cs_reporting/styles/home.css",
55
+ "src/frequenz/cs_reporting/styles/dashboard.css",
56
+ "src/frequenz/cs_reporting/styles/kpi.css",
57
+ "src/frequenz/cs_reporting/views/metric_renderers.py",
58
+ "src/frequenz/cs_reporting/views/sections.py",
59
+ "src/frequenz/cs_reporting/views/plot_renderers.py",
60
+ "src/frequenz/cs_reporting/views/__init__.py",
61
+ "src/frequenz/cs_reporting/views/solar_results.py",
62
+ "src/frequenz/cs_reporting/views/dashboard.py",
63
+ "src/frequenz/cs_reporting/views/table_renderers.py",
64
+ "src/frequenz/cs_reporting/templates/home_stats.html",
65
+ "src/frequenz/cs_reporting/templates/home_hero.html",
31
66
  "src/frequenz/cs_reporting/templates/home_feature_grid.html",
67
+ "src/frequenz/cs_reporting/templates/dashboard_section_divider.html",
32
68
  "src/frequenz/cs_reporting/templates/home_feature_item.html",
33
- "src/frequenz/cs_reporting/templates/home_hero.html",
34
- "src/frequenz/cs_reporting/templates/home_stats.html",
35
69
  "src/frequenz/cs_reporting/templates/home_logo.html",
36
70
  "src/frequenz/cs_reporting/templates/home_stat_item.html",
37
- "src/frequenz/cs_reporting/templates/dashboard_section_divider.html",
38
- "src/frequenz/cs_reporting/styles/dashboard.css",
39
- "src/frequenz/cs_reporting/styles/home.css",
40
- "src/frequenz/cs_reporting/styles/plot_card.css",
41
- "src/frequenz/cs_reporting/styles/kpi.css",
42
- "src/frequenz/cs_reporting/components/sidebar_inputs.py",
71
+ "src/frequenz/cs_reporting/templates/home_footer.html",
72
+ "src/frequenz/cs_reporting/rep_cs_core/page_spec.py",
73
+ "src/frequenz/cs_reporting/rep_cs_core/config.py",
74
+ "src/frequenz/cs_reporting/rep_cs_core/__init__.py",
75
+ "src/frequenz/cs_reporting/components/plot_charts.py",
76
+ "src/frequenz/cs_reporting/components/__init__.py",
43
77
  "src/frequenz/cs_reporting/components/tables.py",
78
+ "src/frequenz/cs_reporting/components/sidebar_inputs.py",
44
79
  "src/frequenz/cs_reporting/components/ui.py",
45
80
  "src/frequenz/cs_reporting/components/inputs.py",
46
- "src/frequenz/cs_reporting/components/plot_charts.py",
47
- "src/frequenz/cs_reporting/components/__init__.py",
48
- "src/frequenz/cs_reporting/app_pages/home.py",
49
- "src/frequenz/cs_reporting/app_pages/reporting.py",
50
- "src/frequenz/cs_reporting/app_pages/solar.py",
51
- "src/frequenz/cs_reporting/app_pages/__init__.py",
52
81
  "src/frequenz/cs_reporting/assets/neustrom_logo.png",
53
- "src/frequenz/cs_reporting/assets/neustrom_background.png",
54
82
  "src/frequenz/cs_reporting/assets/__init__.py",
55
- "src/frequenz/cs_reporting/rep_cs_core/config.py",
56
- "src/frequenz/cs_reporting/rep_cs_core/page_spec.py",
57
- "src/frequenz/cs_reporting/rep_cs_core/__init__.py",
58
- "src/frequenz/cs_reporting/views/plot_renderers.py",
59
- "src/frequenz/cs_reporting/views/metric_renderers.py",
60
- "src/frequenz/cs_reporting/views/table_renderers.py",
61
- "src/frequenz/cs_reporting/views/sections.py",
62
- "src/frequenz/cs_reporting/views/solar_results.py",
63
- "src/frequenz/cs_reporting/views/dashboard.py",
64
- "src/frequenz/cs_reporting/views/__init__.py",
65
- ".github/labeler.yml",
66
- ".github/dependabot.yml",
67
- ".github/keylabeler.yml",
68
- ".github/RELEASE_NOTES.template.md",
69
- ".github/workflows/isort-migration.yaml",
70
- ".github/workflows/labeler.yml",
71
- ".github/workflows/repo-config-migration.yaml",
72
- ".github/workflows/ci-pr.yaml",
73
- ".github/workflows/ci.yaml",
74
- ".github/workflows/black-migration.yaml",
75
- ".github/workflows/dco-merge-queue.yml",
76
- ".github/workflows/release-notes-check.yml",
77
- ".github/workflows/auto-dependabot.yaml",
78
- ".github/workflows/release.yaml",
79
- ".github/ISSUE_TEMPLATE/config.yml",
80
- ".github/ISSUE_TEMPLATE/feature.yml",
81
- ".github/ISSUE_TEMPLATE/bug.yml",
82
- ".streamlit/config.toml",
83
- "tests/test_frequenz_cs_reporting.py",
84
- "docs/SUMMARY.md",
83
+ "src/frequenz/cs_reporting/assets/neustrom_background.png",
85
84
  "docs/CONTRIBUTING.md",
85
+ "docs/SUMMARY.md",
86
86
  "docs/index.md",
87
- "docs/_img/logo.png",
88
87
  "docs/_css/mkdocstrings.css",
89
88
  "docs/_css/style.css",
90
- "docs/_scripts/mkdocstrings_autoapi.py",
91
- "docs/_overrides/main.html"
89
+ "docs/_img/logo.png",
90
+ "docs/_overrides/main.html",
91
+ "docs/_scripts/mkdocstrings_autoapi.py"
92
92
  ]
93
93
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.2",
3
+ "distance": 0,
4
+ "node": "g961f18cd7b218235c1e9d85e021e877c3a3f1b04",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-08-04"
8
+ }
@@ -1,17 +0,0 @@
1
- # Frequenz CS Reporting Library Release Notes
2
-
3
- ## Summary
4
-
5
- ## Upgrading
6
-
7
- - Updating the minimum frequenz-lib-notebooks requirement to v0.16.0
8
-
9
- ## New Features
10
-
11
- - Update the radio button navigation for streamlit in deepnote.
12
- - Add KWK aliases in the line plot.
13
- - Update the KPIs to german format.
14
- `- Replace the solar monitoring `Produktionsstatistiken` table with the
15
- `MicrogridOverviewDashboard` view from `frequenz.lib.notebooks`.
16
-
17
- ## Bug Fixes
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.4.0",
3
- "distance": 0,
4
- "node": "gb14bb5bb0041b0254d8584079fc930047d8d1fca",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-17"
8
- }