Skip to content

Write PV Forecast to the daily log - #4270

Merged
LKuemmel merged 3 commits into
openWB:masterfrom
seaspotter:log-forecast-values
Nov 11, 2026
Merged

LKuemmel merged 3 commits into
openWB:masterfrom
seaspotter:log-forecast-values

Conversation

@seaspotter

@seaspotter seaspotter commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Record the current PV forecast alongside existing daily log data.

New Features:

  • Include the current PV forecast value in each daily log entry.

Enhancements:

  • Select the latest forecast value available at or before the current time when creating log entries.

@sourcery-ai

sourcery-ai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Data structures

Add the current forecast value to the daily log entry so it can be persisted alongside the other measurements.

  • packages/helpermodules/measurement_logging/process_log_entry_builder.py L199

API

Expose a method that retrieves the most recent PV forecast value at or before the current time.

  • packages/control/optional.py L223-236

Uses of the API

Call the forecast accessor while building each log entry, and fall back to no forecast if retrieval fails.

  • packages/helpermodules/measurement_logging/process_log_entry_builder.py L69-74

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="packages/helpermodules/measurement_logging/write_log.py" line_range="150" />
<code_context>
+        existing_forecast = content.get("forecast")
+        if not isinstance(existing_forecast, dict):
+            existing_forecast = {}
+        existing_forecast.update(get_forecast_values())
+        content["forecast"] = existing_forecast
         write_and_check(filepath, content)
</code_context>
<issue_to_address>
**Daily entries disappear on invalid forecast**

When a truthy scalar or list reaches `today_values`, `get_forecast_values()` returns the value and `existing_forecast.update(...)` raises `TypeError`; `save_log` catches it and returns `None`, so the cycle writes no daily entry and downstream daily-yield updates receive no entries.

Validate that `get_forecast_values()` returns a mapping before merging it, and handle invalid values without aborting the daily entry write.
</issue_to_address>

### Comment 2
<location path="packages/helpermodules/measurement_logging/write_log.py" line_range="150" />
<code_context>
+        existing_forecast = content.get("forecast")
+        if not isinstance(existing_forecast, dict):
+            existing_forecast = {}
+        existing_forecast.update(get_forecast_values())
+        content["forecast"] = existing_forecast
         write_and_check(filepath, content)
</code_context>
<issue_to_address>
**Forecast data can exhaust disk**

When an anonymous MQTT client repeatedly publishes `today_values` maps containing new keys, `save_log` merges every supplied key into the daily `forecast` map and never removes old keys, so repeated updates with new keys grow the persisted JSON without bound and eventually fill storage, disrupting writes across the device.

Validate forecast timestamps and cap the map’s size, pruning obsolete keys before persisting it.
</issue_to_address>

### Comment 3
<location path="packages/helpermodules/measurement_logging/write_log.py" line_range="150" />
<code_context>
+        existing_forecast = content.get("forecast")
+        if not isinstance(existing_forecast, dict):
+            existing_forecast = {}
+        existing_forecast.update(get_forecast_values())
+        content["forecast"] = existing_forecast
         write_and_check(filepath, content)
</code_context>
<issue_to_address>
**Forecast lands in wrong day**

When the save spans midnight and the forecast’s today-values are refreshed between filename selection and the merge, `save_log` merges `get_forecast_values()` into the file whose date it captured earlier; if the forecast’s today-values are refreshed across midnight in between, timestamps for the new day are written into the previous day’s file, so the daily forecast history is assigned to the wrong date.

Use one captured date for both the log filename and selecting the forecast values to persist.
</issue_to_address>

### Comment 4
<location path="packages/helpermodules/measurement_logging/write_log_test.py" line_range="152" />
<code_context>
     monkeypatch.setattr(write_log, "get_names", Mock(
         return_value={"cp1": "Bestehender Ladepunkt", "cp2": "Neuer Ladepunkt"}))
     monkeypatch.setattr(write_log, "get_colors", Mock(return_value={"cp1": "#111111", "cp2": "#222222"}))
+    monkeypatch.setattr(write_log, "get_forecast_values", Mock(return_value={"2": 200.0}))
     write_and_check_mock = Mock()
     monkeypatch.setattr(write_log, "write_and_check", write_and_check_mock)
</code_context>
<issue_to_address>
**Forecast lookup failures go unnoticed**

When the production forecast lookup is broken, if `get_forecast_values` reads the wrong path or returns an empty mapping, `test_save_log_keeps_existing_names_and_colors_when_writing_new_entry` still passes: line 152 replaces the real lookup with `{"2": 200.0}`, and the assertion at line 172 only verifies that mocked value was merged.

Exercise the real `get_forecast_values` lookup with forecast data provided through its actual source, or add a separate test for that function.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@seaspotter
seaspotter requested a review from LKuemmel November 5, 2026 22:45
@LKuemmel
LKuemmel requested review from ndrsnhs and removed request for LKuemmel November 6, 2026 10:55
@seaspotter

Copy link
Copy Markdown
Collaborator Author

