feat: implement sensor glitch detection for water levels and update data cleaning logic
This commit is contained in:
@@ -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