# 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) │ │ – 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`.