You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(plan): split low-power charge windows at dawn, not window end
A charge window built from a single long cheap-rate period spanning
sunrise was treated as one window by find_charge_rate's PV-overlap
check (#4373), which abandons low power charging for the whole window
the moment any PV is forecast in it - including the still-dark hours
before sunrise (#4557).
Add calc_dawn: classify each plan_interval_minutes slot as PV-light or
PV-dark and split charge windows at that boundary in find_charge_window
(pv_light_dark param), reusing the same mechanism already used for
export windows' alternate_rate_boundary. The dark portion now stays
low power throughout; the light portion keeps today's behaviour.
Dawn is the first bucket that crosses LOW_POWER_PV_LIGHT_FRACTION of
the peak PV forecast anywhere in the horizon - a fraction of the
forecast's own peak rather than a fixed Watts figure, so the same
threshold is meaningful whether the system is 2kWp or 15kWp. Within a
calendar day it's a one-way latch: once crossed, later buckets stay
light even if PV genuinely dips back under the threshold (a cloud
passing), so an intermittently cloudy morning can't chop the window
into several flip-flopping pieces. The latch resets at each day
boundary, which also makes it behave correctly at extreme latitudes -
a polar-night day never crosses (stays all-dark) and a polar-day day
crosses from the first bucket (stays all-light).
No dusk-side equivalent needed: the PV-overlap check is a forward sum
from "now" to window end, recomputed every loop, so it already
self-corrects once a light patch is behind it - the false-trigger-
then-never-corrects failure mode this fix addresses only exists in
the dawn direction.
Verified against the #4557 reporter's own debug.yaml throughout - the
real 03:00-10:00 window splits cleanly at the actual crossing point,
using that household's own forecast peak.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
if (alt_rateisnotNone) and (alt_rate_lastisnotNone) and (abs(alt_rate-alt_rate_last) >=alt_rate_threshold):
1564
1651
# Create alternate rate boundary if the rate is different
1565
1652
alternate_rate_boundary=True
1653
+
pv_state=pv_light_dark.get(minute, None)
1654
+
if (rate_low_start>=0) and (pv_stateisnotNone) and (pv_light_dark_lastisnotNone) and (pv_state!=pv_light_dark_last):
1655
+
# Create a PV boundary when the light/dark classification changes since the window
1656
+
# started. Gated on rate_low_start so a transition crossed only during the pre-window
1657
+
# search phase (e.g. scanning past last night's sunset before this window is even
1658
+
# found) doesn't leave a stale boundary that breaks the window the moment it starts.
1659
+
pv_boundary=True
1566
1660
ifminuteinrates:
1567
1661
rate=rates[minute]
1568
1662
if ((notfind_high) and (rate<=threshold_rate)) or (find_highand (rate>=threshold_rate) and (rate>0)) or (minuteinself.manual_all_times) or (rate_low_startinself.manual_all_times):
0 commit comments