Files
ems/db/routines/R__016_fn_ev_session_transition.sql
Dusan Vojacek 4095f0f912
All checks were successful
CI and deploy / migration-check (push) Successful in 19s
CI and deploy / deploy (push) Successful in 56s
EV spotřební forecast: týdenní rytmus vozidla → target SoC a deadline session
Myšlenka uživatele: pondělní služebka ~150 km (~35 kWh) chce skoro plnou,
konec týdne stačí míň, víkend = levné sloty na přípravu pondělka.

- V089: ev_vehicle_obs (odometer+SoC při příjezdu/ODJEZDU — auto v obou
  okamžicích vzhůru, žádné buzení navíc), ev_trip (km z odometru, kWh z ΔSoC;
  nabíjení cestou → charged_away flag), ev_usage_stats per (vozidlo, DOW);
  asset_vehicle: target_soc_forecast_enabled (default false), min_target_soc_pct
- R__096: fn_ev_build_trips (párování), fn_update_ev_usage_stats (job 00:50),
  fn_ev_next_departure (příští typický odjezd, >=4 vzorky, >=3 km),
  fn_ev_required_soc (P80 spotřeby dne + 10 p.b., clamp [min_target, 100])
- R__016: session při příjezdu bere forecast target+deadline (za per-vozidlo
  flagem, fallback defaulty, ruční patch vždy vyhrává) → víkendová session
  s pondělním deadline = v2 solver přirozeně nabije v levných slotech
- tesla_client: + vehicle_state endpoint (odometer v MÍLÍCH → km), collector:
  departure hook, lifespan: job 00:50

Aktivace po nasbírání dat: update asset_vehicle set target_soc_forecast_enabled=true.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 09:06:10 +02:00

103 lines
2.9 KiB
PL/PgSQL

create or replace function ems.fn_ev_session_transition(
p_site_id int,
p_charger_id int,
p_prev_status text,
p_new_status text,
p_measured_at timestamptz
)
returns jsonb
language plpgsql
as $fn$
declare
v_vehicle_id int;
begin
if p_prev_status is not distinct from p_new_status then
return jsonb_build_object('action', 'none');
end if;
if p_prev_status = 'available' and p_new_status is distinct from 'available' then
select av.id
into v_vehicle_id
from ems.asset_vehicle av
where av.site_id = p_site_id
and av.default_charger_id = p_charger_id
and av.active = true
order by av.id
limit 1;
perform ems.fn_update_ev_arrival_stats(
p_site_id,
p_charger_id,
v_vehicle_id,
p_measured_at
);
insert into ems.ev_session (
site_id,
charger_id,
vehicle_id,
session_start,
target_soc_pct,
target_deadline
)
select
ac.site_id,
ac.id,
av.id,
now(),
-- forecast z týdenního rytmu (ev_usage_stats), fallback defaulty;
-- ruční přepis přes fn_ev_session_apply_patch vždy vyhrává.
coalesce(fc.required_soc, av.default_target_soc_pct),
coalesce(
fc.expected_departure,
case
when av.default_deadline_hour is not null then
(
(timezone('Europe/Prague', now()))::date + interval '1 day'
+ make_interval(hours => av.default_deadline_hour)
)::timestamp at time zone 'Europe/Prague'
end
)
from ems.asset_ev_charger ac
left join lateral (
select v.id, v.default_target_soc_pct, v.default_deadline_hour,
v.target_soc_forecast_enabled
from ems.asset_vehicle v
where v.default_charger_id = ac.id
and v.site_id = ac.site_id
and v.active = true
order by v.id
limit 1
) av on true
left join lateral (
select dep.expected_departure,
ems.fn_ev_required_soc(av.id, dep.expected_departure) as required_soc
from (
select ems.fn_ev_next_departure(av.id, now()) as expected_departure
) dep
where av.target_soc_forecast_enabled
and dep.expected_departure is not null
) fc on true
where ac.id = p_charger_id
and ac.site_id = p_site_id
on conflict (charger_id) where session_end is null do nothing;
return jsonb_build_object('action', 'arrival');
end if;
if p_prev_status is distinct from 'available' and p_new_status = 'available' then
update ems.ev_session es
set session_end = now()
where es.charger_id = p_charger_id
and es.session_end is null;
return jsonb_build_object('action', 'departure');
end if;
return jsonb_build_object('action', 'none');
end;
$fn$;
comment on function ems.fn_ev_session_transition(int, int, text, text, timestamptz) is
'Detekce příjezdu/odjezdu EV po změně statusu nabíječky (telemetry_collector).';