Skip to content

Plan history: show the latest finished slot, and stop the false 'stale' warning - #5414

Open
chalfontchubby wants to merge 1 commit into
mainfrom
fix/plan-history-latest-slot
Open

chalfontchubby wants to merge 1 commit into
mainfrom
fix/plan-history-latest-slot

Conversation

@chalfontchubby

Copy link
Copy Markdown
Collaborator

Posted by Claude on behalf of @chalfontchubby.

Summary

The web page's Plan → History view had two problems:

  1. The most recent slots were missing. History is rebuilt from what actually happened, at most once an hour, while the live Plan view starts at the current slot. So the slots finished since the last rebuild appeared in neither view. For example, at 21:35 the History stopped at the 20:30 slot and the Plan started at 21:30, so 21:00–21:30 was nowhere.
  2. It said "Plan data is stale" most of the time. The page judged every view against the live plan's 15-minute limit. History is only rebuilt hourly, so the warning showed for about 45 minutes of every hour although nothing was wrong.

With this change History is rebuilt once per completed slot, so the last finished slot shows within one or two re-plans of ending. Each view is judged against how often it is actually republished, so the warning only appears when something has really stopped.

Detail

Rebuild cadence: calculate_yesterday()

  • It now rebuilds once per completed plan slot (plan_interval_minutes), on the first re-plan at least one run (5 minutes) after the slot ends. The wait is so that the boundary run's own predbat.cost_today write is already recorded when its history is read back. The key is history_slot(), a (date, slot number) shifted by PREDICT_STEP.
  • How often that is:
  • A rebuild takes 0.4–0.9 s on a live install.

Stale warning: get_plan_renderer_js()

  • Each dataset the page reads carries refresh_minutes, the longest Predbat should take to republish it:
    • the live plan: calculate_plan_every, set only when the plan is published
    • History and "Yesterday without Predbat": a slot, plus the run it waits, plus a re-plan interval
  • The page warns a run (5 minutes) after that, never under 15 minutes. At the defaults that's Plan 15 minutes (as before) and History 50.
  • The check uses the data the current view shows. Before, the "unchanged" poll always checked the plan's timestamp. It hides the warning when the view has no data. The view-to-data mapping is now a single helper (currentViewData()), and the "no data yet" text no longer says "about once an hour".

Trade-offs, accepted

  • With calculate_plan_every at or above the slot length, re-plans can fall on slot boundaries, and the rebuild then happens on the boundary run.
  • cost_yesterday and savings_yesterday_* roll over at the first re-plan after 00:05, rather than at 00:00. The 1 am savings-total update still sees the new day.
  • cost_yesterday and savings_yesterday_predbat, which have large html/json attributes, are written twice as often.

Tests

  • test_calculate_yesterday:
    • early exit within a slot
    • no rebuild on the boundary run, then a rebuild on the next run (this test fails on main)
    • 15-minute slots and the midnight key
    • refresh_minutes on the published History and baseline data
  • Test doubles: the stubs returned "{}" (a string) where publish_html_plan and plan_write_debug return a dict. They now match the real functions.
  • New test_plan_staleness_js: structural checks on the page script (the per-view data, the limit arithmetic, hiding the warning without data, no remaining bare-timestamp checks). It fails on main's script.
  • Pre-commit and ./run_all --quick pass. The rendered page script passes node --check.

🤖 Generated with Claude Code

…ss by each view's own refresh interval

The plan page's History view is built by calculate_yesterday() from what actually happened, at most once every 59
minutes. The live Plan view starts at the current slot, so the slots finished since the last rebuild showed in
neither view: at 21:35 the History stopped at 20:30 and the Plan started at 21:30. And the page judged every view
against the live plan's 15-minute stale limit, so on the History view it warned "Plan data is stale" for about
45 minutes of every hour although nothing was wrong.

- calculate_yesterday() now rebuilds once per completed plan slot (plan_interval_minutes), on the first re-plan at
  least one run (5 minutes) after the slot ends, so the boundary run's own cost_today write is recorded before
  its history is read back. A rebuild takes 0.4-0.9 s on a live install; at the default 30-minute slots that is
  48 a day instead of 24 (before #2913 it ran on every re-plan, about 144).
- Each dataset the page shows carries refresh_minutes, the longest Predbat should take to republish it (the live
  plan: calculate_plan_every; the plan history: a slot, the run it waits and a re-plan interval). The page warns
  a run after that, never under 15 minutes, against the data the current view shows, and hides the warning when
  the view has no data. At the defaults: Plan 15 minutes (as before), History 50.

Trade-offs, kept: with calculate_plan_every at or above the slot length, re-plans can fall on slot boundaries
and the rebuild then happens there; cost_yesterday and savings_yesterday_* roll over at the first re-plan after
00:05 rather than at 00:00; cost_yesterday/savings_yesterday_predbat, with their large html/json attributes, are
written twice as often.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chalfontchubby chalfontchubby added the bug Something isn't working label Oct 5, 2026

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

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant