@@ -523,29 +523,31 @@ def _grid_slot_now(inp: Inputs, dep: Departure, uncovered_kwh: float,
523523
524524
525525def _cheap_topup_now (inp : Inputs , dep : Departure | None ,
526- surplus : float ) -> tuple [bool , float , tuple [datetime , datetime ] | None ]:
526+ surplus : float ) -> tuple [bool , float , tuple [datetime , datetime ] | None , bool ]:
527527 """Grid top-up while the price is below the cheap threshold — only for
528528 energy the PV forecast won't bring anyway before the next departure
529529 (24 h without one), and only in the cheapest published slots until then
530530 (10.10. live: charged at 05:00 for 18.9 ct, the midday before the
531531 departure was 17.2 ct). The block is planned even while the price is
532- still above the threshold, so the devices know it in advance."""
532+ still above the threshold, so the devices know it in advance. Last value:
533+ the price is cheap now but only the home-battery rule for PV hours holds
534+ it back (status `billig_akku` instead of "too little surplus")."""
533535 car = inp .car
534536 if (inp .cheap_target_soc is None or inp .cheap_threshold is None
535537 or car is None or car .soc is None or not car .present ):
536- return False , 0.0 , None
538+ return False , 0.0 , None , False
537539 target = min (inp .cheap_target_soc , car .limit_soc )
538540 if car .soc >= target :
539- return False , 0.0 , None
541+ return False , 0.0 , None , False
540542 if inp .battery_feeds_car :
541543 # the home battery covers any import first: with energy left it
542544 # would just empty itself into the car (10.10. live: 05:00, battery
543545 # 36 -> 12 % in 45 min, then the house bought at 22-24 ct all
544546 # morning); and with PV for the car there's no top-up at all
545547 if surplus >= car .min_kw :
546- return False , 0.0 , None
548+ return False , 0.0 , None , False
547549 if inp .battery_soc is not None and inp .battery_soc > inp .battery_min_soc + CHEAP_BATTERY_MARGIN :
548- return False , 0.0 , None
550+ return False , 0.0 , None , False
549551 need = (target - car .soc ) / 100.0 * car .capacity_kwh / car .efficiency
550552 end = dep .start if dep is not None else inp .now + timedelta (hours = 24 )
551553 pv , _ = forecast_surplus_kwh (inp .forecast , inp .now , end , inp .base_kw + 0.3 , CHEAP_FORECAST_TRUST )
@@ -554,10 +556,12 @@ def _cheap_topup_now(inp: Inputs, dep: Departure | None,
554556 battery_missing = max (0.0 , (100.0 - inp .battery_soc ) / 100.0 * inp .battery_capacity_kwh )
555557 from_grid = need - max (0.0 , pv - battery_missing )
556558 if from_grid <= 0.5 :
557- return False , 0.0 , None
559+ return False , 0.0 , None , False
558560 in_block , block = _cheapest_free_slot_now (inp , end , from_grid , car .max_kw )
559561 now_ok = inp .price_now is not None and inp .price_now <= inp .cheap_threshold
560- return in_block and now_ok , from_grid , block
562+ waits = (now_ok and not in_block and not inp .battery_feeds_car and inp .pv_kw > inp .base_kw + 0.3
563+ and not cheap_pv_battery_ok (inp ))
564+ return in_block and now_ok , from_grid , block , waits
561565
562566
563567def cheap_pv_battery_ok (inp : Inputs ) -> bool :
@@ -668,8 +672,9 @@ def make_plan(inp: Inputs) -> Plan:
668672 budget = {"pv_kw" : inp .pv_kw , "grundlast_kw" : inp .base_kw , "akku_ziel_kw" : target_b }
669673
670674 grid_reason = "netz_pflicht"
675+ cheap_waits = False
671676 if not grid and car_ok and inp .allow_grid_for_car :
672- cheap , cheap_kwh , cheap_block = _cheap_topup_now (inp , dep , surplus )
677+ cheap , cheap_kwh , cheap_block , cheap_waits = _cheap_topup_now (inp , dep , surplus )
673678 block = block or cheap_block
674679 if cheap :
675680 grid , grid_reason = True , "netz_billig"
@@ -709,7 +714,7 @@ def make_plan(inp: Inputs) -> Plan:
709714 if must_kw > MUST_MIN_SURPLUS_KW and car_must > 0 and _lift_to_min (inp , dep , need_kwh , surplus ):
710715 car_kw , car_reason = car .min_kw , "pflicht_minimum" # battery/grid fill the gap
711716 else :
712- car_kw , car_reason = 0.0 , "zu_wenig_ueberschuss"
717+ car_kw , car_reason = 0.0 , "billig_akku" if cheap_waits else " zu_wenig_ueberschuss"
713718 else :
714719 car_reason = ("pflicht" if topup < 0.05 else "pflicht_und_rest" ) if car_must > 0 else (
715720 "vorrang_rest" if car_first else "rest" )
0 commit comments