@@ -572,7 +572,10 @@ def test_teslemetry_build_tariff_boost_is_strict_max_on_today_dow():
572572 api .base = _rate_base (import_p = 28.0 , export_p = 15.0 )
573573 tariff = api .build_tariff ((1020 , 1080 ), now_min = 600 ) # 17:00-18:00 window, now 10:00 -> today
574574 sell_periods = tariff ["sell_tariff" ]["seasons" ]["AllYear" ]["tou_periods" ]
575- today_dow = api ._tesla_dow (api .base .now .weekday ())
575+ # Absolute, independent expectation (GH#4610): Tesla's fromDayOfWeek uses Monday=0, the same
576+ # convention as plain datetime.weekday() - deliberately NOT routed through _tesla_dow, the
577+ # function under test, so a wrong mapping there cannot make this assertion trivially pass.
578+ today_dow = api .base .now .weekday ()
576579 assert set (p ["fromDayOfWeek" ] for p in sell_periods ["ON_PEAK" ]["periods" ]) == {today_dow }
577580 boost = tariff ["sell_tariff" ]["energy_charges" ]["AllYear" ]["rates" ]["ON_PEAK" ]
578581 real = [v for t , v in tariff ["sell_tariff" ]["energy_charges" ]["AllYear" ]["rates" ].items () if t != "ON_PEAK" ]
@@ -664,7 +667,8 @@ def agile(offset):
664667 tariff = api .build_tariff ((17 * 60 , 18 * 60 + 30 ), now_min = 12 * 60 )
665668 sell = tariff ["sell_tariff" ]["energy_charges" ]["AllYear" ]["rates" ]
666669 periods = tariff ["sell_tariff" ]["seasons" ]["AllYear" ]["tou_periods" ]
667- today = api ._tesla_dow (api ._local_today_weekday ())
670+ # Absolute, independent expectation (GH#4610) - see test_teslemetry_build_tariff_boost_is_strict_max_on_today_dow.
671+ today = api ._local_today_weekday ()
668672
669673 def sell_price_at (minute ):
670674 """Return the sell tier price applying on today's day at the given minute-of-day."""
@@ -704,7 +708,8 @@ def test_teslemetry_quantise_in_range_excluded_price_no_keyerror():
704708 tariff = api .build_tariff ((17 * 60 , 17 * 60 + 30 ), now_min = 12 * 60 ) # must not raise
705709 sell = tariff ["sell_tariff" ]["energy_charges" ]["AllYear" ]["rates" ]
706710 periods = tariff ["sell_tariff" ]["seasons" ]["AllYear" ]["tou_periods" ]
707- today = api ._tesla_dow (api ._local_today_weekday ())
711+ # Absolute, independent expectation (GH#4610) - see test_teslemetry_build_tariff_boost_is_strict_max_on_today_dow.
712+ today = api ._local_today_weekday ()
708713
709714 def tier_at (minute ):
710715 """Return the sell tier applying on today at the given minute-of-day."""
@@ -1891,11 +1896,56 @@ def test_teslemetry_quantise_agile_three_bands_clamped_rounded():
18911896 assert len (today ) == 48 and len (tomorrow ) == 48
18921897
18931898
1894- def test_teslemetry_tesla_dow_sunday_zero ():
1895- """Python weekday (Mon=0..Sun=6) maps to Tesla fromDayOfWeek (Sun=0..Sat=6)."""
1896- assert TeslemetryAPI ._tesla_dow (6 ) == 0 # Sunday
1897- assert TeslemetryAPI ._tesla_dow (0 ) == 1 # Monday
1898- assert TeslemetryAPI ._tesla_dow (5 ) == 6 # Saturday
1899+ def test_teslemetry_tesla_dow_matches_python_weekday ():
1900+ """Tesla's tariff_content_v2 fromDayOfWeek/toDayOfWeek use Monday=0..Sunday=6, the same convention
1901+ as datetime.weekday() (GH#4610) - so _tesla_dow must be the identity function. The previous
1902+ (Sunday=0) mapping shifted every boost band one day late: during the actual export window the
1903+ Powerwall saw only the ordinary off-peak tariff and had no reason to export."""
1904+ for python_weekday in range (7 ):
1905+ assert TeslemetryAPI ._tesla_dow (python_weekday ) == python_weekday
1906+
1907+
1908+ def test_teslemetry_build_tariff_boost_resolves_at_the_real_tesla_day_index ():
1909+ """Resolver-style regression for GH#4610: independently resolve the built tariff's sell price at a
1910+ moment inside the boost window using Tesla's real day convention (Monday=0, i.e. plain
1911+ datetime.weekday(), never routed through _tesla_dow) and assert the boosted ON_PEAK price applies.
1912+ This is the property that actually matters - a real Powerwall evaluating fromDayOfWeek against its
1913+ own Monday=0 clock must land on the boosted band, not the ordinary off-peak one next to it. Before
1914+ the fix this failed: the boost was carved onto (weekday+1)%7, one day away from where a real
1915+ Powerwall would look for it, so the device saw only off-peak rates during the actual window."""
1916+ api = MockTeslemetryAPI ()
1917+ api .base = _rate_base (import_p = 28.0 , export_p = 15.0 ) # now = 2026-07-20 12:00, a Monday
1918+ window = (17 * 60 , 18 * 60 ) # 17:00-18:00, still ahead of now (12:00) -> lands on today
1919+ tariff = api .build_tariff (window , now_min = 12 * 60 )
1920+ sell = tariff ["sell_tariff" ]["energy_charges" ]["AllYear" ]["rates" ]
1921+ periods = tariff ["sell_tariff" ]["seasons" ]["AllYear" ]["tou_periods" ]
1922+
1923+ real_dow = api .base .now .weekday () # Tesla's actual day index for "today" - independent of _tesla_dow
1924+ minute = 17 * 60 + 30 # inside the window
1925+
1926+ def resolve_tiers (dow , minute ):
1927+ """Mimic how a real Powerwall would resolve which tier(s) apply at (dow, minute).
1928+
1929+ Collects every match rather than returning the first: a real device faced with overlapping
1930+ periods would be ambiguous too, and returning only the first match here would let a future
1931+ partitioning regression (overlapping tiers) pass silently depending on dict insertion order.
1932+ """
1933+ matches = []
1934+ for tier , block in periods .items ():
1935+ for period in block ["periods" ]:
1936+ if period ["fromDayOfWeek" ] <= dow <= period ["toDayOfWeek" ]:
1937+ start = period ["fromHour" ] * 60 + period ["fromMinute" ]
1938+ end = (period ["toHour" ] * 60 + period ["toMinute" ]) or 1440
1939+ if start <= minute < end :
1940+ matches .append (tier )
1941+ return matches
1942+
1943+ matches = resolve_tiers (real_dow , minute )
1944+ assert matches == ["ON_PEAK" ], 'a real Powerwall resolving fromDayOfWeek={} at minute={} would see tier(s)={}, expected exactly ["ON_PEAK"]' .format (real_dow , minute , matches )
1945+ # Not just present - genuinely the boosted (highest) price, so this also validates magnitude,
1946+ # not merely that day-indexing happened to land on a tier named ON_PEAK.
1947+ other_prices = [price for tier , price in sell .items () if tier != "ON_PEAK" ]
1948+ assert sell ["ON_PEAK" ] > max (other_prices )
18991949
19001950
19011951def test_teslemetry_boost_price_floor_wins_on_low_rates ():
@@ -2173,7 +2223,8 @@ def test_teslemetry(my_predbat=None):
21732223 test_teslemetry_quantise_flat_single_tier ()
21742224 test_teslemetry_quantise_two_distinct_exact ()
21752225 test_teslemetry_quantise_agile_three_bands_clamped_rounded ()
2176- test_teslemetry_tesla_dow_sunday_zero ()
2226+ test_teslemetry_tesla_dow_matches_python_weekday ()
2227+ test_teslemetry_build_tariff_boost_resolves_at_the_real_tesla_day_index ()
21772228 test_teslemetry_boost_price_floor_wins_on_low_rates ()
21782229 test_teslemetry_side_layout_partitions_every_day ()
21792230 test_teslemetry_render_side_matched_sets_and_day_end ()
0 commit comments