second version

This commit is contained in:
Dusan Vojacek
2026-04-03 14:23:16 +02:00
parent 897b95f728
commit 9f4126946d
105 changed files with 9738 additions and 1470 deletions

View File

@@ -26,21 +26,25 @@ Střídač Deye poskytuje přes Modbus registr `load_power_w` = celková okamži
load_power_w (Deye) = bazální_spotřeba + EV_nabíjení + TUV + ostatní flexibilní
```
### Odvození bazální spotřeby
### Výpočet bazální spotřeby
Bazální výkon je to, co zůstane po odečtení řízených zátěží od celkové spotřeby ze střídače:
```
bazální_w = load_power_w - sum(flexibilní zařízení aktuální výkon)
bazální_w = load_power_w - ev_power_w - heat_pump_power_w
```
V praxi:
```
bazální_w = load_power_w
- ev_charger_1_power_w
- ev_charger_2_power_w
- tuv_power_w (pokud je měřitelná zvlášť)
```
- **`load_power_w`** telemetrie střídače (`telemetry_inverter`), 1min.
- **`ev_power_w`** v agregaci statistik se bere průměr výkonu ze všech nabíječek site v časovém okně ±30 s kolem měření střídače (`telemetry_ev_charger`).
- **`heat_pump_power_w`** stejně z `telemetry_heat_pump` (TČ včetně kompresoru; slouží jako proxy za měřitelný příkon TČ).
> **Předpoklad:** TUV výkon není přímo měřen, pouze víme že je ON/OFF (přes Loxone). Pokud je ON, odečítáme `asset_flexible_device.max_power_w`. Toto je zjednodušení lze zpřesnit později podružným měřením.
**Ukládání profilu:** tabulka `consumption_baseline_stats` (unikátní `(site_id, day_of_week, hour_of_day)`). Plní ji **`ems.fn_update_baseline_stats(site_id, lookback_days)`** z minutové telemetrie za posledních *N* dní; agregace po DOW a hodině (Europe/Prague). Do řádku se zapisuje jen bucket s alespoň 4 vzorky (minuty). **EMA 70/30** při `ON CONFLICT`: nový batch má váhu 30 %, existující průměr 70 % (postupné zpřesňování). Denní job v backendu: **00:30** (`fn_update_baseline_stats(..., 30)`).
**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`.
> **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`.
---
@@ -52,43 +56,16 @@ Celková spotřeba je součástí `telemetry_inverter.load_power_w` (1min zázna
EV nabíječky mají vlastní tabulku `telemetry_ev_charger` s přesným výkonem.
### Agregovaná spotřeba pro plánování
Tabulka `consumption_baseline_interval` ukládá 15min průměry bazální spotřeby:
- `data_type = 'actual'` historická skutečnost (zpětně dopočítáno z telemetrie)
- `data_type = 'forecast'` predikce pro plánování
- **`consumption_baseline_stats`** primární vstup solveru: hodinový profil (DOW + hodina) z telemetrie, EMA, viz výše.
- **`consumption_baseline_interval`** volitelné 15min řady (`actual` / `forecast`) pro jiné účely; solver z ní bazál nečte.
---
## Predikce bazální spotřeby
### Metoda: historický průměr + denní profil
### Metoda: DOW + hodina + EMA
Jednoduchý model pro začátek:
```python
def forecast_baseline_consumption(site_id: int, target_date: date):
"""
Predikce bazální spotřeby na základě průměru posledních N podobných dní.
Podobnost: stejný den v týdnu, přibližně stejná roční doba.
"""
lookback_weeks = 4
day_of_week = target_date.weekday()
# Stáhnout historické bazální hodnoty pro stejné dny v týdnu
historical = db.query("""
SELECT interval_start, power_w
FROM consumption_baseline_interval
WHERE site_id = %s
AND data_type = 'actual'
AND EXTRACT(dow FROM interval_start) = %s
AND interval_start >= %s
ORDER BY interval_start
""", site_id, day_of_week, target_date - timedelta(weeks=lookback_weeks))
# Průměr per 15min slot
profile = aggregate_by_time_of_day(historical) # 96 hodnot (15min sloty)
return profile
```
Operativní predikce je v **`fn_get_baseline_forecast`** a v přímém dotazu v `_load_slots` na **`consumption_baseline_stats`**. Doplňkově lze z historie stavět 15min profily v `consumption_baseline_interval`, pokud je k tomu samostatný job není nutné pro běh LP.
---