frequenz-cs-reporting 0.0.1__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 (47) hide show
  1. frequenz_cs_reporting-0.0.1/.streamlit/config.toml +10 -0
  2. frequenz_cs_reporting-0.0.1/LICENSE +21 -0
  3. frequenz_cs_reporting-0.0.1/MANIFEST.in +13 -0
  4. frequenz_cs_reporting-0.0.1/PKG-INFO +101 -0
  5. frequenz_cs_reporting-0.0.1/README.md +24 -0
  6. frequenz_cs_reporting-0.0.1/RELEASE_NOTES.md +16 -0
  7. frequenz_cs_reporting-0.0.1/app.py +211 -0
  8. frequenz_cs_reporting-0.0.1/pyproject.toml +221 -0
  9. frequenz_cs_reporting-0.0.1/setup.cfg +4 -0
  10. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/__init__.py +4 -0
  11. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/app_pages/__init__.py +4 -0
  12. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/app_pages/dashboard.py +159 -0
  13. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/app_pages/home.py +97 -0
  14. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/app_pages/solar.py +38 -0
  15. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/assets/__init__.py +4 -0
  16. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/assets/neustrom_background.png +0 -0
  17. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/assets/neustrom_logo.png +0 -0
  18. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/components/__init__.py +4 -0
  19. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/components/inputs.py +48 -0
  20. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/components/plot_charts.py +133 -0
  21. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/components/sidebar_inputs.py +164 -0
  22. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/components/tables.py +108 -0
  23. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/components/ui.py +67 -0
  24. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/conftest.py +13 -0
  25. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/constants.py +103 -0
  26. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/py.typed +0 -0
  27. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/rep_cs_core/__init__.py +4 -0
  28. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/rep_cs_core/config.py +39 -0
  29. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/rep_cs_core/page_spec.py +27 -0
  30. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/services/__init__.py +10 -0
  31. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/services/client_factory.py +97 -0
  32. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/services/data_service.py +100 -0
  33. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/utils/__init__.py +9 -0
  34. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/utils/env.py +29 -0
  35. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/utils/time.py +58 -0
  36. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/views/__init__.py +4 -0
  37. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/views/dashboard.py +173 -0
  38. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/views/metric_renderers.py +206 -0
  39. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/views/plot_renderers.py +287 -0
  40. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/views/sections.py +50 -0
  41. frequenz_cs_reporting-0.0.1/src/frequenz/frequenz_cs_reporting/views/table_renderers.py +204 -0
  42. frequenz_cs_reporting-0.0.1/src/frequenz_cs_reporting.egg-info/PKG-INFO +101 -0
  43. frequenz_cs_reporting-0.0.1/src/frequenz_cs_reporting.egg-info/SOURCES.txt +45 -0
  44. frequenz_cs_reporting-0.0.1/src/frequenz_cs_reporting.egg-info/dependency_links.txt +1 -0
  45. frequenz_cs_reporting-0.0.1/src/frequenz_cs_reporting.egg-info/requires.txt +61 -0
  46. frequenz_cs_reporting-0.0.1/src/frequenz_cs_reporting.egg-info/top_level.txt +1 -0
  47. frequenz_cs_reporting-0.0.1/toml_directory/__init__.py +5 -0
