From f8a7be7fa3bc886da723ed2f38f7d5c2cf0f91ac Mon Sep 17 00:00:00 2001 From: David Fencl Date: Mon, 8 Jun 2026 19:45:37 +0200 Subject: [PATCH] feat: implement sensor glitch detection for water levels and update data cleaning logic --- README.md | 23 +++++++--- package.json | 1 + public/data/BEBE.json | 9 ++++ public/data/BEPL.json | 9 ++++ public/data/BIBI.json | 2 +- public/data/CPDR.json | 2 +- public/data/CPZA.json | 2 +- public/data/CRSO.json | 2 +- public/data/HEVR.json | 11 ++++- public/data/KLDP.json | 2 +- public/data/KLHP.json | 2 +- public/data/LUBE.json | 9 ++++ public/data/OPOB.json | 2 +- public/data/OTPI.json | 9 ++++ public/data/OTSU.json | 9 ++++ public/data/PPPI.json | 2 +- public/data/SAPI.json | 2 +- public/data/SAZR.json | 9 ++++ public/data/SCHU.json | 9 ++++ public/data/SPKA.json | 2 +- public/data/SPNE.json | 2 +- public/data/SPZH.json | 2 +- public/data/STST.json | 2 +- public/data/SVSV.json | 2 +- public/data/VLCH.json | 9 ++++ public/data/VLHN.json | 2 +- public/data/lakes_index.json | 68 ++++++++++++++--------------- src/components/LakeDetail.tsx | 81 ++++++++++++++++++++++++++++++++--- 28 files changed, 222 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 6722df1..2153992 100644 --- a/README.md +++ b/README.md @@ -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ů). diff --git a/package.json b/package.json index bfaf6ba..5ba5e87 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/public/data/BEBE.json b/public/data/BEBE.json index ba55352..b432063 100644 --- a/public/data/BEBE.json +++ b/public/data/BEBE.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/BEPL.json b/public/data/BEPL.json index 6bd1b60..8f919de 100644 --- a/public/data/BEPL.json +++ b/public/data/BEPL.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/BIBI.json b/public/data/BIBI.json index 6913ddc..1ac160e 100644 --- a/public/data/BIBI.json +++ b/public/data/BIBI.json @@ -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 diff --git a/public/data/CPDR.json b/public/data/CPDR.json index 9bcac08..8f5343d 100644 --- a/public/data/CPDR.json +++ b/public/data/CPDR.json @@ -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 diff --git a/public/data/CPZA.json b/public/data/CPZA.json index fb4aa11..b5f4ab1 100644 --- a/public/data/CPZA.json +++ b/public/data/CPZA.json @@ -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 diff --git a/public/data/CRSO.json b/public/data/CRSO.json index 68f2d57..2b9cd86 100644 --- a/public/data/CRSO.json +++ b/public/data/CRSO.json @@ -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 diff --git a/public/data/HEVR.json b/public/data/HEVR.json index 1b7cf33..8a2b6b8 100644 --- a/public/data/HEVR.json +++ b/public/data/HEVR.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/KLDP.json b/public/data/KLDP.json index f4f7509..e201093 100644 --- a/public/data/KLDP.json +++ b/public/data/KLDP.json @@ -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 diff --git a/public/data/KLHP.json b/public/data/KLHP.json index c874f6a..0a869a8 100644 --- a/public/data/KLHP.json +++ b/public/data/KLHP.json @@ -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 diff --git a/public/data/LUBE.json b/public/data/LUBE.json index 973d021..1c26713 100644 --- a/public/data/LUBE.json +++ b/public/data/LUBE.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/OPOB.json b/public/data/OPOB.json index 7a34ca6..657b33a 100644 --- a/public/data/OPOB.json +++ b/public/data/OPOB.json @@ -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 diff --git a/public/data/OTPI.json b/public/data/OTPI.json index b99f54a..51584db 100644 --- a/public/data/OTPI.json +++ b/public/data/OTPI.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/OTSU.json b/public/data/OTSU.json index 40abf75..3d9ff0e 100644 --- a/public/data/OTSU.json +++ b/public/data/OTSU.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/PPPI.json b/public/data/PPPI.json index 3874fcd..07fe164 100644 --- a/public/data/PPPI.json +++ b/public/data/PPPI.json @@ -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 diff --git a/public/data/SAPI.json b/public/data/SAPI.json index 0574112..9cd198e 100644 --- a/public/data/SAPI.json +++ b/public/data/SAPI.json @@ -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 diff --git a/public/data/SAZR.json b/public/data/SAZR.json index f9ba61b..73fae6c 100644 --- a/public/data/SAZR.json +++ b/public/data/SAZR.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/SCHU.json b/public/data/SCHU.json index 96d5321..e65ecee 100644 --- a/public/data/SCHU.json +++ b/public/data/SCHU.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/SPKA.json b/public/data/SPKA.json index 1fe7734..9bf09f3 100644 --- a/public/data/SPKA.json +++ b/public/data/SPKA.json @@ -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 diff --git a/public/data/SPNE.json b/public/data/SPNE.json index 64b2ec0..70d7b51 100644 --- a/public/data/SPNE.json +++ b/public/data/SPNE.json @@ -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 diff --git a/public/data/SPZH.json b/public/data/SPZH.json index 63c1db0..9fc4920 100644 --- a/public/data/SPZH.json +++ b/public/data/SPZH.json @@ -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 diff --git a/public/data/STST.json b/public/data/STST.json index 8f6ec22..d633880 100644 --- a/public/data/STST.json +++ b/public/data/STST.json @@ -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 diff --git a/public/data/SVSV.json b/public/data/SVSV.json index 91e247f..e23c781 100644 --- a/public/data/SVSV.json +++ b/public/data/SVSV.json @@ -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 diff --git a/public/data/VLCH.json b/public/data/VLCH.json index 082942a..db62b15 100644 --- a/public/data/VLCH.json +++ b/public/data/VLCH.json @@ -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 } ] \ No newline at end of file diff --git a/public/data/VLHN.json b/public/data/VLHN.json index 1ce7cc5..5c55457 100644 --- a/public/data/VLHN.json +++ b/public/data/VLHN.json @@ -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 diff --git a/public/data/lakes_index.json b/public/data/lakes_index.json index 333d336..71186aa 100644 --- a/public/data/lakes_index.json +++ b/public/data/lakes_index.json @@ -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" diff --git a/src/components/LakeDetail.tsx b/src/components/LakeDetail.tsx index 9768cfa..138b52f 100644 --- a/src/components/LakeDetail.tsx +++ b/src/components/LakeDetail.tsx @@ -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) => ( - + ))} - {!isRiver && staticConfig?.maxLevel && ( - - )} - {!isRiver && staticConfig?.storageLevel && ( - + {!isRiver && staticConfig?.maxLevel && staticConfig?.storageLevel && Math.abs(staticConfig.maxLevel - staticConfig.storageLevel) < 0.05 ? ( + + ) : ( + <> + {!isRiver && staticConfig?.maxLevel && ( + + )} + {!isRiver && staticConfig?.storageLevel && ( + + )} + )}