frequenz-cs-reporting 0.5.3__tar.gz → 0.5.4__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 (81) hide show
  1. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/Dockerfile +0 -1
  2. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/PKG-INFO +1 -1
  3. frequenz_cs_reporting-0.5.4/RELEASE_NOTES.md +15 -0
  4. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/app_pages/reporting.py +80 -61
  5. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/client_factory.py +32 -3
  6. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/parquet_cache.py +10 -2
  7. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz_cs_reporting.egg-info/PKG-INFO +1 -1
  8. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz_cs_reporting.egg-info/scm_file_list.json +1 -0
  9. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz_cs_reporting.egg-info/scm_version.json +2 -2
  10. frequenz_cs_reporting-0.5.3/RELEASE_NOTES.md +0 -17
  11. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/.streamlit/config.toml +0 -0
  12. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/LICENSE +0 -0
  13. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/MANIFEST.in +0 -0
  14. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/README.md +0 -0
  15. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/app.py +0 -0
  16. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/pyproject.toml +0 -0
  17. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/setup.cfg +0 -0
  18. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/__init__.py +0 -0
  19. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/app_pages/__init__.py +0 -0
  20. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/app_pages/battery_optimization.py +0 -0
  21. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/app_pages/data_export.py +0 -0
  22. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/app_pages/home.py +0 -0
  23. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/app_pages/solar.py +0 -0
  24. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/assets/__init__.py +0 -0
  25. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/assets/neustrom_background.png +0 -0
  26. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/assets/neustrom_logo.png +0 -0
  27. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/components/__init__.py +0 -0
  28. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/components/inputs.py +0 -0
  29. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/components/plot_charts.py +0 -0
  30. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/components/sidebar_inputs.py +0 -0
  31. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/components/tables.py +0 -0
  32. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/components/ui.py +0 -0
  33. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/conftest.py +0 -0
  34. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/constants.py +0 -0
  35. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/content/tooltips.toml +0 -0
  36. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/py.typed +0 -0
  37. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/rep_cs_core/__init__.py +0 -0
  38. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/rep_cs_core/config.py +0 -0
  39. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/rep_cs_core/page_spec.py +0 -0
  40. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/__init__.py +0 -0
  41. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/battery_capacity.py +0 -0
  42. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/battery_optimization_calculations.py +0 -0
  43. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/cache_config.py +0 -0
  44. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/component_availability.py +0 -0
  45. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/data_service.py +0 -0
  46. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/deepnote_cache.py +0 -0
  47. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/market_metering.py +0 -0
  48. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/reporting_session.py +0 -0
  49. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/solar_workflow.py +0 -0
  50. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/services/sync_data.py +0 -0
  51. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/styles/dashboard.css +0 -0
  52. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/styles/data_export.css +0 -0
  53. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/styles/home.css +0 -0
  54. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/styles/kpi.css +0 -0
  55. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/styles/plot_card.css +0 -0
  56. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/dashboard_section_divider.html +0 -0
  57. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/home_feature_grid.html +0 -0
  58. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/home_feature_item.html +0 -0
  59. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/home_footer.html +0 -0
  60. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/home_hero.html +0 -0
  61. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/home_logo.html +0 -0
  62. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/home_stat_item.html +0 -0
  63. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/templates/home_stats.html +0 -0
  64. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/ui_resources.py +0 -0
  65. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/utils/__init__.py +0 -0
  66. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/utils/env.py +0 -0
  67. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/utils/time.py +0 -0
  68. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/utils/timing.py +0 -0
  69. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/__init__.py +0 -0
  70. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/battery_optimization.py +0 -0
  71. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/component_sources.py +0 -0
  72. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/dashboard.py +0 -0
  73. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/metric_renderers.py +0 -0
  74. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/plot_renderers.py +0 -0
  75. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/sections.py +0 -0
  76. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/solar_results.py +0 -0
  77. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz/cs_reporting/views/table_renderers.py +0 -0
  78. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz_cs_reporting.egg-info/SOURCES.txt +0 -0
  79. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz_cs_reporting.egg-info/dependency_links.txt +0 -0
  80. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz_cs_reporting.egg-info/requires.txt +0 -0
  81. {frequenz_cs_reporting-0.5.3 → frequenz_cs_reporting-0.5.4}/src/frequenz_cs_reporting.egg-info/top_level.txt +0 -0
