Skip to content

fix(solar): scale PV forecast by the detected slot length for all sources - #4433

Merged
springfall2008 merged 2 commits into
springfall2008:mainfrom
tieskuh:fix/pv-forecast-divide-by-follows-detected-period
Aug 4, 2026
Merged

fix(solar): scale PV forecast by the detected slot length for all sources#4433
springfall2008 merged 2 commits into
springfall2008:mainfrom
tieskuh:fix/pv-forecast-divide-by-follows-detected-period

Conversation

@tieskuh

@tieskuh tieskuh commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Problem

fetch_pv_forecast() detects the real slot length of the forecast data (period, the block around "Detect the actual period of the forecast data") and recalculates divide_by from it. That recalculation was guarded by

if not self.forecast_solar and not (self.solcast_host and self.solcast_api_key):

which excludes exactly the branches that need it.

All three direct API downloaders return pv_estimate as kWh per slot, but the slot length differs:

Source Slot length divide_by before this PR Error factor (period / divide_by)
Forecast.Solar plan_interval_minutes 30.0 0.5 at 15 min, 1.0 at 30 min, 2.0 at 60 min
Open-Meteo (primary or fallback, with forecast_solar set) 60 min 30.0 2.0
Solcast direct PT15M / PT30M from the API 30.0 0.5 on PT15M
Open-Meteo only 60 min 60 1.0, unless Solcast credentials are also present
HA sensors detected period * factor 1.0

minute_data() divides by divide_by once and then writes that value into every minute of the slot via spreading=period, so the energy per slot ends up as period * pv_estimate / divide_by.

The guard dates from #3525, which fixed the HA sensor path. Forecast.Solar had already moved to plan_interval_minutes slots by then (#2875), so the branch was correct only at the default 30-minute plan interval.

The log line "PV Forecast data has N minute resolution, adjusting calculations" fires on these branches even though no adjustment happens, which is why this was easy to miss.

Not related to #3827, which fixed slot accumulation when annotating pv_estimateCL/10/90; the divide_by scaling of the per-minute planner data is a separate step.

Evidence

On a 36 kWp Forecast.Solar Professional install running plan_interval_minutes: 15, integrating the cached raw watts response for 2026-08-03 gives about 87.3 kWh, while Predbat published PV Forecast for today is 43.64 kWh and logged divide_by 2.0. Exactly half. The calibration loop partly masks this (adjustment 1.296x) but cannot converge, because it is asked to correct a systematic factor of two on top of weather noise.

Fix

Drop the guard and always recalculate divide_by from the detected period.

For the direct API branches divide_by is 30.0, so factor becomes 1.0 and divide_by becomes period, which is what kWh-per-slot data requires. For the HA sensor branch divide_by is dp2(30 * factor), so the division returns the same factor and the behaviour is unchanged. Because period is derived from the data that actually came back, this also stays correct on the Open-Meteo fallback paths, where the source switches mid-branch. If period detection fails and falls back to its default of 30, the result is identical to today's behaviour.

Compatibility note

Users on forecast_solar_open_meteo_first will see their forecast drop by half. That is the correction, not a regression: hourly Open-Meteo slots were being counted twice. Users on Forecast.Solar with the default plan_interval_minutes: 30 see no change.

Tests

Five new cases in test_solcast.py, each asserting the published day-0 total against the known kWh sum of the forecast the downloader produced, plus the unit factor (divide_by / period, which must be 1.0 for kWh-per-slot data).

Measured on this branch with the fix reverted, so each case fails in the direction described above:

Case Before After
Forecast.Solar, 15-min plan interval 1.21 kWh (0.5x), unit factor 2.0 2.42 kWh, unit factor 1.0
Forecast.Solar, 30-min plan interval 2.42 kWh 2.42 kWh (unchanged)
Solcast direct, PT15M 1.0 kWh (0.5x), unit factor 2.0 2.0 kWh, unit factor 1.0
Open-Meteo primary, hourly 5.94 kWh (2.0x), unit factor 0.5 2.97 kWh, unit factor 1.0
HA sensors, factor 1.0 / 2.0 / 4.0 2.0 kWh each 2.0 kWh each (unchanged)

The HA sensor case is the one that shows the guard can go: all three valid unit factors survive the recalculation untouched.

unit_test.py --quick passes on this branch (32 of 33 groups; download fails identically on unmodified main in my environment, a local compute_sha1 issue). black, ruff --select=F401 and cspell are clean.

…rces

fetch_pv_forecast() detects the actual forecast period but only applied it
to the HA sensor path, leaving divide_by hard-coded at 30.0 for the direct
API sources. All three direct downloaders emit kWh per slot with a slot
length that is not always 30 minutes, so the per-minute values were scaled
by period/30 instead of 1.

Forecast.Solar with plan_interval_minutes: 15 produced half the real
forecast; Open-Meteo, which is hourly, produced double. Making the
recalculation unconditional leaves the HA sensor path unchanged, since
its divide_by already carries the unit factor.

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.

Pull request overview

This PR fixes PV forecast energy scaling by making fetch_pv_forecast() always recompute divide_by from the detected forecast slot length (period), ensuring consistent kWh totals across Forecast.Solar, Open-Meteo (primary/backup), Solcast direct, and HA sensor-based forecasts.

Changes:

  • In apps/predbat/solcast.py, removed the guard that prevented divide_by rescaling for direct API sources; divide_by is now always recalculated based on detected period.
  • Added a new suite of slot-length scaling regression tests in apps/predbat/tests/test_solcast.py to validate day-0 totals and the unit-factor behaviour across sources and resolutions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
apps/predbat/solcast.py Always recalculates divide_by using detected forecast period so per-minute scaling is correct for all forecast sources.
apps/predbat/tests/test_solcast.py Adds targeted tests verifying correct kWh totals and expected unit-factor across Forecast.Solar, Solcast direct, Open-Meteo primary, and HA sensor paths.

Comment thread apps/predbat/tests/test_solcast.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@springfall2008
springfall2008 merged commit 385c38d into springfall2008:main Aug 4, 2026
2 checks passed
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.

3 participants