Summary
TeslemetryAPI._tesla_dow() converts Python's weekday to a Sunday=0 day-of-week convention, but Tesla's tariff_content_v2 fromDayOfWeek/toDayOfWeek fields use Monday=0 — the same convention as datetime.weekday().
Every ON_PEAK boost band Predbat writes therefore lands one day late. During an actual export window the Powerwall sees only the ordinary off-peak tiers, so it has no reason to export and simply covers house load. Forced export has never worked on the TESLA inverter type.
Confirmed live: correcting only the day index made a Powerwall go from grid=0 to grid=-5156 (full 5 kW battery export) within 50 seconds, with no other change.
The defect
@staticmethod
def _tesla_dow(python_weekday):
"""Map a Python weekday (0=Mon..6=Sun) to Tesla's fromDayOfWeek (0=Sun..6=Sat)."""
return (python_weekday + 1) % 7
The premise in that docstring is wrong. Tesla's day index is Monday=0. Reference: the Teslemetry-maintained python-tesla-fleet-api ships a pure offline resolver for this exact object, and its _minute_of_week is explicit:
def _minute_of_week(moment: datetime) -> int:
"""Monday 00:00 = 0 .. Sunday 23:59 = 10079, matching ``datetime.weekday()``."""
return moment.weekday() * _MINUTES_PER_DAY + moment.hour * 60 + moment.minute
_tesla_dow feeds both _apply_boost (which day the boost is carved onto) and _side_layout (which day today's tier shape is placed on), so the whole per-day layout is shifted, not just the boost.
Evidence
Resolver, run against a tariff read back off a live device
The boost was pushed for Wednesday 21:30-23:30, and was written as fromDayOfWeek: 3:
Device ON_PEAK sell period: [{"fromDayOfWeek": 3, "toDayOfWeek": 3,
"fromHour": 21, "fromMinute": 30, "toHour": 23, "toMinute": 30}]
Wed (intended) 22:00 buy=0.29 (OFF_PEAK) sell=0.12 (SUPER_OFF_PEAK) <- no boost
Thu (day + 1) 22:00 buy=0.58 (ON_PEAK) sell=0.58 (ON_PEAK) <- boost lands here
Live before/after on a Powerwall 2 (firmware 26.18.3)
Same 0.58 boost price Predbat already computes, same autonomous, same battery_ok, same 4% reserve. The only difference is the day index — dow=3 (corrected, Mon=0) instead of dow=4 (current, Sun=0):
before soc=95.0% batt= 2100 load= 2652 grid= 0 <- load-following, as always
t+050s soc=95.0% batt= 5010 load= 430 grid= -5156 <- full 5 kW export to grid
t+100s soc=95.0% batt= 5000 load= 3309 grid= -2288
t+175s soc=95.0% batt= 5000 load= 3309 grid= -2288
Why this went unnoticed
Every prior symptom is explained by the shift, and each one independently looked like something else:
- The battery tracks house load to the watt with
grid=0 during "Exporting" — which is the correct response to the plain OFF_PEAK / SUPER_OFF_PEAK tariff it actually saw.
- An overnight export window let the house import at 6.9p while the battery sat at 97% SoC. Also correct under a plain
SUPER_OFF_PEAK rate.
- Raising the boost to £500/kWh on both buy and sell changed nothing, because the £500 was written to the following day.
- Comparing the generated tariff against Tesla's published example (PGE-EV2-A) does not reveal it — that tariff spans
fromDayOfWeek: 0 -> toDayOfWeek: 6, so the convention is invisible. Same for the library's own real-world fixture.
Why the tests don't catch it
The assertions derive the expected day by calling the function under test:
today_dow = api._tesla_dow(api.base.now.weekday())
assert set(p["fromDayOfWeek"] for p in sell_periods["ON_PEAK"]["periods"]) == {today_dow}
That passes under any mapping. Same pattern in the other build_tariff tests via sell_price_at() / tier_at() helpers, which filter periods using api._tesla_dow(...).
Fix
Make _tesla_dow the identity function — Python's weekday() already matches Tesla's convention. Because both call sites (_apply_boost, _side_layout) take the same value, it stays internally consistent.
In apps/predbat/teslemetry.py:
@staticmethod
def _tesla_dow(python_weekday):
- """Map a Python weekday (0=Mon..6=Sun) to Tesla's fromDayOfWeek (0=Sun..6=Sat).
+ """Return Tesla's fromDayOfWeek for a Python weekday.
- Isolated so any future convention change is a one-line fix.
+ Tesla's tariff_content_v2 fromDayOfWeek/toDayOfWeek use Monday=0..Sunday=6,
+ the same convention as datetime.weekday(), so this is the identity mapping.
+ Kept as a named helper so the convention is stated in exactly one place.
"""
- return (python_weekday + 1) % 7
+ return python_weekday
Verified live: with this mapping and no other change, a Powerwall went from grid=0 to grid=-5156 inside 50 seconds.
Test changes needed alongside it:
- Pin a known date and assert the absolute expected index (e.g. Wednesday 2026-08-19 ->
fromDayOfWeek == 2), never a value derived from _tesla_dow.
- Worth adding a resolver-style test that evaluates the built tariff at a moment inside the window and asserts the ON_PEAK price is returned — that is the property that actually matters and it would have caught this.
Scope
Affects every site on the TESLA inverter type, not one installation. Any Powerwall using Predbat forced export has been writing its boost band to the wrong day, so the export leg has been silently inert while the plan reported Exporting. Charging is unaffected (it does not depend on the boost band).
Supersedes #4600 — the optimization_strategy theory there was a red herring. That field genuinely cannot be set alongside tariff_content_v2 (the API accepts the call and silently discards it), but it was never the reason export failed.
Summary
TeslemetryAPI._tesla_dow()converts Python's weekday to a Sunday=0 day-of-week convention, but Tesla'stariff_content_v2fromDayOfWeek/toDayOfWeekfields use Monday=0 — the same convention asdatetime.weekday().Every ON_PEAK boost band Predbat writes therefore lands one day late. During an actual export window the Powerwall sees only the ordinary off-peak tiers, so it has no reason to export and simply covers house load. Forced export has never worked on the TESLA inverter type.
Confirmed live: correcting only the day index made a Powerwall go from
grid=0togrid=-5156(full 5 kW battery export) within 50 seconds, with no other change.The defect
The premise in that docstring is wrong. Tesla's day index is Monday=0. Reference: the Teslemetry-maintained python-tesla-fleet-api ships a pure offline resolver for this exact object, and its
_minute_of_weekis explicit:_tesla_dowfeeds both_apply_boost(which day the boost is carved onto) and_side_layout(which day today's tier shape is placed on), so the whole per-day layout is shifted, not just the boost.Evidence
Resolver, run against a tariff read back off a live device
The boost was pushed for Wednesday 21:30-23:30, and was written as
fromDayOfWeek: 3:Live before/after on a Powerwall 2 (firmware 26.18.3)
Same 0.58 boost price Predbat already computes, same
autonomous, samebattery_ok, same 4% reserve. The only difference is the day index —dow=3(corrected, Mon=0) instead ofdow=4(current, Sun=0):Why this went unnoticed
Every prior symptom is explained by the shift, and each one independently looked like something else:
grid=0during "Exporting" — which is the correct response to the plainOFF_PEAK/SUPER_OFF_PEAKtariff it actually saw.SUPER_OFF_PEAKrate.fromDayOfWeek: 0 -> toDayOfWeek: 6, so the convention is invisible. Same for the library's own real-world fixture.Why the tests don't catch it
The assertions derive the expected day by calling the function under test:
That passes under any mapping. Same pattern in the other build_tariff tests via
sell_price_at()/tier_at()helpers, which filter periods usingapi._tesla_dow(...).Fix
Make
_tesla_dowthe identity function — Python'sweekday()already matches Tesla's convention. Because both call sites (_apply_boost,_side_layout) take the same value, it stays internally consistent.In
apps/predbat/teslemetry.py:@staticmethod def _tesla_dow(python_weekday): - """Map a Python weekday (0=Mon..6=Sun) to Tesla's fromDayOfWeek (0=Sun..6=Sat). + """Return Tesla's fromDayOfWeek for a Python weekday. - Isolated so any future convention change is a one-line fix. + Tesla's tariff_content_v2 fromDayOfWeek/toDayOfWeek use Monday=0..Sunday=6, + the same convention as datetime.weekday(), so this is the identity mapping. + Kept as a named helper so the convention is stated in exactly one place. """ - return (python_weekday + 1) % 7 + return python_weekdayVerified live: with this mapping and no other change, a Powerwall went from
grid=0togrid=-5156inside 50 seconds.Test changes needed alongside it:
fromDayOfWeek == 2), never a value derived from_tesla_dow.Scope
Affects every site on the TESLA inverter type, not one installation. Any Powerwall using Predbat forced export has been writing its boost band to the wrong day, so the export leg has been silently inert while the plan reported
Exporting. Charging is unaffected (it does not depend on the boost band).Supersedes #4600 — the
optimization_strategytheory there was a red herring. That field genuinely cannot be set alongsidetariff_content_v2(the API accepts the call and silently discards it), but it was never the reason export failed.