Files
ems/docs/02-architecture.md
Dusan Vojacek e3776226a4
Some checks failed
CI and deploy / migration-check (push) Failing after 17s
CI and deploy / deploy (push) Has been skipped
implmentace plan guardu
2026-04-19 23:10:25 +02:00

247 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EMS Platform Architektura
## Vrstvy systému
```
┌─────────────────────────────────────────────┐
│ React Frontend (Vite + TypeScript) │
│ Dashboard, plány, telemetrie; výběr site │
│ (combobox → /api/v1/me/sites + PostgREST) │
└─────────────┬───────────────────────────────┘
│ HTTP/REST
┌─────────────▼───────────────────────────────┐
│ PostgREST │
│ Auto-REST API z PostgreSQL schématu ems │
│ Read: views, tabulky │
│ Write: insert/update přes API │
└─────────────┬───────────────────────────────┘
│ SQL
┌─────────────▼───────────────────────────────┐
│ PostgreSQL 16 + TimescaleDB │
│ Schéma: ems │
│ Funkce, views, hypertables │
└─────────────┬───────────────────────────────┘
┌─────────────▼───────────────────────────────┐
│ FastAPI (Python) │
Scheduled tasks (APScheduler) │
telemetry_collector (každých 60s) │
price_importer (13:30, 14:00, 00:05) │
forecast_service (každé 2h, minute 05)│
planning_engine (denně 15:00) │
control_exporter (každých 15min) │
audit_filler (každých 15min) │
plan_actual_slot_guard (:05,:20,:35,:50) │
verify_modbus (každé 2 min) │
└──────┬──────────────────────────┬────────────┘
│ Modbus TCP │ HTTP
┌──────▼──────┐ ┌───────▼────────────┐
│ Waveshare │ │ Loxone Miniserver │
│ WS-ETH │ │ (setpoint přijímač)│
└──────┬──────┘ └────────────────────┘
│ RS485
┌──────┼──────────────────────────────┐
│ Deye SUN-20K │ Teltonika 2× │ Samsung TČ │
└────────────────┴────────────────────┴──────────────┘
```
---
## Read-model JSONB (`ems.fn_*`)
FastAPI endpointy pro dashboard a konfiguraci preferují **jedno volání** `select ems.fn_*(…)` vracející **jsonb** (pole řádků, agregace, merge locků), aby v Pythonu nezůstávaly ad-hoc `SELECT`/`JOIN`/`WITH`. Pomocník `app.db_json.fetch_json` vrací `dict`/`list`. Telemetrie a IO zůstávají v Pythonu; čisté agregace a sjednocení TZ patří do SQL. Opakované migrace: `db/routines/R__NNN_fn_*.sql`, `db/views/R__NNN_vw_*.sql` (prefix `NNN` = pořadí závislostí pro Flyway).
**SQL-first (stejné pravidlo jako v `CLAUDE.md`):** doménová logika ve funkcích a view ve schématu `ems`; Python neskladá vlastní joiny nad tabulkami — nová data z více zdrojů = nová `fn_*` nebo `vw_*`, ne řetězení SQL v aplikaci.
**Health, joby po aktivních lokalitách a Loxone po změně režimu** jsou v repeatable [`db/routines/R__073_fn_health_site_jobs_mode_bundle.sql`](../db/routines/R__073_fn_health_site_jobs_mode_bundle.sql): `fn_health_summary`, `fn_health_detailed_db`, `fn_vw_site_directory_active`, `fn_site_economics_yesterday_notification`, `fn_site_mode_loxone_bundle`. FastAPI je rozdělené: [`backend/app/main.py`](../backend/app/main.py) (routery, CORS, WebSockety, health, `POST …/mode`) a [`backend/app/lifespan.py`](../backend/app/lifespan.py) (DB pool, APScheduler joby, telemetrická smyčka).
## Komponenty
| Komponenta | Technologie | Port | Popis |
|---|---|---|---|
| `db` | PostgreSQL 16 + TimescaleDB | 5432 | Datová vrstva |
| `postgrest` | PostgREST 12 | 3000 | Auto-REST API |
| `backend` | Python 3.12 / FastAPI | 8000 | Logika, scheduled tasks |
| `frontend` | React + Vite + TypeScript | 5173 (dev) / 80 (prod) | UI |
---
## Adresář projektu
```
ems-platform/
CLAUDE.md
docker-compose.yml
docker-compose.dev.yml
.env.example
.env ← gitignore!
db/
migration/
V001__init_schema.sql
V002__timescale_hypertables.sql
V003__seed_site_home01.sql
routines/
R__003_fn_baseline_consumption.sql
R__005_fn_cop_estimate.sql
R__011_fn_effective_price.sql
R__019_fn_fill_audit_interval.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
R__fn_create_planning_run.sql
views/
R__061_vw_site_effective_price.sql
R__058_vw_latest_telemetry.sql
R__071_vw_telemetry_15m_7d.sql
R__vw_actual_baseline.sql
R__053_vw_audit_summary.sql
R__vw_heat_pump_cop_history.sql
flyway.conf
backend/
Dockerfile
requirements.txt
app/
main.py ← FastAPI app, routery, health, změna režimu
lifespan.py ← lifespan: pool, APScheduler, telemetry task
refresh_negative_prices.py ← sdílený hook po importu cen / forecastu
routers/
sites.py ← /api/v1/sites (ceny, control, forecast)
me.py ← /api/v1/me/sites
config.py ← settings z env
database.py ← asyncpg (alternativní / legacy helper)
services/
telemetry_collector.py
price_importer.py
forecast_service.py
planning_engine.py ← volá ems.fn_create_planning_run()
control_exporter.py
audit_filler.py
modbus/
deye_client.py
ev_charger_client.py
heat_pump_client.py
models/
site.py
assets.py
setpoints.py
frontend/
Dockerfile
package.json
vite.config.ts
src/
main.tsx
App.tsx
api/
postgrest.ts ← PostgREST client
backend.ts ← FastAPI client (/me/sites, …)
context/
SiteSelectionContext.tsx ← výběr lokality, localStorage
pages/
Dashboard.tsx
Planning.tsx
Telemetry.tsx
Settings.tsx
components/
PowerFlowChart.tsx
PriceChart.tsx
SocGauge.tsx
OverridePanel.tsx
docs/
01-overview.md
02-architecture.md ← tento soubor
03-data-model.md
04-modules/
market-prices.md
forecast.md
consumption.md
heat-pump.md
telemetry.md
control.md
planning.md
06-open-questions.md
```
---
## Komunikační toky
### Sběr dat (každých 60s)
```
Zařízení → Waveshare → Modbus TCP → telemetry_collector → PostgreSQL
```
### Denní plánování (15:00)
```
PostgreSQL (ceny + forecast) → fn_create_planning_run() → planning_interval
```
### Operátorské manuální akce (UI)
```
Browser → FastAPI:
GET /api/v1/me/sites ← seznam aktivních lokalit (UI combobox; bez auth zatím = všechny aktivní)
POST /api/v1/sites/{site_id}/prices/import?date=YYYY-MM-DD ← zapisuje globální market_interval_price; site_id jen validace URL
POST /api/v1/sites/{site_id}/forecast/run
POST /api/v1/sites/{site_id}/plan/run?type=daily|rolling
```
### Export setpointů (každých 15min)
```
PostgreSQL (planning_interval + overrides) → control_exporter
→ Modbus TCP → Waveshare → Deye / Teltonika / Samsung
→ HTTP → Loxone
```
### Frontend
```
Browser → PostgREST (čtení views/tabulek, filtr site_id dle výběru v UI)
Browser → FastAPI (seznam lokalit /me/sites, triggery: replanning, import cen, …)
```
---
## Deployment: single-site (výchozí)
Vše na jednom stroji (x86 mini PC nebo silnější RPi 5):
```
Docker Compose:
db (PostgreSQL + TimescaleDB)
postgrest (PostgREST)
backend (FastAPI + všechny services)
frontend (Nginx + React build)
flyway (migrace při startu)
```
### Minimální HW požadavky
| Parametr | Minimum | Doporučeno |
|---|---|---|
| CPU | 2 jádra | 4 jádra (x86) |
| RAM | 4 GB | 8 GB |
| Storage | SSD 64 GB | NVMe 256 GB |
| OS | Debian 12 / Ubuntu 22.04 | Ubuntu 22.04 LTS |
| Síť | 100 Mbps | Gigabit Ethernet |
> **Raspberry Pi 5 (8GB):** Použitelné pro single-site s SSD přes USB 3 nebo NVMe HAT.
> **Nedoporučovat microSD** TimescaleDB zápisy microSD rychle opotřebují.
---
## Flyway konfigurace
```properties
# db/flyway.conf
flyway.url=jdbc:postgresql://db:5432/ems
flyway.user=${DB_USER}
flyway.password=${DB_PASSWORD}
flyway.schemas=ems
flyway.locations=filesystem:/flyway/migration,filesystem:/flyway/routines,filesystem:/flyway/views
flyway.validateOnMigrate=true
flyway.outOfOrder=false
```
Flyway se spouští jako jednorázový kontejner při `docker-compose up`.