fix(dashboardAPI): clean stat panels — dedup stray-tag series, value-only text, meter units

Three display defects surfaced when rendering the live PS/pump/MGC dashboards:

1. Doubled values everywhere. EVOLV telemetry historically carried stray tags
   (tagcode="undefined"/uuid="null") that newer writes dropped, so InfluxDB holds
   two series per field and last() returned two of everything (e.g. Time Left,
   Runtime, Heights). Add |> group(columns:["_field"]) before last()/aggregateWindow
   in every template query so each field collapses to one value/line regardless of
   tag-set history.

2. fields:"/.*/" also rendered the _time column as a stat value. For single-field
   string panels (Direction, Flow Source, Mode, State, Prediction Quality) append
   |> keep(columns:["_value"]); for mixed string+numeric panels (valve/vgc/monster)
   drop _time/_start/_stop instead.

3. Level/Heights showed "0.12 min" — Grafana unit id "m" means minutes, not meters.
   Change to lengthm; normalize m³->m3, m³/h->m3/h on pumpingStation.

Verified live via headless screenshots: PS, pump, and measurement dashboards now
show single clean values with correct units.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-27 21:42:47 +02:00
parent 8bfc67c610
commit c4f5b68c6a
11 changed files with 409 additions and 157 deletions

View File

@@ -69,7 +69,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"direction\")\n |> last()",
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"direction\")\n |> group(columns:[\"_field\"])\n |> last()\n |> keep(columns:[\"_value\"])",
"refId": "A"
}
],
@@ -127,7 +127,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"timeleft\")\n |> last()",
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"timeleft\")\n |> group(columns:[\"_field\"])\n |> last()",
"refId": "A"
}
],
@@ -177,7 +177,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"flowSource\")\n |> last()",
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field==\"flowSource\")\n |> group(columns:[\"_field\"])\n |> last()\n |> keep(columns:[\"_value\"])",
"refId": "A"
}
],
@@ -245,7 +245,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^volumePercent\\.predicted\\.atequipment/)\n |> last()",
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^volumePercent\\.predicted\\.atequipment/)\n |> group(columns:[\"_field\"])\n |> last()",
"refId": "A"
}
],
@@ -264,7 +264,7 @@
},
"fieldConfig": {
"defaults": {
"unit": "m",
"unit": "lengthm",
"thresholds": {
"mode": "absolute",
"steps": [
@@ -295,7 +295,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^level\\.predicted\\.atequipment/)\n |> last()",
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^level\\.predicted\\.atequipment/)\n |> group(columns:[\"_field\"])\n |> last()",
"refId": "A"
}
],
@@ -325,7 +325,7 @@
},
"fieldConfig": {
"defaults": {
"unit": "m",
"unit": "lengthm",
"custom": {
"drawStyle": "line",
"lineWidth": 2,
@@ -352,7 +352,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^level\\.(predicted|measured)\\.atequipment/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^level\\.(predicted|measured)\\.atequipment/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"refId": "A"
}
],
@@ -371,7 +371,7 @@
},
"fieldConfig": {
"defaults": {
"unit": "m\u00b3",
"unit": "m3",
"custom": {
"drawStyle": "line",
"lineWidth": 2,
@@ -398,7 +398,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^volume\\.predicted\\.atequipment/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^volume\\.predicted\\.atequipment/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"refId": "A"
}
],
@@ -428,7 +428,7 @@
},
"fieldConfig": {
"defaults": {
"unit": "m\u00b3/h",
"unit": "m3/h",
"custom": {
"drawStyle": "line",
"lineWidth": 2,
@@ -455,7 +455,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^netFlowRate\\.(predicted|measured)\\.atequipment/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^netFlowRate\\.(predicted|measured)\\.atequipment/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"refId": "A"
}
],
@@ -475,7 +475,7 @@
},
"fieldConfig": {
"defaults": {
"unit": "m\u00b3/h",
"unit": "m3/h",
"custom": {
"drawStyle": "line",
"lineWidth": 2,
@@ -502,7 +502,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^flow\\.(predicted|measured)\\.(upstream|in|out|overflow)/)\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"query": "from(bucket: \"${bucket}\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and r._field =~ /^flow\\.(predicted|measured)\\.(upstream|in|out|overflow)/)\n |> group(columns:[\"_field\"])\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
"refId": "A"
}
],
@@ -533,7 +533,7 @@
},
"fieldConfig": {
"defaults": {
"unit": "m",
"unit": "lengthm",
"thresholds": {
"mode": "absolute",
"steps": [
@@ -564,7 +564,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"inflowLevel\" or r._field==\"outflowLevel\" or r._field==\"overflowLevel\" or r._field==\"heightBasin\"))\n |> last()",
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"inflowLevel\" or r._field==\"outflowLevel\" or r._field==\"overflowLevel\" or r._field==\"heightBasin\"))\n |> group(columns:[\"_field\"])\n |> last()",
"refId": "A"
}
],
@@ -584,7 +584,7 @@
},
"fieldConfig": {
"defaults": {
"unit": "m\u00b3",
"unit": "m3",
"thresholds": {
"mode": "absolute",
"steps": [
@@ -615,7 +615,7 @@
},
"targets": [
{
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"maxVol\" or r._field==\"minVol\" or r._field==\"maxVolAtOverflow\" or r._field==\"minVolAtOutflow\" or r._field==\"minVolAtInflow\"))\n |> last()",
"query": "from(bucket: \"${bucket}\")\n |> range(start: -7d)\n |> filter(fn:(r) => r._measurement==\"${measurement}\" and (r._field==\"maxVol\" or r._field==\"minVol\" or r._field==\"maxVolAtOverflow\" or r._field==\"minVolAtOutflow\" or r._field==\"minVolAtInflow\"))\n |> group(columns:[\"_field\"])\n |> last()",
"refId": "A"
}
],