v2: měkký EV cíl — oportunistické nabíjení nad target (+ strop energie)
All checks were successful
CI and deploy / migration-check (push) Successful in 44s
CI and deploy / deploy (push) Has been skipped

Uživatel: 'potřebuju do X % (tvrdý), ale klidně dobij na 100 % když je to
skoro zadarmo; při záporných cenách radši do auta než nechat na střeše'.

- V094 asset_vehicle.opportunistic_value_czk_kwh (default 1.0; = hodnota
  ušetřeného BUDOUCÍHO nabíjení — auto neumí zpět, žádný noční prodej)
- R__039 ev_sessions: + headroom_wh ((100−target) % kapacity) + opp value;
  session se nenuluje po dosažení targetu, dokud má headroom
- solver_v2: dekompozice Σ(EV) == needed − unmet + opp, opp ∈ [0, headroom],
  odměna opp×value; zároveň FIX latentního bugu — při buy<0 chyběl strop
  celkové energie do auta (model mohl pumpovat bez limitu)
- 3 testy (neg ceny sají nad target po strop; běžné ceny ne; cap při opp=0);
  eval fixtures beze změny (sessions null)

Víkend (pátek nízký tvrdý cíl + víkendová negativa → samo doplní do 100 %)
vyplývá z mechanismu, žádná speciální logika.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-12 12:17:59 +02:00
parent 2325bbcbd6
commit 85dff7f13e
6 changed files with 117 additions and 4 deletions

View File

@@ -191,7 +191,11 @@ begin
- es.soc_at_connect_pct::numeric) / 100.0
* (v.battery_capacity_kwh * 1000)
- coalesce(es.energy_delivered_wh, 0)::numeric
) <= 0 then null::jsonb
) <= 0
and (
coalesce(v.opportunistic_value_czk_kwh, 0) <= 0
or (100 - coalesce(es.target_soc_pct, v.default_target_soc_pct)) <= 0
) then null::jsonb
else jsonb_build_object(
'target_deadline', es.target_deadline,
'energy_needed_wh', greatest(
@@ -200,7 +204,16 @@ begin
- es.soc_at_connect_pct::numeric) / 100.0
* (v.battery_capacity_kwh * 1000)
- coalesce(es.energy_delivered_wh, 0)::numeric
)
),
'headroom_wh', case
when coalesce(v.opportunistic_value_czk_kwh, 0) > 0 then greatest(
0,
(100 - coalesce(es.target_soc_pct, v.default_target_soc_pct))::numeric
/ 100.0 * (v.battery_capacity_kwh * 1000)
)
else 0
end,
'opportunistic_value_czk_kwh', coalesce(v.opportunistic_value_czk_kwh, 0)
)
end
from ems.ev_session es
@@ -223,7 +236,11 @@ begin
- es.soc_at_connect_pct::numeric) / 100.0
* (v.battery_capacity_kwh * 1000)
- coalesce(es.energy_delivered_wh, 0)::numeric
) <= 0 then null::jsonb
) <= 0
and (
coalesce(v.opportunistic_value_czk_kwh, 0) <= 0
or (100 - coalesce(es.target_soc_pct, v.default_target_soc_pct)) <= 0
) then null::jsonb
else jsonb_build_object(
'target_deadline', es.target_deadline,
'energy_needed_wh', greatest(
@@ -232,7 +249,16 @@ begin
- es.soc_at_connect_pct::numeric) / 100.0
* (v.battery_capacity_kwh * 1000)
- coalesce(es.energy_delivered_wh, 0)::numeric
)
),
'headroom_wh', case
when coalesce(v.opportunistic_value_czk_kwh, 0) > 0 then greatest(
0,
(100 - coalesce(es.target_soc_pct, v.default_target_soc_pct))::numeric
/ 100.0 * (v.battery_capacity_kwh * 1000)
)
else 0
end,
'opportunistic_value_czk_kwh', coalesce(v.opportunistic_value_czk_kwh, 0)
)
end
from ems.ev_session es