@@ -31,7 +31,6 @@ RUN pip install --no-cache-dir \
31
31
  "kaleido>=0.2.1,<1.2.0" \
32
32
  "marshmallow>=4.1.0,<5" \
33
33
  "grpcio>=1.81.1,<2" \
34
- "frequenz-cs-reporting>=0.5.0,<0.6.0"
35
34
 
36
35
  # Prefer packages installed in Deepnote's venv over Deepnote's toolkit server libs.
37
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.5.3
3
+ Version: 0.5.4
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
@@ -0,0 +1,15 @@
1
+ # Frequenz CS Reporting Library Release Notes
2
+
3
+ ## Summary
4
+
5
+
6
+ ## Upgrading
7
+
8
+
9
+ ## New Features
10
+
11
+
12
+ ## Bug Fixes
13
+
14
+ - Reporting data is now loaded directly instead of waiting for a warm cache or a background refresh, so the displayed data is current for the selected range.
15
+ - Battery state-of-charge and grid metering data are fetched in parallel, reducing reporting-page load time.
@@ -6,8 +6,11 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import re
9
- from concurrent.futures import Future
9
+ import threading
10
+ from collections.abc import Callable
11
+ from concurrent.futures import Future, ThreadPoolExecutor
10
12
  from datetime import UTC, datetime, timedelta
13
+ from typing import Any
11
14
 
12
15
  import pandas as pd
13
16
  import streamlit as st
@@ -16,6 +19,7 @@ from frequenz.lib.notebooks.reporting.utils.helpers import (
16
19
  normalize_date_for_reporting,
17
20
  set_date_to_midnight,
18
21
  )
22
+ from streamlit.runtime.scriptrunner import add_script_run_ctx, get_script_run_ctx
19
23
 
20
24
  from frequenz.cs_reporting.components.sidebar_inputs import collect_sidebar_inputs
21
25
  from frequenz.cs_reporting.rep_cs_core.page_spec import PageSpec
@@ -26,12 +30,10 @@ from frequenz.cs_reporting.services.client_factory import (
26
30
  get_microgrid_names,
27
31
  )
28
32
  from frequenz.cs_reporting.services.data_service import (
29
- get_cached_microgrid_data,
30
33
  get_dataset_sync_metadata,
31
34
  get_market_metering_data,
32
35
  get_microgrid_data,
33
36
  get_microgrid_soc_data,
34
- start_background_microgrid_data_sync,
35
37
  )
36
38
  from frequenz.cs_reporting.services.reporting_session import (
37
39
  ReportingDataKey,
@@ -75,21 +77,48 @@ def _scroll_to_section_if_requested() -> None:
75
77
  )
76
78
 
77
79
 
78
- @st.fragment(run_every=2.0)
79
- def _poll_active_power_sync(
80
- future: Future[pd.DataFrame],
81
- ) -> None:
82
- """Rerun the page when its already-started active-power refresh finishes."""
83
- if not future.done():
84
- st.info("Microgrid-Daten werden aktualisiert …")
85
- return
80
+ def _submit_with_script_context(
81
+ executor: ThreadPoolExecutor, fn: Callable[..., Any], *args: Any, **kwargs: Any
82
+ ) -> Future[Any]:
83
+ """Run ``fn`` in ``executor`` with the current Streamlit script context.
86
84
 
87
- try:
88
- future.result()
89
- except Exception as exc: # pylint: disable=broad-exception-caught
90
- st.warning(f"Microgrid-Daten konnten nicht aktualisiert werden: {exc}")
91
- return
92
- st.rerun()
85
+ Streamlit caches and session state need the script context, which worker
86
+ threads do not inherit.
87
+ """
88
+ ctx = get_script_run_ctx()
89
+
90
+ def run() -> Any:
91
+ if ctx is not None:
92
+ add_script_run_ctx(threading.current_thread(), ctx)
93
+ return fn(*args, **kwargs)
94
+
95
+ return executor.submit(run)
96
+
97
+
98
+ def _load_battery_soc(
99
+ microgrid_id: int, start: datetime, end: datetime, resolution: timedelta
100
+ ) -> pd.DataFrame:
101
+ """Load battery SOC data, timing the whole load."""
102
+ with log_duration("reporting.load_battery_soc"):
103
+ return get_microgrid_soc_data(
104
+ microgrid_id=microgrid_id,
105
+ start_date=start,
106
+ end_date=end,
107
+ resolution=resolution,
108
+ )
109
+
110
+
111
+ def _load_market_metering(
112
+ microgrid_id: int, start: datetime, end: datetime, resolution: timedelta
113
+ ) -> pd.DataFrame:
114
+ """Load DSO market-metering data, timing the whole load."""
115
+ with log_duration("reporting.load_market_metering"):
116
+ return get_market_metering_data(
117
+ microgrid_id=microgrid_id,
118
+ start_date=start,
119
+ end_date=end,
120
+ resolution=resolution,
121
+ )
93
122
 
