Merge branch 'worktree-agent-a288972b643cdefcc' into dev
All checks were successful
CI and deploy / migration-check (push) Successful in 22s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-06-12 19:17:01 +02:00
8 changed files with 520 additions and 109 deletions

View File

@@ -45,23 +45,14 @@ begin
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
)
-- kaskáda fn_ev_session_defaults: týdenní požadavek (ev_weekly_requirement)
-- → forecast (ev_usage_stats) → defaulty vozidla; ruční přepis přes
-- fn_ev_session_apply_patch vždy vyhrává.
(d.defaults ->> 'target_soc_pct')::double precision,
(d.defaults ->> 'deadline')::timestamptz
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
select v.id
from ems.asset_vehicle v
where v.default_charger_id = ac.id
and v.site_id = ac.site_id
@@ -69,15 +60,9 @@ begin
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
cross join lateral (
select ems.fn_ev_session_defaults(av.id, now()) as defaults
) d
where ac.id = p_charger_id
and ac.site_id = p_site_id
on conflict (charger_id) where session_end is null do nothing;
@@ -98,5 +83,5 @@ begin
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).';
comment on function ems.fn_ev_session_transition is
'Detekce příjezdu/odjezdu EV po změně statusu nabíječky (telemetry_collector); defaulty nové session z ems.fn_ev_session_defaults.';

View File

@@ -0,0 +1,103 @@
-- Defaulty nové ev_session pro vozidlo: kaskáda
-- 1) ems.ev_weekly_requirement — nejbližší budoucí výskyt enabled řádku
-- do 48 h od příjezdu (deadline_hour v den dow, Europe/Prague),
-- 2) forecast z týdenního rytmu (V089: fn_ev_next_departure +
-- fn_ev_required_soc), jen při asset_vehicle.target_soc_forecast_enabled,
-- 3) defaulty vozidla (default_target_soc_pct; deadline = příští výskyt
-- default_deadline_hour v Europe/Prague).
-- Volá fn_ev_session_transition při založení session; ruční přepis přes
-- fn_ev_session_apply_patch (Discord / UI) vždy vyhrává.
create or replace function ems.fn_ev_session_defaults(
p_vehicle_id int,
p_arrival timestamptz
)
returns jsonb
language plpgsql
stable
as $fn$
declare
v_vehicle record;
v_weekly record;
v_forecast_departure timestamptz;
v_deadline timestamptz;
begin
select av.default_target_soc_pct, av.default_deadline_hour,
av.target_soc_forecast_enabled
into v_vehicle
from ems.asset_vehicle av
where av.id = p_vehicle_id;
if not found then
return jsonb_build_object(
'target_soc_pct', null, 'deadline', null, 'source', 'none'
);
end if;
-- 1) týdenní požadavek: nejbližší budoucí výskyt do 48 h (Europe/Prague)
select wr.target_soc_pct, occ.deadline
into v_weekly
from generate_series(0, 2) as offs
cross join lateral (
select ((p_arrival at time zone 'Europe/Prague')::date + offs) as d
) day
join ems.ev_weekly_requirement wr
on wr.vehicle_id = p_vehicle_id
and wr.enabled
and wr.dow = extract(isodow from day.d)::int - 1
cross join lateral (
select (day.d::timestamp + make_interval(hours => wr.deadline_hour))
at time zone 'Europe/Prague' as deadline
) occ
where occ.deadline > p_arrival
and occ.deadline <= p_arrival + interval '48 hours'
order by occ.deadline
limit 1;
if v_weekly.deadline is not null then
return jsonb_build_object(
'target_soc_pct', v_weekly.target_soc_pct,
'deadline', v_weekly.deadline,
'source', 'weekly'
);
end if;
-- 2) forecast z týdenního rytmu (chování shodné s dřívějším
-- fn_ev_session_transition: deadline = typický odjezd; target P80,
-- při málo datech default target)
if v_vehicle.target_soc_forecast_enabled then
v_forecast_departure := ems.fn_ev_next_departure(p_vehicle_id, p_arrival);
if v_forecast_departure is not null then
return jsonb_build_object(
'target_soc_pct', coalesce(
ems.fn_ev_required_soc(p_vehicle_id, v_forecast_departure),
v_vehicle.default_target_soc_pct
),
'deadline', v_forecast_departure,
'source', 'forecast'
);
end if;
end if;
-- 3) defaulty vozidla: deadline = příští výskyt default_deadline_hour
v_deadline := (
(p_arrival at time zone 'Europe/Prague')::date::timestamp
+ make_interval(hours => v_vehicle.default_deadline_hour)
) at time zone 'Europe/Prague';
if v_deadline <= p_arrival then
v_deadline := (
((p_arrival at time zone 'Europe/Prague')::date + 1)::timestamp
+ make_interval(hours => v_vehicle.default_deadline_hour)
) at time zone 'Europe/Prague';
end if;
return jsonb_build_object(
'target_soc_pct', v_vehicle.default_target_soc_pct,
'deadline', v_deadline,
'source', 'default'
);
end;
$fn$;
comment on function ems.fn_ev_session_defaults is
'Target SoC + deadline pro novou ev_session: jsonb {target_soc_pct, deadline, source}. Kaskáda ev_weekly_requirement (výskyt do 48 h, Europe/Prague) → forecast (target_soc_forecast_enabled) → defaulty vozidla (deadline = příští výskyt default_deadline_hour). p_vehicle_id null/neznámé → null hodnoty.';