feat(planner): EV anti-fragmentace + 3f power floor (Fix B)

3f floor (phases>=3 → 6A×fáze×230 ≈4140W, ruší 1f trickle) + block-start penalta
(asset_ev_charger.planner_ev_start_penalty_czk V108, default 0=no-op). Golden gate
zelená (363 passed). Postaveno paralelním worktree agentem, zvalidováno sériově.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-14 22:55:17 +02:00
parent fd7012e23d
commit a32839bf67
6 changed files with 134 additions and 10 deletions

View File

@@ -0,0 +1,11 @@
-- EV anti-fragmentace (Fix B): per-wallbox cena za START nabíjecí dávky.
-- solver_v2 zavádí per-slot binárku ev_on a hranu ev_start[t] >= ev_on[t] - ev_on[t-1];
-- do objektivu přidá Σ ev_start × tato cena. Drobná penalta (filozofie v2: nejistota /
-- opotřebení = cena) tlačí solver k SOUVISLÉ dávce místo rozsekaného nabíjení přes
-- nesouvislé sloty. Default 0 = no-op (golden gate beze změny); kalibruje se per site.
alter table ems.asset_ev_charger
add column if not exists planner_ev_start_penalty_czk numeric(6, 3) not null default 0;
comment on column ems.asset_ev_charger.planner_ev_start_penalty_czk is
'Cena (Kč) za START nabíjecí dávky v solver_v2: do objektivu jde Σ ev_start × tato hodnota (ev_start = náběžná hrana ev_on mezi sloty). Drobná penalta proti fragmentaci nabíjení (rozsekané nesouvislé sloty) — souvislá dávka na 3f místo scattered 1f trickle. 0 = vypnuto (no-op, golden-safe). Kalibruje se per wallbox.';

View File

@@ -162,11 +162,17 @@ begin
)
);
-- vehicles nesou parametry SVÉHO wallboxu (join přes default_charger_id,
-- výběr DYNAMICKY podle site_id + id, NE podle kódu): min_power_w, počet fází
-- (phases — solver_v2 z něj odvozuje 3f power floor proti 1f trickle) a
-- planner_ev_start_penalty_czk (anti-fragmentace nabíjení, Fix B; default 0 = no-op).
select coalesce(
jsonb_agg(
jsonb_build_object(
'max_charge_power_w', v.max_charge_power_w,
'min_power_w', coalesce(ch.min_power_w, 0),
'phases', coalesce(ch.phases, 3),
'planner_ev_start_penalty_czk', coalesce(ch.planner_ev_start_penalty_czk, 0),
'battery_capacity_kwh', v.battery_capacity_kwh,
'default_target_soc_pct', v.default_target_soc_pct
)
@@ -259,4 +265,4 @@ end;
$fn$;
comment on function ems.fn_planning_site_context is
'Kontext pro planning_engine / LP (bez samotného solveru). EV session přes fn_ev_session_planning_json: session se nevyřazuje při needed_wh=0 (auto nad targetem) — zůstává v plánu kvůli oportunismu i jako známá zátěž; opportunistic_value = coalesce(session, vehicle); headroom_wh od max(target, soc_at_connect), 0 při vypnutém oportunismu; vehicles nesou min_power_w wallboxu.';
'Kontext pro planning_engine / LP (bez samotného solveru). EV session přes fn_ev_session_planning_json: session se nevyřazuje při needed_wh=0 (auto nad targetem) — zůstává v plánu kvůli oportunismu i jako známá zátěž; opportunistic_value = coalesce(session, vehicle); headroom_wh od max(target, soc_at_connect), 0 při vypnutém oportunismu; vehicles nesou parametry svého wallboxu (min_power_w, phases, planner_ev_start_penalty_czk — anti-fragmentace EV v solver_v2, default 0 = no-op).';