Skip to content

feat(predbat): update_pred can plan at a given instant - #5875

Merged
springfall2008 merged 2 commits into
mainfrom
feat/update-pred-plan-at
Oct 31, 2026
Merged

springfall2008 merged 2 commits into
mainfrom
feat/update-pred-plan-at

Conversation

@mgazza

@mgazza mgazza commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

update_pred(scheduled=True, plan_at=None) now takes the same optional plan_at as plan_once() (#5811) and passes it through.

Why

A host that drives the engine on its own schedule can already plan at a chosen instant with plan_once(plan_at=...), but to do so it has to call plan_once, execute_once and the post-run bookkeeping itself, and plan_once(scheduled=False) forces a recompute every time. With plan_at on update_pred, such a host gets the normal cycle with its planning clock at that instant, including reuse of a fresh plan until calculate_plan_every runs out and the late recompute of an ageing plan.

Notes

  • plan_at follows plan_once's rules: timezone-aware, at or behind the wall clock; a naive datetime raises ValueError before any state changes.
  • The late-recompute branch needs no change: plan_once sets now_utc from plan_at, the inverter re-read does not reset the clock, and the recomputed plan is stamped at now_utc.
  • plan_at sets the planning clock (now_utc, midnight_utc, minutes_now). Wall-clock bookkeeping (control ledger pruning, inverter fetch timestamps, latency monitors) stays on the real clock.
  • The late-recompute check assumes the next call comes about RUN_EVERY minutes later, as it does today; a host calling on another cadence should keep that in mind.
  • plan_at=None is today's behaviour exactly.

Tests

Added to tests/test_plan_execute_split.py:

  • test_update_pred_plan_at_stamps_plan: a forced run at T leaves plan_last_updated == T.
  • test_update_pred_scheduled_reuses_fresh_plan: a scheduled run at T + 5 min with calculate_plan_every = 30 does not recompute, and the clock and logged plan age are those of T + 5 min.
  • test_update_pred_scheduled_recomputes_old_plan: the same at T + 30 min recomputes.
  • test_update_pred_rejects_naive_plan_at: raises ValueError.

unit_test.py -k plan_execute_split passes. --quick stops at test_data_age_metrics_round_trip, which fails the same way on main without this change.

🤖 Generated with Claude Code

update_pred takes an optional timezone-aware plan_at, passed to plan_once with the same rules. The late recompute of an ageing plan runs at that same instant. The default None keeps today's behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the BOT_REVIEW Trigger an autotriage label Oct 30, 2026
…he passthrough

Assert now_utc, plan_last_updated and the logged age in the fresh-plan reuse test, and state in the update_pred docstring exactly what plan_at moves.

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

@springfall2008 springfall2008 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Automated comment from the triage bot.

Reviewed at head 0fe097c6 (CI green: pre-commit and kernel-binaries both SUCCESS). No issues found — 0 inline, 0 in body.

The change is add-only: update_pred(scheduled=True, plan_at=None) passes plan_at through to plan_once, which already owns all the semantics from PR #5811. What I checked against the head:

  • Default path unchanged. Both production call sites (apps/predbat/predbat.py:2468 initial run, :2547 the 5-minute tick) pass no plan_at; with None the new call is identical to the old one.
  • PR Notes verified. The naive-plan_at check is plan_once's first statement (apps/predbat/predbat.py:1305), so ValueError precedes any state change. The late-recompute branch reads self.now_utc, which plan_once sets from plan_at (:1311), and nothing mid-cycle re-derives it — update_time() is called only from plan_once and initialize (:2315, :2321), so the inverter re-read in execute_once leaves the clock alone. The plan is stamped at now_utc (apps/predbat/plan.py:2443), which the reuse/recompute tests pin.
  • Wall-clock bookkeeping claim verified. Control ledger pruning and inverter ledger stamps use time.time() (apps/predbat/predbat.py:1510, apps/predbat/inverter.py:2457+), the inverter clock-skew check uses datetime.now() (apps/predbat/inverter.py:755), and the IOG latency monitor uses time.time() (apps/predbat/execute.py:768) — all independent of plan_at.
  • Tests fail without the change. Each new scenario calls update_pred(plan_at=...), which is a TypeError on main; the reuse scenario's now_utc == T+5 and logged '5.0 minutes old' assertions also fail if the parameter existed but were not passed through. The scenarios run against the real update_time (real_time=True), so plan_last_updated == plan_at tests real clock plumbing, not the mocked calculate_plan. Mutated fixture state (calculate_plan_every = 30, clock_skew, the clock attributes) is restored in the finally via _restore_state/_STATE_ATTRS; the module is already registered in TEST_REGISTRY (apps/predbat/unit_test.py:580).

One pre-existing, non-blocking note: calculate_plan's start-of-day check (apps/predbat/plan.py:2073, plan_last_updated_minutes > minutes_now) reads a plan stamp ahead of the current run's clock as a day wrap and forces one spurious recompute. That is only reachable if a host passes a plan_at ahead of the wall clock — an input plan_once's docstring already rules out — and the cost is one extra recompute, never a wrong plan. No action needed for this PR.

@springfall2008 springfall2008 removed the BOT_REVIEW Trigger an autotriage label Oct 30, 2026
@springfall2008
springfall2008 merged commit a444e41 into main Oct 31, 2026
2 checks passed
@springfall2008
springfall2008 deleted the feat/update-pred-plan-at branch October 31, 2026 18:48
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