FIX RYCHLOST EKONOMIKA
Some checks failed
CI and deploy / migration-check (push) Failing after 10s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-27 19:47:18 +02:00
parent c52946a4ce
commit 8114ec5e63
5 changed files with 177 additions and 12 deletions

View File

@@ -8,7 +8,7 @@
- **SQL-first:** horizont a sloty z DB funkcí (`fn_planning_horizon_end`, `fn_load_planning_slots_full`, …); viz **`CLAUDE.md`** → sekce *SQL-first a read-model*.
- **Dynamický horizont (jen OTE):** konec plánu z **`ems.fn_planning_horizon_end(site_id, horizon_start)`** (výchozí strop **36 h**, minimum pro rolling **1 h** obojí jako defaultní argumenty v SQL, úprava přes repeatable migraci). Pomocná `ems.fn_last_effective_ote` vrací konec posledního OTE intervalu. Rolling replan při `NULL` přeskočí; denní plán použije krátký (1 h) fallback v Pythonu. Sloty v solveru jsou bez predikovaných cen v rámci tohoto horizontu.
- **Terminal SoC shadow price:** v objective je člen `(avg_buy_prvních_24h × 0,9 / 1000) × soc[T1]` (Kč), aby konec horizontu nekončil zbytečně vyprázdněnou baterií (receding horizon).
- **Terminal SoC shadow price:** v objective je člen `(avg_buy_prvních_24h × planner_terminal_soc_value_factor / 1000) × soc[T1]` (Kč), kde faktor je **`ems.asset_battery.planner_terminal_soc_value_factor`** přes **`ems.fn_planning_site_context`** (default v DB **0.9**); viz sekci *Tuning pro malé baterie* níže. Účel: konec horizontu nemusí končit zbytečně vyprázdněnou baterií (receding horizon).
- **Runtime guard v exportu setpointů (legacy):**
- při `AUTO` + `is_predicted_price=true` se na exportní vrstvě vynutí PASSIVE/no-export chování (u nových plánů by `is_predicted_price` v horizontu nemělo nastat).
- **Ekonomika baterie:**
@@ -473,13 +473,31 @@ COMMENT ON COLUMN ems.planning_interval.pv_a_curtailed_w IS
## Tuning pro malé baterie (např. BA81)
### Terminal SoC shadow price (kritický parametr)
V účelové funkci LP je člen **„terminal SoC shadow price“**: energie ponechaná v baterii na konci horizontu je oceněná jako záporný příspěvek k nákladům (solver má motivaci držet část SoC, pokud se to ekonomicky vyplatí oproti okamžitému vývozu / nákupu).
**Výpočet (zjednodušeně):**
`terminal_soc_kcz_per_wh = (průměr buy v prvních 24 h slotů) × planner_terminal_soc_value_factor / 1000`
a v objective se přičítá `- terminal_soc_kcz_per_wh × soc[T1]` (viz `solve_dispatch` v `backend/services/planning_engine.py`).
**Kde se bere faktor (jediný kanonický zdroj):**
1. Sloupec **`ems.asset_battery.planner_terminal_soc_value_factor`** (`NOT NULL`, default **0.9** — migrace **V062**, idempotentní upevnění **V069**).
2. Hodnota se do solveru dostává výhradně přes **`ems.fn_planning_site_context(site_id)`** → pole `battery.planner_terminal_soc_value_factor` v JSONu.
3. Backend v **`_load_site_context()`** mapuje JSON na `SimpleNamespace` a **`solve_dispatch()` už nemá žádný skrytý fallback z kódu** — chybí-li klíč v JSONu, je to chyba konfigurace / nasazení.
> **Historická chyba (opraveno):** dříve `fn_planning_site_context` sloupec z tabulky **nepropisoval** do `battery` JSONu a Python atribut **vůbec nenačítal**, takže se v praxi používala **pevná 0.9** z kódu bez ohledu na DB. To umělo zcela převrátit chování (např. BA81 s **0.2** v tabulce se chovalo jako **0.9**). Po opravě musí projít **repeatable** `R__039_fn_planning_site_context.sql` i backend.
### Doporučené hodnoty
Pokud solver „šetří baterku“ a raději importuje ze sítě (kvůli terminal SoC shadow price), lze per baterii upravit váhu této kotvy:
- `ems.asset_battery.planner_terminal_soc_value_factor`
- `0.0` = žádná motivace držet SoC na konci horizontu (agresivnější arbitráž / vybití)
- `0.9` = default (konzervativnější držení energie)
- **`0.0`** = žádná motivace držet SoC na konci horizontu (agresivnější arbitráž / vybití)
- **`0.9`** = výchozí default v DB (konzervativnější držení energie)
Pro BA81 typicky dává smysl menší hodnota (např. 00.3), aby solver klidně „vylil“ baterii do sítě při kladné `sell_price`
Pro BA81 typicky dává smysl menší hodnota (např. **00.3**), aby solver klidně „vylil“ baterii do sítě při kladné `sell_price`
a nechal si kapacitu na nabití v oknech záporných cen.
## Konfigurace (env proměnné)