@@ -0,0 +1,10 @@
1
+ [theme]
2
+ base = "light"
3
+ primaryColor = "#FF4B4B"
4
+ backgroundColor = "#FFFFFF"
5
+ secondaryBackgroundColor = "#F0F2F6"
6
+ textColor = "#262730"
7
+ font = "sans serif"
8
+
9
+ [server]
10
+ headless = true
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2026 Frequenz Energy-as-a-Service GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ exclude .cookiecutter-replay.json
2
+ exclude .editorconfig
3
+ exclude .gitignore
4
+ exclude CODEOWNERS
5
+ exclude CONTRIBUTING.md
6
+ exclude mkdocs.yml
7
+ exclude noxfile.py
8
+ exclude src/conftest.py
9
+ recursive-exclude .github *
10
+ recursive-exclude benchmarks *
11
+ recursive-exclude docs *
12
+ recursive-exclude tests *
13
+ recursive-include py *.pyi
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.4
2
+ Name: frequenz-cs-reporting
3
+ Version: 0.0.1
4
+ Summary: Streamlit application for reporting and solar monitoring
5
+ Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
+ License: MIT
7
+ Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-cs-reporting/
8
+ Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-cs-reporting/releases
9
+ Project-URL: Issues, https://github.com/frequenz-floss/frequenz-cs-reporting/issues
10
+ Project-URL: Repository, https://github.com/frequenz-floss/frequenz-cs-reporting
11
+ Project-URL: Support, https://github.com/frequenz-floss/frequenz-cs-reporting/discussions/categories/support
12
+ Keywords: frequenz,python,lib,library,frequenz-cs-reporting,tooling,notebooks,streamlit
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: <4,>=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: css-inline>=0.17.0
24
+ Requires-Dist: frequenz-lib-notebooks<0.15.0,>=0.13.0
25
+ Requires-Dist: matplotlib>=3.10.6
26
+ Requires-Dist: numpy>=2.3.2
27
+ Requires-Dist: pandas>=2.3.2
28
+ Requires-Dist: plotly>=6.3.0
29
+ Requires-Dist: pyarrow>=21.0.0
30
+ Requires-Dist: python-box>=7.3.2
31
+ Requires-Dist: python-dateutil>=2.9.0.post0
32
+ Requires-Dist: python-dotenv>=1.1.1
33
+ Requires-Dist: pyyaml>=6.0.3
34
+ Requires-Dist: streamlit>=1.49.1
35
+ Requires-Dist: streamlit-aggrid>=1.1.8.post1
36
+ Requires-Dist: watchdog>=6.0.0
37
+ Provides-Extra: dev-flake8
38
+ Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
39
+ Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
40
+ Requires-Dist: flake8-pyproject==1.2.4; extra == "dev-flake8"
41
+ Requires-Dist: pydoclint==0.8.3; extra == "dev-flake8"
42
+ Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
43
+ Provides-Extra: dev-formatting
44
+ Requires-Dist: black==26.1.0; extra == "dev-formatting"
45
+ Requires-Dist: isort==7.0.0; extra == "dev-formatting"
46
+ Provides-Extra: dev-mkdocs
47
+ Requires-Dist: Markdown==3.10; extra == "dev-mkdocs"
48
+ Requires-Dist: black==26.1.0; extra == "dev-mkdocs"
49
+ Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
50
+ Requires-Dist: mkdocs-gen-files==0.6.0; extra == "dev-mkdocs"
51
+ Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
52
+ Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
53
+ Requires-Dist: mkdocs-material==9.7.1; extra == "dev-mkdocs"
54
+ Requires-Dist: mkdocstrings[python]==1.0.1; extra == "dev-mkdocs"
55
+ Requires-Dist: mkdocstrings-python==2.0.1; extra == "dev-mkdocs"
56
+ Requires-Dist: frequenz-repo-config[lib]==0.14.0; extra == "dev-mkdocs"
57
+ Provides-Extra: dev-mypy
58
+ Requires-Dist: mypy==1.19.1; extra == "dev-mypy"
59
+ Requires-Dist: types-Markdown==3.10.0.20251106; extra == "dev-mypy"
60
+ Requires-Dist: pandas-stubs; extra == "dev-mypy"
61
+ Requires-Dist: frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
62
+ Provides-Extra: dev-noxfile
63
+ Requires-Dist: nox==2025.11.12; extra == "dev-noxfile"
64
+ Requires-Dist: frequenz-repo-config[lib]==0.14.0; extra == "dev-noxfile"
65
+ Provides-Extra: dev-pylint
66
+ Requires-Dist: frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
67
+ Provides-Extra: dev-pytest
68
+ Requires-Dist: pytest==9.0.2; extra == "dev-pytest"
69
+ Requires-Dist: pylint==4.0.4; extra == "dev-pytest"
70
+ Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.14.0; extra == "dev-pytest"
71
+ Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
72
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "dev-pytest"
73
+ Requires-Dist: async-solipsism==0.9; extra == "dev-pytest"
74
+ Provides-Extra: dev
75
+ Requires-Dist: frequenz-cs-reporting[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
76
+ Dynamic: license-file
77
+
78
+ # Frequenz CS Reporting Library
79
+
80
+ [![Build Status](https://github.com/frequenz-floss/frequenz-cs-reporting/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-cs-reporting/actions/workflows/ci.yaml)
81
+ [![PyPI Package](https://img.shields.io/pypi/v/frequenz-cs-reporting)](https://pypi.org/project/frequenz-cs-reporting/)
82
+ [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-cs-reporting/)
83
+
84
+ ## Introduction
85
+
86
+ Streamlit application for reporting and solar monitoring
87
+
88
+ TODO(cookiecutter): Improve the README file
89
+
90
+ ## Supported Platforms
91
+
92
+ The following platforms are officially supported (tested):
93
+
94
+ - **Python:** 3.11
95
+ - **Operating System:** Ubuntu Linux 20.04
96
+ - **Architectures:** amd64, arm64
97
+
98
+ ## Contributing
99
+
100
+ If you want to know how to build this project and contribute to it, please
101
+ check out the [Contributing Guide](CONTRIBUTING.md).
@@ -0,0 +1,24 @@
1
+ # Frequenz CS Reporting Library
2
+
3
+ [![Build Status](https://github.com/frequenz-floss/frequenz-cs-reporting/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-cs-reporting/actions/workflows/ci.yaml)
4
+ [![PyPI Package](https://img.shields.io/pypi/v/frequenz-cs-reporting)](https://pypi.org/project/frequenz-cs-reporting/)
5
+ [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-cs-reporting/)
6
+
7
+ ## Introduction
8
+
9
+ Streamlit application for reporting and solar monitoring
10
+
11
+ TODO(cookiecutter): Improve the README file
12
+
13
+ ## Supported Platforms
14
+
15
+ The following platforms are officially supported (tested):
16
+
17
+ - **Python:** 3.11
18
+ - **Operating System:** Ubuntu Linux 20.04
19
+ - **Architectures:** amd64, arm64
20
+
21
+ ## Contributing
22
+
23
+ If you want to know how to build this project and contribute to it, please
24
+ check out the [Contributing Guide](CONTRIBUTING.md).
@@ -0,0 +1,16 @@
1
+ # Frequenz CS Reporting Library Release Notes
2
+
3
+ ## Summary
4
+ - Initial Streamlit-based reporting app that discovers packaged pages, shows branded navigation, and renders microgrid reporting dashboards backed by the frequenz data stack.
5
+
6
+ ## Upgrading
7
+
8
+
9
+ ## New Features
10
+ - Added app.py entrypoint that loads PageSpec definitions, persists navigation in query params, and renders sidebar branding.
11
+ - New Home and Reporting pages: the dashboard offers microgrid/date/timezone/resolution filters, fetches/caches microgrid power data, builds a master dataframe, and drives overview metrics plus consumption breakdowns.
12
+ - Dashboard visuals now include time-series plots, energy-mix pie, component-specific tabs (PV, battery, wind, BHKW, EV), and styled plot cards.
13
+ - Data tables use AgGrid with CSV downloads for power mix, component analyses, and the combined master dataframe; reusable UI helpers and constants were added for consistent styling and column naming.
14
+
15
+ ## Bug Fixes
16
+
@@ -0,0 +1,211 @@
1
+ # License: MIT
2
+ # Copyright © 2026 Frequenz Energy-as-a-Service GmbH
3
+
4
+ """Main application module for the Frequenz CS Reporting Suite."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import importlib
9
+ import sys
10
+ from pathlib import Path
11
+ import importlib.resources as resources
12
+ import importlib.util
13
+ import streamlit as st
14
+ import pkgutil
15
+ import os
16
+
17
+ # --- Check if running in deepnote to pick up environment variables ---
18
+ def running_in_deepnote() -> bool:
19
+ # Common Deepnote env vars (if one changes, the others may still work)
20
+ return any(k in os.environ for k in (
21
+ "DEEPNOTE_PROJECT_ID",
22
+ "DEEPNOTE_WORKSPACE_ID",
23
+ "DEEPNOTE",
24
+ ))
25
+
26
+ IN_DEEPNOTE = running_in_deepnote()
27
+
28
+ if IN_DEEPNOTE:
29
+ import deepnote_toolkit
30
+ deepnote_toolkit.set_integration_env()
31
+
32
+ # --- Project paths & sys.path wiring ----------------------------------------
33
+ PACKAGE_DIR = Path(__file__).resolve().parent
34
+ LIB_PAGES_ROOT = "frequenz.frequenz_cs_reporting.app_pages"
35
+ LOGO_NAME = "neustrom_logo.png"
36
+
37
+
38
+ def _resolve_package_root() -> Path:
39
+ """Return the installed frequenz package path, with a local fallback for dev."""
40
+ spec = importlib.util.find_spec("frequenz.frequenz_cs_reporting")
41
+ if spec and spec.submodule_search_locations:
42
+ return Path(next(iter(spec.submodule_search_locations))).resolve()
43
+ if spec and spec.origin:
44
+ return Path(spec.origin).resolve().parent
45
+ return (PACKAGE_DIR / "src" / "frequenz" / "frequenz_cs_reporting").resolve()
46
+
47
+
48
+ PACKAGE_ROOT = _resolve_package_root()
49
+ APP_PAGES_DIR = PACKAGE_ROOT / "app_pages"
50
+ ASSETS_DIR = PACKAGE_ROOT / "assets"
51
+
52
+ package_parent = str(PACKAGE_ROOT.parent)
53
+ if package_parent not in sys.path:
54
+ sys.path.insert(0, package_parent)
55
+
56
+ # Import PageSpec from your custom library
57
+ from frequenz.frequenz_cs_reporting.rep_cs_core.page_spec import PageSpec
58
+
59
+ # --- Local page loader (from ./app_pages) -----------------------------------
60
+ def _load_local_pages() -> list[PageSpec]:
61
+ """Load PAGE specs from local app_pages/*.py files."""
62
+ if not APP_PAGES_DIR.exists():
63
+ return []
64
+
65
+ pages: list[PageSpec] = []
66
+
67
+ for module_path in sorted(APP_PAGES_DIR.glob("*.py")):
68
+ if module_path.name.startswith("_") or module_path.name == "__init__.py":
69
+ continue
70
+ spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
71
+ if spec is None or spec.loader is None:
72
+ continue
73
+
74
+ module = importlib.util.module_from_spec(spec)
75
+ spec.loader.exec_module(module)
76
+ page = getattr(module, "PAGE", None)
77
+
78
+ if page and all(
79
+ hasattr(page, attr)
80
+ for attr in ("key", "title", "icon", "order", "render")
81
+ ):
82
+ pages.append(page)
83
+
84
+ return pages
85
+
86
+
87
+ # --- Library page discovery (from frequenz.app_pages) -----------------------
88
+ def discover_library_pages(pkg_root: str = LIB_PAGES_ROOT) -> list[PageSpec]:
89
+ """Discover PAGE specs from the installed frequenz app_pages package.
90
+
91
+ Falls back to local ./app_pages if the library package is missing.
92
+ """
93
+ try:
94
+ pkg = importlib.import_module(pkg_root)
95
+ except ModuleNotFoundError:
96
+ # Library not installed; fall back to local pages
97
+ return _load_local_pages()
98
+
99
+ pages: list[PageSpec] = []
100
+
101
+ for _, modname, _ in pkgutil.iter_modules(pkg.__path__, pkg.__name__ + "."):
102
+ short = modname.rsplit(".", 1)[-1]
103
+ if short.startswith("_"):
104
+ continue
105
+
106
+ module = importlib.import_module(modname)
107
+ page = getattr(module, "PAGE", None)
108
+
109
+ if page and all(
110
+ hasattr(page, attr)
111
+ for attr in ("key", "title", "icon", "order", "render")
112
+ ):
113
+ pages.append(page)
114
+
115
+ # Sort by (order, title) for stable navigation
116
+ return sorted(pages, key=lambda p: (p.order, p.title.lower()))
117
+
118
+
119
+ def _load_logo_bytes() -> bytes | None:
120
+ """Fetch the sidebar logo from the installed package resources."""
121
+ try:
122
+ return resources.files("frequenz.frequenz_cs_reporting.assets").joinpath(LOGO_NAME).read_bytes()
123
+ except (FileNotFoundError, ModuleNotFoundError):
124
+ pass
125
+
126
+ local_logo = ASSETS_DIR / LOGO_NAME
127
+ if local_logo.exists():
128
+ return local_logo.read_bytes()
129
+ return None
130
+
131
+
132
+ # Sidebar navigation
133
+ def sidebar(pages: list[PageSpec]) -> PageSpec:
134
+ if logo_bytes := _load_logo_bytes():
135
+ st.sidebar.image(logo_bytes, width='stretch')
136
+
137
+ st.sidebar.divider()
138
+
139
+ state_key = "selected_page"
140
+ valid_keys = {p.key for p in pages}
141
+ default_key = st.query_params.get("page", [pages[0].key])[0]
142
+
143
+ if default_key not in valid_keys:
144
+ default_key = pages[0].key
145
+
146
+ # Initialize session state with the URL query parameter
147
+ st.session_state.setdefault(state_key, default_key)
148
+
149
+ # Ensure the state matches the URL param on the initial load/rerun
150
+ if st.session_state[state_key] != default_key:
151
+ st.session_state[state_key] = default_key
152
+
153
+ # Map display labels to internal keys
154
+ options = {f"{p.icon} {p.title}": p.key for p in pages}
155
+ display_options = list(options.keys())
156
+
157
+ current_key = st.session_state[state_key]
158
+ initial_index = next(
159
+ (idx for idx, page in enumerate(pages) if page.key == current_key), 0
160
+ )
161
+
162
+ # Function to resolve the selected key from the display label
163
+ def get_key_from_label(label: str) -> str:
164
+ return options.get(label, pages[0].key)
165
+
166
+ # Add page navigation header
167
+ st.sidebar.header("📑 Seiten")
168
+
169
+ # Use st.sidebar.radio to manage selection
170
+ selected_label = st.sidebar.radio(
171
+ "Navigation",
172
+ options=display_options,
173
+ index=initial_index,
174
+ key="navigation_radio",
175
+ label_visibility="collapsed",
176
+ )
177
+
178
+ # Add divider after navigation
179
+ st.sidebar.divider()
180
+
181
+ # Update session state and query params based on radio selection
182
+ selected_key = get_key_from_label(selected_label)
183
+ if st.session_state[state_key] != selected_key:
184
+ st.session_state[state_key] = selected_key
185
+ st.query_params.page = selected_key
186
+
187
+ selected = next(p for p in pages if p.key == st.session_state[state_key])
188
+ return selected
189
+
190
+
191
+ # --- Main entrypoint --------------------------------------------------------
192
+ def main() -> None:
193
+ st.set_page_config(
194
+ page_title="Enterprise Reporting App",
195
+ page_icon="🏢",
196
+ layout="wide",
197
+ )
198
+
199
+ pages = discover_library_pages()
200
+ if not pages:
201
+ st.info(f"No pages found under `{LIB_PAGES_ROOT}` (or local app_pages).")
202
+ return
203
+
204
+ selected = sidebar(pages)
205
+
206
+ # Render selected page
207
+ selected.render()
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()
@@ -0,0 +1,221 @@
1
+ # License: MIT
2
+ # Copyright © 2026 Frequenz Energy-as-a-Service GmbH
3
+
4
+ [build-system]
5
+ requires = [
6
+ "setuptools == 80.9.0",
7
+ "setuptools_scm[toml] == 9.2.2",
8
+ "frequenz-repo-config[lib] == 0.14.0",
9
+ ]
10
+ build-backend = "setuptools.build_meta"
11
+
12
+ [project]
13
+ name = "frequenz-cs-reporting"
14
+ description = "Streamlit application for reporting and solar monitoring"
15
+ readme = "README.md"
16
+ dynamic = ["version"]
17
+ license = { text = "MIT" }
18
+ keywords = ["frequenz", "python", "lib", "library", "frequenz-cs-reporting", "tooling", "notebooks", "streamlit"]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Topic :: Software Development :: Libraries",
26
+ "Typing :: Typed",
27
+ ]
28
+ requires-python = ">= 3.11, < 4"
29
+
30
+ dependencies = [
31
+ "css-inline>=0.17.0",
32
+ "frequenz-lib-notebooks>=0.13.0, < 0.15.0",
33
+ "matplotlib>=3.10.6",
34
+ "numpy>=2.3.2",
35
+ "pandas>=2.3.2",
36
+ "plotly>=6.3.0",
37
+ "pyarrow>=21.0.0",
38
+ "python-box>=7.3.2",
39
+ "python-dateutil>=2.9.0.post0",
40
+ "python-dotenv>=1.1.1",
41
+ "pyyaml>=6.0.3",
42
+ "streamlit>=1.49.1",
43
+ "streamlit-aggrid>=1.1.8.post1",
44
+ "watchdog>=6.0.0",
45
+ ]
46
+
47
+ [[project.authors]]
48
+ name = "Frequenz Energy-as-a-Service GmbH"
49
+ email = "floss@frequenz.com"
50
+
51
+ [project.optional-dependencies]
52
+ dev-flake8 = [
53
+ "flake8 == 7.3.0",
54
+ "flake8-docstrings == 1.7.0",
55
+ "flake8-pyproject == 1.2.4", # For reading the flake8 config from pyproject.toml
56
+ "pydoclint == 0.8.3",
57
+ "pydocstyle == 6.3.0",
58
+ ]
59
+ dev-formatting = ["black == 26.1.0", "isort == 7.0.0"]
60
+ dev-mkdocs = [
61
+ "Markdown == 3.10",
62
+ "black == 26.1.0",
63
+ "mike == 2.1.3",
64
+ "mkdocs-gen-files == 0.6.0",
65
+ "mkdocs-literate-nav == 0.6.2",
66
+ "mkdocs-macros-plugin == 1.5.0",
67
+ "mkdocs-material == 9.7.1",
68
+ "mkdocstrings[python] == 1.0.1",
69
+ "mkdocstrings-python == 2.0.1",
70
+ "frequenz-repo-config[lib] == 0.14.0",
71
+ ]
72
+ dev-mypy = [
73
+ "mypy == 1.19.1",
74
+ "types-Markdown == 3.10.0.20251106",
75
+ "pandas-stubs",
76
+ # For checking the noxfile, docs/ script, and tests
77
+ "frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]",
78
+ ]
79
+ dev-noxfile = [
80
+ "nox == 2025.11.12",
81
+ "frequenz-repo-config[lib] == 0.14.0",
82
+ ]
83
+ dev-pylint = [
84
+ # dev-pytest already defines a dependency to pylint because of the examples
85
+ # For checking the noxfile, docs/ script, and tests
86
+ "frequenz-cs-reporting[dev-mkdocs,dev-noxfile,dev-pytest]",
87
+ ]
88
+ dev-pytest = [
89
+ "pytest == 9.0.2",
90
+ "pylint == 4.0.4", # We need this to check for the examples
91
+ "frequenz-repo-config[extra-lint-examples] == 0.14.0",
92
+ "pytest-mock == 3.15.1",
93
+ "pytest-asyncio == 1.3.0",
94
+ "async-solipsism == 0.9",
95
+ ]
96
+ dev = [
97
+ "frequenz-cs-reporting[dev-mkdocs,dev-flake8,dev-formatting,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
98
+ ]
99
+
100
+ [project.urls]
101
+ Documentation = "https://frequenz-floss.github.io/frequenz-cs-reporting/"
102
+ Changelog = "https://github.com/frequenz-floss/frequenz-cs-reporting/releases"
103
+ Issues = "https://github.com/frequenz-floss/frequenz-cs-reporting/issues"
104
+ Repository = "https://github.com/frequenz-floss/frequenz-cs-reporting"
105
+ Support = "https://github.com/frequenz-floss/frequenz-cs-reporting/discussions/categories/support"
106
+
107
+ [tool.setuptools]
108
+ # This tells setuptools to look for packages inside the 'src' directory.
109
+ package-dir = {"" = "src"}
110
+ include-package-data = true
111
+
112
+ [tool.setuptools.packages.find]
113
+ where = ["src"]
114
+ include = ["frequenz*"]
115
+ namespaces = true
116
+
117
+ [tool.setuptools.package-data]
118
+ "frequenz.frequenz_cs_reporting" = [
119
+ "assets/*.png",
120
+ "assets/*.jpg",
121
+ "assets/*.svg",
122
+ "assets/*.json",
123
+ ".streamlit/config.toml",
124
+ ]
125
+
126
+ [tool.black]
127
+ line-length = 88
128
+ target-version = ["py311", "py312"]
129
+
130
+ [tool.isort]
131
+ profile = "black"
132
+ line_length = 88
133
+ src_paths = ["benchmarks", "examples", "src", "tests"]
134
+
135
+ [tool.flake8]
136
+ # We give some flexibility to go over 88, there are cases like long URLs or
137
+ # code in documenation that have extra indentation. Black will still take care
138
+ # of making everything that can be 88 wide, 88 wide.
139
+ max-line-length = 100
140
+ extend-ignore = [
141
+ "E203", # Whitespace before ':' (conflicts with black)
142
+ "W503", # Line break before binary operator (conflicts with black)
143
+ ]
144
+ # pydoclint options
145
+ style = "google"
146
+ check-return-types = false
147
+ check-yield-types = false
148
+ arg-type-hints-in-docstring = false
149
+ arg-type-hints-in-signature = true
150
+ allow-init-docstring = true
151
+ check-class-attributes = false
152
+
153
+ [tool.pylint.similarities]
154
+ ignore-comments = ['yes']
155
+ ignore-docstrings = ['yes']
156
+ ignore-imports = ['no']
157
+ min-similarity-lines = 40
158
+
159
+ [tool.pylint.messages_control]
160
+ disable = [
161
+ "too-few-public-methods",
162
+ "too-many-return-statements",
163
+ # disabled because it conflicts with isort
164
+ "wrong-import-order",
165
+ "ungrouped-imports",
166
+ # Checked by mypy (and pylint is very flaky checking these)
167
+ "unsubscriptable-object",
168
+ "no-member",
169
+ "no-name-in-module",
170
+ "possibly-used-before-assignment",
171
+ # Checked by flake8
172
+ "f-string-without-interpolation",
173
+ "line-too-long",
174
+ "missing-function-docstring",
175
+ "redefined-outer-name",
176
+ "unnecessary-lambda-assignment",
177
+ "unused-import",
178
+ "unused-variable",
179
+ ]
180
+
181
+ [tool.pytest.ini_options]
182
+ addopts = "-vv"
183
+ filterwarnings = [
184
+ "error",
185
+ "once::DeprecationWarning",
186
+ "once::PendingDeprecationWarning",
187
+ # We ignore warnings about protobuf gencode version being one version older
188
+ # than the current version, as this is supported by protobuf, and we expect to
189
+ # have such cases. If we go too far, we will get a proper error anyways.
190
+ # We use a raw string (single quotes) to avoid the need to escape special
191
+ # characters as this is a regex.
192
+ 'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
193
+ ]
194
+ testpaths = ["tests", "src"]
195
+ asyncio_mode = "auto"
196
+ asyncio_default_fixture_loop_scope = "function"
197
+ required_plugins = ["pytest-asyncio", "pytest-mock"]
198
+
199
+ [tool.mypy]
200
+ explicit_package_bases = true
201
+ namespace_packages = true
202
+ # This option disables mypy cache, and it is sometimes useful to enable it if
203
+ # you are getting weird intermittent error, or error in the CI but not locally
204
+ # (or vice versa). In particular errors saying that type: ignore is not
205
+ # used but getting the original ignored error when removing the type: ignore.
206
+ # See for example: https://github.com/python/mypy/issues/2960
207
+ #no_incremental = true
208
+ packages = ["frequenz.frequenz_cs_reporting"]
209
+ strict = true
210
+
211
+ [[tool.mypy.overrides]]
212
+ module = ["mkdocs_macros.*", "sybil", "sybil.*", "pvlib", "pvlib.*", "plotly", "plotly.*"]
213
+ ignore_missing_imports = true
214
+
215
+ [tool.setuptools_scm]
216
+ version_scheme = "post-release"
217
+
218
+ [dependency-groups]
219
+ dev = [
220
+ "pandas-stubs>=2.3.3.260113",
221
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ # License: MIT
2
+ # Copyright © 2026 Frequenz Energy-as-a-Service GmbH
3
+
4
+ """Top-level package for Frequenz reporting utilities and views."""
@@ -0,0 +1,4 @@
1
+ # License: MIT
2
+ # Copyright © 2026 Frequenz Energy-as-a-Service GmbH
3
+
4
+ """Frequenz CS Reporting App Pages."""