diff --git a/backend/services/planning_engine.py b/backend/services/planning_engine.py index f990294..25b2a26 100644 --- a/backend/services/planning_engine.py +++ b/backend/services/planning_engine.py @@ -24,6 +24,7 @@ from app.config import get_settings logger = logging.getLogger(__name__) +from services.planning.solver_v2 import solve_dispatch_v2 from services.planning.types import ( PlannerSolverError, _timestamptz_from_db, @@ -242,6 +243,63 @@ def _planner_compare_enabled() -> bool: return bool(get_settings().planning_engine_compare_enabled) +def _solve_dispatch_for_version( + version: str, + slots: list["PlanningSlot"], + battery, + heat_pump, + grid, + ev_sessions: list, + vehicles: list, + current_soc_wh: float, + current_tuv_temp_c: float, + *, + tuv_delta_stats: Optional[dict[tuple[int, int], float]] = None, + operating_mode: str = "AUTO", + charge_commitment_prev_w: Optional[list[Optional[float]]] = None, + evening_push_ts_override: Optional[set[int]] = None, +) -> tuple[list["DispatchResult"], int, dict[str, Any]]: + """ + Router verzí plánovače: "v2" = čisté ekonomické jádro (services.planning.solver_v2, + bez heuristických penalt; commitment/evening_push override nemá — koncepty v1), + jinak v1 two-pass. Chybu v2 balí do PlannerSolverError kvůli failure pipeline. + """ + if str(version).strip().lower() == "v2": + try: + return solve_dispatch_v2( + slots, + battery, + heat_pump, + grid, + ev_sessions, + vehicles, + current_soc_wh, + current_tuv_temp_c, + tuv_delta_stats=tuv_delta_stats, + operating_mode=operating_mode, + planner_version="v2", + ) + except PlannerSolverError: + raise + except RuntimeError as exc: + raise PlannerSolverError(f"v2: {exc}", relax_chain=["v2"]) from exc + return solve_dispatch_two_pass( + slots, + battery, + heat_pump, + grid, + ev_sessions, + vehicles, + current_soc_wh, + current_tuv_temp_c, + tuv_delta_stats=tuv_delta_stats, + operating_mode=operating_mode, + charge_commitment_prev_w=charge_commitment_prev_w, + planner_version=version, + evening_push_ts_override=evening_push_ts_override, + ) + + def _planner_peer_version(version: str) -> str: v = str(version).strip().lower() if v == "v1": @@ -344,7 +402,8 @@ def _maybe_add_planner_comparison( if peer_version == active_version: return None try: - peer_results, peer_ms, peer_snapshot = solve_dispatch_two_pass( + peer_results, peer_ms, peer_snapshot = _solve_dispatch_for_version( + peer_version, slots, battery, heat_pump, @@ -356,7 +415,6 @@ def _maybe_add_planner_comparison( tuv_delta_stats=tuv_delta_stats, operating_mode=operating_mode, charge_commitment_prev_w=charge_commitment_prev_w, - planner_version=peer_version, evening_push_ts_override=None, ) except RuntimeError as exc: @@ -3502,7 +3560,14 @@ async def run_daily_plan( om = operating_mode or "AUTO" try: - if om == "AUTO": + if planner_version_resolved == "v2": + results, duration_ms, solver_snapshot = _solve_dispatch_for_version( + "v2", + slots, battery, hp, grid, ev_sessions, vehicles, soc_wh, tuv_temp, + tuv_delta_stats=tuv_stats, + operating_mode=om, + ) + elif om == "AUTO": results, duration_ms, solver_snapshot = solve_dispatch_two_pass( slots, battery, hp, grid, ev_sessions, vehicles, soc_wh, tuv_temp, tuv_delta_stats=tuv_stats, @@ -3739,7 +3804,14 @@ async def run_rolling_replan( om = operating_mode or "AUTO" try: - if om == "AUTO": + if planner_version_resolved == "v2": + results, duration_ms, solver_snapshot = _solve_dispatch_for_version( + "v2", + slots, battery, hp, grid, ev_sessions, vehicles, soc_wh, tuv_temp, + tuv_delta_stats=tuv_stats, + operating_mode=om, + ) + elif om == "AUTO": results, duration_ms, solver_snapshot = solve_dispatch_two_pass( slots, battery, hp, grid, ev_sessions, vehicles, soc_wh, tuv_temp, tuv_delta_stats=tuv_stats,