V098: týdenní požadavky EV (ev_weekly_requirement) + fn_ev_session_defaults
Tabulka ems.ev_weekly_requirement (dow 0=pondělí..6, target_soc_pct,
deadline_hour Europe/Prague, enabled; unique per vozidlo+den) se seedem
tesla-my pondělí 07:00 → 90 %. Nová ems.fn_ev_session_defaults(vehicle,
arrival) → jsonb {target_soc_pct, deadline, source}: kaskáda týdenní
požadavek (výskyt do 48 h) → forecast z ev_usage_stats
(target_soc_forecast_enabled, chování V089 beze změny) → defaulty vozidla
(deadline = příští výskyt default_deadline_hour). fn_ev_session_transition
ji volá při založení session (SQL-first, Python beze změny); comment
funkce sjednocen na styl bez parametrů.
Docs: ev-charging.md sekce Týdenní požadavky + kaskáda, CLAUDE.md seznam fn.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
36
db/migration/V098__ev_weekly_requirement.sql
Normal file
36
db/migration/V098__ev_weekly_requirement.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
-- Týdenní požadavky na EV: explicitní cíl (target SoC) + deadline pro den
|
||||
-- v týdnu (0 = pondělí .. 6 = neděle; čas Europe/Prague). Při příjezdu vozidla
|
||||
-- je čte ems.fn_ev_session_defaults (R__099): nejbližší budoucí výskyt do 48 h
|
||||
-- má přednost před forecastem z ev_usage_stats i před defaulty vozidla.
|
||||
-- Ruční přepis (Discord výběry / UI → fn_ev_session_apply_patch) vždy vyhrává.
|
||||
|
||||
create table ems.ev_weekly_requirement (
|
||||
id serial primary key,
|
||||
vehicle_id int not null references ems.asset_vehicle (id),
|
||||
dow int not null check (dow between 0 and 6),
|
||||
target_soc_pct numeric(5, 2) not null check (target_soc_pct between 0 and 100),
|
||||
deadline_hour int not null check (deadline_hour between 0 and 23),
|
||||
enabled boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
unique (vehicle_id, dow)
|
||||
);
|
||||
|
||||
comment on table ems.ev_weekly_requirement is
|
||||
'Týdenní požadavek na nabití vozidla: v den dow (0 = pondělí .. 6 = neděle) v deadline_hour (Europe/Prague) má mít vozidlo target_soc_pct. Vstup ems.fn_ev_session_defaults při zakládání ev_session (přednost před forecastem i defaulty).';
|
||||
comment on column ems.ev_weekly_requirement.vehicle_id is
|
||||
'Vozidlo (ems.asset_vehicle), max. 1 řádek na den v týdnu.';
|
||||
comment on column ems.ev_weekly_requirement.dow is
|
||||
'Den v týdnu DEADLINE: 0 = pondělí .. 6 = neděle (ISO pořadí, POZOR: ne postgres extract(dow) ani ev_usage_stats, kde 0 = neděle).';
|
||||
comment on column ems.ev_weekly_requirement.target_soc_pct is
|
||||
'Cílový SoC vozidla (%) v okamžiku deadline.';
|
||||
comment on column ems.ev_weekly_requirement.deadline_hour is
|
||||
'Hodina deadline v Europe/Prague (7 = 07:00 daného dne dow).';
|
||||
comment on column ems.ev_weekly_requirement.enabled is
|
||||
'false = řádek se při výběru defaultů ignoruje (požadavek dočasně vypnut bez smazání).';
|
||||
|
||||
-- Seed: Tesla Model Y (home-01) — pondělí 07:00 nabitá na 90 % (služebka).
|
||||
insert into ems.ev_weekly_requirement (vehicle_id, dow, target_soc_pct, deadline_hour)
|
||||
select av.id, 0, 90.0, 7
|
||||
from ems.asset_vehicle av
|
||||
join ems.site s on s.id = av.site_id
|
||||
where s.code = 'home-01' and av.code = 'tesla-my';
|
||||
@@ -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.';
|
||||
|
||||
103
db/routines/R__099_fn_ev_session_defaults.sql
Normal file
103
db/routines/R__099_fn_ev_session_defaults.sql
Normal 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.';
|
||||
Reference in New Issue
Block a user