fix(output): stop load_energy_predicted's whole-day total drifting through the day - #4519
Merged
Merged
Conversation
…rough the day load_today_comparison() applied load_scaling/load_scaling_dynamic/manual_load_adjust to future minutes only (#4506, #4511), which correctly fixed today_remaining but also fed load_total_pred - the accumulator the whole-day "today" total is read from - a mix of unscaled past buckets and scaled future ones. As minutes_now advanced, buckets kept flipping from the scaled group to the unscaled one, so the published "today" total (and its chart) drifted by whatever load_scaling wasn't 1.0, even though nothing about the underlying forecast changed. Adds a separate, consistently-scaled accumulator (load_total_pred_day) used only for load_energy_predicted's state/today/today_so_far/today_remaining/results, leaving load_total_pred itself (and the actual-vs-predicted divergence ratio it feeds) untouched.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a drift bug in load_energy_predicted where the published whole-day “today” total (and state/chart) changed over the course of the day purely because minutes_now advanced when load_scaling/load_scaling_dynamic/manual_load_adjust were non-1.0. It does this by introducing a separate, consistently-scaled full-day accumulator used exclusively for the predicted sensor’s totals, and adds a regression test to lock the behavior.
Changes:
- Introduce a new full-day accumulator (
load_total_pred_day) and corresponding timestamp series soload_energy_predictedtotals are computed with consistent scaling across all minutes (past and future). - Keep the existing raw/unscaled accumulators (
load_total_pred,load_total_pred_now) intact for the actual-vs-predicted divergence logic. - Add a regression test that recomputes the same day at 00:00 and 15:00 with
load_scaling=1.05and assertstoday/stateremain stable.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| apps/predbat/output.py | Adds a consistently-scaled full-day predicted accumulator and uses it as the sole source for load_energy_predicted state/today/today_so_far/today_remaining/results to prevent clock-driven drift. |
| apps/predbat/tests/test_load_today_comparison.py | Adds a regression test ensuring load_energy_predicted whole-day totals do not drift as minutes_now advances when scaling is constant. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Trefor,
Thanks for the update. This now sounds like a solid approach.
I’ve briefly tested on main and things have instantly improved. There is a slight up/down drift at the 5 minute re-evaluation, but when it hits the 30 min mark it resets back and rolls through the same 5 minute predictions, preventing that continual downward drift as the day progresses.

Thanks to yourself and Rik for putting this none to bed.
Regards,
Nigel.
… On 15 Aug 2026, at 13:19, Trefor Southwell ***@***.***> wrote:
Merged #4519 <#4519> into main.
—
Reply to this email directly, view it on GitHub <#4519?email_source=notifications&email_token=BGEK3XIPFLXZOOTVXUCEXTD5KBIL5A5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTEOJUHE4DQOBVHEZTLJTSMVQXG33OU5WWK3TUNFXW5JLFOZSW45FMMZXW65DFOJPWG3DJMNVQ#event-29498885935>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/BGEK3XPZATXQDIBGCJMLRO35KBIL5AVCNFSNUABFKJSXA33TNF2G64TZHM3DGNBUHA2TCMZWHNEXG43VMU5TKMJUHE4TONRXGU32C5QC>.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS <https://github.com/notifications/mobile/ios/BGEK3XKEGBHWIDAQQFCIVZD5KBIL5A5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTEOJUHE4DQOBVHEZTLJTSMVQXG33OU5WWK3TUNFXW5JLFOZSW45FKMZXW65DFOJPWS33T> and Android <https://github.com/notifications/mobile/android/BGEK3XNC4HNIMUFGY7MYDA35KBIL5A5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTEOJUHE4DQOBVHEZTLJTSMVQXG33OU5WWK3TUNFXW5JLFOZSW45FOMZXW65DFOJPWC3TEOJXWSZA>. Download it today!
You are receiving this because you were mentioned.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the remaining part of #4496:
load_energy_predicted's whole-daytodaytotal (and itsstate/chart) drifted through the day wheneverload_scaling/load_scaling_dynamic!= 1.0, even with nothing about the underlying forecast changing.load_today_comparison()applyload_scaling/load_scaling_dynamic/manual_load_adjustto future minutes only, which correctly fixedtoday_remaining(it now matches the plan's ownstep_data_history()convention). But the same accumulator (load_total_pred) that those future-scaled buckets feed into is also the source the whole-daytodaytotal is read from - and it sums every minute, unscaled-past alongside scaled-future. Asminutes_nowadvances, buckets keep flipping from the scaled group to the unscaled one, so the publishedtodaytotal mechanically shrinks (load_scaling > 1) or grows (load_scaling < 1) as the day goes on.load_total_pred_day) applies the same scaling consistently to every minute regardless of past/future, and is now the sole source forload_energy_predicted'sstate/today/today_so_far/today_remaining/results.load_total_preditself, and the actual-vs-predicted divergence ratio it feeds (which deliberately wants raw/unscaled past minutes to compare against real consumption), are untouched.load_today_comparison()twice on the same day (00:00 and 15:00) withload_scaling=1.05and asserts thetodaytotal andstateare identical - confirmed it fails onmain(30.24 → 29.34) and passes with this fix.On the "should it be perfectly flat?" question
@nbullus, to be precise about what this fix does and doesn't claim:
load_energy_predicted'stodaytotal was never architecturally "frozen at midnight" -load_today_comparison()recomputes the whole day from scratch every 5-minute cycle, there's no snapshot. We don't expect it to be a perfectly flat line forever - if the underlying days_previous/load-ML model's own forecast genuinely updates (e.g. as new historical data ages in), or ifload_scaling_dynamic/manual_load_adjustconfig itself changes mid-day (a saving session gets added, a manual override starts), the total should move, and that's legitimate evolution, not a bug. What this fixes is the total moving for no reason other than the clock ticking forward - theminutes_nowboundary shouldn't itself be a source of drift when nothing else has changed.@gcoan - separately, your
today/today_so_farconfusion onload_energy_predictedis a real docs gap, not (as far as I can tell) a code bug: those two attributes aren't documented indocs/output-data.mdat all, onlytoday_remainingis, and even that section's description ("to end of plan") doesn't match what the code actually computes (strictly midnight-to-midnight). I haven't fixed the docs as part of this PR - flagging it separately rather than scope-creeping this fix.After this
This is the third pass at #4496 in a fairly short window (#4506, #4511, now this). Given that history, we're going to step back from this specific area for a bit rather than keep iterating reactively - if either of you spot more drift after this lands, please do keep reporting it with a debug.yaml, but we'd rather let this one bed in and be looked at with fresh eyes next time than risk another narrow fix that misses a fourth edge case.
Test plan
_test_predicted_today_stable_across_the_dayintest_load_today_comparison.py) fails onmain, passes with this fixload_today_comparisonsuite still passes./run_all --quickpassespre-commitclean