Skip to content

Commit cef511d

Browse files
xchillxxclaude
andcommitted
Departure-aware battery target only while the car can still charge (v3.0.2)
Counting the PV after a departure into the battery plan frees PV for the car before it leaves. With the car already at its limit there is nothing to free it for, and the battery was simply reserved less than it needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent b7bf917 commit cef511d

3 files changed

Lines changed: 18 additions & 2 deletions

File tree

‎custom_components/surplus_pilot/manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
"iot_class": "local_polling",
1010
"issue_tracker": "https://github.com/xchillxx/surplus-pilot/issues",
1111
"requirements": [],
12-
"version": "3.0.1"
12+
"version": "3.0.2"
1313
}

‎custom_components/surplus_pilot/planner.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ def battery_target_kw(inp: Inputs, dep: Departure | None) -> tuple[float, str]:
186186
hours_left = (inp.pv_end - inp.now).total_seconds() / 3600.0
187187
target = min(BATTERY_MAX_TARGET_KW, missing / max(hours_left, MIN_HOURS_LEFT))
188188
mode = "frist"
189-
car_home = inp.car is not None and inp.car.present
189+
# only worth it while the car can still use the PV it frees up
190+
car_home = inp.car is not None and inp.car.present and (
191+
inp.car.soc is None or inp.car.soc < inp.car.limit_soc)
190192
if dep is not None and car_home and inp.now < dep.start < inp.pv_end and inp.forecast:
191193
after = forecast_kwh(inp.forecast, dep.start, inp.pv_end) * FORECAST_SAFETY
192194
h_dep = (dep.start - inp.now).total_seconds() / 3600.0

‎tests/test_planner.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,17 @@ def test_slots_rhythm_and_oneoff():
7272
starts = [d.start.strftime("%m-%d %H:%M") for d in deps]
7373
assert starts == ["10-06 16:30", "10-08 09:00"] # every 4 days: next one 10-10, beyond the 4-day horizon
7474
assert deps[0].returns == deps[0].start + timedelta(hours=14)
75+
76+
77+
def test_departure_battery_mode_only_while_car_can_charge():
78+
now = datetime(2026, 10, 6, 14, 0, tzinfo=TZ)
79+
dep = P.Departure(start=now.replace(hour=16, minute=30), target_soc=50.0)
80+
fc = [P.ForecastHour(end=now.replace(hour=0) + timedelta(hours=h), kwh=6.0 if 10 <= h <= 17 else 0.0)
81+
for h in range(24)]
82+
full = P.CarInput(present=True, soc=80.0, limit_soc=80.0, capacity_kwh=72.9, efficiency=0.9, min_kw=3.45,
83+
max_kw=11.04)
84+
plan = P.make_plan(base_inputs(now, pv_kw=6.0, battery_soc=80.0, car=full, departures=[dep], forecast=fc))
85+
assert plan.battery_mode == "frist"
86+
half = P.CarInput(**{**full.__dict__, "soc": 60.0})
87+
plan = P.make_plan(base_inputs(now, pv_kw=6.0, battery_soc=80.0, car=half, departures=[dep], forecast=fc))
88+
assert plan.battery_mode == "abfahrt"

0 commit comments

Comments
 (0)