"Daily entries disappear on invalid forecast" und "Forecast lookup failures go unnoticed" waren berechtigt und sind behoben: existing_forecast.update() ist jetzt gegen einen ungültigen Rückgabewert von get_forecast_values() abgesichert (dieselbe Absicherung wie bereits bei names/colors), und ein neuer Test prüft get_forecast_values() direkt gegen den echten data.data-Zugriffspfad statt nur gegen einen Mock. "Forecast data can exhaust disk" ist nicht umsetzbar, da today_values durch den Forecast-Store selbst immer auf einen Kalendertag begrenzt ist, kein unbegrenztes Wachstum möglich. "Forecast lands in wrong day" ist real aber vernachlässigbar - das Zeitfenster zwischen Dateinamen- und Forecast-Auswahl liegt im Sub-Millisekundenbereich, der Forecast-Store aktualisiert deutlich seltener, und der Worst Case korrigiert sich im nächsten Zyklus von selbst, daher hier nicht behoben.

@LKuemmel LKuemmel left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wäre es nicht genauer, wenn man immer nur die aktuelle Prognose erfasst?
Werden im Tagesverlauf auch Prognosewerte für vergangene Stunden nachträglich korrigiert, sieht die abends gespeicherte Kurve womöglich deutlich treffender aus, als die Prognose tatsächlich war, als die Regelung ihre Entscheidungen getroffen hat. Das wäre ein Rückschaufehler.
Selbst wenn der Anbieter zurückliegende Stunden nicht mehr korrigiert, hat man mit der oben vorgeschlagenen Methode trotzdem noch einen Rückschaufehler, weil die Prognose möglicherweise ganz anders aussah, als die openWB morgens bei günstigen Netzpreisen die Ladung gestartet hat und im Log dann der Tagesverlauf der Prognose gespeichert wird.
Speichert man morgen von 6.00 die Prognose im Log, hat man die Verfeinerung über den Tag nicht. Und zu jeder Uhrzeit die Prognose vom restlichen Tag zu speichern, kann man nicht sinnvoll grafisch aufbereiten. Das sind zu viele Daten.
Ich bin dafür, den aktuellen Prognosewert zu loggen.

Unabhängig davon, sollte der Prognosewert mit in die Zeitstempel-Einträge und nicht als separates Dict geführt werden. Das sind Dictionaries, die Daten enthalten, die für alle Einträge gelten. Die zeitlichen Verläufe sind alle unter dem jeweiligen Zeitstempel abgelegt und werden anhand dessen grafisch aufbereitet. Die Strompreise werden ebenfalls unter den Zeitstempeln geloggt, da kannst Du dich für die Prognosedaten dran orientieren.

@LKuemmel
LKuemmel removed the request for review from ndrsnhs November 11, 2026 09:00
@seaspotter

Copy link
Copy Markdown
Collaborator Author

Wäre es nicht genauer, wenn man immer nur die aktuelle Prognose erfasst? Werden im Tagesverlauf auch Prognosewerte für vergangene Stunden nachträglich korrigiert, sieht die abends gespeicherte Kurve womöglich deutlich treffender aus, als die Prognose tatsächlich war, als die Regelung ihre Entscheidungen getroffen hat. Das wäre ein Rückschaufehler. Selbst wenn der Anbieter zurückliegende Stunden nicht mehr korrigiert, hat man mit der oben vorgeschlagenen Methode trotzdem noch einen Rückschaufehler, weil die Prognose möglicherweise ganz anders aussah, als die openWB morgens bei günstigen Netzpreisen die Ladung gestartet hat und im Log dann der Tagesverlauf der Prognose gespeichert wird. Speichert man morgen von 6.00 die Prognose im Log, hat man die Verfeinerung über den Tag nicht. Und zu jeder Uhrzeit die Prognose vom restlichen Tag zu speichern, kann man nicht sinnvoll grafisch aufbereiten. Das sind zu viele Daten. Ich bin dafür, den aktuellen Prognosewert zu loggen.

Unabhängig davon, sollte der Prognosewert mit in die Zeitstempel-Einträge und nicht als separates Dict geführt werden. Das sind Dictionaries, die Daten enthalten, die für alle Einträge gelten. Die zeitlichen Verläufe sind alle unter dem jeweiligen Zeitstempel abgelegt und werden anhand dessen grafisch aufbereitet. Die Strompreise werden ebenfalls unter den Zeitstempeln geloggt, da kannst Du dich für die Prognosedaten dran orientieren.

Du hast völlig recht ja, das hab ich nicht so ganz bedacht. Hab's umgesetzt: es wird jetzt nur noch der aktuelle Prognosewert geloggt (kein nachträglich verfeinerter Verlauf mehr), und zwar direkt im jeweiligen Zeitstempel-Eintrag, analog zu den Strompreisen.

@LKuemmel
LKuemmel merged commit 41f1b91 into openWB:master Nov 11, 2026
2 checks passed
@seaspotter
seaspotter deleted the log-forecast-values branch November 11, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants