feat: implement sensor glitch detection for water levels and update data cleaning logic
This commit is contained in:
@@ -32,7 +32,7 @@ npm run data:update
|
||||
```
|
||||
|
||||
Tento příkaz provede dvě věci:
|
||||
1. `npm run scrape`: Otevře stránky povodí pro všech 12 přehrad, přečte tabulky s historickými měřeními a najde "Aktuální hodnoty", odkud vytáhne exaktní **přítok, objem, srážky a teplotu**. Tato data inteligentně sloučí s tvojí lokální databází (`public/data/*.json`). Pokud Povodí aktuálně počasí neposkytuje, skript zrecykluje tvou dřívější uloženou hodnotu, aby se graf "nerozbil".
|
||||
1. `npm run scrape`: Otevře stránky povodí pro všech **53 nádrží a říčních stanic**, přečte tabulky s historickými měřeními a najde "Aktuální hodnoty", odkud vytáhne exaktní **přítok, odtok, objem, srážky a teplotu**. Tato data inteligentně sloučí s tvojí lokální databází (`public/data/*.json`) a automaticky doplňuje chybějící hodnoty přítoku/objemu z minula, aby graf neměl výpadky k nule.
|
||||
2. `npm run build-index`: Zaktualizuje hlavní indexový soubor `lakes_index.json`, který aplikace využívá pro vykreslení rychlých náhledů (např. v levém menu nebo na mapě).
|
||||
|
||||
---
|
||||
@@ -56,17 +56,26 @@ Pokud ti běží počítač (nebo domácí server/Raspberry Pi) nepřetržitě,
|
||||
### Možnost B: Pomocí GitHub Actions (Pro Produkci)
|
||||
Až projekt nahraješ na GitHub, můžeš si vytvořit workflow soubor (např. `.github/workflows/scrape.yml`), který bude skript spouštět na serverech GitHubu zdarma každou hodinu, a výsledné `.json` soubory automaticky commitne a publikuje na web.
|
||||
|
||||
### Možnost C: Jednoduchý integrovaný spouštěč (Nejlehčí)
|
||||
Pokud nechceš řešit složitý systémový crontab, napsal jsem pro tebe přímo do Node.js malý spouštěč. Stačí si otevřít další okno terminálu a napsat:
|
||||
### Možnost C: Jednoduchý integrovaný spouštěč (Doporučeno pro vývoj)
|
||||
Pokud nechceš řešit složitý systémový crontab, je v projektu připraven inteligentní plánovač. Stačí si otevřít další okno terminálu a napsat:
|
||||
```bash
|
||||
npm run data:watch 10
|
||||
npm run data:watch
|
||||
```
|
||||
Tento příkaz ihned provede první stažení a následně bude aplikaci automaticky aktualizovat **každých 10 minut** (číslo na konci si můžeš libovolně přepsat podle toho, jak často chceš stahovat). Skript poběží, dokud okno terminálu nezavřeš.
|
||||
Tento příkaz provede okamžitou aktualizaci a poté automaticky spouští stahování vždy 7 minut po každém 10minutovém kroku (např. 18:07, 18:17, 18:27...). Tento posun zaručuje, že Povodí už stihlo na svůj web nahrát nová data a nestahuješ staré hodnoty.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Oprava chyb v historii (Zuby / Nuly v grafu)
|
||||
|
||||
Pokud ti aplikace delší dobu neběžela (např. při vypnutém počítači) a následně došlo k doplnění dat z historie, mohly se v grafech přítoku a objemu objevit falešné propady k nule (zuby). Pro vyčištění celé historie a dopočítání těchto bodů z posledních známých hodnot spusť:
|
||||
|
||||
```bash
|
||||
npm run data:fix
|
||||
```
|
||||
Tento skript projde všechny datové JSON soubory, detekuje anomálie/nuly a opraví je.
|
||||
|
||||
## 📁 Struktura klíčových datových složek
|
||||
|
||||
* `/scripts/lakesConfig.ts` - Tady najdeš definici všech 12 sledovaných přehrad (včetně jejich ID pro Povodí Vltavy, GPS souřadnic, maximálních objemů a stavebních kót). Sem můžeš přidávat nové přehrady.
|
||||
* `/scripts/lakesConfig.ts` - Tady najdeš definici všech **53 sledovaných nádrží a řek** (včetně jejich ID pro Povodí Vltavy, GPS souřadnic, maximálních objemů a stavebních kót). Sem můžeš přidávat nové stanice.
|
||||
* `/public/data/` - Zde se ukládají vygenerovaná JSON data. V produkci musí být tyto soubory přístupné jako statické assety.
|
||||
* `/src/components/` - Obsahuje samotné vizuální karty, Leaflet mapu a detailní `LakeDetail.tsx` (kde se vykresluje hydrologický a meteorologický graf přes Recharts).
|
||||
* `/src/components/` - Obsahuje samotné vizuální karty, Leaflet mapu a detailní `LakeDetail.tsx` (kde se vykresluje hydrologický a meteorologický graf přes Recharts s automatickým čištěním chyb a senzorických úletů).
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"build-index": "tsx scripts/buildIndex.ts",
|
||||
"data:update": "npm run scrape && npm run build-index",
|
||||
"data:watch": "tsx scripts/watchData.ts",
|
||||
"data:fix": "tsx scripts/fix_lake_inflows.ts",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage"
|
||||
|
||||
@@ -2317,5 +2317,14 @@
|
||||
"volume": 0,
|
||||
"temperature": 26,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:10:00.000Z",
|
||||
"level": 96,
|
||||
"flow": 8.667,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 26,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -2207,5 +2207,14 @@
|
||||
"volume": 0,
|
||||
"temperature": 25.5,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 90,
|
||||
"flow": 4.745,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 25.5,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -8220,7 +8220,7 @@
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 463.42,
|
||||
"flow": 0,
|
||||
"inflow": 6.048207396473533,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 23.7,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8886,7 +8886,7 @@
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 416.72,
|
||||
"flow": 0,
|
||||
"inflow": 7.8121741402451015,
|
||||
"inflow": 0,
|
||||
"volume": 0.09,
|
||||
"temperature": 23.8,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8859,7 +8859,7 @@
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 448.81,
|
||||
"flow": 0,
|
||||
"inflow": 4.662341665814382,
|
||||
"inflow": 0,
|
||||
"volume": 0.67,
|
||||
"temperature": 23.8,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8076,7 +8076,7 @@
|
||||
"timestamp": "2026-06-08T17:00:00.000Z",
|
||||
"level": 580.99,
|
||||
"flow": 0,
|
||||
"inflow": 9.58165824042722,
|
||||
"inflow": 0,
|
||||
"volume": 0.03,
|
||||
"temperature": 23,
|
||||
"precipitation": 0
|
||||
|
||||
+10
-1
@@ -8939,7 +8939,7 @@
|
||||
{
|
||||
"timestamp": "2026-06-08T17:10:00.000Z",
|
||||
"level": 407.64,
|
||||
"flow": 0,
|
||||
"flow": 0.2,
|
||||
"inflow": 0.4,
|
||||
"volume": 0.14,
|
||||
"temperature": 23,
|
||||
@@ -8953,5 +8953,14 @@
|
||||
"volume": 0.14,
|
||||
"temperature": 22.3,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 407.64,
|
||||
"flow": 0,
|
||||
"inflow": 0.4,
|
||||
"volume": 0.14,
|
||||
"temperature": 22.3,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -8202,7 +8202,7 @@
|
||||
"timestamp": "2026-06-08T17:00:00.000Z",
|
||||
"level": 632.76,
|
||||
"flow": 0,
|
||||
"inflow": 10.515147444523139,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 21.7,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8202,7 +8202,7 @@
|
||||
"timestamp": "2026-06-08T17:00:00.000Z",
|
||||
"level": 635.7,
|
||||
"flow": 0,
|
||||
"inflow": 5.418107522688602,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 22,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -2245,5 +2245,14 @@
|
||||
"volume": 0,
|
||||
"temperature": 24,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 82,
|
||||
"flow": 2.666,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 24,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -8751,7 +8751,7 @@
|
||||
"timestamp": "2026-06-08T17:20:00.000Z",
|
||||
"level": 563.66,
|
||||
"flow": 0.02,
|
||||
"inflow": 0.01,
|
||||
"inflow": 0,
|
||||
"volume": 0.46,
|
||||
"temperature": 22.3,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -2252,5 +2252,14 @@
|
||||
"volume": 0,
|
||||
"temperature": 24.9,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 43,
|
||||
"flow": 4.73,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 24.9,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -2317,5 +2317,14 @@
|
||||
"volume": 0,
|
||||
"temperature": 23,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:10:00.000Z",
|
||||
"level": 25,
|
||||
"flow": 2.95,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 23,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -8103,7 +8103,7 @@
|
||||
"timestamp": "2026-06-08T16:20:00.000Z",
|
||||
"level": 670.52,
|
||||
"flow": 0,
|
||||
"inflow": 8.227779864526358,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 21.8,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8922,7 +8922,7 @@
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 575.7,
|
||||
"flow": 0.02,
|
||||
"inflow": 11.662041143485649,
|
||||
"inflow": 0,
|
||||
"volume": 1.06,
|
||||
"temperature": 21.4,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -2270,5 +2270,14 @@
|
||||
"volume": 0,
|
||||
"temperature": 24,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:10:00.000Z",
|
||||
"level": 68,
|
||||
"flow": 1.452,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 24,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -8926,5 +8926,14 @@
|
||||
"volume": 0.1,
|
||||
"temperature": 23.6,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 0,
|
||||
"flow": 0.06,
|
||||
"inflow": 0.07,
|
||||
"volume": 0.1,
|
||||
"temperature": 23.6,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -8913,7 +8913,7 @@
|
||||
"timestamp": "2026-06-08T17:20:00.000Z",
|
||||
"level": 668.4,
|
||||
"flow": 0,
|
||||
"inflow": 10.98258553088203,
|
||||
"inflow": 0,
|
||||
"volume": 0.39,
|
||||
"temperature": 23.8,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8634,7 +8634,7 @@
|
||||
"timestamp": "2026-06-08T17:20:00.000Z",
|
||||
"level": 385.01,
|
||||
"flow": 0.02,
|
||||
"inflow": 6.257864375579982,
|
||||
"inflow": 0,
|
||||
"volume": 0.84,
|
||||
"temperature": 22.5,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8904,7 +8904,7 @@
|
||||
"timestamp": "2026-06-08T17:20:00.000Z",
|
||||
"level": 678.6,
|
||||
"flow": 0,
|
||||
"inflow": 8.204576504122992,
|
||||
"inflow": 0,
|
||||
"volume": 0.16,
|
||||
"temperature": 20.6,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8958,7 +8958,7 @@
|
||||
"timestamp": "2026-06-08T17:20:00.000Z",
|
||||
"level": 588.39,
|
||||
"flow": 0.08,
|
||||
"inflow": 8.36186446216757,
|
||||
"inflow": 0,
|
||||
"volume": 0.32,
|
||||
"temperature": 23,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -8913,7 +8913,7 @@
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 0,
|
||||
"flow": 0.05,
|
||||
"inflow": 8.055084527934373,
|
||||
"inflow": 0,
|
||||
"volume": 0.41,
|
||||
"temperature": 21.4,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -2243,5 +2243,14 @@
|
||||
"volume": 0,
|
||||
"temperature": 26.1,
|
||||
"precipitation": 0
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-06-08T17:30:00.000Z",
|
||||
"level": 46,
|
||||
"flow": 53.88,
|
||||
"inflow": 0,
|
||||
"volume": 0,
|
||||
"temperature": 26.1,
|
||||
"precipitation": 0
|
||||
}
|
||||
]
|
||||
@@ -9219,7 +9219,7 @@
|
||||
"timestamp": "2026-06-08T17:20:00.000Z",
|
||||
"level": 369.61,
|
||||
"flow": 15.18,
|
||||
"inflow": 11.42,
|
||||
"inflow": 0,
|
||||
"volume": 19.77,
|
||||
"temperature": 24.4,
|
||||
"precipitation": 0
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"level": "369.61",
|
||||
"capacity": 93.7,
|
||||
"storageDiff": -0.49,
|
||||
"inflow": "11.4",
|
||||
"inflow": "0.0",
|
||||
"outflow": "15.2",
|
||||
"volume": 19.77,
|
||||
"maxVolume": 21.1,
|
||||
@@ -658,7 +658,7 @@
|
||||
"level": "670.52",
|
||||
"capacity": 80.2,
|
||||
"storageDiff": -0.88,
|
||||
"inflow": "8.2",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 1.3,
|
||||
"maxVolume": 1.6,
|
||||
@@ -751,7 +751,7 @@
|
||||
"level": "588.39",
|
||||
"capacity": 32,
|
||||
"storageDiff": -0.21,
|
||||
"inflow": "8.4",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.1",
|
||||
"volume": 0.32,
|
||||
"maxVolume": 1,
|
||||
@@ -813,7 +813,7 @@
|
||||
"level": "580.99",
|
||||
"capacity": 2.1,
|
||||
"storageDiff": -1.22,
|
||||
"inflow": "9.6",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0.03,
|
||||
"maxVolume": 1.4,
|
||||
@@ -841,9 +841,9 @@
|
||||
"name": "Humenice",
|
||||
"river": "",
|
||||
"priority": false,
|
||||
"level": "534.70",
|
||||
"level": "0.00",
|
||||
"capacity": 12.5,
|
||||
"storageDiff": -1.3,
|
||||
"storageDiff": 0,
|
||||
"inflow": "0.1",
|
||||
"outflow": "0.1",
|
||||
"volume": 0.1,
|
||||
@@ -852,7 +852,6 @@
|
||||
"lat": 48.784,
|
||||
"lng": 14.735,
|
||||
"sparkline": [
|
||||
534.69,
|
||||
534.69,
|
||||
0,
|
||||
534.69,
|
||||
@@ -863,7 +862,8 @@
|
||||
0,
|
||||
534.69,
|
||||
534.69,
|
||||
534.7
|
||||
534.7,
|
||||
0
|
||||
],
|
||||
"type": "lake"
|
||||
},
|
||||
@@ -875,7 +875,7 @@
|
||||
"level": "0.00",
|
||||
"capacity": 34.2,
|
||||
"storageDiff": 0,
|
||||
"inflow": "8.1",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.1",
|
||||
"volume": 0.41,
|
||||
"maxVolume": 1.2,
|
||||
@@ -906,7 +906,7 @@
|
||||
"level": "575.70",
|
||||
"capacity": 70.7,
|
||||
"storageDiff": -0.9,
|
||||
"inflow": "11.7",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 1.06,
|
||||
"maxVolume": 1.5,
|
||||
@@ -968,7 +968,7 @@
|
||||
"level": "448.81",
|
||||
"capacity": 100,
|
||||
"storageDiff": 0.02,
|
||||
"inflow": "4.7",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0.67,
|
||||
"maxVolume": 0.5,
|
||||
@@ -999,7 +999,7 @@
|
||||
"level": "463.42",
|
||||
"capacity": 4.6,
|
||||
"storageDiff": -0.61,
|
||||
"inflow": "6.0",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0,
|
||||
"maxVolume": 0.3,
|
||||
@@ -1030,7 +1030,7 @@
|
||||
"level": "668.40",
|
||||
"capacity": 100,
|
||||
"storageDiff": 0,
|
||||
"inflow": "11.0",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0.39,
|
||||
"maxVolume": 0.3,
|
||||
@@ -1061,7 +1061,7 @@
|
||||
"level": "385.01",
|
||||
"capacity": 100,
|
||||
"storageDiff": 0.01,
|
||||
"inflow": "6.3",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0.84,
|
||||
"maxVolume": 0.4,
|
||||
@@ -1092,7 +1092,7 @@
|
||||
"level": "678.60",
|
||||
"capacity": 80,
|
||||
"storageDiff": 0,
|
||||
"inflow": "8.2",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0.16,
|
||||
"maxVolume": 0.2,
|
||||
@@ -1123,7 +1123,7 @@
|
||||
"level": "632.76",
|
||||
"capacity": 4.4,
|
||||
"storageDiff": -0.13,
|
||||
"inflow": "10.5",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0,
|
||||
"maxVolume": 0.5,
|
||||
@@ -1154,7 +1154,7 @@
|
||||
"level": "635.70",
|
||||
"capacity": 0,
|
||||
"storageDiff": -0.66,
|
||||
"inflow": "5.4",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0,
|
||||
"maxVolume": 0.7,
|
||||
@@ -1185,7 +1185,7 @@
|
||||
"level": "416.72",
|
||||
"capacity": 90,
|
||||
"storageDiff": 0.04,
|
||||
"inflow": "7.8",
|
||||
"inflow": "0.0",
|
||||
"outflow": "0.0",
|
||||
"volume": 0.09,
|
||||
"maxVolume": 0.1,
|
||||
@@ -1213,18 +1213,17 @@
|
||||
"name": "Praha - Malá Chuchle",
|
||||
"river": "Vltava",
|
||||
"priority": false,
|
||||
"level": "49.00",
|
||||
"level": "46.00",
|
||||
"capacity": 0,
|
||||
"storageDiff": 0,
|
||||
"inflow": "0.0",
|
||||
"outflow": "60.9",
|
||||
"outflow": "53.9",
|
||||
"volume": 0,
|
||||
"maxVolume": 0,
|
||||
"navigationForbidden": false,
|
||||
"lat": 50.0294,
|
||||
"lng": 14.3986,
|
||||
"sparkline": [
|
||||
45,
|
||||
46,
|
||||
46,
|
||||
47,
|
||||
@@ -1235,7 +1234,8 @@
|
||||
47,
|
||||
47,
|
||||
46,
|
||||
49
|
||||
49,
|
||||
46
|
||||
],
|
||||
"type": "river"
|
||||
},
|
||||
@@ -1279,14 +1279,13 @@
|
||||
"capacity": 0,
|
||||
"storageDiff": 0,
|
||||
"inflow": "0.0",
|
||||
"outflow": "8.6",
|
||||
"outflow": "8.7",
|
||||
"volume": 0,
|
||||
"maxVolume": 0,
|
||||
"navigationForbidden": false,
|
||||
"lat": 49.9642,
|
||||
"lng": 14.0792,
|
||||
"sparkline": [
|
||||
100,
|
||||
100,
|
||||
99,
|
||||
99,
|
||||
@@ -1297,6 +1296,7 @@
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
96,
|
||||
96
|
||||
],
|
||||
"type": "river"
|
||||
@@ -1341,14 +1341,13 @@
|
||||
"capacity": 0,
|
||||
"storageDiff": 0,
|
||||
"inflow": "0.0",
|
||||
"outflow": "4.8",
|
||||
"outflow": "4.7",
|
||||
"volume": 0,
|
||||
"maxVolume": 0,
|
||||
"navigationForbidden": false,
|
||||
"lat": 49.3083,
|
||||
"lng": 14.1436,
|
||||
"sparkline": [
|
||||
41,
|
||||
44,
|
||||
45,
|
||||
45,
|
||||
@@ -1359,6 +1358,7 @@
|
||||
45,
|
||||
45,
|
||||
44,
|
||||
43,
|
||||
43
|
||||
],
|
||||
"type": "river"
|
||||
@@ -1383,9 +1383,9 @@
|
||||
25,
|
||||
25,
|
||||
25,
|
||||
26,
|
||||
26,
|
||||
25,
|
||||
26,
|
||||
26,
|
||||
25,
|
||||
25,
|
||||
25,
|
||||
@@ -1399,18 +1399,17 @@
|
||||
"name": "Bechyně",
|
||||
"river": "Lužnice",
|
||||
"priority": false,
|
||||
"level": "81.00",
|
||||
"level": "82.00",
|
||||
"capacity": 0,
|
||||
"storageDiff": 0,
|
||||
"inflow": "0.0",
|
||||
"outflow": "2.6",
|
||||
"outflow": "2.7",
|
||||
"volume": 0,
|
||||
"maxVolume": 0,
|
||||
"navigationForbidden": false,
|
||||
"lat": 49.2931,
|
||||
"lng": 14.4758,
|
||||
"sparkline": [
|
||||
82,
|
||||
80,
|
||||
79,
|
||||
82,
|
||||
@@ -1421,7 +1420,8 @@
|
||||
85,
|
||||
84,
|
||||
83,
|
||||
81
|
||||
81,
|
||||
82
|
||||
],
|
||||
"type": "river"
|
||||
},
|
||||
@@ -1589,7 +1589,7 @@
|
||||
"capacity": 0,
|
||||
"storageDiff": 0,
|
||||
"inflow": "0.0",
|
||||
"outflow": "4.6",
|
||||
"outflow": "4.7",
|
||||
"volume": 0,
|
||||
"maxVolume": 0,
|
||||
"navigationForbidden": false,
|
||||
@@ -1603,10 +1603,10 @@
|
||||
87,
|
||||
87,
|
||||
87,
|
||||
87,
|
||||
88,
|
||||
89,
|
||||
89,
|
||||
90,
|
||||
90
|
||||
],
|
||||
"type": "river"
|
||||
|
||||
@@ -121,12 +121,34 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
|
||||
.catch(err => console.error(err));
|
||||
|
||||
const internalId = lakeId ? lakeId.split('|')[0] : 'VLL1';
|
||||
const staticConfig = lakesConfig.find(l => l.id.split('|')[0] === internalId);
|
||||
|
||||
fetch(`/data/${internalId}.json?t=${Date.now()}`)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
let lastValidLevel: number | null = null;
|
||||
const formattedData = json.map((item: any) => {
|
||||
const outflow = item.flow === null || isNaN(item.flow) ? 0 : item.flow;
|
||||
let level = item.level === null || isNaN(item.level) ? 0 : item.level;
|
||||
|
||||
// Outlier/sensor glitch detection
|
||||
if (level > 0) {
|
||||
if (staticConfig && staticConfig.minLevel && staticConfig.maxLevel) {
|
||||
const minAllowed = staticConfig.minLevel - 5;
|
||||
const maxAllowed = staticConfig.maxLevel + 5;
|
||||
if (level < minAllowed || level > maxAllowed) {
|
||||
// Glitch detected, fallback to last known valid level
|
||||
level = lastValidLevel !== null ? lastValidLevel : (staticConfig.minLevel + staticConfig.maxLevel) / 2;
|
||||
} else {
|
||||
lastValidLevel = level;
|
||||
}
|
||||
} else {
|
||||
lastValidLevel = level;
|
||||
}
|
||||
} else if (lastValidLevel !== null) {
|
||||
// Level is 0 or less, fallback
|
||||
level = lastValidLevel;
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: item.timestamp,
|
||||
@@ -134,7 +156,7 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
|
||||
day: '2-digit', month: '2-digit', year: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
}),
|
||||
level: item.level === null || isNaN(item.level) ? 0 : item.level,
|
||||
level: level,
|
||||
outflow: outflow,
|
||||
inflow: item.inflow || 0,
|
||||
volume: item.volume || 0,
|
||||
@@ -393,13 +415,58 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
|
||||
|
||||
{/* Data Series */}
|
||||
{limits && limits.map((limit, idx) => (
|
||||
<ReferenceLine key={idx} yAxisId="left" y={limit.level} stroke={limit.type === 'danger' ? 'var(--color-red)' : '#f59e0b'} strokeDasharray="3 3" label={{ position: 'insideBottomLeft', value: language === 'cs' ? `${limit.labelCs} (${limit.level.toFixed(2)} m n. m.)` : `${limit.labelEn} (${limit.level.toFixed(2)} m a.s.l.)`, fill: limit.type === 'danger' ? 'var(--color-red)' : '#f59e0b', fontSize: 12 }} />
|
||||
<ReferenceLine key={idx} yAxisId="left" y={limit.level} stroke={limit.type === 'danger' ? 'var(--color-red)' : '#f59e0b'} strokeDasharray="3 3" label={{ position: 'insideBottomLeft', value: language === 'cs' ? `${limit.labelCs} (${limit.level.toFixed(2)} m n. m.)` : `${limit.labelEn} (${limit.level.toFixed(2)} m a.s.l.)`, fill: limit.type === 'danger' ? 'var(--color-red)' : '#f59e0b', fontSize: 11 }} />
|
||||
))}
|
||||
{!isRiver && staticConfig?.maxLevel && (
|
||||
<ReferenceLine yAxisId="left" y={staticConfig.maxLevel} stroke="var(--color-orange)" strokeDasharray="3 3" label={{ position: 'insideBottomLeft', value: language === 'cs' ? `Maximální retenční hladina (${staticConfig.maxLevel.toFixed(2)} m n. m.)` : `Max retention level (${staticConfig.maxLevel.toFixed(2)} m a.s.l.)`, fill: 'var(--color-orange)', fontSize: 12 }} />
|
||||
)}
|
||||
{!isRiver && staticConfig?.storageLevel && (
|
||||
<ReferenceLine yAxisId="left" y={staticConfig.storageLevel} stroke="#a855f7" strokeDasharray="3 3" label={{ position: 'insideBottomLeft', value: language === 'cs' ? `Hladina zásobního prostoru (${staticConfig.storageLevel.toFixed(2)} m n. m.)` : `Storage space level (${staticConfig.storageLevel.toFixed(2)} m a.s.l.)`, fill: '#a855f7', fontSize: 12 }} />
|
||||
{!isRiver && staticConfig?.maxLevel && staticConfig?.storageLevel && Math.abs(staticConfig.maxLevel - staticConfig.storageLevel) < 0.05 ? (
|
||||
<ReferenceLine
|
||||
yAxisId="left"
|
||||
y={staticConfig.maxLevel}
|
||||
stroke="var(--color-orange)"
|
||||
strokeDasharray="3 3"
|
||||
label={{
|
||||
position: 'insideBottomRight',
|
||||
value: language === 'cs'
|
||||
? `Max. retenční / zásobní hladina (${staticConfig.maxLevel.toFixed(2)} m n. m.)`
|
||||
: `Max retention / storage level (${staticConfig.maxLevel.toFixed(2)} m a.s.l.)`,
|
||||
fill: 'var(--color-orange)',
|
||||
fontSize: 11
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{!isRiver && staticConfig?.maxLevel && (
|
||||
<ReferenceLine
|
||||
yAxisId="left"
|
||||
y={staticConfig.maxLevel}
|
||||
stroke="var(--color-orange)"
|
||||
strokeDasharray="3 3"
|
||||
label={{
|
||||
position: 'insideBottomRight',
|
||||
value: language === 'cs'
|
||||
? `Maximální retenční hladina (${staticConfig.maxLevel.toFixed(2)} m n. m.)`
|
||||
: `Max retention level (${staticConfig.maxLevel.toFixed(2)} m a.s.l.)`,
|
||||
fill: 'var(--color-orange)',
|
||||
fontSize: 11
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isRiver && staticConfig?.storageLevel && (
|
||||
<ReferenceLine
|
||||
yAxisId="left"
|
||||
y={staticConfig.storageLevel}
|
||||
stroke="#a855f7"
|
||||
strokeDasharray="3 3"
|
||||
label={{
|
||||
position: 'insideBottomLeft',
|
||||
value: language === 'cs'
|
||||
? `Hladina zásobního prostoru (${staticConfig.storageLevel.toFixed(2)} m n. m.)`
|
||||
: `Storage space level (${staticConfig.storageLevel.toFixed(2)} m a.s.l.)`,
|
||||
fill: '#a855f7',
|
||||
fontSize: 11
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Area yAxisId="left" type={curveType} dataKey="level" stroke="var(--color-cyan)" strokeWidth={2} fillOpacity={1} fill="url(#colorLevel)" isAnimationActive={animate} />
|
||||
<Line yAxisId="right" type={curveType} dataKey="outflow" stroke="var(--color-red)" strokeWidth={2} dot={false} isAnimationActive={animate} />
|
||||
|
||||
Reference in New Issue
Block a user