Replaces the configuration row's Heights + Volume Limits stat panels and
the radial Fill % gauge with an integrated basin visual that conveys tank
geometry and live water level at a glance.
Configuration row → Basin row:
- Vertical bar gauge bound to level (m) with min=0/max=basinHeight and
thresholds at outflow/dryRun/inflow/highSafety/overflow safety levels.
- Canvas panel with tank outline, zone tints (dead/operating/highSafety/
spill), threshold lines + named labels, and live numeric readouts for
each threshold value plus current level/volume/fill at the bottom.
- Level + Volume timeseries moved next to the basin visual so the row
reads as basin → trends left-to-right.
Other layout polish:
- Status row Fill % gauge removed; remaining 4 stats widen to w:6 each.
- Old "Basin" row header dropped (its panels migrated into the new row).
- Configuration row renamed to "Basin".
Mechanics:
- dashboardAPI substitutes mustache {{var}} placeholders in templates at
JSON.parse time. Per-softwareType var sets live in _templateVarsForNode;
pumpingStation gets basin geometry + derived safety levels + canvas
pixel y-positions + min-gap-enforced label positions.
- Mustache braces stay distinct from Grafana's ${var} dashboard variables.
- Canvas Flux query pivots heights + predicted level/volume/percent into
one row with normalized field names so metric-value elements can bind.
No node-side telemetry change: dryRunLevel + highVolumeSafetyLevel already
reach Influx via getOutput() (specificClass.js:248,250) and outputUtils
iterates every key with no filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Grafana Stat/Gauge panels default the Fields option to "Numeric Fields"
(reduceOptions.fields == ""), so string-valued fields (mode, state,
movementState, direction, flowSource, predictionQuality, running) are excluded
and the panel renders "No data" even though the Flux query (last()) returns the
string correctly.
Set reduceOptions.fields = "/.*/" ("All fields") on every stat panel bound to a
string field across machine, machineGroup, pumpingStation, valve,
valveGroupControl, and monster templates. lastNotNull calc was already correct.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generate dashboards for an entire parent-child subtree from a single root
registration (pre-order, cycle/diamond-safe), so wiring only the subtree root
(e.g. pumpingStation) to dashboardAPI yields dashboards for every descendant.
Fix two contract drifts that left generated panels blank against live telemetry:
- _measurement var now mirrors outputUtils.formatMsg (general.name ||
<softwareType>_<id>); previously it always used the fallback form, so any
named node's dashboard queried a non-existent series.
- pumpingStation template field keys realigned to emitted telemetry
(flow.*.{upstream,out,overflow}, netFlowRate.measured, inflowLevel/
outflowLevel/overflowLevel, maxVolAtOverflow/minVolAt{Inflow,Outflow}).
Adds template alias resolution (softwareType -> shared template file) and
locks parity with slice44/45/46 tests + output manifest. 67/67 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When generateDashboardsForGraph builds a root dashboard for a parent (e.g.
pumpingStation) and a set of child dashboards (e.g. measurements), it now
removes any non-row panel from the root whose meta.emittedFields are fully
covered by panels declared in any child dashboard. Result: the parent
shows only metrics its children don't already plot, eliminating redundant
rendering of the same series in two dashboards.
- config/pumpingStation.json: 11 non-row panels annotated with
meta.emittedFields (Direction, Time Left, Flow Source, Fill %, Level (x2),
Volume, Net Flow Rate, Inflow+Outflow, Heights, Volume Limits).
- src/specificClass.js: generateDashboardsForGraph runs the parent-panel
filter after composing children; row panels always kept; panels without
emittedFields declaration always kept (no silent removal).
- test/basic/slice39-no-duplication.basic.test.js: 4 cases — annotation
presence, child-covered removal, no-overlap preservation, row preservation.
Closes#39