Skip to content

feat(log): log the inputs each plan uses as "Replay input:" lines, so a log can be replayed exactly - #5427

Open
chalfontchubby wants to merge 2 commits into
mainfrom
feat/log-replay-inputs
Open

chalfontchubby wants to merge 2 commits into
mainfrom
feat/log-replay-inputs

Conversation

@chalfontchubby

@chalfontchubby chalfontchubby commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Opened by Claude on Rik's behalf. Draft: the base of a stack; the forward log replay (#5363) builds on it.

Summary

A debug yaml captures one moment. Replaying the log that follows it (#5363) needs the values each later plan started from, which the human-readable log lines round or leave out. This adds Replay input: log lines carrying them exactly. None of it changes what Predbat plans or does.

Each line reads back exactly (repr floats, or dicts that ast.literal_eval reads), and inputs that rarely change are logged only when they change. Tariff comparison runs (save=False / publish=False) log none of it, as their inputs are not the live plan's.

Line When Content
PV forecast changed on change p50/p10/p90 per minute to the end of the forecast, as runs of [kWh, minutes]
load from HH:MM base X Wh, Wh/5min [...] every cycle the two values per 5-minute step the plan reads, as whole tenths of a Wh; falls back to full cumulative kWh for a forecast not in whole tenths
ML load forecast changed on change Load ML predictions as read from the sensor
rates changed on change import/export and their base rates from now, as change points
state every cycle SoC, SoC max, in-day adjustment, cost so far, day counters, charge/discharge rate in force, battery temperature, iBoost used today, load_forecast_only
cars changed, inverter changed on change the car state and the inverter's programmed state the plan reads
load forecast empty on change the forecast became empty, so the plan uses load history alone
load divergence every plan the divergence exactly as the plan uses it (None when off)
dynamic load baseline on change the load floor dynamic_load() sets for a high-load cycle (usually {})

Safety

  • Every logger catches its own errors, and a value that cannot be logged warns once per change, not every cycle. The on-change lines share one helper, log_replay_changed.
  • No secrets are logged.

Measured on a live system (Sigenergy, IOG, 5-6 Oct 2026)

Changes

  • apps/predbat/fetch.py: the loggers and the shared on-change helper; PV and ML logging gated on save.
  • apps/predbat/plan.py: the load divergence and dynamic load baseline lines, gated on publish.
  • apps/predbat/predbat.py: logs the plan's starting state and the inverter state each cycle.
  • apps/predbat/tests/test_log_replay_inputs.py (new), registered in unit_test.py.

Test plan

🤖 Generated with Claude Code

… a log can be replayed exactly

A debug yaml captures one moment; a forward replay of the log after it (draft PR #5363) needs the values each
later plan started from, which the human-readable lines round or omit. Each line is prefixed "Replay input:",
reads back exactly (repr floats, or dicts that ast.literal_eval reads), and is logged on change where the value
rarely changes. None of it changes what Predbat does: every logger catches its own errors, and a value that
cannot be logged warns once per change. Tariff comparison runs (save=False / publish=False) log none of it, as
their inputs are not the live plan's.

- PV forecast, on change: p50/p10/p90 per minute from now to the end of the forecast, as runs of [kWh, minutes].
- Load forecast, every cycle: the two values per 5-minute step the plan reads (step start and the minute after).
  Rounded to 0.1 Wh, so logged compactly as Wh per step from a cumulative base, about half the size of the full
  cumulative kWh line it falls back to for a forecast that is not in whole tenths of a Wh.
- Load ML predictions, on change, exactly as read from the sensor.
- Rates, on change: import/export and their base rates from now, as change points.
- Plan starting state every cycle: SoC, SoC max, in-day adjustment, cost so far, the day counters, the charge
  and discharge rates in force and the battery temperature.
- Car state and the inverter's programmed state (windows, charging/exporting and targets, reserve, rate limits),
  each on change; and the load divergence exactly as the plan uses it (None when divergence is off).

The on-change lines share one helper (log_replay_changed). On a live system the replay lines are about 5% of
the log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🟡 Changes recommended

Empty forecasts, dynamic-load baselines, and current iBoost usage are not captured, preventing exact replay in supported runtime scenarios.

3 open findings
What changed in this PR

Adds exact Replay input: logging to support deterministic forward plan replay.

Changes:

  • Logs forecasts, rates, runtime state, cars, inverter state, and load divergence.
  • Suppresses replay logging during tariff comparisons.
  • Adds comprehensive logger tests and test registration.
File Description
apps/​predbat/​fetch.py Implements replay-input serialization and logging hooks.
apps/​predbat/​plan.py Logs exact load divergence.
apps/​predbat/​predbat.py Logs plan-start and inverter state.
apps/​predbat/​tests/​test_log_replay_inputs.py Tests serialization, change detection, and error handling.
apps/​predbat/​unit_test.py Registers the new test suite.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/predbat/fetch.py
Comment thread apps/predbat/fetch.py
Comment thread apps/predbat/plan.py
…st_only and the dynamic load baseline (#5427 review)

Four inputs the plan reads that the replay lines did not carry:
- an empty load forecast, logged when the forecast becomes empty (not every cycle, as many setups have none), so a
  replay drops the forecast it still holds and plans on history as live did;
- iboost_today, which the iBoost plan and the prediction's remaining iBoost allowance read, and load_forecast_only,
  which decides whether the load history is used at all, both now in the state line;
- the dynamic load baseline that dynamic_load() sets for a high-load cycle and every load scenario adds, logged on
  change from the live plan (usually {}).

The replay does not read these yet; they are logged now so that logs carry them when it does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants