Skip to content

fix(history): reconstruct car slots into today-so-far, not just yesterday (#5004) - #5048

Merged
chalfontchubby merged 3 commits into
mainfrom
fix/car-slot-history-today-5004
Sep 12, 2026
Merged

chalfontchubby merged 3 commits into
mainfrom
fix/car-slot-history-today-5004

Conversation

@chalfontchubby

Copy link
Copy Markdown
Collaborator

Posted by Claude on behalf of @chalfontchubby.

Fixes #5004.

Problem

The #5004 fix (#5025) correctly widened yesterday_load_step to cover today-so-far - calculate_yesterday() deliberately extends the History view's axis to 24*60 + minutes_now so it can show today alongside yesterday - but left yesterday_reconstruct_car_slots()'s own end_record capped at the bare 24*60.

The reconstruction loop (for start_minute in range(0, end_record, ...)) never reached past yesterday midnight, so any car session that happened today stayed in the raw, unsubtracted load band until the next day's run rolled it into the now-covered "yesterday" range. Reported by @tdbegley28 on the closed issue: car charging correctly separates out for every prior day, but today's stays as plain load until midnight.

Fix

Pass end_record + minutes_now to the reconstruction call, matching the axis yesterday_load_step already covers, rather than the yesterday-only cap.

Verification

Reproduced directly against yesterday_reconstruct_car_slots(): an identical fixture with the session moved from yesterday 01:00 to today 01:00 went from correctly reconstructing a slot to reconstructing nothing at all, at the unfixed end_record; fixed, both place the slot and subtract the load.

Two new tests pin this:

  • one exercising the function directly at both widths (5i in _test_reconstruct_car_slots)
  • one confirming calculate_yesterday()'s own call site passes the widened value, so a future regression at the call site is caught even if 5i alone would still look correct in isolation

Both verified to fail against the unpatched call site and pass with the fix.

  • ./run_all --quick green
  • ./run_pre_commit green

🤖 Generated with Claude Code

…rday (#5004)

The #5004 fix (#5025) widened yesterday_load_step to cover today-so-far -
calculate_yesterday deliberately extends the History view's axis to
24*60 + minutes_now so it can show today alongside yesterday - but left
yesterday_reconstruct_car_slots' own end_record capped at the bare 24*60.

The reconstruction loop (for start_minute in range(0, end_record, ...)) never
reached past yesterday midnight, so any car session that happened today stayed
in the raw, unsubtracted load band until the next day's run rolled it into the
now-covered "yesterday" range. Reported by tdbegley28 on the closed issue: car
charging correctly separates out for every prior day, but today's stays as
plain load until midnight.

Pass end_record + minutes_now to the reconstruction call, matching the axis
yesterday_load_step already covers, rather than the yesterday-only cap.

Verified directly against yesterday_reconstruct_car_slots(): an identical
fixture with the session moved from yesterday 01:00 to today 01:00 went from
correctly reconstructing a slot to reconstructing nothing at all, at the
unfixed end_record; fixed, both place the slot and subtract the load. Two new
tests pin this - one exercising the function directly at both widths (5i),
one confirming calculate_yesterday's own call site passes the widened value
so a future regression there is caught even if 5i alone would still look
correct in isolation.

Co-Authored-By: Claude Sonnet 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

One critical reconstruction issue and one moderate test issue remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Fixes History car-slot reconstruction so today-so-far charging is separated from household load, with regression coverage.

Changes:

  • Extends reconstruction through today-so-far.
  • Adds direct and call-site regression tests.
File summaries
File Summary
apps/predbat/output.py Widens reconstruction range. A critical issue (2 votes) remains for unaligned minutes_now causing incorrect index wrapping.
apps/predbat/tests/test_calculate_yesterday.py Adds regression coverage. A moderate issue (1 vote) remains because the expected value can mask failure to restore the clock.
Review details

Suppressed comments (1)

apps/predbat/tests/test_calculate_yesterday.py:1134

  • This expected value is derived from my_predbat.minutes_now after calculate_yesterday() returns, even though that method temporarily rewrites the attribute to 0. If a future regression both passes the bare end_record and fails to restore the clock, captured_end_record[0] and this expected value would both be 1440, so the call-site regression would pass unnoticed. Base the expectation on the now_utc fixture (or capture minutes_now before the call) instead.
    expected = 24 * 60 + my_predbat.minutes_now
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Comment thread apps/predbat/output.py
chalfontchubby and others added 2 commits September 11, 2026 20:50
…end_record (Copilot review on #5048)

minutes_now is only rounded to PREDICT_STEP (5 minutes), not to plan_interval_minutes (30), so
end_record = minutes_now + 24*60 (the real "now" on the widened axis) is not generally a bucket
boundary. The reconstruction loop's inner scan (range(start_minute, start_minute +
plan_interval_minutes)) ran to the full bucket width regardless, so the last bucket - the one
still in progress - could scan a few minutes past the real "now".

get_historical_base()'s minute_previous = base_minutes - minute goes negative for those extra
minutes, and get_from_incrementing() silently wraps a negative index by +24*60 - reading
yesterday's data at roughly the same clock time instead of "nothing has happened yet", and
miscounting it into today's still-in-progress bucket. The exact ghost-slot mechanism #5004 was
fixed for elsewhere in this same function.

Confirmed numerically before fixing: with minutes_now=365 (06:05, 5-aligned but not 30-aligned),
the unclamped inner loop for the last bucket [1800, 1830) read wrapped indices 1416-1439 -
yesterday's data - contributing 3.0 kWh from a session placed there, versus 0.0 once clamped to
end_record.

Verified with mutation testing: the new test (5j) fails with the exact reverted-loop bound,
reproducing a spurious slot in the in-progress final bucket; passes with the clamp restored.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ate_yesterday can fake it (Copilot review on #5048)

The end_record assertion compared against my_predbat.minutes_now read AFTER
calculate_yesterday() returns, but that call fakes self.minutes_now to 0 for its
duration and only restores it near the end. A regression breaking both the call
site (passing bare end_record) and the restore simultaneously would leave the
captured value and the after-the-fact expected value drifting identically,
passing vacuously - confirmed by reverting both together. Capture minutes_now
up front instead, before it can be faked, and assert the wrapper is actually
called with that real value too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chalfontchubby

Copy link
Copy Markdown
Collaborator Author

(Posted by Rik; written by Claude.)

Addressed a suppressed Copilot finding on the test added for the clamped-bucket fix (_test_reconstruct_car_slots_called_with_widened_end_record):

The test's expected value was computed from my_predbat.minutes_now after calculate_yesterday() returns. That call fakes self.minutes_now to 0 for its duration and only restores it near the end - so a regression that broke both the call site (passing a bare end_record again) and the restore simultaneously would leave the captured value and the after-the-fact expected drifting identically, passing the test vacuously. Confirmed this directly by reverting both together and observing the test pass silently.

Fixed by capturing the real minutes_now up front, before it can be faked, and asserting the wrapper is actually called with that real value.

Mutation-tested: reverted the double-mutation (bare end_record + no restore) again and confirmed the fixed assertion now correctly fails (end_record=1440, expected 1800). Restored, full quick suite + pre-commit green, pushed as 48673b6.

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.

🟢 Approval recommended

The focused fix and regression coverage address the reported issue without unresolved blockers.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@chalfontchubby
chalfontchubby merged commit 2499e86 into main Sep 12, 2026
3 checks passed
@chalfontchubby
chalfontchubby deleted the fix/car-slot-history-today-5004 branch September 30, 2026 07:57
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.

Predbat not removing EV charge from history tab and ghost EV load incorrectly showing

3 participants