Fix savings_yesterday_predbat fluctuating throughout the day on dynamic tariffs#3881
Conversation
… range only Agent-Logs-Url: https://github.com/springfall2008/batpred/sessions/b0e50bd2-2673-47ec-a8db-839f0584af47 Co-authored-by: springfall2008 <48591903+springfall2008@users.noreply.github.com>
Agent-Logs-Url: https://github.com/springfall2008/batpred/sessions/b0e50bd2-2673-47ec-a8db-839f0584af47 Co-authored-by: springfall2008 <48591903+springfall2008@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes non-deterministic savings_yesterday_predbat recalculation on dynamic tariffs by ensuring the baseline “lowest rate” threshold and flat-tariff variability detection are derived only from yesterday’s rate range, preventing today’s progressively-added rates from skewing the baseline.
Changes:
- Scope
rate_lowderivation incalculate_yesterday()tok < end_record(yesterday-only), with safe fallbacks. - Scope the “variable tariff” check (
min != max) to yesterday-only rates to avoid false positives from today’s rates. - Add a new unit test and register it in the unit test runner.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| apps/predbat/output.py | Restricts baseline threshold + variability checks to yesterday’s rate range to stabilize savings calculations. |
| apps/predbat/tests/test_savings_stability.py | Adds a regression-style test scenario for the threshold stability / window finding behavior. |
| apps/predbat/unit_test.py | Registers the new savings_stability test in the unit test harness. |
| """ | ||
| Test that the rate_low threshold computed in calculate_yesterday is derived only from | ||
| yesterday's rate range (k < end_record = 1440) and not from today's rates. | ||
|
|
||
| Bug: rate_low = min(past_rates.values()) included today's dynamically-added rates | ||
| (beyond minute 1440), so as minutes_now increased through the day and cheaper | ||
| today-rates entered the dict, the threshold dropped. That caused rate_scan_window to | ||
| find fewer (or no) charge windows for yesterday, producing a different metric_baseline | ||
| and thus a different savings_yesterday value on each hourly recalculation. | ||
|
|
||
| Fix: compute rate_low only from k < end_record (yesterday's window). |
There was a problem hiding this comment.
Done in da44d39. Extracted the rate_low selection into a compute_rate_low_for_yesterday() method on the Output class, which calculate_yesterday() now calls. The test was rewritten to call the production methods directly: history_to_future_rates() to build past_rates exactly as production does, compute_rate_low_for_yesterday() to assert stability across three minutes_now values, and rate_scan_window() to confirm correct window detection. All shared state (combine_charge_slots, minutes_now, forecast_minutes) is now saved and restored in a try/finally block with deterministic values set before rate_scan_window is called.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Agent-Logs-Url: https://github.com/springfall2008/batpred/sessions/e7f0a977-1174-4ffe-a0b7-5dc6c67622b7 Co-authored-by: springfall2008 <48591903+springfall2008@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
savings_yesterday_predbatwas non-deterministic across its hourly recalculations — the value would swing wildly (spikes of 150+ p, then collapse to ~0 or negative) within a single completed day.Root cause
calculate_yesterday()buildspast_ratesviahistory_to_future_rates(..., end_record + self.minutes_now), which grows as the day progresses to include today's rates beyond minute 1440. The baseline charge-window threshold was then:On Agile (or any tariff with a cheap/negative slot today), once that slot entered
past_rates,rate_lowdropped below yesterday's minimum — e.g. from 5p → 0p.rate_scan_windowthen found no charge windows in yesterday's data (all of yesterday's rates > 0p), so the baseline ran with no scheduled charging, producing a highermetric_baselineand thus inflated apparent savings. The next recalculation (before that cheap slot was included) would find charge windows again and give a lower value — hence the oscillation.The same issue affected the flat-tariff variability guard (
min != max): today's variable rates could trigger charge-window detection even when yesterday had a flat tariff.Fix —
output.pyExtracted a new
compute_rate_low_for_yesterday(past_rates, end_record)helper method on theOutputclass. It restricts both therate_lowthreshold and the variability check to yesterday's rate range only (k < end_record), so they are unaffected by today's rates being progressively appended:calculate_yesterday()now callsself.compute_rate_low_for_yesterday(past_rates, end_record)instead of the inlinemin(past_rates.values()). The variability guard forpast_rates_no_iois similarly scoped tok < end_record.The
past_ratesdict still coversend_record + minutes_nowentries so that overnight charge windows spanning midnight are detected correctly — only the threshold derivation is scoped to yesterday.Test —
tests/test_savings_stability.pyNew test calls the production methods directly to exercise the fix:
history_to_future_rates()buildspast_ratesexactly ascalculate_yesterday()does for three time-of-day scenarios (midnight, mid-morning, noon)compute_rate_low_for_yesterday()is asserted to return the stable 5p yesterday minimum at all three points, regardless of today's 0p Agile slot entering the dictrate_scan_window()confirms the correct yesterday charge window is found with the fixed threshold and nothing is found with the old broken 0p thresholdmy_predbatstate (combine_charge_slots,minutes_now,forecast_minutes) is saved and restored viatry/finallywith deterministic values set before scanning