gpt5.5 - odladeni dokumentace dle kodu #1

Merged
vojacekd merged 1 commits from docs-sync-with-implementation into main 2026-05-02 19:18:38 +02:00
9 changed files with 161 additions and 114 deletions

View File

@@ -41,7 +41,7 @@ Systém přebírá rozhodovací logiku od Loxone a stává se „mozkem" pl
| Vrstva | Technologie | | Vrstva | Technologie |
|---|---| |---|---|
| DB | PostgreSQL 16 + TimescaleDB | | DB | PostgreSQL 18 + TimescaleDB |
| API / BFF | PostgREST (automatické REST z DB schématu) | | API / BFF | PostgREST (automatické REST z DB schématu) |
| Backend logika | Python (FastAPI) plánovač, sběr dat, integrace | | Backend logika | Python (FastAPI) plánovač, sběr dat, integrace |
| Frontend | React + TypeScript | | Frontend | React + TypeScript |

View File

@@ -12,12 +12,12 @@
┌─────────────▼───────────────────────────────┐ ┌─────────────▼───────────────────────────────┐
│ PostgREST │ │ PostgREST │
│ Auto-REST API z PostgreSQL schématu ems │ │ Auto-REST API z PostgreSQL schématu ems │
│ Read: views, tabulky │ Read-only: views, vybrané tabulky
│ Write: insert/update přes API │ Write operace jdou přes FastAPI │
└─────────────┬───────────────────────────────┘ └─────────────┬───────────────────────────────┘
│ SQL │ SQL
┌─────────────▼───────────────────────────────┐ ┌─────────────▼───────────────────────────────┐
│ PostgreSQL 16 + TimescaleDB │ │ PostgreSQL 18 + TimescaleDB │
│ Schéma: ems │ │ Schéma: ems │
│ Funkce, views, hypertables │ │ Funkce, views, hypertables │
└─────────────┬───────────────────────────────┘ └─────────────┬───────────────────────────────┘
@@ -26,13 +26,19 @@
│ FastAPI (Python) │ │ FastAPI (Python) │
Scheduled tasks (APScheduler) │ Scheduled tasks (APScheduler) │
telemetry_collector (každých 60s) │ telemetry_collector (každých 60s) │
price_importer (13:30, 14:00, 00:05) │ heartbeat (každých 60s) │
price_importer (13:25, 13/14:12, │
│ 13/14:45, 14:00, │
│ 00:05) │
forecast_service (každé 2h, minute 05)│ forecast_service (každé 2h, minute 05)│
planning_engine (denně 15:00) │ planning_engine (denně 15:00) │
rolling_replan (každých 15min) │
control_exporter (každých 15min) │ control_exporter (každých 15min) │
audit_filler (každých 15min) │ audit_filler (každých 15min) │
forecast_accuracy (:02,:17,:32,:47) │
plan_actual_slot_guard (:05,:20,:35,:50) │ plan_actual_slot_guard (:05,:20,:35,:50) │
verify_modbus (každé 2 min) │ verify_modbus (každé 2 min) │
signal_outbound (každých 15s) │
└──────┬──────────────────────────┬────────────┘ └──────┬──────────────────────────┬────────────┘
│ Modbus TCP │ HTTP │ Modbus TCP │ HTTP
┌──────▼──────┐ ┌───────▼────────────┐ ┌──────▼──────┐ ┌───────▼────────────┐
@@ -59,7 +65,7 @@ FastAPI endpointy pro dashboard a konfiguraci preferují **jedno volání** `sel
| Komponenta | Technologie | Port | Popis | | Komponenta | Technologie | Port | Popis |
|---|---|---|---| |---|---|---|---|
| `db` | PostgreSQL 16 + TimescaleDB | 5432 | Datová vrstva | | `db` | PostgreSQL 18 + TimescaleDB | 5432 | Datová vrstva |
| `postgrest` | PostgREST 12 | 3000 | Auto-REST API | | `postgrest` | PostgREST 12 | 3000 | Auto-REST API |
| `backend` | Python 3.12 / FastAPI | 8000 | Logika, scheduled tasks | | `backend` | Python 3.12 / FastAPI | 8000 | Logika, scheduled tasks |
| `frontend` | React + Vite + TypeScript | 5173 (dev) / 80 (prod) | UI | | `frontend` | React + Vite + TypeScript | 5173 (dev) / 80 (prod) | UI |
@@ -88,7 +94,8 @@ ems-platform/
R__019_fn_fill_audit_interval.sql R__019_fn_fill_audit_interval.sql
R__073_fn_health_site_jobs_mode_bundle.sql R__073_fn_health_site_jobs_mode_bundle.sql
(historicky) R__fn_plan_day.sql primární plánování je PuLP v Pythonu (historicky) R__fn_plan_day.sql primární plánování je PuLP v Pythonu
R__fn_create_planning_run.sql R__037_fn_planning_run_commit.sql
R__063_fn_load_planning_slots_full.sql
views/ views/
R__061_vw_site_effective_price.sql R__061_vw_site_effective_price.sql
R__058_vw_latest_telemetry.sql R__058_vw_latest_telemetry.sql
@@ -114,7 +121,7 @@ ems-platform/
telemetry_collector.py telemetry_collector.py
price_importer.py price_importer.py
forecast_service.py forecast_service.py
planning_engine.py ← volá ems.fn_create_planning_run() planning_engine.py ← PuLP solver, ukládá přes ems.fn_planning_run_commit()
control_exporter.py control_exporter.py
audit_filler.py audit_filler.py
modbus/ modbus/
@@ -173,9 +180,10 @@ ems-platform/
Zařízení → Waveshare → Modbus TCP → telemetry_collector → PostgreSQL Zařízení → Waveshare → Modbus TCP → telemetry_collector → PostgreSQL
``` ```
### Denní plánování (15:00) ### Denní plánování (15:00) a rolling replan
``` ```
PostgreSQL (ceny + forecast) → fn_create_planning_run() → planning_interval PostgreSQL (ceny + forecast + telemetrie) → planning_engine (PuLP)
→ fn_planning_run_commit() → planning_interval
``` ```
### Operátorské manuální akce (UI) ### Operátorské manuální akce (UI)
@@ -200,6 +208,10 @@ Browser → PostgREST (čtení views/tabulek, filtr site_id dle výběru v UI)
Browser → FastAPI (seznam lokalit /me/sites, triggery: replanning, import cen, …) Browser → FastAPI (seznam lokalit /me/sites, triggery: replanning, import cen, …)
``` ```
PostgREST je v aktuální produkční konfiguraci určený pro čtení (`ems_anon`
`SELECT` na vybrané views/tabulky). Zápisy a operátorské akce se provádí přes
FastAPI, které používá vlastní DB connection.
--- ---
## Deployment: single-site (výchozí) ## Deployment: single-site (výchozí)

View File

@@ -22,6 +22,8 @@ CREATE TABLE site (
code TEXT UNIQUE NOT NULL, -- např. 'home-01' code TEXT UNIQUE NOT NULL, -- např. 'home-01'
name TEXT NOT NULL, name TEXT NOT NULL,
timezone TEXT NOT NULL DEFAULT 'Europe/Prague', timezone TEXT NOT NULL DEFAULT 'Europe/Prague',
latitude NUMERIC(9,6), -- Open-Meteo / pvlib
longitude NUMERIC(9,6), -- Open-Meteo / pvlib
active BOOLEAN NOT NULL DEFAULT true, active BOOLEAN NOT NULL DEFAULT true,
notes TEXT, notes TEXT,
created_at TIMESTAMPTZ DEFAULT now() created_at TIMESTAMPTZ DEFAULT now()
@@ -39,6 +41,7 @@ CREATE TABLE site_endpoint (
host TEXT NOT NULL, host TEXT NOT NULL,
port INT, port INT,
protocol TEXT, -- 'modbus_tcp', 'http', 'https' protocol TEXT, -- 'modbus_tcp', 'http', 'https'
unit_id INT, -- Modbus Unit ID pro modbus_tcp
auth_reference TEXT, -- odkaz na secret / env proměnnou auth_reference TEXT, -- odkaz na secret / env proměnnou
enabled BOOLEAN DEFAULT true, enabled BOOLEAN DEFAULT true,
notes TEXT notes TEXT
@@ -119,6 +122,11 @@ CREATE TABLE asset_battery (
charge_efficiency NUMERIC(5,4) DEFAULT 0.95, charge_efficiency NUMERIC(5,4) DEFAULT 0.95,
discharge_efficiency NUMERIC(5,4) DEFAULT 0.95, discharge_efficiency NUMERIC(5,4) DEFAULT 0.95,
degradation_cost_czk_kwh NUMERIC(8,4) DEFAULT 0.5 -- náklad na cyklus degradation_cost_czk_kwh NUMERIC(8,4) DEFAULT 0.5 -- náklad na cyklus
-- pozdější migrace přidávají plánovací tunables:
-- charge_slot_buffer, discharge_slot_buffer,
-- planner_max_soc_percent, planner_discharge_floor_percent,
-- planner_extreme_buy_threshold_czk_kwh,
-- planner_terminal_soc_value_factor
); );
``` ```
@@ -135,7 +143,7 @@ CREATE TABLE asset_pv_array (
code TEXT NOT NULL, code TEXT NOT NULL,
name TEXT, name TEXT,
nominal_power_wp INT NOT NULL, -- 10000 nominal_power_wp INT NOT NULL, -- 10000
azimuth_deg NUMERIC(6,2), -- 0=S, 90=Z, -90=V azimuth_deg NUMERIC(6,2), -- kompasově/pvlib: 0=N, 90=E, 180=S, 270=W
tilt_deg NUMERIC(5,2), tilt_deg NUMERIC(5,2),
module_count INT, module_count INT,
shading_factor NUMERIC(4,3) DEFAULT 1.0, shading_factor NUMERIC(4,3) DEFAULT 1.0,
@@ -169,22 +177,30 @@ CREATE TABLE asset_ev_charger (
); );
``` ```
### `asset_flexible_device` ### `asset_heat_pump`
Generická tabulka pro ostatní flexibilní spotřebiče (TUV, tepelné čerpadlo, ...). Tepelné čerpadlo / TUV. Aktuální implementace má samostatnou tabulku místo
historického generického `asset_flexible_device`.
```sql ```sql
CREATE TABLE asset_flexible_device ( CREATE TABLE asset_heat_pump (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
site_id INT REFERENCES site(id), site_id INT REFERENCES site(id),
code TEXT NOT NULL, code TEXT NOT NULL,
device_type TEXT NOT NULL, -- 'tuv', 'heat_pump', 'pool', ... manufacturer TEXT,
control_mode TEXT DEFAULT 'loxone', -- jak se řídí model TEXT,
max_power_w INT, endpoint_id INT REFERENCES site_endpoint(id),
min_power_w INT DEFAULT 0, rated_heating_power_w INT NOT NULL,
interruptible BOOLEAN DEFAULT true, cop_rated NUMERIC(4,2),
schedulable BOOLEAN DEFAULT true, cop_temp_reference_c NUMERIC(5,2),
priority INT DEFAULT 50, -- 0=nejvyšší priorita min_run_duration_min INT NOT NULL DEFAULT 30,
notes TEXT min_stop_duration_min INT NOT NULL DEFAULT 15,
tuv_tank_volume_l INT,
tuv_min_temp_c NUMERIC(5,2) NOT NULL DEFAULT 45,
tuv_max_temp_c NUMERIC(5,2) NOT NULL DEFAULT 60,
tuv_target_temp_c NUMERIC(5,2) NOT NULL DEFAULT 55,
tuv_temp_sensor_ref TEXT,
schedulable BOOLEAN NOT NULL DEFAULT true,
notes TEXT
); );
``` ```
@@ -424,7 +440,9 @@ CREATE TABLE consumption_baseline_interval (
``` ```
### Flexibilní spotřebiče ### Flexibilní spotřebiče
Flexibilní spotřeba se neukládá souhrnně odvozuje se ze součtu `telemetry_ev_charger` + stavů `asset_flexible_device` per interval. Plánovaná flexibilní spotřeba je součástí `planning_interval`. Flexibilní spotřeba se neukládá souhrnně odvozuje se ze součtu
`telemetry_ev_charger` + `telemetry_heat_pump` / plánovaných setpointů.
Plánovaná flexibilní spotřeba je součástí `planning_interval`.
--- ---

View File

@@ -116,7 +116,8 @@ Operativní predikce je v **`fn_get_baseline_forecast`** a v přímém dotazu v
**Telemetrie:** **Telemetrie:**
- Stav ON/OFF (čteme z Loxone HTTP výstupu nebo Virtual Output stavu) - Stav ON/OFF (čteme z Loxone HTTP výstupu nebo Virtual Output stavu)
- Teplota zásobníku (pokud je čidlo v Loxone doporučeno) - Teplota zásobníku (pokud je čidlo v Loxone doporučeno)
- Aktuální výkon: není přímo měřen, používáme `max_power_w` z `asset_flexible_device` - Aktuální výkon: zatím není reálně čten z TČ; placeholder telemetrie a plánování
používají parametry z `asset_heat_pump`, hlavně `rated_heating_power_w`
**Plánování:** **Plánování:**
- TUV se ohřívá v době přebytku FVE nebo levného spotu - TUV se ohřívá v době přebytku FVE nebo levného spotu

View File

@@ -5,8 +5,8 @@
- Čte aktivní plán z DB pro daný 15min interval - Čte aktivní plán z DB pro daný 15min interval
- Zkontroluje override záznamy - Zkontroluje override záznamy
- Zapíše setpointy do Deye přes Modbus TCP - Zapíše setpointy do Deye přes Modbus TCP
- Zapíše setpointy EV nabíječek přes Modbus TCP - EV nabíječky a tepelné čerpadlo zatím pouze vyhodnotí a zaloguje; konkrétní
- Zapíše setpointy tepelného čerpadla přes Modbus TCP Modbus registry jsou TODO
- Odešle potvrzovací setpointy do Loxone přes HTTP (Loxone jako exekutor fallback logiky) - Odešle potvrzovací setpointy do Loxone přes HTTP (Loxone jako exekutor fallback logiky)
- Loguje každý write pro audit - Loguje každý write pro audit
@@ -19,10 +19,10 @@ DB (planning_interval + site_override)
control_exporter.py (každých 15min nebo on-demand) control_exporter.py (každých 15min nebo on-demand)
├── Modbus write → Deye (baterie, grid limit) ├── Modbus write → Deye (baterie, grid limit)
├── Modbus write → Teltonika EV nabíječka 1 ├── TODO/log → Teltonika EV nabíječka 1
├── Modbus write → Teltonika EV nabíječka 2 ├── TODO/log → Teltonika EV nabíječka 2
├── Modbus write → Samsung TČ ├── TODO/log → Samsung TČ
└── HTTP POST → Loxone Virtual Inputs (informační setpointy) └── HTTP GET → Loxone Virtual Inputs (informační setpointy)
``` ```
**Loxone role:** Loxone dostává setpointy jako informaci a jako fallback ochranu. **Loxone role:** Loxone dostává setpointy jako informaci a jako fallback ochranu.
@@ -34,7 +34,7 @@ Rozhodovací logika je v EMS, ne v Loxone.
| Trigger | Čas | Popis | | Trigger | Čas | Popis |
|---|---|---| |---|---|---|
| Scheduled | každých 15min (xx:00, xx:15, xx:30, xx:45) | Standardní export na začátku intervalu | | Scheduled | každých 15min (`xx:14`, `xx:29`, `xx:44`, `xx:59`) | Export těsně před dalším slotem |
| On-demand | po vytvoření nového plánu | Okamžitý export pokud plán překrývá aktuální čas | | On-demand | po vytvoření nového plánu | Okamžitý export pokud plán překrývá aktuální čas |
| On-demand | po vytvoření override | Okamžitá aplikace přepisu | | On-demand | po vytvoření override | Okamžitá aplikace přepisu |
@@ -51,6 +51,8 @@ Ověření: logy backendu kolem pokusu **nebo** `select id,status,created_at fro
## Logika exportu ## Logika exportu
```python ```python
# Zjednodušený historický náčrt. Aktuální implementace používá
# ems.fn_planning_interval_at_offset(), ControlSetpoints a Deye journal.
async def export_setpoints_for_interval(site_id: int, interval_start: datetime, db): async def export_setpoints_for_interval(site_id: int, interval_start: datetime, db):
""" """
Načte plánované setpointy pro daný interval, aplikuje overrides Načte plánované setpointy pro daný interval, aplikuje overrides
@@ -84,14 +86,11 @@ async def export_setpoints_for_interval(site_id: int, interval_start: datetime,
setpoints = apply_overrides(plan, overrides) setpoints = apply_overrides(plan, overrides)
# 3. Zapsat do zařízení (paralelně) # 3. Zapsat Deye, zalogovat EV/TČ TODO, poslat Loxone
await asyncio.gather( await write_inverter_setpoints(site_id, setpoints, db)
write_inverter_setpoints(site_id, setpoints, db), await write_ev_setpoints(site_id, setpoints, db) # TODO registry
write_ev_charger_setpoints(site_id, setpoints, db), await write_heat_pump_setpoint(site_id, setpoints, db) # TODO registry
write_heat_pump_setpoints(site_id, setpoints, db), await send_loxone_setpoints(site_id, setpoints, mode, db)
write_loxone_setpoints(site_id, setpoints, db),
return_exceptions=True
)
def apply_overrides(plan, overrides) -> Setpoints: def apply_overrides(plan, overrides) -> Setpoints:
@@ -153,7 +152,7 @@ bits 01). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
| **CHARGE** | `battery_w` > 0 **a** `grid_setpoint_w` > 0 | | **CHARGE** | `battery_w` > 0 **a** `grid_setpoint_w` > 0 |
| **PASSIVE (ZERO)** | vše ostatní — reg. **108/109** dle `_deye_zero_export_amps_for_passive` (viz `operating-modes.md`) | | **PASSIVE (ZERO)** | vše ostatní — reg. **108/109** dle `_deye_zero_export_amps_for_passive` (viz `operating-modes.md`) |
**PASSIVE** (AUTO, ZERO): výchozí **108** i **109** = maximum z DB; u exportu bez vybíjení **108 = 0**, u importu bez nabíjení **109 = 0** (`_deye_zero_export_amps_for_passive`). **TOU** z plánu. Reg. **142** = `deye_zero_export_mode`. Reg. **145** (solar sell): v kódu vždy **1** — význam přepínače a rozdíl vůči neřízeným FVE polím je v [`operating-modes.md`](operating-modes.md) (sekce *ZERO a zakázaný export*). **PASSIVE** (AUTO, ZERO): výchozí **108** i **109** = maximum z DB; u exportu bez vybíjení **108 = 0**, u importu bez nabíjení **109 = 0** (`_deye_zero_export_amps_for_passive`). **TOU** z plánu. Reg. **142** = `deye_zero_export_mode`. Reg. **145** (solar sell): **0** při `export_ban` mimo SELL, jinak **1** — význam přepínače a rozdíl vůči neřízeným FVE polím je v [`operating-modes.md`](operating-modes.md) (sekce *ZERO a zakázaný export*).
**SELF_SUSTAIN** zůstává **PASSIVE** v `get_deye_mode`; **108/109** jsou vždy **max z DB** (bez variant ZERO). Rozdíl je **`self_sustain_local_use=True`**: **TOU SOC** = **`min_soc_percent`**, `battery_w=None`. **SELF_SUSTAIN** zůstává **PASSIVE** v `get_deye_mode`; **108/109** jsou vždy **max z DB** (bez variant ZERO). Rozdíl je **`self_sustain_local_use=True`**: **TOU SOC** = **`min_soc_percent`**, `battery_w=None`.
@@ -165,7 +164,7 @@ bits 01). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
| **109** (discharge A) | **0** | max / **0** (import, držet bat.) | **max z DB** | dle varianty | | **109** (discharge A) | **0** | max / **0** (import, držet bat.) | **max z DB** | dle varianty |
| **142** (limit control) | `deye_zero_export_mode` | `deye_zero_export_mode` | **0** (selling first) | `deye_zero_export_mode` | | **142** (limit control) | `deye_zero_export_mode` | `deye_zero_export_mode` | **0** (selling first) | `deye_zero_export_mode` |
| **143** (export cap) | max z DB | max z DB | `min(max_site, max(200, \|grid_setpoint_w\|))` | max z DB | | **143** (export cap) | max z DB | max z DB | `min(max_site, max(200, \|grid_setpoint_w\|))` | max z DB |
| **145** (solar sell) | 1 | 1 | 1 | 1 | | **145** (solar sell) | 1 / 0 při `export_ban` | 1 / 0 při `export_ban` | 1 | 1 / 0 při `export_ban` |
| **178** (peak shaving) | 48 | 48 | **32** | 48 | | **178** (peak shaving) | 48 | 48 | **32** | 48 |
U **AUTO PASSIVE** závisí **108/109** na znaménkách plánu (viz `operating-modes.md`). **SELF_SUSTAIN** drží oba **max z DB**; **TOU SOC** ve všech PASSIVE větvích je **`min_soc_percent`** (viz `_deye_passive_tou_battery_soc_pct`). Liší se především **`battery_w`** a mapování **108/109**. U **AUTO PASSIVE** závisí **108/109** na znaménkách plánu (viz `operating-modes.md`). **SELF_SUSTAIN** drží oba **max z DB**; **TOU SOC** ve všech PASSIVE větvích je **`min_soc_percent`** (viz `_deye_passive_tou_battery_soc_pct`). Liší se především **`battery_w`** a mapování **108/109**.
@@ -184,6 +183,9 @@ Po zápisu na Modbus se hodnoty ověřují v `verify_modbus_commands` (`control_
Při přechodu **SELF_SUSTAIN → AUTO** (`run_fn_set_mode_with_discord`) se na pozadí spustí **rolling replan**, aby aktivní plán odpovídal plné optimalizaci. Viz [`modbus-command-journal.md`](modbus-command-journal.md). Při přechodu **SELF_SUSTAIN → AUTO** (`run_fn_set_mode_with_discord`) se na pozadí spustí **rolling replan**, aby aktivní plán odpovídal plné optimalizaci. Viz [`modbus-command-journal.md`](modbus-command-journal.md).
```python ```python
# Historický pseudokód. Aktuální Deye implementace používá journal
# ems.modbus_command a FC 0x10 (`write_registers`) nad registry
# 108/109/141/142/143/145/178/340 + TOU bloky.
async def write_inverter_setpoints(site_id: int, setpoints: Setpoints, db): async def write_inverter_setpoints(site_id: int, setpoints: Setpoints, db):
inverters = await db.fetch( inverters = await db.fetch(
"SELECT ai.*, se.host, se.port, se.unit_id " "SELECT ai.*, se.host, se.port, se.unit_id "
@@ -215,6 +217,10 @@ async def write_inverter_setpoints(site_id: int, setpoints: Setpoints, db):
## Zápis do Teltonika EV nabíječek (Modbus) ## Zápis do Teltonika EV nabíječek (Modbus)
Aktuální implementace registry zatím nezapisuje. Funkce `write_ev_setpoints`
načte schedulable nabíječky, spočítá proud podle `ev1/ev2_setpoint_w` a jen ho
zaloguje jako `Modbus TODO`.
```python ```python
async def write_ev_charger_setpoints(site_id: int, setpoints: Setpoints, db): async def write_ev_charger_setpoints(site_id: int, setpoints: Setpoints, db):
chargers = await db.fetch( chargers = await db.fetch(
@@ -250,6 +256,10 @@ async def write_ev_charger_setpoints(site_id: int, setpoints: Setpoints, db):
## Zápis do Samsung TČ (Modbus) ## Zápis do Samsung TČ (Modbus)
Aktuální implementace registry zatím nezapisuje. Funkce
`write_heat_pump_setpoint` načte schedulable TČ a zaloguje požadované
`heat_pump_enable` jako `Modbus TODO`.
```python ```python
async def write_heat_pump_setpoints(site_id: int, setpoints: Setpoints, db): async def write_heat_pump_setpoints(site_id: int, setpoints: Setpoints, db):
heat_pumps = await db.fetch( heat_pumps = await db.fetch(
@@ -297,20 +307,22 @@ async def write_loxone_setpoints(site_id: int, setpoints: Setpoints, db):
# Loxone Virtual HTTP Input každý setpoint = jeden HTTP GET/POST # Loxone Virtual HTTP Input každý setpoint = jeden HTTP GET/POST
# Formát: /dev/sps/io/{VirtualInputName}/{value} # Formát: /dev/sps/io/{VirtualInputName}/{value}
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
await session.get(f"{base_url}/EMS_BatterySetpoint/{setpoints.battery_setpoint_w}") await session.get(f"{base_url}/EMS_Mode/{mode.loxone_mode_value}")
await session.get(f"{base_url}/EMS_GridSetpoint/{setpoints.grid_setpoint_w or 0}") await session.get(f"{base_url}/EMS_Battery_Setpoint_W/{battery_w}")
await session.get(f"{base_url}/EMS_EVChargeTotal/{setpoints.ev_charge_power_w or 0}") await session.get(f"{base_url}/EMS_Grid_Setpoint_W/{grid_w}")
await session.get(f"{base_url}/EMS_HeatPumpEnable/{1 if setpoints.heat_pump_enabled else 0}") await session.get(f"{base_url}/EMS_EV1_Power_W/{ev1_power_w}")
await session.get(f"{base_url}/EMS_EV2_Power_W/{ev2_power_w}")
await session.get(f"{base_url}/EMS_HeatPump_Enable/{heat_pump_enable}")
``` ```
> Virtual Input jména v Loxone (`EMS_BatterySetpoint` atd.) je nutné vytvořit při konfiguraci Loxone projektu. > Virtual Input jména v Loxone (`EMS_Battery_Setpoint_W`, `EMS_Grid_Setpoint_W`
> atd.) je nutné vytvořit při konfiguraci Loxone projektu.
--- ---
## Konfigurace (env proměnné) ## Konfigurace (env proměnné)
```env ```env
CONTROL_EXPORT_LEAD_TIME_SEC=10 # kolik sekund před začátkem intervalu exportovat
CONTROL_MODBUS_TIMEOUT_SEC=5 CONTROL_MODBUS_TIMEOUT_SEC=5
LOXONE_USER=admin # nebo přes auth_reference v site_endpoint LOXONE_USER=admin # nebo přes auth_reference v site_endpoint
LOXONE_PASSWORD=secret LOXONE_PASSWORD=secret
@@ -331,9 +343,8 @@ Fallback: pokud per-site webhook není vyplněný, použije se env `DISCORD_WEBH
## Otevřené body ## Otevřené body
- [ ] Doplnit Modbus write registry Deye (charge/discharge/export limit)
- [ ] Doplnit Modbus write registry Teltonika (current limit, enable) - [ ] Doplnit Modbus write registry Teltonika (current limit, enable)
- [ ] Doplnit Modbus write registry Samsung TČ (enable, target temp) - [ ] Doplnit Modbus write registry Samsung TČ (enable, target temp)
- [ ] Loxone Virtual Input jména dohodnout a vytvořit v Loxone projektu - [ ] Loxone Virtual Input jména z tohoto dokumentu vytvořit v Loxone projektu
- [ ] Strategie rozdělení EV výkonu mezi 2 nabíječky (rovnoměrně vs dle stavu session) - [ ] Strategie rozdělení EV výkonu mezi 2 nabíječky (rovnoměrně vs dle stavu session)
- [ ] Co dělat při selhání zápisu do jednoho zařízení (rollback ostatních?) - [ ] Co dělat při selhání zápisu do jednoho zařízení (rollback ostatních?)

View File

@@ -5,7 +5,9 @@
- Stahuje meteorologická data (irradiance, teplota) pro každé FVE pole zvlášť - Stahuje meteorologická data (irradiance, teplota) pro každé FVE pole zvlášť
- Vypočítává predikovaný výkon v 15min intervalech - Vypočítává predikovaný výkon v 15min intervalech
- Ukládá výsledek per `pv_array_id` + `run_id` - Ukládá výsledek per `pv_array_id` + `run_id`
- Predikce se spouští denně a před každým plánovacím během - Predikce se spouští každé 2 hodiny v `:05` a ručně přes API. Plánovač používá
poslední dostupné uložené forecasty; forecast nespouští implicitně před každým
plánovacím během.
--- ---
@@ -13,12 +15,15 @@
| Pole | Výkon | Azimut | Sklon | Střídač | Řízení | | Pole | Výkon | Azimut | Sklon | Střídač | Řízení |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| A | 10 kWp | TBD | TBD | Deye 20kW | řídíme | | A | 10 kWp | 184° | 22° | Deye 20kW | řídíme |
| B | 10 kWp | TBD | TBD | Ongridový | autonomní, **nepredikujeme odděleně** | | B | 10 kWp | 184° | 35° | Ongridový | autonomní, predikujeme jako samostatné pole |
> **Předpoklad:** Pole B (ongridový) je zapojeno do GEN portu Deye. Jeho výkon se projeví v `pv_power_w` telemetrie jako součást celkového výkonu. Pro plánování modelujeme jen pole A. Pole B bereme jako šum / bonus který se projeví v auditu. > **Aktuální implementace:** Forecast služba počítá všechna FVE pole lokality,
> která mají vyplněný `azimuth_deg` a `tilt_deg`; plánovač pracuje odděleně s
> `pv_a_forecast_w` i `pv_b_forecast_w`.
> Azimuty a sklony je nutné doplnit při konfiguraci lokality do `asset_pv_array`. > Azimut je uložen v kompasové / pvlib konvenci: `0=N`, `90=E`, `180=S`,
> `270=W`.
--- ---
@@ -36,10 +41,9 @@
GET https://api.open-meteo.com/v1/forecast GET https://api.open-meteo.com/v1/forecast
?latitude={lat} ?latitude={lat}
&longitude={lon} &longitude={lon}
&hourly=shortwave_radiation,temperature_2m &minutely_15=direct_normal_irradiance,diffuse_radiation,shortwave_radiation,temperature_2m
&minutely_15=shortwave_radiation,temperature_2m &timezone=auto
&timezone=Europe/Prague &forecast_days=7
&forecast_days=3
``` ```
**Záložní / budoucí: Solcast** **Záložní / budoucí: Solcast**
@@ -51,34 +55,28 @@ GET https://api.open-meteo.com/v1/forecast
## Výpočet výkonu z irradiance ## Výpočet výkonu z irradiance
Jednoduchý fyzikální model (dostatečný pro plánování): Implementace používá `pvlib` a model POA irradiance `haydavies`:
```python ```python
def calculate_pv_power( poa_global = pvlib.irradiance.get_total_irradiance(
irradiance_wm2: float, # GHI ze weather service surface_tilt=tilt_deg,
temp_c: float, surface_azimuth=azimuth_deg, # 0=N, 90=E, 180=S, 270=W
nominal_power_wp: int, solar_zenith=solar_pos["apparent_zenith"],
azimuth_deg: float, solar_azimuth=solar_pos["azimuth"],
tilt_deg: float, dni=dni,
shading_factor: float = 1.0, ghi=ghi,
temp_coeff: float = -0.004 # typicky -0.4%/°C pro křemík dhi=dhi,
) -> int: dni_extra=dni_extra,
# 1. Korekce na teplotu panelu model="haydavies",
panel_temp = temp_c + 25 # zjednodušený NOCT model )["poa_global"].fillna(0).clip(lower=0)
temp_correction = 1 + temp_coeff * (panel_temp - 25)
# 2. Korekce na azimut a sklon (zjednodušená, bez přesného GHI→POA) area_m2 = nominal_power_wp / (1000.0 * 0.20)
# Přesnější model: pvlib knihovna (doporučeno pro produkci) power_w = (poa_global * area_m2 * 0.20 * shading_factor).clip(
orientation_factor = cos_angle_of_incidence(azimuth_deg, tilt_deg) lower=0,
upper=nominal_power_wp * 1.1,
# 3. Výsledný výkon )
power_w = (irradiance_wm2 / 1000) * nominal_power_wp * temp_correction * orientation_factor * shading_factor
return max(0, int(power_w))
``` ```
> **Doporučení pro implementaci:** Použít knihovnu `pvlib` (Python) pro přesný POA irradiance výpočet z GHI + azimut + sklon. Je to standardní nástroj, dobře dokumentovaný.
--- ---
## Kdo spouští predikci ## Kdo spouští predikci
@@ -107,15 +105,15 @@ def calculate_pv_power(
## Logika běhu predikce ## Logika běhu predikce
```python ```python
def run_forecast(site_id: int, horizon_days: int = 2): def run_forecast(site_id: int):
site = db.get_site(site_id) site = db.get_site(site_id)
arrays = db.get_pv_arrays(site_id, controllable=True) arrays = db.get_pv_arrays_with_azimuth_and_tilt(site_id)
for array in arrays: for array in arrays:
# 1. Stáhnout meteorologická data # 1. Stáhnout meteorologická data
weather = open_meteo_client.fetch( weather = open_meteo_client.fetch(
lat=site.lat, lon=site.lon, lat=site.lat, lon=site.lon,
start=today, end=today + horizon_days forecast_days=clamp(OPEN_METEO_FORECAST_DAYS, 2, 16)
) )
# 2. Vytvořit forecast_pv_run # 2. Vytvořit forecast_pv_run
@@ -147,8 +145,7 @@ def run_forecast(site_id: int, horizon_days: int = 2):
temp_c=slot.temperature_2m temp_c=slot.temperature_2m
)) ))
db.upsert_forecast_intervals(intervals) db.insert_forecast_intervals(intervals)
db.update_forecast_run_status(run.id, "ok")
``` ```
--- ---
@@ -204,23 +201,20 @@ Hromadně: **`ems.fn_pv_forecast_sync_reference_days(site_id, p_days_local date[
```env ```env
OPEN_METEO_API_URL=https://api.open-meteo.com/v1/forecast OPEN_METEO_API_URL=https://api.open-meteo.com/v1/forecast
OPEN_METEO_FORECAST_DAYS=7 OPEN_METEO_FORECAST_DAYS=7
FORECAST_MAX_AGE_HOURS=2 # plánovač odmítne starší predikci
FORECAST_RETRY_COUNT=3
``` ```
--- ---
## Monitoring ## Monitoring
- Alert pokud forecast pro dnešden + zítřek není k dispozici do 15:00 - Zatím není samostatný `/health/forecast` endpoint.
- Endpoint `GET /health/forecast?site_id=1&date=YYYY-MM-DD` → čerstvost a počet intervalů - Stav se kontroluje přes logy běhu `scheduled_forecast_refresh`, přes forecast API
a přes obecné health endpointy.
- Log každého běhu (délka horizontu, počet intervalů, trvání, zdroj) - Log každého běhu (délka horizontu, počet intervalů, trvání, zdroj)
--- ---
## Otevřené body ## Otevřené body
- [ ] Doplnit přesný azimut a sklon obou FVE polí při instalaci - [ ] Ověřit přesný azimut a sklon obou FVE polí proti skutečné instalaci
- [ ] Rozhodnout: pvlib pro přesnější POA výpočet vs jednoduchý model doporučujeme pvlib od začátku
- [ ] Pole B (ongridový) zda vůbec modelovat nebo ignorovat v plánu a jen sledovat v auditu
- [ ] Solcast jako alternativa v budoucnu `forecast_source` to umožňuje bez DB změn - [ ] Solcast jako alternativa v budoucnu `forecast_source` to umožňuje bez DB změn

View File

@@ -2,7 +2,9 @@
## Co modul dělá ## Co modul dělá
- Čte data ze střídače Deye, EV nabíječek Teltonika a tepelného čerpadla Samsung přes Modbus TCP - Čte data ze střídače Deye přes Modbus TCP
- EV nabíječky Teltonika a tepelné čerpadlo Samsung mají zatím placeholder
vzorky; konkrétní registry jsou TODO
- Ukládá surová měření do DB (1min granularita) - Ukládá surová měření do DB (1min granularita)
- Detekuje výpadky komunikace a loguje chyby - Detekuje výpadky komunikace a loguje chyby
- Agreguje 1min data na 15min průměry pro spotřebu, audit a plánování - Agreguje 1min data na 15min průměry pro spotřebu, audit a plánování
@@ -18,15 +20,15 @@ Samostatná Python služba. Běží jako smyčka, nezávislá na FastAPI.
| Zařízení | Interval | Důvod | | Zařízení | Interval | Důvod |
|---|---|---| |---|---|---|
| Deye střídač | 60 s | 1min granularita telemetrie | | Deye střídač | 60 s | 1min granularita telemetrie |
| Teltonika EV nabíječka 1 | 60 s | | | Teltonika EV nabíječka 1 | 60 s | zatím placeholder `available`, 0 W |
| Teltonika EV nabíječka 2 | 60 s | | | Teltonika EV nabíječka 2 | 60 s | zatím placeholder `available`, 0 W |
| Samsung tepelné čerpadlo | 60 s | | | Samsung tepelné čerpadlo | 60 s | zatím placeholder hodnoty |
### Chování při chybě ### Chování při chybě
- Chyba komunikace: záznam se nezapíše, chyba se loguje - Chyba komunikace: záznam se nezapíše, chyba se loguje
- 3 po sobě jdoucí chyby = alert (log WARNING) - Kód zatím nedrží počítadlo po sobě jdoucích chyb podle zařízení; chyby se logují
- 10 po sobě jdoucích chyb = log ERROR + pokus o reconnect při jednotlivých poll pokusech
- Data se neinterpolují chybějící minuty zůstanou prázdné (audit to pozná) - Data se neinterpolují chybějící minuty zůstanou prázdné (audit to pozná)
--- ---
@@ -43,7 +45,7 @@ Komunikace: Modbus TCP, Unit ID dle DIP přepínače na střídači (typicky 1).
| 514 (0x0202) | uint16 | Dnešní nabití baterie | Wh | `batt_charge_today_wh` | | 514 (0x0202) | uint16 | Dnešní nabití baterie | Wh | `batt_charge_today_wh` |
| 515 (0x0203) | uint16 | Dnešní vybití baterie | Wh | `batt_discharge_today_wh` | | 515 (0x0203) | uint16 | Dnešní vybití baterie | Wh | `batt_discharge_today_wh` |
| 588 (0x024C) | uint16 | Battery SoC | % | `battery_soc_percent` | | 588 (0x024C) | uint16 | Battery SoC | % | `battery_soc_percent` |
| 590 (0x024E) | int16 | Tok výkonu baterie | W | signed: **+ vybíjení, nabíjení** | | 590 (0x024E) | int16 | Tok výkonu baterie | W | signed z Deye; v DB `battery_power_w` platí **+ nabíjení, vybíjení** |
| 625 (0x0271) | int16 | Výkon sítě | W | signed: **+ import, export** | | 625 (0x0271) | int16 | Výkon sítě | W | signed: **+ import, export** |
| 653 (0x028D) | uint16 | Celková spotřeba | W | `load_power_w` | | 653 (0x028D) | uint16 | Celková spotřeba | W | `load_power_w` |
| 667 (0x029B) | int16 | Výkon GEN portu (FVE pole B) | W (signed) | `gen_port_power_w`; záporné při zpětném toku / bez výroby — **číst signed** | | 667 (0x029B) | int16 | Výkon GEN portu (FVE pole B) | W (signed) | `gen_port_power_w`; záporné při zpětném toku / bez výroby — **číst signed** |
@@ -60,6 +62,13 @@ Komunikace: Modbus TCP, Unit ID dle DIP přepínače na střídači (typicky 1).
**Zápis setpointů (plánování → Deye):** **Zápis setpointů (plánování → Deye):**
Aktuální řízení Deye je popsané v [`control.md`](control.md) a
[`modbus-registers.md`](modbus-registers.md). Nepoužívá starý `write_register`
model, ale journal `ems.modbus_command` a FC 0x10 (`write_registers`) pro
registry 108/109/141/142/143/145/178/340 + TOU bloky.
Historická orientační mapa níže neplatí jako implementační kontrakt:
| Registr (hex) | Typ | Popis | Hodnota | | Registr (hex) | Typ | Popis | Hodnota |
|---|---|---|---| |---|---|---|---|
| 0x00F3 | Write Single | Battery charge power limit | W | | 0x00F3 | Write Single | Battery charge power limit | W |
@@ -114,14 +123,15 @@ Komunikace: Modbus TCP přes Waveshare.
## Kód telemetrie (Python) ## Kód telemetrie (Python)
Implementace: `backend/services/telemetry_collector.py``poll_inverter()` používá konstanty `DEYE_REG_*` a třídu `ModbusDevice`; hlavní smyčka je `run_telemetry_loop` / `run_telemetry_loop_wrapper`. Implementace: `backend/services/telemetry_collector.py``poll_inverter()` používá konstanty `DEYE_REG_*` a sdíleného Modbus klienta z `services.modbus_client`; hlavní smyčka je `run_telemetry_loop` / `run_telemetry_loop_wrapper`.
--- ---
## Agregace 1min → 15min ## Agregace 1min → 15min
Prováděna PostgreSQL funkcí `ems.fn_fill_audit_interval()` a `ems.fn_fill_baseline_consumption()`. Prováděna PostgreSQL funkcí `ems.fn_fill_audit_interval()` a navazujícími
Spouštěna každých 15 minut jako scheduled task (Python APScheduler nebo pg_cron). funkcemi pro baseline/accuracy. Spouští ji Python APScheduler: audit filler v
minutách `:01,:16,:31,:46`, forecast accuracy v `:02,:17,:32,:47`.
```sql ```sql
-- Příklad agregace telemetrie na 15min průměr -- Příklad agregace telemetrie na 15min průměr
@@ -161,12 +171,13 @@ Nad `ems.telemetry_inverter` běží dva **continuous aggregate** (TimescaleDB);
```env ```env
TELEMETRY_POLL_INTERVAL_SEC=60 TELEMETRY_POLL_INTERVAL_SEC=60
TELEMETRY_ERROR_WARN_THRESHOLD=3 # počet chyb před WARNING logem
TELEMETRY_ERROR_RECONNECT_THRESHOLD=10
MODBUS_CONNECT_TIMEOUT_SEC=5 MODBUS_CONNECT_TIMEOUT_SEC=5
MODBUS_READ_TIMEOUT_SEC=3 MODBUS_READ_TIMEOUT_SEC=3
``` ```
`TELEMETRY_POLL_INTERVAL_SEC` a chybové prahy zatím nejsou v kódu používány;
smyčka běží každých 60 s přímo v `run_telemetry_loop_wrapper`.
--- ---
## Otevřené body ## Otevřené body

View File

@@ -45,12 +45,12 @@ Věci bez kterých nelze bezpečně napojit fyzická zařízení, spustit smyslu
| Popis | Kde | Kdo | | Popis | Kde | Kdo |
|-------|-----|-----| |-------|-----|-----|
| Doplnit **GPS** (`latitude`, `longitude`) pro lokalitu `home-01` vstup Open-Meteo. | `db/migration/V003__seed_site_home01.sql` ř. 1117 (`INSERT` + komentáře TODO); `docs/06-open-questions.md` ř. 1516 | majitel (souřadnice) → programátor (úprava seedu/SQL) | | Ověřit **GPS** (`latitude`, `longitude`) pro lokalitu `home-01` proti skutečné instalaci v seedu už jsou vyplněné hodnoty, ale forecast na nich stojí. | `db/migration/V003__seed_site_home01.sql` (`INSERT INTO ems.site`) | majitel (ověření souřadnic) → programátor |
| Doplnit **skutečné IP** Waveshare (Deye), obou Teltonika WB, Samsung TČ a **Loxone**; ověřit **Modbus Unit ID** u zařízení. | `db/migration/V003__seed_site_home01.sql` ř. 2730, 3336, 3941, 4446, 4952 (TODO komentáře); `docs/04-modules/telemetry.md` ř. 215 (ověření Unit ID) | majitel / instalatér (síť) → programátor (seed nebo `site_endpoint` v DB) | | Doplnit **skutečné IP** Waveshare (Deye), obou Teltonika WB, Samsung TČ a **Loxone**; ověřit **Modbus Unit ID** u zařízení. | `db/migration/V003__seed_site_home01.sql` ř. 2730, 3336, 3941, 4446, 4952 (TODO komentáře); `docs/04-modules/telemetry.md` ř. 215 (ověření Unit ID) | majitel / instalatér (síť) → programátor (seed nebo `site_endpoint` v DB) |
| Doplnit **azimut a sklon** FVE polí A a B pro přesný výpočet predikce. | `db/migration/V003__seed_site_home01.sql` ř. 125132, 140146; `docs/06-open-questions.md` ř. 1314; `docs/04-modules/forecast.md` ř. 1617 (tabulka TBD), 177 | majitel / projektant FVE → programátor | | Ověřit **azimut a sklon** FVE polí A a B proti skutečné instalaci. Seed aktuálně obsahuje `pv-a` 184°/22° a `pv-b` 184°/35° v kompasové konvenci `0=N, 90=E, 180=S, 270=W`. | `db/migration/V003__seed_site_home01.sql` (`asset_pv_array` seed); `docs/04-modules/forecast.md` | majitel / projektant FVE → programátor |
| Doplnit **model TČ**, **jmenovitý topný výkon (W)**, **COP rated**, **objem zásobníku TUV**, **odkaz na čidlo TUV** v seedu (`asset_heat_pump` má povinné numerické sloupce bez platných hodnot nelze konzistentně plánovat / migrovat). | `db/migration/V003__seed_site_home01.sql` ř. 182200 | majitel (datasheet) → programátor | | Doplnit **model TČ**, **jmenovitý topný výkon (W)**, **COP rated**, **objem zásobníku TUV**, **odkaz na čidlo TUV** v seedu (`asset_heat_pump` má povinné numerické sloupce bez platných hodnot nelze konzistentně plánovat / migrovat). | `db/migration/V003__seed_site_home01.sql` ř. 182200 | majitel (datasheet) → programátor |
| **Rozhodnout Teltonika: OCPP 1.6 vs REST API** před implementací EV řízení a sběru. | `docs/06-open-questions.md` ř. 910; `docs/04-modules/consumption.md` ř. 184 | majitel + programátor | | **Rozhodnout Teltonika: OCPP 1.6 vs REST API** před implementací EV řízení a sběru. | `docs/06-open-questions.md` ř. 910; `docs/04-modules/consumption.md` ř. 184 | majitel + programátor |
| **Doplnit přesné Modbus registry** (čtení i zápis) pro Deye, Teltonika, Samsung bez mapy registrů nejde napsat funkční `telemetry_collector` / `control_exporter`. | `docs/04-modules/telemetry.md` ř. 63, 76105 (tabulky TBD), 212214; `docs/04-modules/heat-pump.md` ř. 7985, 102; `docs/04-modules/control.md` ř. 249251; pseudokód `TBD_*_REGISTER` ř. 166171, 192197; `docs/loxone-integration.md` ř. 259261 | majitel dodá PDF/šablony → programátor; část ověření s **Loxone programátor** | | **Doplnit přesné Modbus registry** pro Teltonika a Samsung a držet je jako TODO v kódu. Deye čtení a hlavní Deye řízení už jsou implementované přes registry popsané v `modbus-registers.md`; stále ověřit fyzické hodnoty pro konkrétní instalaci. | `docs/04-modules/telemetry.md`; `docs/04-modules/heat-pump.md`; `docs/04-modules/control.md`; `docs/loxone-integration.md`; `backend/services/telemetry_collector.py`; `backend/services/control/exporter_monolith.py` | majitel dodá PDF/šablony → programátor; část ověření s **Loxone programátor** |
| Ověřit **Modbus registr Output Power Limit** (curtailment pole A) na Deye SUN-20K. | `docs/04-modules/planning.md` ř. 422 | programátor (+ dokumentace od majitele) | | Ověřit **Modbus registr Output Power Limit** (curtailment pole A) na Deye SUN-20K. | `docs/04-modules/planning.md` ř. 422 | programátor (+ dokumentace od majitele) |
| Doplnit **skutečnou sazbu zeleného bonusu** do `asset_pv_array.green_bonus_czk_kwh` pro `pv-b` (aktuální placeholder: **7.135** Kč/kWh ověřit ze smlouvy s EG.D). | `db/migration/V017__green_bonus.sql` (seed `pv-b`) | majitel (smlouva) → programátor | | Doplnit **skutečnou sazbu zeleného bonusu** do `asset_pv_array.green_bonus_czk_kwh` pro `pv-b` (aktuální placeholder: **7.135** Kč/kWh ověřit ze smlouvy s EG.D). | `db/migration/V017__green_bonus.sql` (seed `pv-b`) | majitel (smlouva) → programátor |
| Doplnit **`green_bonus_meter_code`** (EAN zeleného elektroměru) pro `pv-b` v `asset_pv_array`. | `db/migration/V017__green_bonus.sql` / přímá úprava DB | majitel → programátor | | Doplnit **`green_bonus_meter_code`** (EAN zeleného elektroměru) pro `pv-b` v `asset_pv_array`. | `db/migration/V017__green_bonus.sql` / přímá úprava DB | majitel → programátor |

View File

@@ -10,9 +10,9 @@ Tento soubor slouží jako živý seznam věcí které je potřeba rozhodnout p
- [ ] **Kurz EUR/CZK** Fixní hodnota v konfiguraci nebo denní stahování z ČNB API? Ovlivňuje `price_importer.py`. - [ ] **Kurz EUR/CZK** Fixní hodnota v konfiguraci nebo denní stahování z ČNB API? Ovlivňuje `price_importer.py`.
- [ ] **Azimut a sklon FVE polí** Doplnit přesné hodnoty pro home-01 (pole A). Nutné pro `forecast_service.py`. - [ ] **Azimut a sklon FVE polí** Ověřit hodnoty v seedu pro home-01 (`pv-a` 184°/22°, `pv-b` 184°/35°). Nutné pro `forecast_service.py`; konvence je kompasově/pvlib `0=N, 90=E, 180=S, 270=W`.
- [ ] **GPS souřadnice lokality home-01** Nutné pro Open-Meteo API (lat/lon). - [ ] **GPS souřadnice lokality home-01** V seedu jsou vyplněné; ověřit proti skutečné instalaci, protože jsou vstupem pro Open-Meteo API.
--- ---
@@ -20,7 +20,7 @@ Tento soubor slouží jako živý seznam věcí které je potřeba rozhodnout p
- [ ] **Dvě úrovně min SoC v DB** Dnes jedno `min_soc_percent` (provozní podlaha pro LP i TOU PASSIVE). Budoucí oddělení „tvrdé BMS minimum“ vs „plánovací minimum“ by vyžadovalo nový sloupec nebo politiku per site. - [ ] **Dvě úrovně min SoC v DB** Dnes jedno `min_soc_percent` (provozní podlaha pro LP i TOU PASSIVE). Budoucí oddělení „tvrdé BMS minimum“ vs „plánovací minimum“ by vyžadovalo nový sloupec nebo politiku per site.
- [ ] **TUV výkon** Je TUV výkon měřitelný zvlášť nebo jen ON/OFF? Pokud jen ON/OFF, použijeme `asset_flexible_device.max_power_w` jako aproximaci. - [ ] **TUV výkon** Je TUV výkon měřitelný zvlášť nebo jen ON/OFF? Pokud jen ON/OFF, použijeme `asset_heat_pump.rated_heating_power_w` jako aproximaci.
- [ ] **Pole B (ongridový)** Zahrnout do auditu jako "neřízená výroba"? Nebo ignorovat úplně? Komplikuje audit ale zpřesňuje ho. - [ ] **Pole B (ongridový)** Zahrnout do auditu jako "neřízená výroba"? Nebo ignorovat úplně? Komplikuje audit ale zpřesňuje ho.