94
123
 
95
124
  def _parse_resolution(resolution_str: str) -> timedelta:
@@ -258,7 +287,6 @@ def _render_reporting_page(*, export_only: bool) -> None:
258
287
  # We extend the fetch end date by 1 day to ensure the API returns data
259
288
  # for the full duration of the selected end_time.
260
289
 
261
- active_power_sync: Future[pd.DataFrame] | None = None
262
290
  try:
263
291
  with st.spinner("Microgrid-Daten werden geladen..."):
264
292
  with log_duration("reporting.load_component_types"):
@@ -275,6 +303,8 @@ def _render_reporting_page(*, export_only: bool) -> None:
275
303
  else:
276
304
  st.title(heading)
277
305
  snapshot = take_reporting_data_snapshot(data_key)
306
+ soc_future: Future[Any] | None = None
307
+ dso_future: Future[Any] | None = None
278
308
  if snapshot is not None:
279
309
  df = snapshot.active_power.copy()
280
310
  sync_metadata = snapshot.sync_metadata
@@ -287,31 +317,39 @@ def _render_reporting_page(*, export_only: bool) -> None:
287
317
  None if snapshot.dso_energy is None else snapshot.dso_energy.copy()
288
318
  )
289
319
  else:
320
+ # The datasets are independent, so fetch them concurrently.
321
+ executor = ThreadPoolExecutor(max_workers=2)
322
+ soc_future = (
323
+ _submit_with_script_context(
324
+ executor,
325
+ _load_battery_soc,
326
+ microgrid_id,
327
+ previous_start_time,
328
+ end_time,
329
+ resolution,
330
+ )
331
+ if "battery" in component_types
332
+ else None
333
+ )
334
+ dso_future = _submit_with_script_context(
335
+ executor,
336
+ _load_market_metering,
337
+ microgrid_id,
338
+ previous_start_time,
339
+ end_time,
340
+ resolution,
341
+ )
342
+ executor.shutdown(wait=False)
290
343
  with log_duration("reporting.load_active_power"):
291
- df, sync_metadata = get_cached_microgrid_data(
344
+ df = get_microgrid_data(
292
345
  microgrid_id=microgrid_id,
293
346
  start_date=previous_start_time,
294
347
  end_date=end_time,
295
348
  resolution=resolution,
296
349
  )
297
- if df.empty:
298
- with log_duration("reporting.initial_active_power_sync"):
299
- df = get_microgrid_data(
300
- microgrid_id=microgrid_id,
301
- start_date=previous_start_time,
302
- end_date=end_time,
303
- resolution=resolution,
304
- )
305
- sync_metadata = get_dataset_sync_metadata(
306
- "active_power", microgrid_id, resolution
307
- )
308
- else:
309
- active_power_sync = start_background_microgrid_data_sync(
310
- microgrid_id=microgrid_id,
311
- start_date=previous_start_time,
312
- end_date=end_time,
313
- resolution=resolution,
314
- )
350
+ sync_metadata = get_dataset_sync_metadata(
351
+ "active_power", microgrid_id, resolution
352
+ )
315
353
  battery_soc_df = None
316
354
  dso_energy_df = None
317
355
  if sync_metadata.status == "error":
@@ -320,30 +358,18 @@ def _render_reporting_page(*, export_only: bool) -> None:
320
358
  "Die Aktualisierung ist fehlgeschlagen; angezeigt werden die "
321
359
  f"zuletzt erfolgreich gespeicherten Daten ({last_sync})."
322
360
  )
