third version before modbus cleanup
This commit is contained in:
@@ -110,8 +110,8 @@ CREATE TABLE asset_battery (
|
||||
inverter_id INT REFERENCES asset_inverter(id),
|
||||
code TEXT NOT NULL,
|
||||
usable_capacity_wh INT NOT NULL, -- 64000
|
||||
min_soc_percent NUMERIC(5,2) DEFAULT 10,
|
||||
reserve_soc_percent NUMERIC(5,2) DEFAULT 20, -- rezerva pro výpadek
|
||||
min_soc_percent NUMERIC(5,2) DEFAULT 10, -- absolutní podlaha LP
|
||||
reserve_soc_percent NUMERIC(5,2) DEFAULT 20, -- ekonomická podlaha (export/arbitráž)
|
||||
max_soc_percent NUMERIC(5,2) DEFAULT 95,
|
||||
charge_efficiency NUMERIC(5,4) DEFAULT 0.95,
|
||||
discharge_efficiency NUMERIC(5,4) DEFAULT 0.95,
|
||||
@@ -362,10 +362,15 @@ CREATE TABLE planning_interval (
|
||||
expected_cost_czk NUMERIC(10,4),
|
||||
effective_buy_price NUMERIC(10,6),
|
||||
effective_sell_price NUMERIC(10,6),
|
||||
-- + sloupce z migrací (curtailment, EV1/2, predicted price, vstupy solveru):
|
||||
-- load_baseline_w, pv_a_forecast_raw_w, pv_b_forecast_raw_w,
|
||||
-- pv_a_forecast_solver_w, pv_b_forecast_solver_w
|
||||
PRIMARY KEY (run_id, interval_start)
|
||||
);
|
||||
```
|
||||
|
||||
Tabulka `baseline_load_forecast_accuracy` (migrace V027+) ukládá zpětně plánovaný bazál vs. skutečný bazál z auditu; plní `fn_fill_baseline_load_forecast_accuracy` po `fn_fill_audit_interval`.
|
||||
|
||||
---
|
||||
|
||||
## Audit
|
||||
|
||||
@@ -42,7 +42,7 @@ bazální_w = load_power_w - ev_power_w - heat_pump_power_w
|
||||
|
||||
**Predikce do horizontu:** **`ems.fn_get_baseline_forecast(site_id, from, to)`** generuje 15min sloty (`generate_series`), pro každý slot najde řádek podle DOW+hodiny v Praze. **`forecast_w`** = uložený průměr; **`confidence_w`** = konzervativní odhad `avg + 0.5 * COALESCE(stddev, 100)`. Pokud pro slot neexistuje statistika, fallback **`forecast_w = 500` W** (málo nebo žádná historie; prakticky odpovídá situaci před ~4 týdny kvalitních dat v jednotlivých hodinách). Směrodatná odchylka je v DB k dispozici pro budoucí použití v solveru (fáze 2).
|
||||
|
||||
**Solver (`planning_engine._load_slots`):** pro každý 15min interval efektivní ceny bere **`avg_power_w` z `consumption_baseline_stats`** podle DOW+hodiny slotu, jinak **500 W** – nečte `consumption_baseline_interval`.
|
||||
**Solver (`planning_engine._load_slots`):** pro každý 15min interval efektivní ceny bere **`avg_power_w` z `consumption_baseline_stats`** podle DOW+hodiny slotu, jinak **500 W** – nečte `consumption_baseline_interval`. Stejná hodnota se ukládá do **`planning_interval.load_baseline_w`** při každém běhu plánovače (přehled v UI / PostgREST). Odchylka vs. skutečnost: tabulka **`baseline_load_forecast_accuracy`**, plněno po auditu.
|
||||
|
||||
> **Poznámka:** TUV jako samostatný odečet zůstává otevřený bod, pokud není měřen zvlášť; aktuálně je TČ zahrnut v `heat_pump_power_w`.
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ Deye má 6 časových bloků. EMS přepisuje **bloky 1–2** při každém `cont
|
||||
|------|---------------------------|-------------|------|---------|-------------|
|
||||
| 1 | **`current_slot_hhmm()`** – začátek **probíhajícího** 15min slotu | `planning_interval` pro **aktuální** slot (`_fetch_plan_row_for_slot_offset(..., 0)`) | PASSIVE / SELL / CHARGE dle `_deye_tou_params` | viz tabulka níže | viz tabulka níže |
|
||||
| 2 | **`next_slot_hhmm()`** – začátek **následujícího** 15min slotu | `planning_interval` pro **další** slot (`_fetch_plan_row_for_slot_offset(..., 1)`) | Přechod na další čtvrthodinu | viz tabulka níže | viz tabulka níže |
|
||||
| 3–6 | 23:59 | — | Neaktivní (rezerva) | `reserve_soc` (DB) | NE |
|
||||
| 3–6 | **23:55** (2355) | — | Neaktivní (rezerva); ne 23:59 — firmware Deye často 2359 neuloží → verify mismatch | `reserve_soc` (DB) | NE |
|
||||
|
||||
**Registry 108 / 109 / 142 / 178 / 143** odpovídají **aktuálnímu** plánu (okamžitý výstup; `setpoints_now` v `write_inverter_setpoints`). TOU řádky 1–2 doplňují stejnou logiku pro časové segmenty (`_deye_tou_params`).
|
||||
|
||||
|
||||
@@ -13,14 +13,19 @@
|
||||
- **Runtime guard v exportu setpointů:**
|
||||
- při `AUTO` + `is_predicted_price=true` se na exportní vrstvě vynutí PASSIVE/no-export chování.
|
||||
- **Ekonomika baterie:**
|
||||
- `reserve_soc_percent` naladěn na 10 %,
|
||||
- `degradation_cost_czk_kwh` naladěn na 0.1500,
|
||||
- penalizace cyklu je v objective symetrická (`0.5*(charge+discharge)`).
|
||||
- `min_soc_percent` = tvrdá spodní mez SoC v LP (typicky 10 %),
|
||||
- `reserve_soc_percent` = ekonomická („arbitrážní“) podlaha – pod ní MILP s binární proměnnou omezuje vybíjení tak, aby export z baterie nečerpal hluboké pásmo (typicky 20 %; migrace V027 může vrátit hodnotu po V026),
|
||||
- `degradation_cost_czk_kwh` (např. 0.15) / penalizace cyklu v objective symetrická (`0.5*(charge+discharge)`).
|
||||
- **PV-aware nejistota:**
|
||||
- objective používá `pv_scarcity_factor` (0.65..1.0), odvozený z forecastu slunce,
|
||||
- při slabém slunci je plán ochotnější držet energii v baterii.
|
||||
- **SoC buffer bez hard pravidel:**
|
||||
- místo explicitních pravidel se používá ekonomická penalizace deficitu vůči bezpečnostnímu SoC cíli na konci 24h horizontu.
|
||||
- **SoC buffer:**
|
||||
- měkký cíl na konci 24h přes `_soc_security_profile` + tvrdé dvouúrovňové pravidlo výše.
|
||||
- **Dynamická ekonomická podlaha (fáze 2):**
|
||||
- `_dynamic_arb_floor_wh_series`: podle součtu FVE výkonu v dalších ~8 h (`ARB_LOOKAHEAD_SLOTS`) se `arb_floor_wh[t]` posouvá mezi `min_soc_wh` a rezervou z DB – silné očekávané slunce ji sníží (ráno / po obloze); vynutit konstantní chování lze `battery.disable_dynamic_arb_floor=True` jen pro testy / ladění.
|
||||
- **Záporná nákupní cena:**
|
||||
- horní mez `grid_import` zahrnuje `load_baseline_w` + nabíjení/EV/TČ (bez nekonečného importu).
|
||||
- **Uložené vstupy plánu** (`planning_interval`): `load_baseline_w`, `pv_*_forecast_raw_w`, `pv_*_forecast_solver_w` pro UI a audit.
|
||||
|
||||
Solver optimalizuje celý horizont (typicky 36h) najednou, čímž přirozeně zvládá:
|
||||
- pohled dopředu (ráno ví že přes poledne bude záporná cena → prodává z baterie)
|
||||
@@ -179,11 +184,11 @@ soc[0] == current_soc_wh # počáteční podmínka z telemetrie
|
||||
|
||||
### SoC limity
|
||||
```python
|
||||
soc_min_wh <= soc[t] <= soc_max_wh
|
||||
soc_min_wh <= soc[t] <= soc_max_wh # min_soc_percent z DB (např. 10 %)
|
||||
|
||||
# Rezerva pro výpadek sítě – nikdy nesahat
|
||||
soc_reserve_wh = battery.reserve_soc_percent / 100 * battery.usable_capacity_wh
|
||||
soc[t] >= soc_reserve_wh # za normálních podmínek
|
||||
# Ekonomická podlaha (reserve_soc_percent, např. 20 %): binární w_arb[t] v MILP –
|
||||
# pod touto hranicí je bd omezeno na load+EV+TČ+bc (žádné „nadbytečné“ vybíjení pro export z baterie).
|
||||
# Měkký buffer na konci 24h dál přes soc_deficit_24h.
|
||||
```
|
||||
|
||||
### Limity výkonu
|
||||
@@ -266,7 +271,7 @@ def solve_dispatch(
|
||||
batt_charge = [pulp.LpVariable(f"bc_{t}", 0, battery.max_charge_power_w) for t in range(T)]
|
||||
batt_discharge = [pulp.LpVariable(f"bd_{t}", 0, battery.max_discharge_power_w) for t in range(T)]
|
||||
soc = [pulp.LpVariable(f"soc_{t}",
|
||||
battery.reserve_soc_wh,
|
||||
battery.min_soc_wh,
|
||||
battery.soc_max_wh) for t in range(T)]
|
||||
curtail_a = [pulp.LpVariable(f"ca_{t}", 0, slots[t].pv_a_forecast_w) for t in range(T)]
|
||||
ev_charge = [pulp.LpVariable(f"ev_{t}", 0, ev_max_total_w) for t in range(T)]
|
||||
|
||||
Reference in New Issue
Block a user