Fáze 3.4: router verzí plánovače — v2 zapojeno do shadow porovnání

_solve_dispatch_for_version: 'v2' → services.planning.solver_v2 (čisté jádro),
jinak v1 two-pass; chyby v2 balené do PlannerSolverError (failure pipeline).
Zapojeno do _maybe_add_planner_comparison (peer) i aktivních běhů
run_daily_plan / run_rolling_replan (gated PLANNING_ENGINE_VERSION).

Aktivace shadow: env PLANNING_ENGINE_COMPARE_ENABLED=true (aktivní zůstává v1,
v2 se počítá paralelně, srovnání v planning_run.solver_params.comparison).
Přepnutí: PLANNING_ENGINE_VERSION=v2. Default beze změny — golden 7/7,
plná sada 245 passed (1 předexistující reg340 fail), 4 xfailed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-11 14:23:24 +02:00
parent 7d9ce5746a
commit a8b4342099

View File

@@ -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,