diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml
new file mode 100644
index 0000000..e2d0689
--- /dev/null
+++ b/.idea/data_source_mapping.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/db-forest-config.xml b/.idea/db-forest-config.xml
new file mode 100644
index 0000000..3c4438f
--- /dev/null
+++ b/.idea/db-forest-config.xml
@@ -0,0 +1,10 @@
+
+
+
+ .
+ ----------------------------------------
+ 1:0:26ebef46-ff23-475b-8adc-082723263a02
+ 2:0:65d65f76-7a9d-423e-8815-7d8efa5fd7f1
+ .
+
+
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..5e9b2aa
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/db/migration/V035__postgrest_ems_anon_schema_usage.sql b/db/migration/V035__postgrest_ems_anon_schema_usage.sql
new file mode 100644
index 0000000..53277a6
--- /dev/null
+++ b/db/migration/V035__postgrest_ems_anon_schema_usage.sql
@@ -0,0 +1,4 @@
+-- Po pg_restore --no-acl často chybí GRANT USAGE ON SCHEMA ems pro ems_anon.
+-- PostgREST pak vrací: permission denied for schema ems (42501).
+
+GRANT USAGE ON SCHEMA ems TO ems_anon;
diff --git a/db/views/R__z_postgrest_ems_anon_grants.sql b/db/views/R__z_postgrest_ems_anon_grants.sql
index 3400dbe..218aa12 100644
--- a/db/views/R__z_postgrest_ems_anon_grants.sql
+++ b/db/views/R__z_postgrest_ems_anon_grants.sql
@@ -34,3 +34,5 @@ GRANT SELECT ON ems.market_price_stats TO ems_anon;
GRANT SELECT ON ems.tuv_usage_stats TO ems_anon;
GRANT SELECT ON ems.baseline_load_forecast_accuracy TO ems_anon;
GRANT SELECT ON ems.vw_baseline_load_forecast_accuracy_daily TO ems_anon;
+
+
diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
index 2b04ec8..5aa1036 100644
--- a/deploy/docker-compose.yml
+++ b/deploy/docker-compose.yml
@@ -25,7 +25,7 @@ services:
retries: 5
flyway:
- image: flyway/flyway:10
+ image: flyway/flyway:12
depends_on:
db:
condition: service_healthy
diff --git a/docker-compose.yml b/docker-compose.yml
index 1c7a5e9..822e47f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -18,7 +18,7 @@ services:
retries: 5
flyway:
- image: flyway/flyway:10
+ image: flyway/flyway:12
depends_on:
db:
condition: service_healthy
diff --git a/docs/database-reset-and-restore.md b/docs/database-reset-and-restore.md
index 3c061ab..e04c3f3 100644
--- a/docs/database-reset-and-restore.md
+++ b/docs/database-reset-and-restore.md
@@ -1,194 +1,282 @@
# Reset PostgreSQL / Timescale (Docker) a obnova z dumpu
-Přesný postup pro **smazání dat databáze** (volume) a **nový import** `pg_dump -Fc` na serveru typu `/opt/ems-deploy`. Po resetu zmizí veškerá data v DB EMS.
+Postup pro **smazání dat databáze** (volume), **nový čistý cluster** a **obnovu** z `pg_dump -Fc`. Platí stejně **na lokálu** i **na cílovém serveru** (např. `/opt/ems-deploy`): neprováděj in-place „upgrade“ starého datadisku jen přepnutím Docker image mezi major verzemi PostgreSQL.
-**Související skripty v repu:** `scripts/import_ems_db.sh`, `scripts/export_ems_db.sh`.
+**Související skripty v repu:** `scripts/export_ems_db.sh`, `scripts/import_ems_db.sh`.
Obecný kontext self-hosted deploye: [deployment-self-hosted.md](deployment-self-hosted.md).
---
+## Proč ne „jen změnit image“ z PG16 na PG18
+
+Major upgrade PostgreSQL se má dělat **`pg_upgrade`** nebo **dump/restore**. Prosté přepnutí image u existujícího volume nestačí a může vést k poškozenému nebo nekompatibilnímu datadir. Oficiální dokumentace: [PostgreSQL: pg_upgrade](https://www.postgresql.org/docs/current/pgupgrade.html).
+
+V Docker Compose je **dump/restore** obvykle jednodušší než `pg_upgrade` (potřebuješ staré i nové binárky a dva datadiry najednou). Bezpečná cesta: **nový prázdný cluster** (nový volume) + **restore zálohy**.
+
+---
+
+## Sladění verzí TimescaleDB
+
+Zdroj zálohy a cílový kontejner musí mít **sladěnou verzi rozšíření TimescaleDB** vůči tomu, co očekává image — jinak často narazíš na **catalog mismatch** při restore nebo po něm. Před migrací ověř na obou stranách:
+
+```bash
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select version();"
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select extname, extversion from pg_extension where extname='timescaledb';"
+```
+
+Kontext a restore workflow: [TimescaleDB: Troubleshooting (self-hosted)](https://docs.timescale.com/self-hosted/latest/troubleshooting/), [Upgrade TimescaleDB in Docker](https://docs.timescale.com/self-hosted/latest/upgrades/upgrade-docker/).
+
+V repozitáři je pro nové instalace očekávaný image např. `timescale/timescaledb:2.26.1-pg18` (viz `docker-compose.yml`). Pinni tag na cíli stejně jako u zdroje dumpu.
+
+---
+
## 0. Před začátkem
-- Máš zálohu `.dump` (custom format), ideálně z `scripts/export_ems_db.sh` nebo ekvivalentního `pg_dump -Fc`.
-- **`DB_USER` / `DB_PASSWORD` v `.env` na cíli** odpovídají tomu, co očekává init kontejneru `db` (po smazání volume se DB vytvoří znovu se stejnými údaji z `.env`).
-- Timescale: image `timescale/timescaledb:latest-pg16` se v čase mění. Záloha ze **staršího** Timescale na **novější** imagi vyžaduje po restore `ALTER EXTENSION timescaledb UPDATE` — to už dělá `import_ems_db.sh`. Při problémech zvaž **pin** imagi (např. `:2.25.2-pg16`) stejně jako u zdroje dumpu.
-- **`pg_restore` nikdy s `-j`** (paralelní restore) u Timescale.
+- Máš zálohu `.dump` (custom format `-Fc`), ideálně podle kroku 1 níže nebo `./scripts/export_ems_db.sh`.
+- **`DB_USER` / `DB_PASSWORD` v `.env`** na cíli odpovídají initu kontejneru `db` (po smazání volume se DB založí znovu z `.env`).
+- **`pg_restore` nikdy s `-j`** (paralelní restore) u Timescale — může rozbít pořadí objektů u hypertable.
----
-
-## 1. (Volitelně) Export z vývojového stroje
-
-Z kořene repa, kde běží lokální `docker compose` a `.env` s `DB_USER` / `DB_PASSWORD`:
-
-```bash
-cd /cesta/k/ems-cursor
-./scripts/export_ems_db.sh ~/ems_zaloha_$(date +%Y%m%d_%H%M%S).dump
-```
-
-Přenos na server (příklad):
-
-```bash
-scp ~/ems_zaloha_*.dump root@server:/tmp/ems.dump
-```
-
----
-
-## 2. Na serveru: zastavit služby, které píší do DB
-
-Aby při mazání volume a restore nebyl konflikt:
+Na serveru před mazáním volume často zastav služby, které píší do DB:
```bash
cd /opt/ems-deploy
docker compose --env-file .env stop backend postgrest frontend
```
-(`db` nech může běžet do kroku 4, nebo všechno zastavit — viz krok 3.)
+(`db` může běžet až do smazání volume; nebo celý stack sestřelíš v jednom kroku níže.)
---
-## 3. Zastavit stack a smazat datový volume Postgresu
+## 1) Na zdroji: dump
+
+Z kořene projektu (lokálně nebo na stroji, kde běží zdrojová DB):
```bash
-cd /opt/ems-deploy
-docker compose --env-file .env down
+docker compose exec -T db pg_dump \
+ -U "$DB_USER" \
+ -d ems \
+ -Fc \
+ --no-owner \
+ --no-privileges \
+ -f /tmp/ems.dump
+
+docker compose cp db:/tmp/ems.dump ./ems.dump
+ls -lh ./ems.dump
```
-**Jméno volume** závisí na názvu Compose projektu (složka nebo `COMPOSE_PROJECT_NAME`). Zjisti ho:
+Alternativa z repa: `./scripts/export_ems_db.sh ./ems.dump`.
+
+Přenos na server (příklad):
```bash
-docker volume ls | grep -E 'db_data|ems'
+scp ./ems.dump root@server:/tmp/ems.dump
```
-Typicky u deploye z `/opt/ems-deploy` uvidíš něco jako `ems-deploy_db_data` (prefix `{project}_db_data` k klíči `db_data:` z compose).
-
-**Smaž volume** (doplň přesný název z výpisu):
-
-```bash
-docker volume rm ems-deploy_db_data
-```
-
-Tím zmizí **celý** obsah dat Postgresu v tom projektu. Ostatní Docker volume (pokud nějaké máš) se nedotknou.
-
---
-## 4. Naběhnout jen databázi
+## 2) Ověření zdroje (a případně cíle)
```bash
-cd /opt/ems-deploy
-docker compose --env-file .env up -d db
-docker compose --env-file .env ps
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select version();"
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select extname, extversion from pg_extension where extname='timescaledb';"
```
-Počkej, až je služba `db` **healthy** (`pg_isready` v healthchecku).
-
-Init image vytvoří prázdnou databázi **`ems`** a uživatele z `POSTGRES_USER` / `POSTGRES_PASSWORD`.
-
---
-## 5. Import dumpu (Timescale hooks + restore)
-
-Dump musí být na serveru, např. `/tmp/ems.dump`.
+## 3) Zastavit stack a smazat jen DB volume
```bash
-cd /opt/ems-deploy
-bash app/scripts/import_ems_db.sh /tmp/ems.dump
+docker compose down
+docker volume ls | grep db_data
```
-Skript v kontejneru `db` postupně:
+Smaž volume pro databázi. Název závisí na **Compose project name** (složka nebo `COMPOSE_PROJECT_NAME`), typicky `{projekt}_db_data`, např. `ems_db_data` nebo `ems-deploy_db_data`:
-1. `CREATE EXTENSION IF NOT EXISTS timescaledb`
-2. `SELECT timescaledb_pre_restore();`
-3. `pg_restore` **bez** `-j` (`--clean --if-exists --no-owner --no-acl`)
-4. `ALTER EXTENSION timescaledb UPDATE;` (srovnání katalogu se verzí v imagi)
-5. `SELECT timescaledb_post_restore();`
+```bash
+docker volume rm ems_db_data
+```
-Výjimky: `EMS_SKIP_TIMESCALE_RESTORE_HOOKS=1` v prostředí přeskočí body 2 a 5 (jen výjimečně).
+Jestli nechceš hledat přesný název a máš jen compose-definované volumes:
+
+```bash
+docker compose down -v
+```
+
+**Pozor:** `down -v` smaže volumes definované v daném compose souboru — lokální DB tím kompletně zahodíš (v tomto scénáři je to záměr, obnova jde z dumpu).
+
+---
+
+## 4) Compose na cílové verzi Postgresu / Timescale
+
+V `docker-compose.yml` nastav konkrétní tag, např.:
+
+```yaml
+services:
+ db:
+ image: timescale/timescaledb:2.26.1-pg18
+```
+
+Nespoléhej na „upgrade“ starého volume změnou image — viz úvod.
+
+---
+
+## 5) Nový prázdný cluster (jen `db`)
+
+```bash
+docker compose up -d db
+```
+
+Počkej na **healthy** (`pg_isready` v healthchecku). Init vytvoří prázdnou databázi **`ems`** a uživatele z `POSTGRES_USER` / `POSTGRES_PASSWORD`.
+
+Ověření:
+
+```bash
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select version();"
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select extname, extversion from pg_extension where extname='timescaledb';"
+```
+
+---
+
+## 6) Nahraj dump do kontejneru
+
+```bash
+docker compose cp ./ems.dump db:/tmp/ems.dump
+```
+
+Na serveru může být cesta `/tmp/ems.dump` už na disku — pak `cp` z hosta odpovídá tvé cestě k souboru.
+
+---
+
+## 7) Restore (Timescale pre / post)
+
+Nejdřív příprava podle dokumentace Timescale (full restore):
+
+```bash
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_pre_restore();"
+```
+
+Restore:
+
+```bash
+docker compose exec -T db pg_restore \
+ -U "$DB_USER" \
+ -d ems \
+ --no-owner \
+ --no-privileges \
+ -v \
+ /tmp/ems.dump
+```
+
+Dokončení:
+
+```bash
+docker compose exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_post_restore();"
+```
+
+**Alternativa:** `bash scripts/import_ems_db.sh /tmp/ems.dump` (z kořene repa na serveru s naklonovanou aplikací) — skript doplní `CREATE EXTENSION`, `timescaledb_pre_restore`, `pg_restore` s `--clean --if-exists`, pak **`ALTER EXTENSION timescaledb UPDATE`** (srovnání katalogu zálohy s verzí v imagi) a `timescaledb_post_restore`. Na úplně čerstvém volume je ruční sekvence výše ekvivalentní v principu; skript je praktičtější při opakovaném importu na serveru.
**Časté jevy:**
-- `pg_restore: warning: errors ignored` — často kvůli FK na hypertable (`ALTER TABLE ONLY`). Po importu **vždy** Flyway (migrace **V034** doplní chybějící FK).
-- Selhání **catalog version mismatch** — typicky vyřeší `ALTER EXTENSION` ve skriptu; jinak pin Timescale imagi nebo nový dump po upgrade zdroje.
+- `pg_restore: warning: errors ignored` — často kvůli FK na hypertable (`ALTER TABLE ONLY`). Po importu **vždy** Flyway (migrace **V034** a okolí doplňují chybějící FK podle verze repa).
+- **Catalog version mismatch** — sladit Timescale tag na cíli se zdrojem nebo pořídit nový dump po upgrade zdroje; skript `import_ems_db.sh` řeší část přes `ALTER EXTENSION timescaledb UPDATE`.
+
+Výjimka v prostředí: `EMS_SKIP_TIMESCALE_RESTORE_HOOKS=1` u skriptu přeskočí pre/post (jen výjimečně).
---
-## 6. Flyway migrace
-
-Doplní schéma oproti dumpu (včetně oprav PK/chunků/FK z V032–V034 podle verze repa):
+## 8) Flyway migrace (EMS)
```bash
cd /opt/ems-deploy
docker compose --env-file .env run --rm flyway migrate
```
----
-
-## 7. Celý stack
+Lokálně z kořene repa (bez `--env-file`, pokud používáš výchozí `.env`):
+
+```bash
+docker compose run --rm flyway migrate
+```
+
+---
+
+## 9) Celý stack
+
+```bash
+docker compose up -d
+```
+
+Na serveru např.:
```bash
-cd /opt/ems-deploy
docker compose --env-file .env up -d
```
-Nebo jednorázový deploy z aplikace:
-
-```bash
-/opt/ems-deploy/deploy.sh
-```
-
-(`deploy.sh` dělá git sync, `flyway migrate`, `build`, `up -d` — pokud už jsi Flyway spustil v kroku 6, znovu obvykle jen doplní repeatable migrace.)
+nebo `/opt/ems-deploy/deploy.sh` (git sync, flyway, build, `up -d` — pokud už je Flyway z kroku 8 hotový, znovu typicky jen doplní repeatable migrace).
---
-## 8. Rychlá kontrola
+## 10) Rychlá kontrola
```bash
-docker compose -f /opt/ems-deploy/docker-compose.yml --env-file /opt/ems-deploy/.env ps
-```
-
-Volitelně SQL (z hosta přes `docker compose exec`):
-
-```bash
-docker compose --env-file .env exec -T -e PGPASSWORD="$DB_PASSWORD" db \
+docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \
psql -U "$DB_USER" -d ems -c "SELECT count(*) FROM ems.site;"
```
---
-## 9. Slabší varianta bez mazání volume
+## 11) Úplně čistá varianta bez mazání stávajícího volume (rollback)
-Pokud nechceš mazat celý volume, lze zkusit přepnout databázi uvnitř (méně typické u Timescale; u poškozeného katalogu chunků je **spolehlivější smazat volume**):
+Můžeš vedle současného stacku spustit **druhý compose projekt** (jiné `COMPOSE_PROJECT_NAME` nebo jiná složka), **jiný volume** a **jiný mapovaný port**, např.:
+
+- starý stack na host portu `5432`;
+- nový PG18 dočasně na `5433`;
+- restore do nového, ověření;
+- pak starý stack zastavit a volume zahodit, nebo porty přemapovat podle potřeby.
+
+Tím máš možnost vrátit se ke staré DB, dokud novou neověříš.
+
+---
+
+## 12) Slabší varianta: drop databáze `ems` bez mazání celého volume
+
+Pokud nechceš mazat celý Postgres volume, lze zrušit jen databázi `ems` a znovu ji vytvořit (méně typické u hlubších problémů s katalogem Timescale; u poškozených chunků je spolehlivější **smazat volume**):
```bash
-docker compose --env-file .env exec -T -e PGPASSWORD="$DB_PASSWORD" db \
+docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \
psql -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS ems WITH (FORCE);"
-docker compose --env-file .env exec -T -e PGPASSWORD="$DB_PASSWORD" db \
+docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \
psql -U "$DB_USER" -d postgres -c "CREATE DATABASE ems OWNER \"${DB_USER}\";"
```
-Pak znovu **`import_ems_db.sh`** a **Flyway**.
+Pak znovu krok 7 (restore) a Flyway.
---
-## 10. Robustnější strategie (odkaz)
+## 13) Robustnější strategie (odkaz)
-Plný „schema odděleně od dat“ a obnova hypertable přes `create_hypertable` popisuje **Tiger Data / Timescale** v dokumentaci k backupu a restore. Tento repozitář používá praktický jednokrokový `-Fc` restore přes `import_ems_db.sh`; u opakovaných problémů zvaž jejich oficiální postup (pre-data dump bez `_timescaledb*`, data zvlášť).
+Plný postup „schéma odděleně od dat“ a obnova hypertable přes `create_hypertable` popisuje Tiger Data / Timescale v dokumentaci k zálohám. Tento repozitář používá praktický jednosouborový `-Fc` restore; při opakovaných problémech zvaž jejich oficiální postup.
---
-## 11. Shrnutí příkazů (kostra)
+## 14) Shrnutí příkazů (server `/opt/ems-deploy`)
```bash
-# server
cd /opt/ems-deploy
docker compose --env-file .env stop backend postgrest frontend
docker compose --env-file .env down
-docker volume rm # z docker volume ls
+docker volume rm # z: docker volume ls | grep db_data
docker compose --env-file .env up -d db
-# počkat healthy
+# počkat až je db healthy
+
+docker compose cp /tmp/ems.dump db:/tmp/ems.dump
+docker compose --env-file .env exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_pre_restore();"
+docker compose --env-file .env exec -T db pg_restore -U "$DB_USER" -d ems --no-owner --no-privileges -v /tmp/ems.dump
+docker compose --env-file .env exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_post_restore();"
-bash app/scripts/import_ems_db.sh /tmp/ems.dump
docker compose --env-file .env run --rm flyway migrate
docker compose --env-file .env up -d
```
+
+Nebo místo tří `exec` řádků s restore použij `bash app/scripts/import_ems_db.sh /tmp/ems.dump` a pak Flyway + `up -d`.