323
- if snapshot is None and "battery" in component_types:
361
+ if soc_future is not None:
324
362
  try:
325
- with log_duration("reporting.load_battery_soc"):
326
- fetched_soc_df = get_microgrid_soc_data(
327
- microgrid_id=microgrid_id,
328
- start_date=previous_start_time,
329
- end_date=end_time,
330
- resolution=resolution,
331
- )
363
+ fetched_soc_df = soc_future.result()
332
364
  except Exception as exc: # pylint: disable=broad-except
333
365
  st.warning(
334
366
  f"Batterie-SOC-Daten konnten nicht geladen werden: {exc}"
335
367
  )
336
368
  else:
337
369
  battery_soc_df = None if fetched_soc_df.empty else fetched_soc_df
338
- if snapshot is None:
370
+ if dso_future is not None:
339
371
  try:
340
- with log_duration("reporting.load_market_metering"):
341
- fetched_dso_energy_df = get_market_metering_data(
342
- microgrid_id=microgrid_id,
343
- start_date=previous_start_time,
344
- end_date=end_time,
345
- resolution=resolution,
346
- )
372
+ fetched_dso_energy_df = dso_future.result()
347
373
  except Exception as exc: # pylint: disable=broad-except
348
374
  st.warning(
349
375
  "Netzbetreiber-Messdaten konnten nicht geladen werden: "
@@ -353,7 +379,7 @@ def _render_reporting_page(*, export_only: bool) -> None:
353
379
  dso_energy_df = (
354
380
  None if fetched_dso_energy_df.empty else fetched_dso_energy_df
355
381
  )
356
- if snapshot is None and active_power_sync is None:
382
+ if snapshot is None:
357
383
  store_reporting_data_snapshot(
358
384
  ReportingDataSnapshot(
359
385
  key=data_key,
@@ -399,9 +425,6 @@ def _render_reporting_page(*, export_only: bool) -> None:
399
425
  resolution,
400
426
  )
401
427
  if current_master_df.empty:
402
- if active_power_sync is not None and not active_power_sync.done():
403
- _poll_active_power_sync(active_power_sync)
404
- st.stop()
405
428
  st.warning(_NO_REPORTING_DATA_WARNING)
406
429
  st.stop()
407
430
 
@@ -431,10 +454,6 @@ def _render_reporting_page(*, export_only: bool) -> None:
431
454
  dso_sync_metadata.skipped_market_location_ids
432
455
  ),
433
456
  )
434
- if active_power_sync is not None and not active_power_sync.done():
435
- # Rerun once the refresh finishes so the cached-but-stale data shown
436
- # above is replaced without requiring a second filter click.
437
- _poll_active_power_sync(active_power_sync)
438
457
  if not export_only:
439
458
  _scroll_to_section_if_requested()
440
459
 
@@ -342,13 +342,15 @@ def _load_assets_config() -> AssetsConfig:
342
342
  return _load_assets_config_cached(*_microgrid_config_source())
343
343
 
344
344
 
