Threshold labels were sitting right on top of their lines (label center
at line_y - 8) and were right-aligned at the tank's right edge. They now:
- Sit clearly above the line (label bottom 6 px above) by default, or
below the line (label top 6 px below) when an adjacent threshold is
closer than 24 px (would crowd both labels above their lines). For
the current basin config this puts overflowLevel + inflowLevel +
dryRunLevel ABOVE their lines, and highSafety + outflowLevel BELOW.
- Are centered horizontally in the tank (name at left:115 width:95
right-aligned, value at left:215 width:80 left-aligned) so the
combined phrase "overflowLevel 3.22 m" reads as one centered string.
Value width 60 → 80 so 'mm'-formatted small-meter values don't wrap to
two lines. Footer floor moved to y:728 to keep clear of the BELOW labels
near the tank floor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Canvas frame height 600 → 760 px and tank rectangle height 520 → 680 px
so the visual fills the card aspect (taller than wide). Floor footer
moves to y=702 (was 542) to stay just below the new tank floor.
In-canvas bottom readouts (level / volume / fill mini-stats) removed —
they were redundant with the Status row Level stat, the bar gauge, and
the Level/Volume timeseries, and were getting clipped below the card's
visible area anyway. The basin canvas now shows only basin-structure
information (geometry, zones, thresholds).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tank visual now fills the Canvas card edge-to-edge instead of leaving
horizontal padding for external name + value label columns. Each
threshold's name and value sit INSIDE the tank near its line ('overflow-
Level 3.22 m', 'highSafety 3.16 m', etc.), right-aligned at the tank's
inner right edge.
Tank rectangle, zone tints, threshold lines, header rim, and footer floor
all widen from left:80 width:200 → left:10 width:380 to fill the frame.
Label colors darkened slightly (e.g. #e54343 → #c92020) to keep contrast
against the semi-transparent zone tint backgrounds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Canvas frame logical width: 480 → 400 px (was leaving ~104 px of empty
space on the right inside the card). Panel grid width: 8 → 6 cols so the
card pixel width matches the frame logical width and content fills it
without horizontal padding, instead of letterboxing in the centre.
Bottom readouts repositioned to fit within 400 px (level/volume/fill all
inside the new frame width) and per-field decimal overrides added so unit
formatting doesn't truncate ('100.00 mm' fits in the value label width).
Freed grid cols flow to the Level + Volume timeseries on the right
(w:12 → 14 each, x:12 → 10) so the right half consumes the rest of the
row without a gap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Basin row grows from h:10 to h:20. Bar gauge, Canvas, and Level/Volume
timeseries all scale proportionally. Canvas internal frame doubled (480x600)
and tank rectangle stretched (height 240→520) so the canvas content fills
the panel instead of letterboxing in the top half. Bottom readouts moved
from y=280 to y=562 to stay just below the taller tank floor.
Flow row + its panels shifted down by 10 grid rows to make room.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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