Summary
The web plan tab's "History" button (data-view="yesterday", web.py:2465) shows Load kWh = 0 for a chunk of "today so far" starting around a fixed early-morning time, for the rest of the day - self-healing the next day once that day becomes a complete "yesterday" in the following day's History view.
Root cause
calculate_yesterday() (output.py:2858) builds yesterday_load_step/yesterday_pv_step via step_data_history() at output.py:2873, while self.forecast_minutes is still the live plan's normal forecast horizon.
step_data_history()'s internal loop (fetch.py:118) only populates dictionary keys from 0 up to self.forecast_minutes + plan_interval_minutes - it has no visibility into how far forward it'll later be asked to read.
But the History view needs a full "yesterday" (1440 minutes) plus "today so far" up to the current moment - i.e. offsets up to end_record + self.minutes_now = 1440 + minutes_now (the second publish_html_plan() call at output.py:3151). Whenever minutes_now > forecast_minutes - 1440, the required offset range exceeds what yesterday_load_step actually contains. Every offset past forecast_minutes is simply missing from the dict, and publish_html_plan()'s accumulation (load_forecast += load_minutes_step.get(offset, 0.0), output.py:1112) silently defaults to 0.0 for the rest of the day.
The widening fix for this is in the code, but applied far too late - self.forecast_minutes only gets set to end_record + minutes_now at output.py:3150, right before the second publish_html_plan() call, well after yesterday_load_step/yesterday_pv_step were already built too small at line 2873.
Evidence
Reproduced against a real debug.yaml capture (forecast_plan_hours: 30, forecast_minutes: 1800, minutes_now: 910 i.e. 15:10 local time):
yesterday_load_step.get(1800) → 0.0046 (real value, present)
yesterday_load_step.get(1860) onward → missing from the dict entirely
Offset 1800 (relative to the faked "yesterday midnight" anchor used inside calculate_yesterday()) corresponds to 06:00 the next day. This matches the reported symptom precisely: Load kWh reads 0 in the History view from ~6:00-6:30am onward, every day, self-healing the next day once that day is no longer "today" - a complete 24h "yesterday" only ever needs 1440 minutes of data, comfortably inside an 1800-minute range; it only breaks while trying to reach all the way to "now" on the same day.
Why this likely affects most installations, not just an edge-case config
The documented default for forecast_plan_hours is 24h (1440 minutes) - docs/customisation.md: "The default of 24 hours is the recommended value." At the default, the shortfall threshold is 1440 - 1440 = 0 hours, meaning this would trigger for essentially any time of day past midnight, for anyone on the default setting. A longer forecast_plan_hours (e.g. 48h) pushes the threshold later in the day but doesn't eliminate it unless forecast_plan_hours >= 48.
Suggested fix direction
Widen self.forecast_minutes to at least end_record + self.minutes_now before building yesterday_load_step/yesterday_pv_step at output.py:2873, rather than only right before the second publish_html_plan() call at line 3150. Worth double-checking nothing else in calculate_yesterday() between those two points implicitly depends on forecast_minutes staying at its original/live value in the meantime (e.g. the charge-window trimming at line 2910 explicitly checks c["start"] < 24*60, independent of forecast_minutes, so looks unaffected, but worth a careful pass).
Summary
The web plan tab's "History" button (
data-view="yesterday",web.py:2465) showsLoad kWh = 0for a chunk of "today so far" starting around a fixed early-morning time, for the rest of the day - self-healing the next day once that day becomes a complete "yesterday" in the following day's History view.Root cause
calculate_yesterday()(output.py:2858) buildsyesterday_load_step/yesterday_pv_stepviastep_data_history()atoutput.py:2873, whileself.forecast_minutesis still the live plan's normal forecast horizon.step_data_history()'s internal loop (fetch.py:118) only populates dictionary keys from0up toself.forecast_minutes + plan_interval_minutes- it has no visibility into how far forward it'll later be asked to read.But the History view needs a full "yesterday" (1440 minutes) plus "today so far" up to the current moment - i.e. offsets up to
end_record + self.minutes_now=1440 + minutes_now(the secondpublish_html_plan()call atoutput.py:3151). Wheneverminutes_now > forecast_minutes - 1440, the required offset range exceeds whatyesterday_load_stepactually contains. Every offset pastforecast_minutesis simply missing from the dict, andpublish_html_plan()'s accumulation (load_forecast += load_minutes_step.get(offset, 0.0),output.py:1112) silently defaults to0.0for the rest of the day.The widening fix for this is in the code, but applied far too late -
self.forecast_minutesonly gets set toend_record + minutes_nowatoutput.py:3150, right before the secondpublish_html_plan()call, well afteryesterday_load_step/yesterday_pv_stepwere already built too small at line 2873.Evidence
Reproduced against a real debug.yaml capture (
forecast_plan_hours: 30,forecast_minutes: 1800,minutes_now: 910i.e. 15:10 local time):yesterday_load_step.get(1800)→0.0046(real value, present)yesterday_load_step.get(1860)onward → missing from the dict entirelyOffset 1800 (relative to the faked "yesterday midnight" anchor used inside
calculate_yesterday()) corresponds to 06:00 the next day. This matches the reported symptom precisely: Load kWh reads 0 in the History view from ~6:00-6:30am onward, every day, self-healing the next day once that day is no longer "today" - a complete 24h "yesterday" only ever needs 1440 minutes of data, comfortably inside an 1800-minute range; it only breaks while trying to reach all the way to "now" on the same day.Why this likely affects most installations, not just an edge-case config
The documented default for
forecast_plan_hoursis 24h (1440 minutes) -docs/customisation.md: "The default of 24 hours is the recommended value." At the default, the shortfall threshold is1440 - 1440 = 0hours, meaning this would trigger for essentially any time of day past midnight, for anyone on the default setting. A longerforecast_plan_hours(e.g. 48h) pushes the threshold later in the day but doesn't eliminate it unlessforecast_plan_hours >= 48.Suggested fix direction
Widen
self.forecast_minutesto at leastend_record + self.minutes_nowbefore buildingyesterday_load_step/yesterday_pv_stepatoutput.py:2873, rather than only right before the secondpublish_html_plan()call at line 3150. Worth double-checking nothing else incalculate_yesterday()between those two points implicitly depends onforecast_minutesstaying at its original/live value in the meantime (e.g. the charge-window trimming at line 2910 explicitly checksc["start"] < 24*60, independent offorecast_minutes, so looks unaffected, but worth a careful pass).