@@ -2549,6 +2549,7 @@ def load_today_comparison(self, load_minutes, load_forecast, car_minutes, import
25492549 """
25502550 load_total_pred = 0
25512551 load_total_pred_now = 0
2552+ load_total_pred_day = 0
25522553 car_total_pred = 0
25532554 car_total_actual = 0
25542555 car_value_pred = 0
@@ -2557,7 +2558,7 @@ def load_today_comparison(self, load_minutes, load_forecast, car_minutes, import
25572558 actual_total_today = 0
25582559 import_ignored_load_pred = 0
25592560 import_ignored_load_actual = 0
2560- load_predict_stamp = {}
2561+ load_predict_day_stamp = {}
25612562 load_actual_stamp = {}
25622563 load_predict_data = {}
25632564 total_forecast_value_pred = 0
@@ -2588,34 +2589,44 @@ def load_today_comparison(self, load_minutes, load_forecast, car_minutes, import
25882589 load_value_pred += forecast_value_pred
25892590 load_value_pred_raw += forecast_value_pred
25902591
2592+ # Consistently-scaled prediction for THIS minute, applying load_scaling,
2593+ # load_scaling_dynamic, and manual_load_adjust the same way whether the minute is
2594+ # in the past or future. This feeds load_total_pred_day below, which is the sole
2595+ # source for load_energy_predicted's state/today/today_so_far/today_remaining/results
2596+ # (batpred#4496 follow-up). Using it consistently across the whole day is what makes
2597+ # that "predicted" total (and its chart) stay flat as minutes_now advances - it isn't
2598+ # meant to reflect what actually happened today, only the model's day-ahead forecast
2599+ # under today's scaling settings, so every bucket needs the same treatment regardless
2600+ # of whether it has elapsed yet.
2601+ manual_adjust_day = 0.0
2602+ if self .manual_load_adjust :
2603+ manual_adjust_day = self .manual_load_adjust .get (minute , 0 ) * step / float (self .plan_interval_minutes )
2604+ manual_adjust_day = max (manual_adjust_day , - load_value_pred )
2605+ scaling_dynamic_day = self .load_scaling_dynamic .get (minute , 1.0 ) if self .load_scaling_dynamic else 1.0
2606+ load_value_pred_day = (load_value_pred + manual_adjust_day ) * self .load_scaling * scaling_dynamic_day
2607+
25912608 # For FUTURE minutes only, apply load_scaling, load_scaling_dynamic, and
2592- # manual_load_adjust so the published predicted/adjusted curves (and their
2593- # today_remaining attribute) match step_data_history() (fetch.py), which the plan
2594- # itself uses to build load_minutes_step as
2595- # (value + load_extra) * scaling_dynamic * scale_fixed, where load_extra includes
2596- # manual_load_adjust, scaling_dynamic is load_scaling_dynamic, and scale_fixed
2597- # includes the flat load_scaling. load_scaling_dynamic carries saving-session/
2598- # free-electricity-event scaling as well as any per-window override from
2599- # rates_import_override/the manual API (e.g. a "power up" event) - a first pass at
2600- # this fix (#4506) only applied the flat load_scaling and missed both of these,
2609+ # manual_load_adjust so the published today_remaining attribute matches
2610+ # step_data_history() (fetch.py), which the plan itself uses to build
2611+ # load_minutes_step as (value + load_extra) * scaling_dynamic * scale_fixed, where
2612+ # load_extra includes manual_load_adjust, scaling_dynamic is load_scaling_dynamic,
2613+ # and scale_fixed includes the flat load_scaling. load_scaling_dynamic carries
2614+ # saving-session/free-electricity-event scaling as well as any per-window override
2615+ # from rates_import_override/the manual API (e.g. a "power up" event) - a first pass
2616+ # at this fix (#4506) only applied the flat load_scaling and missed both of these,
26012617 # confirmed against a real follow-up report on issue #4496 where a 1.5x
26022618 # load_scaling_dynamic override for a 2-hour power-up event wasn't reflected in
26032619 # today_remaining at all.
26042620 #
2605- # Minutes already elapsed today are deliberately left untouched: load_total_pred_now
2606- # below feeds the actual-vs-predicted divergence ratio, which compares actual
2607- # consumption against the raw model, not an adjusted one.
2621+ # Minutes already elapsed today are deliberately left untouched here: load_total_pred
2622+ # and load_total_pred_now below feed the actual-vs-predicted divergence ratio, which
2623+ # compares actual consumption against the raw model, not an adjusted one.
26082624 if minute >= minutes_now :
2609- manual_adjust = 0.0
2610- if self .manual_load_adjust :
2611- manual_adjust = self .manual_load_adjust .get (minute , 0 ) * step / float (self .plan_interval_minutes )
2612- manual_adjust = max (manual_adjust , - load_value_pred )
2613- load_value_pred += manual_adjust
2614- load_value_pred_raw += manual_adjust
2625+ load_value_pred += manual_adjust_day
2626+ load_value_pred_raw += manual_adjust_day
26152627
2616- scaling_dynamic = self .load_scaling_dynamic .get (minute , 1.0 ) if self .load_scaling_dynamic else 1.0
2617- load_value_pred *= self .load_scaling * scaling_dynamic
2618- load_value_pred_raw *= self .load_scaling * scaling_dynamic
2628+ load_value_pred *= self .load_scaling * scaling_dynamic_day
2629+ load_value_pred_raw *= self .load_scaling * scaling_dynamic_day
26192630
26202631 # Track (but no longer exclude) periods where import exceeds raw load, assumed to
26212632 # include deliberate battery charging (overnight for example). The house's own load
@@ -2643,14 +2654,15 @@ def load_today_comparison(self, load_minutes, load_forecast, car_minutes, import
26432654 actual_total_today += load_value_pred
26442655
26452656 load_total_pred += load_value_pred
2657+ load_total_pred_day += load_value_pred_day
26462658 total_forecast_value_pred += forecast_value_pred
26472659
26482660 load_predict_data [minute ] = load_value_pred
26492661
26502662 # Store for charts
26512663 minute_timestamp = self .midnight_utc + timedelta (seconds = 60 * minute )
26522664 stamp = minute_timestamp .strftime (TIME_FORMAT )
2653- load_predict_stamp [stamp ] = dp3 (load_total_pred )
2665+ load_predict_day_stamp [stamp ] = dp3 (load_total_pred_day )
26542666 load_actual_stamp [stamp ] = dp3 (actual_total_today )
26552667
26562668 # Fetch yesterday's in-day adjustment factor from history
@@ -2753,18 +2765,18 @@ def load_today_comparison(self, load_minutes, load_forecast, car_minutes, import
27532765 "icon" : "mdi:percent" ,
27542766 },
27552767 )
2756- load_so_far = self .filtered_today (load_predict_stamp , stamp = self .now_utc )
2757- load_today = self .filtered_today (load_predict_stamp )
2768+ load_so_far = self .filtered_today (load_predict_day_stamp , stamp = self .now_utc )
2769+ load_today = self .filtered_today (load_predict_day_stamp )
27582770 load_today_remaining = None
27592771 if (load_so_far is not None ) and (load_today is not None ):
27602772 load_today_remaining = load_today - load_so_far
27612773
27622774 if save :
27632775 self .dashboard_item (
27642776 self .prefix + ".load_energy_predicted" ,
2765- state = dp3 (load_total_pred ),
2777+ state = dp3 (load_total_pred_day ),
27662778 attributes = {
2767- "results" : self .filtered_times (load_predict_stamp ),
2779+ "results" : self .filtered_times (load_predict_day_stamp ),
27682780 "today" : dp2 (load_today ) if load_today is not None else 0.0 ,
27692781 "today_so_far" : dp2 (load_so_far ) if load_so_far is not None else 0.0 ,
27702782 "today_remaining" : dp2 (load_today_remaining ) if load_today_remaining is not None else 0.0 ,
0 commit comments