345
- def get_market_location_ids(microgrid_id: int) -> tuple[str, ...]:
346
- """Return configured market-location IDs associated with a microgrid."""
345
+ def _market_location_ids(
346
+ assets_config: AssetsConfig, microgrid_id: int
347
+ ) -> tuple[str, ...]:
348
+ """Return the market-location IDs related to a microgrid."""
347
349
  return tuple(
348
350
  sorted(
349
351
  {
350
352
  str(relation.market_location_id)
351
- for relation in _load_assets_config().relations.values()
353
+ for relation in assets_config.relations.values()
352
354
  if relation.microgrid_id == microgrid_id
353
355
  and relation.market_location_id is not None
354
356
  }
@@ -356,6 +358,33 @@ def get_market_location_ids(microgrid_id: int) -> tuple[str, ...]:
356
358
  )
357
359
 
358
360
 
361
+ def get_market_location_ids(microgrid_id: int) -> tuple[str, ...]:
362
+ """Return configured market-location IDs associated with a microgrid.
363
+
364
+ Only the TOML files defining the microgrid are loaded first, which avoids
365
+ parsing the whole configuration directory. If they hold no market
366
+ relation for the microgrid, the complete configuration is used instead, in
367
+ case the relations live in a different file.
368
+ """
369
+ files = _enterprise_config_files(microgrid_id)
370
+ if not files:
371
+ config_files, _ = _microgrid_config_source()
372
+ files = _microgrid_file_index_cached(config_files).get(microgrid_id)
373
+ if files:
374
+ ids = _market_location_ids(
375
+ _load_assets_config_cached(files, get_microgrid_config_revision()),
376
+ microgrid_id,
377
+ )
378
+ if ids:
379
+ return ids
380
+ _logger.warning(
381
+ "No market relation for microgrid %s in %s; loading all configs.",
382
+ microgrid_id,
383
+ files[0],
384
+ )
385
+ return _market_location_ids(_load_assets_config(), microgrid_id)
386
+
387
+
359
388
  def get_market_metering_client(timeout: float) -> MarketMeteringApiClient:
360
389
  """Create a Market Metering client for one bounded request."""
361
390
  return MarketMeteringApiClient(
@@ -12,6 +12,7 @@ import os
12
12
  import tempfile
13
13
  import time
14
14
  from collections.abc import Callable, Iterator
15
+ from concurrent.futures import ThreadPoolExecutor
15
16
  from contextlib import contextmanager
16
17
  from dataclasses import asdict, dataclass, field
17
18
  from datetime import UTC, datetime, timedelta
@@ -233,9 +234,16 @@ class ParquetRangeCache:
233
234
  unavailable_errors: list[Exception] = []
234
235
  skipped_market_location_ids: set[str] = set()
235
236
  incoming_axis: tuple[str, str | None] | None = None
236
- for range_start, range_end in ranges:
237
+ # Fetch all ranges concurrently; results are consumed in order below.
238
+ executor = ThreadPoolExecutor(max_workers=len(ranges))
239
+ futures = [
240
+ executor.submit(self._fetch_with_retry, fetch, start_, end_)
241
+ for start_, end_ in ranges
242
+ ]
243
+ executor.shutdown(wait=False)
244
+ for (range_start, range_end), future in zip(ranges, futures):
237
245
  try:
238
- raw_frame = self._fetch_with_retry(fetch, range_start, range_end)
246
+ raw_frame = future.result()
239
247
  except Exception as exc: # pylint: disable=broad-except
240
248
  if not (
241
249
  key.name.startswith("day_ahead_prices_")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frequenz-cs-reporting
3
- Version: 0.5.3
3
+ Version: 0.5.4
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
@@ -107,6 +107,7 @@
107
107
  "tests/test_frequenz_cs_reporting.py",
108
108
  "tests/test_market_metering.py",
109
109
  "tests/test_parquet_cache.py",
110
+ "tests/test_reporting_page.py",
110
111
  "tests/test_reporting_session.py",
111
112
  "tests/test_sync_data.py",
112
113
  "tests/test_timing.py"
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "0.5.3",
2
+ "tag": "0.5.4",
3
3
  "distance": 0,
4
- "node": "ge2254faa4e4b939f37772c0878f1eb4ef32a1ac3",
4
+ "node": "gaace9f5f4fb47433e0218b2f7216e1f3dc82eeb9",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-11-10"
@@ -1,17 +0,0 @@
1
- # Frequenz CS Reporting Library Release Notes
2
-
3
- ## Summary
4
-
5
-
6
- ## Upgrading
7
- - Add cs-reporting library dependency to the dockerfile.
8
-
9
- ## New Features
10
-
11
-
12
- ## Bug Fixes
13
-
14
- - Background data synchronizations are shared between dashboard sessions and failed synchronizations are throttled before retrying.
15
- - Hybrid cache persistence now copies data atomically, ignores temporary files, and persists available data when a synchronization fails.
16
- - Time series plots with many points now render with WebGL, and the battery overview plot shows the grid feed-in as "Netzeinspeisung".
17
- - Table column choosers are refreshed when the available columns change, e.g. after applying a new date filter.