diff --git a/docker/grafana/provisioning/dashboards/coresync-frost-demo.json b/docker/grafana/provisioning/dashboards/coresync-frost-demo.json new file mode 100644 index 0000000..d411344 --- /dev/null +++ b/docker/grafana/provisioning/dashboards/coresync-frost-demo.json @@ -0,0 +1,546 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { "type": "grafana", "uid": "-- Grafana --" }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { "limit": 100, "matchAny": false, "tags": [], "type": "dashboard" }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "id": 50, + "type": "text", + "title": "How to read this dashboard", + "gridPos": { "h": 5, "w": 24, "x": 0, "y": 0 }, + "options": { + "mode": "markdown", + "content": "**Each metric below is mentally verifiable. Hover any panel title for its definition.**\n\n| Term | Definition | Where it comes from |\n|---|---|---|\n| **raw** | every numeric sample EVOLV nodes wrote to InfluxDB before CoreSync | `_measurement = FROST Flow Sensor FT-101` (field `mAbs`) and `_measurement = rotatingmachine_cse_rm_pump` (5 named fields) |\n| **knots** | the CoreSync-reduced samples actually kept | `_measurement = coresync_knots`, `_field = knot` |\n| **reductionPct** | `100 × (1 − knots/raw)` — % of writes CoreSync skipped (higher is better) | computed in-query |\n| **kept fraction** | `knots / raw` (inverse; lower is better) | computed in-query |\n| **reason** | why CoreSync emitted a knot: `first` (1st sample), `angle-change` (slope direction shifted), `max-gap` (silent too long), `flush` (periodic) | tag on `coresync_knots` |\n\n**Sanity checks:** open the Per-stream table — `raw × (1 − reductionPct/100) = knots` should hold to the integer. The headline scoreboard sums all rows. The Knot interarrival panel should never go below ~2 s for streams updating at 1 Hz (if it does, CoreSync is over-emitting → burst-window bug)." + } + }, + { + "id": 100, + "type": "row", + "title": "Scoreboard — raw vs knots over the selected time range", + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 }, + "collapsed": false, + "panels": [] + }, + { + "id": 1, + "type": "stat", + "title": "Raw samples written", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "Total raw sample writes from EVOLV nodes into InfluxDB across all known CoreSync-tracked streams (FT-101 flow, P-101 pressures, efficiency, cog, SEC). This is what InfluxDB would store WITHOUT CoreSync compression.", + "gridPos": { "h": 4, "w": 6, "x": 0, "y": 6 }, + "fieldConfig": { + "defaults": { + "color": { "mode": "fixed", "fixedColor": "#1f6feb" }, + "unit": "short", + "decimals": 0, + "mappings": [] + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, + "textMode": "auto" + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "raw_ft101 = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"FROST Flow Sensor FT-101\" and r._field == \"mAbs\") |> count() |> keep(columns:[\"_value\"])\nraw_rm = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\") |> filter(fn:(r)=> r._field == \"pressure.measured.downstream.dashboard-sim-downstream\" or r._field == \"pressure.measured.upstream.dashboard-sim-upstream\" or r._field == \"efficiency.predicted.atequipment.cse_rm_pump\" or r._field == \"cog\" or r._field == \"specificEnergyConsumption.predicted.atequipment.cse_rm_pump\") |> group(columns:[\"_field\"]) |> count() |> group() |> keep(columns:[\"_value\"])\nunion(tables:[raw_ft101, raw_rm]) |> sum()" + } + ] + }, + { + "id": 2, + "type": "stat", + "title": "CoreSync knots kept", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "Total CoreSync knots actually written to InfluxDB. Each knot represents a 'meaningful' sample chosen by the angle-change reducer plus periodic flushes.", + "gridPos": { "h": 4, "w": 6, "x": 6, "y": 6 }, + "fieldConfig": { + "defaults": { + "color": { "mode": "fixed", "fixedColor": "#2f9e44" }, + "unit": "short", + "decimals": 0, + "mappings": [] + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, + "textMode": "auto" + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"coresync_knots\" and r._field == \"knot\") |> group() |> count()" + } + ] + }, + { + "id": 3, + "type": "gauge", + "title": "Reduction % (1 − knots / raw)", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "Headline compression number. 100% = perfect compression (impossible). 0% = CoreSync is keeping every sample (broken). Sweet spot for the FROST demo: 60–95% depending on stream.", + "gridPos": { "h": 4, "w": 6, "x": 12, "y": 6 }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "min": 0, + "max": 100, + "unit": "percent", + "decimals": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "#d64545", "value": null }, + { "color": "#e8a23a", "value": 40 }, + { "color": "#2f9e44", "value": 70 } + ] + } + }, + "overrides": [] + }, + "options": { + "orientation": "auto", + "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "import \"array\"\nraw_ft101 = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"FROST Flow Sensor FT-101\" and r._field == \"mAbs\") |> count() |> keep(columns:[\"_value\"])\nraw_rm = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\") |> filter(fn:(r)=> r._field == \"pressure.measured.downstream.dashboard-sim-downstream\" or r._field == \"pressure.measured.upstream.dashboard-sim-upstream\" or r._field == \"efficiency.predicted.atequipment.cse_rm_pump\" or r._field == \"cog\" or r._field == \"specificEnergyConsumption.predicted.atequipment.cse_rm_pump\") |> group(columns:[\"_field\"]) |> count() |> group() |> keep(columns:[\"_value\"])\nraw_total = union(tables:[raw_ft101, raw_rm]) |> sum() |> findRecord(fn:(key)=> true, idx:0)\nknot_total = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"coresync_knots\" and r._field == \"knot\") |> group() |> count() |> findRecord(fn:(key)=> true, idx:0)\nrawN = if exists raw_total._value then float(v: raw_total._value) else 0.0\nknotN = if exists knot_total._value then float(v: knot_total._value) else 0.0\narray.from(rows: [{_value: (if rawN > 0.0 then 100.0 * (1.0 - knotN / rawN) else 0.0)}])" + } + ] + }, + { + "id": 4, + "type": "stat", + "title": "Approx. bytes saved", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "Rough estimate: (raw − knots) × 80 bytes per line-protocol record. Order-of-magnitude only; actual savings depend on tag cardinality and retention policy.", + "gridPos": { "h": 4, "w": 6, "x": 18, "y": 6 }, + "fieldConfig": { + "defaults": { + "color": { "mode": "fixed", "fixedColor": "#a347e1" }, + "unit": "decbytes", + "decimals": 0, + "mappings": [] + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "values": false, "calcs": ["lastNotNull"], "fields": "" }, + "textMode": "auto" + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "import \"array\"\nraw_ft101 = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"FROST Flow Sensor FT-101\" and r._field == \"mAbs\") |> count() |> keep(columns:[\"_value\"])\nraw_rm = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\") |> filter(fn:(r)=> r._field == \"pressure.measured.downstream.dashboard-sim-downstream\" or r._field == \"pressure.measured.upstream.dashboard-sim-upstream\" or r._field == \"efficiency.predicted.atequipment.cse_rm_pump\" or r._field == \"cog\" or r._field == \"specificEnergyConsumption.predicted.atequipment.cse_rm_pump\") |> group(columns:[\"_field\"]) |> count() |> group() |> keep(columns:[\"_value\"])\nraw_total = union(tables:[raw_ft101, raw_rm]) |> sum() |> findRecord(fn:(key)=> true, idx:0)\nknot_total = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"coresync_knots\" and r._field == \"knot\") |> group() |> count() |> findRecord(fn:(key)=> true, idx:0)\nrawN = if exists raw_total._value then raw_total._value else 0\nknotN = if exists knot_total._value then knot_total._value else 0\narray.from(rows: [{_value: (rawN - knotN) * 80}])" + } + ] + }, + { + "id": 200, + "type": "row", + "title": "Per-stream verification table — every line is mentally checkable", + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 10 }, + "collapsed": false, + "panels": [] + }, + { + "id": 5, + "type": "table", + "title": "Per-stream raw vs knots vs reduction %", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "One row per CoreSync stream. raw = raw samples written to InfluxDB. knots = CoreSync-kept samples. reductionPct = 100 × (1 − knots/raw). Streams with reductionPct < 50 are flagged red. Each cell is line-of-sight to a known Flux query — see the dashboard's 'How to read' panel at top.", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 11 }, + "fieldConfig": { + "defaults": { + "custom": { "align": "auto", "cellOptions": { "type": "auto" }, "inspect": false }, + "color": { "mode": "thresholds" } + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "reductionPct" }, + "properties": [ + { "id": "unit", "value": "percent" }, + { "id": "decimals", "value": 1 }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "gradient" } }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { "color": "#d64545", "value": null }, + { "color": "#e8a23a", "value": 40 }, + { "color": "#2f9e44", "value": 70 } + ] + } + } + ] + }, + { + "matcher": { "id": "byName", "options": "raw" }, + "properties": [{ "id": "unit", "value": "short" }, { "id": "decimals", "value": 0 }] + }, + { + "matcher": { "id": "byName", "options": "knots" }, + "properties": [{ "id": "unit", "value": "short" }, { "id": "decimals", "value": 0 }] + } + ] + }, + "options": { + "cellHeight": "sm", + "footer": { "countRows": false, "fields": "", "reducer": ["sum"], "show": false }, + "showHeader": true, + "sortBy": [{ "desc": false, "displayName": "reductionPct" }] + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "import \"join\"\n\nraw_ft101 = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"FROST Flow Sensor FT-101\" and r._field == \"mAbs\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"P-101:flow:measured:upstream:FT-101\", raw:r._value }))\nraw_rm_pdn = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"pressure.measured.downstream.dashboard-sim-downstream\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:pressure:measured:downstream:dashboard-sim-downstream\", raw:r._value }))\nraw_rm_pup = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"pressure.measured.upstream.dashboard-sim-upstream\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:pressure:measured:upstream:dashboard-sim-upstream\", raw:r._value }))\nraw_rm_eff = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"efficiency.predicted.atequipment.cse_rm_pump\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:efficiency:predicted:atequipment:cse_rm_pump\", raw:r._value }))\nraw_rm_cog = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"cog\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:cog:measured:atEquipment:MEASURED-p-101\", raw:r._value }))\nraw_rm_sec = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"specificEnergyConsumption.predicted.atequipment.cse_rm_pump\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:specificenergyconsumption:predicted:atequipment:cse_rm_pump\", raw:r._value }))\n\nraw = union(tables:[raw_ft101, raw_rm_pdn, raw_rm_pup, raw_rm_eff, raw_rm_cog, raw_rm_sec]) |> group(columns:[\"streamKey\"])\n\nknots = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement==\"coresync_knots\" and r._field==\"knot\") |> keep(columns:[\"streamKey\",\"_value\"]) |> group(columns:[\"streamKey\"]) |> count(column:\"_value\") |> rename(columns:{_value:\"knots\"})\n\njoin.left(left: raw, right: knots, on: (l, r) => l.streamKey == r.streamKey, as: (l, r) => ({ streamKey: l.streamKey, raw: l.raw, knots: if exists r.knots then r.knots else 0 }))\n |> map(fn:(r)=> ({ r with reductionPct: if r.raw > 0 then 100.0 * (1.0 - float(v:r.knots) / float(v:r.raw)) else 0.0 }))\n |> group()\n |> sort(columns:[\"reductionPct\"])" + } + ], + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { "streamKey": 0, "raw": 1, "knots": 2, "reductionPct": 3 }, + "renameByName": {} + } + } + ] + }, + { + "id": 300, + "type": "row", + "title": "Signal reconstruction — do the knots faithfully represent the raw signal?", + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 19 }, + "collapsed": false, + "panels": [] + }, + { + "id": 6, + "type": "timeseries", + "title": "Flow FT-101 — raw 1 Hz vs CoreSync knots (m³/h)", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "FT-101 raw flow values vs the CoreSync knots written for the same stream. If knots reconstruct the signal, big dots sit exactly on the raw line at every direction change. Same Y-axis so they should overlap.", + "gridPos": { "h": 10, "w": 12, "x": 0, "y": 20 }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "m³/h", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 5, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { "type": "linear" }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "flowm3h" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "knot (m³/h)" }, + "properties": [ + { "id": "custom.drawStyle", "value": "points" }, + { "id": "custom.pointSize", "value": 10 }, + { "id": "custom.showPoints", "value": "always" }, + { "id": "color", "value": { "mode": "fixed", "fixedColor": "#d64545" } } + ] + }, + { + "matcher": { "id": "byName", "options": "raw (m³/h)" }, + "properties": [ + { "id": "custom.lineWidth", "value": 2 }, + { "id": "color", "value": { "mode": "fixed", "fixedColor": "#1f6feb" } } + ] + } + ] + }, + "options": { + "legend": { "calcs": ["lastNotNull", "count", "min", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "raw = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"FROST Flow Sensor FT-101\" and r._field == \"mAbs\") |> map(fn:(r)=>({ r with _field: \"raw (m³/h)\" }))\nknots = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"coresync_knots\" and r._field == \"result\" and r.streamKey == \"P-101:flow:measured:upstream:FT-101\") |> map(fn:(r)=>({ r with _field: \"knot (m³/h)\" }))\nunion(tables:[raw, knots])" + } + ] + }, + { + "id": 7, + "type": "timeseries", + "title": "Pressure downstream — raw 0.5 Hz vs CoreSync knots (mbar)", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "P-101 simulated downstream pressure raw values vs CoreSync knots for the same stream. Pressure cycles every 2 s; knots should appear at each direction change plus every 15 s flush.", + "gridPos": { "h": 10, "w": 12, "x": 12, "y": 20 }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "mbar", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 5, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { "type": "linear" }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "pressuremb" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "knot (mbar)" }, + "properties": [ + { "id": "custom.drawStyle", "value": "points" }, + { "id": "custom.pointSize", "value": 10 }, + { "id": "custom.showPoints", "value": "always" }, + { "id": "color", "value": { "mode": "fixed", "fixedColor": "#d64545" } } + ] + }, + { + "matcher": { "id": "byName", "options": "raw (mbar)" }, + "properties": [ + { "id": "custom.lineWidth", "value": 2 }, + { "id": "color", "value": { "mode": "fixed", "fixedColor": "#1f6feb" } } + ] + } + ] + }, + "options": { + "legend": { "calcs": ["lastNotNull", "count", "min", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "raw = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"pressure.measured.downstream.dashboard-sim-downstream\") |> map(fn:(r)=>({ r with _field: \"raw (mbar)\" }))\nknots = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"coresync_knots\" and r._field == \"result\" and r.streamKey == \"p-101:pressure:measured:downstream:dashboard-sim-downstream\") |> map(fn:(r)=>({ r with _field: \"knot (mbar)\" }))\nunion(tables:[raw, knots])" + } + ] + }, + { + "id": 400, + "type": "row", + "title": "Diagnostics — why CoreSync chose to emit (or not)", + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 30 }, + "collapsed": false, + "panels": [] + }, + { + "id": 8, + "type": "timeseries", + "title": "Knot interarrival time per stream (seconds since previous knot)", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "Time between successive knots per stream. A stream emitting a knot every tick (~1 s) is not compressing. A healthy stream shows seconds-to-tens-of-seconds between knots and a hard cap at 15 s (the flush interval).", + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 31 }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "s", + "axisPlacement": "auto", + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineWidth": 0, + "pointSize": 4, + "scaleDistribution": { "type": "log", "log": 10 }, + "showPoints": "always", + "spanNulls": false, + "thresholdsStyle": { "mode": "line+area" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "transparent", "value": null }, + { "color": "rgba(214, 69, 69, 0.15)", "value": 0 }, + { "color": "transparent", "value": 2 } + ] + }, + "unit": "s", + "min": 0.1 + }, + "overrides": [] + }, + "options": { + "legend": { "calcs": ["mean", "min", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"coresync_knots\" and r._field == \"knot\") |> drop(columns:[\"_value\"]) |> group(columns:[\"streamKey\"]) |> sort(columns:[\"_time\"]) |> elapsed(unit:1ms, columnName:\"_value\") |> map(fn:(r)=>({ r with _value: float(v:r._value) / 1000.0 })) |> filter(fn:(r)=> r._value > 0.0)" + } + ] + }, + { + "id": 9, + "type": "table", + "title": "Compression health — full math per stream (knots ÷ raw = kept; 1 − kept = saved)", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "description": "Each row shows every number that goes into the compression decision so the math is verifiable in your head. 'kept' is the inverse of the reductionPct in the table above (knots/raw). 'savedPct' equals reductionPct in the per-stream table — same number, different visualization. Verify: kept + savedPct/100 ≈ 1 for every row.", + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 31 }, + "fieldConfig": { + "defaults": { + "custom": { "align": "auto", "cellOptions": { "type": "auto" }, "inspect": false }, + "color": { "mode": "thresholds" } + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "kept" }, + "properties": [ + { "id": "unit", "value": "percentunit" }, + { "id": "decimals", "value": 3 }, + { "id": "min", "value": 0 }, + { "id": "max", "value": 1 }, + { "id": "custom.cellOptions", "value": { "type": "gauge", "mode": "gradient", "valueDisplayMode": "color" } }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { "color": "#2f9e44", "value": null }, + { "color": "#e8a23a", "value": 0.30 }, + { "color": "#d64545", "value": 0.50 } + ] + } + } + ] + }, + { + "matcher": { "id": "byName", "options": "savedPct" }, + "properties": [ + { "id": "unit", "value": "percent" }, + { "id": "decimals", "value": 1 }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "gradient" } }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { "color": "#d64545", "value": null }, + { "color": "#e8a23a", "value": 50 }, + { "color": "#2f9e44", "value": 70 } + ] + } + } + ] + }, + { + "matcher": { "id": "byName", "options": "raw" }, + "properties": [{ "id": "unit", "value": "short" }, { "id": "decimals", "value": 0 }] + }, + { + "matcher": { "id": "byName", "options": "knots" }, + "properties": [{ "id": "unit", "value": "short" }, { "id": "decimals", "value": 0 }] + } + ] + }, + "options": { + "cellHeight": "sm", + "footer": { "countRows": false, "fields": "", "reducer": ["sum"], "show": false }, + "showHeader": true, + "sortBy": [{ "desc": true, "displayName": "kept" }] + }, + "targets": [ + { + "refId": "A", + "datasource": { "type": "influxdb", "uid": "cdzg44tv250jkd" }, + "query": "import \"join\"\n\nraw_ft101 = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"FROST Flow Sensor FT-101\" and r._field == \"mAbs\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"P-101:flow:measured:upstream:FT-101\", raw:r._value }))\nraw_rm_pdn = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"pressure.measured.downstream.dashboard-sim-downstream\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:pressure:measured:downstream:dashboard-sim-downstream\", raw:r._value }))\nraw_rm_pup = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"pressure.measured.upstream.dashboard-sim-upstream\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:pressure:measured:upstream:dashboard-sim-upstream\", raw:r._value }))\nraw_rm_eff = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"efficiency.predicted.atequipment.cse_rm_pump\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:efficiency:predicted:atequipment:cse_rm_pump\", raw:r._value }))\nraw_rm_cog = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"cog\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:cog:measured:atEquipment:MEASURED-p-101\", raw:r._value }))\nraw_rm_sec = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement == \"rotatingmachine_cse_rm_pump\" and r._field == \"specificEnergyConsumption.predicted.atequipment.cse_rm_pump\") |> count() |> keep(columns:[\"_value\"]) |> map(fn:(r)=>({ streamKey:\"p-101:specificenergyconsumption:predicted:atequipment:cse_rm_pump\", raw:r._value }))\n\nraw = union(tables:[raw_ft101, raw_rm_pdn, raw_rm_pup, raw_rm_eff, raw_rm_cog, raw_rm_sec]) |> group(columns:[\"streamKey\"])\n\nknots = from(bucket:\"telemetry\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn:(r)=> r._measurement==\"coresync_knots\" and r._field==\"knot\") |> keep(columns:[\"streamKey\",\"_value\"]) |> group(columns:[\"streamKey\"]) |> count(column:\"_value\") |> rename(columns:{_value:\"knots\"})\n\njoin.left(left: raw, right: knots, on: (l, r) => l.streamKey == r.streamKey, as: (l, r) => ({ streamKey: l.streamKey, raw: l.raw, knots: if exists r.knots then r.knots else 0 }))\n |> map(fn:(r)=> ({ streamKey: r.streamKey, raw: r.raw, knots: r.knots, kept: if r.raw > 0 then float(v:r.knots) / float(v:r.raw) else 0.0, savedPct: if r.raw > 0 then 100.0 * (1.0 - float(v:r.knots) / float(v:r.raw)) else 0.0 }))\n |> group()\n |> sort(columns:[\"kept\"], desc:true)" + } + ], + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { "streamKey": 0, "raw": 1, "knots": 2, "kept": 3, "savedPct": 4 }, + "renameByName": {} + } + } + ] + } + ], + "refresh": "5s", + "schemaVersion": 39, + "style": "dark", + "tags": ["EVOLV", "CoreSync", "FROST"], + "templating": { "list": [] }, + "time": { "from": "now-3m", "to": "now" }, + "timepicker": {}, + "timezone": "", + "title": "CoreSync FROST Demo", + "uid": "coresync-frost-demo", + "version": 2, + "weekStart": "" +} diff --git a/nodes/coresync b/nodes/coresync index aefec90..21d77a8 160000 --- a/nodes/coresync +++ b/nodes/coresync @@ -1 +1 @@ -Subproject commit aefec90485b1d4438360c67a5fc160e89e67db7e +Subproject commit 21d77a8afa2a0754255c08d478edc3c9bfbb49e0