Migrate to new Gitea instance (gitea.wbd-rd.nl)

- Update all submodule URLs from gitea.centraal.wbd-rd.nl to gitea.wbd-rd.nl
- Add settler as proper submodule in .gitmodules
- Add agent skills, function anchors, decisions, and improvements
- Add Docker configuration and scripts
- Add manuals and third_party docs
- Update .gitignore with secrets and build artifacts
- Remove stale .tgz build artifact

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-03-04 21:07:04 +01:00
parent fbd9e6ec11
commit 6a6c04d34b
169 changed files with 21332 additions and 1512 deletions

View File

@@ -0,0 +1,810 @@
<!doctype html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>EVOLV RotatingMachine Anchor</title>
<style>
:root {
--bg: #f3f6fb;
--bg-grad: radial-gradient(circle at 0% 0%, #e8efff 0%, #f3f6fb 42%);
--panel: #ffffff;
--text: #172435;
--muted: #5c6a7c;
--line: #d7e0ee;
--line-strong: #a9bad7;
--blue: #2059d8;
--teal: #0d9f9e;
--green: #0fa57d;
--amber: #cf8a11;
--red: #d63f50;
--chip: #f7faff;
--chip-border: #d4deef;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
--sans: "Segoe UI", "Avenir Next", "Helvetica Neue", Arial, sans-serif;
--shadow: 0 8px 24px rgba(14, 25, 42, 0.08);
--radius: 14px;
}
html[data-theme="dark"] {
--bg: #0d1420;
--bg-grad: radial-gradient(circle at 0% 0%, #1a2538 0%, #0d1420 44%);
--panel: #131c2a;
--text: #e6edf9;
--muted: #9eb0cb;
--line: #26344a;
--line-strong: #355079;
--blue: #4b86ff;
--teal: #25c9c4;
--green: #24c794;
--amber: #e9ad42;
--red: #ff6f7b;
--chip: #172234;
--chip-border: #324968;
--shadow: 0 8px 24px rgba(0, 0, 0, 0.34);
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg-grad), var(--bg);
color: var(--text);
font-family: var(--sans);
line-height: 1.42;
}
.wrap { max-width: 1240px; margin: 0 auto; padding: 18px; }
.toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
}
.btn {
border: 1px solid var(--line-strong);
background: var(--panel);
color: var(--text);
padding: 7px 11px;
border-radius: 9px;
font-size: 0.82rem;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease;
}
.btn:hover { border-color: var(--blue); }
.btn.active { background: var(--blue); color: #fff; border-color: var(--blue); }
.hero {
background: linear-gradient(128deg, #1e47ac 0%, #1f5ad6 48%, #1a8eb9 100%);
color: #fff;
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 20px;
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 14px;
}
.hero h1 { margin: 0 0 7px; font-size: 1.58rem; letter-spacing: 0.2px; }
.hero p { margin: 0; opacity: 0.94; font-size: 0.93rem; }
.badge-row { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 7px; }
.badge {
padding: 4px 9px;
border: 1px solid rgba(255,255,255,0.3);
background: rgba(255,255,255,0.12);
border-radius: 999px;
font-size: 0.77rem;
white-space: nowrap;
}
.metric-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0,1fr));
gap: 8px;
align-content: start;
}
.metric {
border: 1px solid rgba(255,255,255,0.28);
background: rgba(255,255,255,0.13);
border-radius: 10px;
padding: 8px;
}
.metric .k { font-size: 0.7rem; text-transform: uppercase; opacity: 0.86; letter-spacing: 0.45px; }
.metric .v { font-size: 1rem; font-weight: 700; margin-top: 2px; }
.grid { margin-top: 14px; display: grid; gap: 12px; }
.panel {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 12px;
box-shadow: var(--shadow);
padding: 14px;
}
.panel h2 { margin: 0 0 10px; font-size: 1.03rem; }
.muted { color: var(--muted); font-size: 0.84rem; }
.split { display: grid; grid-template-columns: 1.2fr 1fr; gap: 12px; }
.chip-list { display: flex; flex-wrap: wrap; gap: 7px; }
.chip {
border: 1px solid var(--chip-border);
background: var(--chip);
color: var(--text);
border-radius: 999px;
font-size: 0.77rem;
padding: 4px 8px;
}
.kpi-cards {
display: grid;
grid-template-columns: repeat(4, minmax(0,1fr));
gap: 10px;
}
.kpi {
border: 1px solid var(--line);
border-radius: 10px;
background: var(--panel);
padding: 10px;
}
.kpi .label { color: var(--muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.38px; }
.kpi .value { font-size: 1.15rem; font-weight: 700; margin-top: 4px; }
.table-tools { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
th, td {
text-align: left;
vertical-align: top;
border-bottom: 1px solid var(--line);
border-right: 1px solid color-mix(in srgb, var(--line) 82%, transparent);
padding: 7px 6px;
}
th:last-child, td:last-child { border-right: 0; }
th {
color: var(--muted);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.35px;
font-weight: 700;
}
tr.group-row td {
background: color-mix(in srgb, var(--panel) 80%, var(--line));
color: var(--text);
font-weight: 700;
border-bottom: 1px solid var(--line-strong);
border-top: 1px solid var(--line-strong);
text-transform: uppercase;
font-size: 0.72rem;
letter-spacing: 0.45px;
}
code {
font-family: var(--mono);
background: color-mix(in srgb, var(--blue) 10%, transparent);
color: var(--text);
border-radius: 6px;
padding: 1px 5px;
font-size: 0.76rem;
}
.svg-box {
border: 1px solid var(--line);
border-radius: 10px;
padding: 10px;
background: color-mix(in srgb, var(--panel) 92%, var(--line));
}
.graph-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.timeline { display: grid; gap: 8px; }
.step { display: grid; grid-template-columns: 28px 1fr; gap: 8px; align-items: start; }
.dot {
width: 28px;
height: 28px;
border-radius: 999px;
color: #fff;
display: grid;
place-items: center;
font-size: 0.78rem;
font-weight: 700;
}
.d1 { background: var(--blue); }
.d2 { background: var(--teal); }
.d3 { background: var(--amber); }
.d4 { background: var(--green); }
details {
border: 1px solid var(--line);
border-radius: 10px;
padding: 8px 10px;
background: color-mix(in srgb, var(--panel) 96%, var(--line));
}
details + details { margin-top: 8px; }
summary {
cursor: pointer;
font-weight: 600;
color: var(--text);
user-select: none;
}
.risk {
border-left: 4px solid var(--amber);
background: color-mix(in srgb, var(--amber) 10%, transparent);
border-radius: 8px;
padding: 8px 10px;
margin-top: 7px;
font-size: 0.82rem;
}
.risk.ok { border-left-color: var(--green); background: color-mix(in srgb, var(--green) 10%, transparent); }
.risk.bad { border-left-color: var(--red); background: color-mix(in srgb, var(--red) 10%, transparent); }
.foot {
font-size: 0.76rem;
color: var(--muted);
margin-top: 6px;
}
@media (max-width: 1040px) {
.hero, .split, .graph-grid { grid-template-columns: 1fr; }
.kpi-cards { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 640px) {
.kpi-cards { grid-template-columns: 1fr; }
.table-tools { flex-wrap: wrap; }
table { font-size: 0.78rem; }
}
</style>
</head>
<body>
<main class="wrap">
<div class="toolbar">
<button id="themeToggle" class="btn" type="button">Toggle Dark Mode</button>
</div>
<section class="hero">
<div>
<h1>RotatingMachine Engineering Anchor</h1>
<p>Function-design truth source for runtime behavior, control contracts, units/signals, and integration boundaries.</p>
<div class="badge-row">
<span class="badge">Node Type: <code>rotatingMachine</code></span>
<span class="badge">Domain Class: <code>specificClass.js</code></span>
<span class="badge">Wrapper: <code>nodeClass.js</code></span>
<span class="badge">Ports: <code>3</code></span>
</div>
</div>
<div class="metric-grid">
<div class="metric"><div class="k">Control Topics In</div><div class="v">9</div></div>
<div class="metric"><div class="k">Signal Rows Catalogued</div><div class="v">31</div></div>
<div class="metric"><div class="k">Anchored Tests</div><div class="v">9</div></div>
<div class="metric"><div class="k">Known Risks</div><div class="v">4</div></div>
</div>
</section>
<section class="grid">
<section class="panel">
<h2>Connection Map (Always Visible)</h2>
<div class="split">
<div>
<div class="muted">Primary integrations and contracts</div>
<div class="chip-list" style="margin-top:8px; margin-bottom:10px">
<span class="chip">machineGroupControl -> parent command source</span>
<span class="chip">pumpingStation -> orchestration source</span>
<span class="chip">measurement -> real pressure child via registerChild</span>
<span class="chip">dashboard flows -> simulateMeasurement + chart consumers</span>
<span class="chip">output[2] -> registerChild to parent</span>
<span class="chip">/rotatingMachine/menu.js</span>
<span class="chip">/rotatingMachine/configData.js</span>
</div>
<div class="kpi-cards">
<div class="kpi"><div class="label">Pressure Policy</div><div class="value">Real > Virtual > 0</div></div>
<div class="kpi"><div class="label">Mode Gate</div><div class="value">Action + Source</div></div>
<div class="kpi"><div class="label">Tick Rate</div><div class="value">1s</div></div>
<div class="kpi"><div class="label">Operational States</div><div class="value">4 Active</div></div>
</div>
</div>
<div class="svg-box">
<svg viewBox="0 0 540 230" width="100%" role="img" aria-label="integration topology">
<defs>
<marker id="arr" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#6f85aa"></path>
</marker>
</defs>
<rect x="200" y="86" width="140" height="52" rx="10" fill="#2059d8"/>
<text x="270" y="117" text-anchor="middle" fill="#fff" font-size="13" font-family="Segoe UI">rotatingMachine</text>
<rect x="18" y="20" width="160" height="44" rx="9" fill="#e9f1ff" stroke="#aac0ef"/>
<text x="98" y="46" text-anchor="middle" fill="#244271" font-size="12">machineGroupControl</text>
<rect x="18" y="164" width="160" height="44" rx="9" fill="#e7faf5" stroke="#9edcca"/>
<text x="98" y="190" text-anchor="middle" fill="#1e5244" font-size="12">pumpingStation</text>
<rect x="360" y="24" width="160" height="44" rx="9" fill="#fff5e5" stroke="#e9c589"/>
<text x="440" y="50" text-anchor="middle" fill="#634319" font-size="12">dashboard / examples</text>
<rect x="360" y="156" width="160" height="44" rx="9" fill="#eef8e8" stroke="#b7d89e"/>
<text x="440" y="182" text-anchor="middle" fill="#2e5a22" font-size="12">measurement</text>
<line x1="178" y1="44" x2="200" y2="96" stroke="#6f85aa" stroke-width="2" marker-end="url(#arr)"/>
<line x1="178" y1="186" x2="200" y2="130" stroke="#6f85aa" stroke-width="2" marker-end="url(#arr)"/>
<line x1="360" y1="45" x2="338" y2="97" stroke="#6f85aa" stroke-width="2" marker-end="url(#arr)"/>
<line x1="341" y1="130" x2="360" y2="50" stroke="#8ea4c8" stroke-dasharray="4 4" stroke-width="2" marker-end="url(#arr)"/>
<line x1="360" y1="178" x2="340" y2="126" stroke="#6f85aa" stroke-width="2" marker-end="url(#arr)"/>
<text x="184" y="66" fill="#5c7091" font-size="11">setMode / exec*</text>
<text x="182" y="162" fill="#5c7091" font-size="11">station-level dispatch</text>
<text x="334" y="78" fill="#5c7091" font-size="11">simulateMeasurement</text>
<text x="350" y="94" fill="#5c7091" font-size="11">process + influx out</text>
<text x="342" y="168" fill="#5c7091" font-size="11">registerChild + pressure.measured.*</text>
</svg>
</div>
</div>
</section>
<section class="panel">
<h2>Engineering Unit & Signal Catalog</h2>
<div class="table-tools">
<button id="btnInput" class="btn active" type="button">Input</button>
<button id="btnOutput" class="btn" type="button">Output</button>
<span class="muted" id="tableMeta">Showing input topics/signals grouped by engineering function.</span>
</div>
<table id="signalTable">
<thead>
<tr>
<th>Group</th>
<th>Signal / Topic</th>
<th>Direction</th>
<th>Unit</th>
<th>Typical Range</th>
<th>Criticality</th>
<th>Meaning</th>
<th>Primary Source</th>
<th>Conversion Hints</th>
<th>Fallback / Notes</th>
</tr>
</thead>
<tbody></tbody>
</table>
</section>
<section class="panel">
<h2>Functional Parameter Sheet (Template + Example)</h2>
<div class="muted">Engineering-oriented parameter table for BEP/curve/mechanical context. Example values are placeholders and scenario-dependent.</div>
<table id="paramTable" style="margin-top:10px">
<thead>
<tr>
<th>Parameter</th>
<th>Symbool</th>
<th>Eenheid</th>
<th>Waarde (+/-)</th>
<th>Toelichting</th>
<th>Node Mapping</th>
</tr>
</thead>
<tbody id="paramBody"></tbody>
</table>
</section>
<section class="panel">
<h2>True Graphs (Data-Derived)</h2>
<div class="muted">Curves below are drawn from repository data in <code>nodes/rotatingMachine/misc/measured_curve.json</code> (pressure slice <code>175</code>), plus derived efficiency ratio.</div>
<div class="graph-grid" style="margin-top:10px">
<div class="svg-box">
<div class="muted" style="margin-bottom:6px">Flow vs Control (pressure=175)</div>
<svg id="flowChart" viewBox="0 0 520 280" width="100%" role="img" aria-label="flow vs control"></svg>
</div>
<div class="svg-box">
<div class="muted" style="margin-bottom:6px">Power vs Control (pressure=175)</div>
<svg id="powerChart" viewBox="0 0 520 280" width="100%" role="img" aria-label="power vs control"></svg>
</div>
</div>
<div class="graph-grid" style="margin-top:10px">
<div class="svg-box">
<div class="muted" style="margin-bottom:6px">Derived Efficiency Index (flow/power) vs Control</div>
<svg id="effChart" viewBox="0 0 520 280" width="100%" role="img" aria-label="efficiency index vs control"></svg>
</div>
<div class="svg-box">
<div class="muted" style="margin-bottom:6px">Allowed Actions by Mode (config defaults)</div>
<svg id="modeChart" viewBox="0 0 520 280" width="100%" role="img" aria-label="allowed actions by mode"></svg>
</div>
</div>
</section>
<section class="panel">
<h2>Execution Flow (Core Open)</h2>
<div class="timeline">
<div class="step"><div class="dot d1">1</div><div><strong>Construct</strong><div class="muted">Load defaults and model, initialize predictors/state/measurement containers, attach state listeners.</div></div></div>
<div class="step"><div class="dot d2">2</div><div><strong>Connect</strong><div class="muted">Register virtual pressure children, listen to real/virtual measurement streams, register self to parent.</div></div></div>
<div class="step"><div class="dot d3">3</div><div><strong>Control</strong><div class="muted">Route topics, validate mode/source/action, execute movement or sequence transitions.</div></div></div>
<div class="step"><div class="dot d4">4</div><div><strong>Compute + Emit</strong><div class="muted">Pressure basis selection -> flow/power prediction -> efficiency/CoG/BEP -> output formatting and status update.</div></div></div>
</div>
</section>
<section class="panel">
<h2>Extended Sections (Selective Collapse)</h2>
<details>
<summary>Function Inventory Snapshot</summary>
<div class="muted" style="margin-top:8px">Anchored files: <code>rotatingMachine.js</code>, <code>src/nodeClass.js</code>, <code>src/specificClass.js</code>. 44+ callable methods/paths inventoried in markdown anchor.</div>
<div class="chip-list" style="margin-top:8px">
<span class="chip">constructor + init</span>
<span class="chip">registerChild + handler dispatch</span>
<span class="chip">handleInput + setMode + sequences</span>
<span class="chip">calcFlow/calcPower/calcCtrl</span>
<span class="chip">calcEfficiency + calcCog + getOutput</span>
</div>
</details>
<details>
<summary>Risks And Invariants</summary>
<div class="risk bad"><strong>Risk:</strong> <code>CoG</code> input path calls <code>showCoG()</code> but method is not present in current <code>specificClass.js</code>.</div>
<div class="risk bad"><strong>Risk:</strong> emergency sequence key mismatch (<code>emergencyStop</code> vs config <code>emergencystop</code>).</div>
<div class="risk"><strong>Risk:</strong> <code>eneableLog</code> typo in state logging mapping.</div>
<div class="risk"><strong>Risk:</strong> node label expression precedence may render unexpected labels.</div>
<div class="risk ok"><strong>Invariant:</strong> mechanical truth remains in <code>specificClass.js</code>; wrapper remains routing/lifecycle.</div>
<div class="risk ok"><strong>Invariant:</strong> pressure selection order stays real sensor > virtual sensor > fallback 0.</div>
<div class="risk ok"><strong>Invariant:</strong> output channels remain process/influx/parent registration separation.</div>
</details>
<details>
<summary>Test Evidence</summary>
<div class="chip-list" style="margin-top:8px">
<span class="chip">constructor.basic.test.js</span>
<span class="chip">mode-and-input.basic.test.js</span>
<span class="chip">error-paths.edge.test.js</span>
<span class="chip">nodeClass-routing.edge.test.js</span>
<span class="chip">sequences.integration.test.js</span>
<span class="chip">registration.integration.test.js</span>
<span class="chip">pressure-initialization.integration.test.js</span>
<span class="chip">coolprop.integration.test.js</span>
<span class="chip">basic-flow-dashboard.integration.test.js</span>
</div>
</details>
</section>
<div class="foot">
Iteration 3: engineering-grade signal table (ranges, criticality, conversion hints), functional parameter sheet, input/output filters, dark mode toggle, selective collapsible sections, and data-derived graphs.
</div>
</section>
</main>
<script>
(function () {
const signalRows = [
{ dir: "input", group: "Control Topic", signal: "topic:registerChild", unit: "n/a", meaning: "Attach child node source by node id", source: "nodeClass.js:268", notes: "Warns if child/source not found" },
{ dir: "input", group: "Control Topic", signal: "topic:setMode", unit: "enum", meaning: "Set current command policy mode", source: "nodeClass.js:278", notes: "Validated by setMode()" },
{ dir: "input", group: "Control Topic", signal: "topic:execSequence", unit: "json payload", meaning: "Execute named transition sequence", source: "nodeClass.js:281", notes: "Mode + source gated" },
{ dir: "input", group: "Control Topic", signal: "topic:execMovement", unit: "json payload", meaning: "Move to explicit setpoint", source: "nodeClass.js:285", notes: "Setpoint coerced to Number" },
{ dir: "input", group: "Control Topic", signal: "topic:flowMovement", unit: "json payload", meaning: "Convert desired flow to control setpoint", source: "nodeClass.js:289", notes: "Uses calcCtrl()" },
{ dir: "input", group: "Control Topic", signal: "topic:emergencystop", unit: "json payload", meaning: "Emergency stop command path", source: "nodeClass.js:294", notes: "Sequence-name mismatch risk in logic" },
{ dir: "input", group: "Control Topic", signal: "topic:simulateMeasurement", unit: "json payload", meaning: "Inject measured values from dashboard/test flow", source: "nodeClass.js:298", notes: "Finite numeric value required" },
{ dir: "input", group: "Control Topic", signal: "topic:showWorkingCurves", unit: "n/a", meaning: "Request current curve and CoG diagnostics", source: "nodeClass.js:336", notes: "Immediate response on output[0]" },
{ dir: "input", group: "Control Topic", signal: "topic:CoG", unit: "n/a", meaning: "Request CoG diagnostics", source: "nodeClass.js:339", notes: "Calls showCoG() method" },
{ dir: "input", group: "Measured Signal", signal: "pressure.measured.upstream", unit: "mbar (default)", meaning: "Upstream pressure input", source: "specificClass.js:52,703", notes: "Real child preferred over virtual" },
{ dir: "input", group: "Measured Signal", signal: "pressure.measured.downstream", unit: "mbar (default)", meaning: "Downstream pressure input", source: "specificClass.js:52,703", notes: "Combined with upstream for differential" },
{ dir: "input", group: "Measured Signal", signal: "flow.measured.upstream", unit: "config general.unit", meaning: "Measured inflow reference", source: "specificClass.js:727", notes: "Used for reconciliation when present" },
{ dir: "input", group: "Measured Signal", signal: "flow.measured.downstream", unit: "config general.unit", meaning: "Measured discharge reference", source: "specificClass.js:727", notes: "Accepted only in operational states" },
{ dir: "input", group: "Measured Signal", signal: "temperature.measured.atEquipment", unit: "C (init), K used", meaning: "Fluid temperature for density/efficiency path", source: "specificClass.js:149,858", notes: "Starts at 15 C" },
{ dir: "input", group: "Measured Signal", signal: "power.measured.atEquipment", unit: "kW", meaning: "Measured power if provided by child", source: "specificClass.js:686", notes: "Read helper exists" },
{ dir: "output", group: "Port Contract", signal: "output[0]", unit: "process msg", meaning: "Process payload from flattened output", source: "nodeClass.js:250", notes: "Formatted by outputUtils" },
{ dir: "output", group: "Port Contract", signal: "output[1]", unit: "influx msg", meaning: "InfluxDB payload", source: "nodeClass.js:251", notes: "Formatted by outputUtils" },
{ dir: "output", group: "Port Contract", signal: "output[2]", unit: "registerChild msg", meaning: "Parent registration plumbing", source: "nodeClass.js:222", notes: "Topic registerChild" },
{ dir: "output", group: "Predicted Signal", signal: "flow.predicted.downstream", unit: "config general.unit", meaning: "Predicted discharge flow", source: "specificClass.js:423", notes: "0 when inactive/no curve" },
{ dir: "output", group: "Predicted Signal", signal: "flow.predicted.atEquipment", unit: "config general.unit", meaning: "Predicted flow at equipment", source: "specificClass.js:424", notes: "0 when inactive/no curve" },
{ dir: "output", group: "Predicted Signal", signal: "flow.predicted.min", unit: "config general.unit", meaning: "Curve min flow at current pressure", source: "specificClass.js:156", notes: "Seeded in init/fallback paths" },
{ dir: "output", group: "Predicted Signal", signal: "flow.predicted.max", unit: "config general.unit", meaning: "Curve max flow at current pressure", source: "specificClass.js:155", notes: "Seeded in init/fallback paths" },
{ dir: "output", group: "Predicted Signal", signal: "power.predicted.atEquipment", unit: "kW", meaning: "Predicted power draw", source: "specificClass.js:448", notes: "0 when inactive/no curve" },
{ dir: "output", group: "Predicted Signal", signal: "ctrl.predicted.atEquipment", unit: "unitless (%)", meaning: "Predicted control setpoint", source: "specificClass.js:482", notes: "From requested flow" },
{ dir: "output", group: "Derived KPI", signal: "efficiency.predicted.atEquipment", unit: "flow/power ratio", meaning: "Specific flow proxy", source: "specificClass.js:881", notes: "Computed when power and flow non-zero" },
{ dir: "output", group: "Derived KPI", signal: "specificEnergyConsumption.predicted.atEquipment", unit: "power/flow", meaning: "Specific energy proxy", source: "specificClass.js:882", notes: "Computed when power and flow non-zero" },
{ dir: "output", group: "Derived KPI", signal: "nHydraulicEfficiency.predicted.atEquipment", unit: "unitless", meaning: "Hydraulic-efficiency-like metric", source: "specificClass.js:887", notes: "Uses pressure diff + density + conversions" },
{ dir: "output", group: "Derived KPI", signal: "cog / NCog / NCogPercent", unit: "unitless / %", meaning: "Best efficiency operating index", source: "specificClass.js:796,950", notes: "Used by higher-level optimization" },
{ dir: "output", group: "Derived KPI", signal: "effDistFromPeak / effRelDistFromPeak", unit: "unitless", meaning: "Distance from best-efficiency point", source: "specificClass.js:924,962", notes: "Absolute + relative" },
{ dir: "output", group: "Runtime State", signal: "state", unit: "enum", meaning: "Current movement/state-machine state", source: "specificClass.js:943", notes: "Exposed in output object" },
{ dir: "output", group: "Runtime State", signal: "mode", unit: "enum", meaning: "Current command mode", source: "specificClass.js:947", notes: "auto/virtualControl/fysicalControl" },
{ dir: "output", group: "Runtime State", signal: "ctrl", unit: "%", meaning: "Current position setpoint", source: "specificClass.js:945", notes: "From state module" },
{ dir: "output", group: "Runtime State", signal: "runtime", unit: "h", meaning: "Accumulated runtime", source: "specificClass.js:944", notes: "From state module" },
{ dir: "output", group: "Runtime State", signal: "moveTimeleft", unit: "s", meaning: "Time remaining for movement", source: "specificClass.js:946", notes: "From state module" },
{ dir: "output", group: "Runtime State", signal: "maintenanceTime", unit: "h", meaning: "Maintenance counter", source: "specificClass.js:951", notes: "From state module" },
{ dir: "output", group: "Runtime State", signal: "flowNrmse / flowLongterNRMSD / flowImmediateLevel / flowLongTermLevel", unit: "mixed", meaning: "Drift diagnostics when available", source: "specificClass.js:953", notes: "Present only when flowDrift exists" }
];
const tbody = document.querySelector("#signalTable tbody");
const btnInput = document.getElementById("btnInput");
const btnOutput = document.getElementById("btnOutput");
const tableMeta = document.getElementById("tableMeta");
const paramBody = document.getElementById("paramBody");
let activeDir = "input";
function inferTypicalRange(r) {
if (r.signal.indexOf("pressure.measured.upstream") >= 0) return "200-1500 mbar";
if (r.signal.indexOf("pressure.measured.downstream") >= 0) return "400-2500 mbar";
if (r.signal.indexOf("flow.measured") >= 0) return "0-2.5 m3/s or project unit";
if (r.signal.indexOf("flow.predicted") >= 0) return "Curve-bound min-max";
if (r.signal.indexOf("power.measured") >= 0) return "0-250 kW";
if (r.signal.indexOf("power.predicted") >= 0) return "Curve-bound min-max";
if (r.signal.indexOf("temperature.measured") >= 0) return "5-40 C (278-313 K)";
if (r.signal.indexOf("ctrl") >= 0) return "0-100% (project may scale)";
if (r.signal.indexOf("runtime") >= 0 || r.signal.indexOf("maintenanceTime") >= 0) return "0+ h";
if (r.signal.indexOf("moveTimeleft") >= 0) return "0+ s";
if (r.signal.indexOf("efficiency") >= 0 || r.signal.indexOf("nHydraulicEfficiency") >= 0) return "0-1+ (index)";
if (r.signal.indexOf("NCog") >= 0 || r.signal.indexOf("cog") >= 0) return "0-1 (NCog), 0-100% (NCogPercent)";
if (r.group === "Control Topic") return "Discrete commands";
if (r.group === "Port Contract") return "Per tick / event message";
return "Project-specific";
}
function inferCriticality(r) {
if (r.signal.indexOf("emergencystop") >= 0) return "High";
if (r.signal.indexOf("setMode") >= 0 || r.signal.indexOf("execSequence") >= 0 || r.signal.indexOf("execMovement") >= 0 || r.signal.indexOf("flowMovement") >= 0) return "High";
if (r.signal.indexOf("pressure.measured") >= 0) return "High";
if (r.signal.indexOf("power.predicted") >= 0 || r.signal.indexOf("flow.predicted") >= 0 || r.signal.indexOf("output[") >= 0) return "High";
if (r.signal.indexOf("temperature.measured") >= 0 || r.signal.indexOf("power.measured") >= 0) return "Medium";
if (r.signal.indexOf("efficiency") >= 0 || r.signal.indexOf("NCog") >= 0 || r.signal.indexOf("flowNrmse") >= 0) return "Medium";
if (r.signal.indexOf("showWorkingCurves") >= 0 || r.signal.indexOf("CoG") >= 0) return "Low";
return "Medium";
}
function inferConversionHints(r) {
if (r.signal.indexOf("pressure") >= 0) return "mbar <-> Pa (1 mbar = 100 Pa)";
if (r.signal.indexOf("flow") >= 0) return "m3/h <-> m3/s (divide/multiply by 3600); l/s <-> m3/s (x/1000)";
if (r.signal.indexOf("power") >= 0) return "kW <-> W (x1000)";
if (r.signal.indexOf("temperature") >= 0) return "C <-> K (+/-273.15)";
if (r.signal.indexOf("ctrl") >= 0) return "Unitless ratio <-> % (x100)";
if (r.signal.indexOf("runtime") >= 0 || r.signal.indexOf("maintenanceTime") >= 0) return "h <-> s (x3600)";
return "No conversion required / message contract";
}
function renderParameterSheet() {
const params = [
{ p: "Nominaal toerental", s: "n", u: "t/min", v: "1477 (voorbeeld)", t: "50 Hz in bedrijf", m: "Not directly output; machine/asset datasheet parameter" },
{ p: "Debiet bij BEP", s: "Q", u: "L/s", v: "86 (voorbeeld)", t: "Beste efficientiepunt", m: "Derivable from curve + CoG/efficiency path" },
{ p: "Opvoerhoogte", s: "H", u: "m", v: "11 (voorbeeld)", t: "Bij Q = 86 L/s", m: "Related to pressure differential conversion path" },
{ p: "Opgenomen vermogen", s: "P2", u: "kW", v: "13-15 (voorbeeld)", t: "Inclusief mechanische verliezen", m: "output: power.predicted.atEquipment / measured power path" },
{ p: "Rendement", s: "η", u: "%", v: "73 (voorbeeld)", t: "Maximaal rendement", m: "output: efficiency.* and nHydraulicEfficiency.*" },
{ p: "Benodigde NPSH", s: "NPSHr", u: "m", v: "2-3 (voorbeeld)", t: "Lage cavitatie gevoeligheid", m: "Not explicit in current output; candidate future anchor metric" },
{ p: "Persaansluiting", s: "-", u: "DN150 / PN16", v: "-", t: "Horizontale uitlaat", m: "Asset/mechanical metadata (datasheet-level)" },
{ p: "Zuigaansluiting", s: "-", u: "DN200 / PN10", v: "-", t: "Instroomzijde pomp", m: "Asset/mechanical metadata (datasheet-level)" },
{ p: "Pomphuismateriaal", s: "-", u: "EN-GJL-250", v: "-", t: "Gietijzer", m: "Asset/mechanical metadata (datasheet-level)" },
{ p: "Waaiermateriaal", s: "-", u: "1.4122 RVS", v: "-", t: "Schroefcentrifugaalwaaier", m: "Asset/mechanical metadata (datasheet-level)" },
{ p: "Pompgewicht", s: "-", u: "kg", v: "183 (voorbeeld)", t: "Droge uitvoering", m: "Asset/mechanical metadata (datasheet-level)" }
];
paramBody.innerHTML = params.map(function (row) {
return "<tr>" +
"<td>" + row.p + "</td>" +
"<td><code>" + row.s + "</code></td>" +
"<td>" + row.u + "</td>" +
"<td>" + row.v + "</td>" +
"<td>" + row.t + "</td>" +
"<td>" + row.m + "</td>" +
"</tr>";
}).join("");
}
function renderRows() {
const rows = signalRows.filter((r) => r.dir === activeDir);
const groups = [...new Set(rows.map((r) => r.group))];
tbody.innerHTML = "";
groups.forEach((group) => {
const gtr = document.createElement("tr");
gtr.className = "group-row";
const gtd = document.createElement("td");
gtd.colSpan = 10;
gtd.textContent = group;
gtr.appendChild(gtd);
tbody.appendChild(gtr);
rows.filter((r) => r.group === group).forEach((r) => {
const typicalRange = inferTypicalRange(r);
const criticality = inferCriticality(r);
const conversionHints = inferConversionHints(r);
const tr = document.createElement("tr");
tr.innerHTML = "<td>" + r.group + "</td>" +
"<td><code>" + r.signal + "</code></td>" +
"<td>" + r.dir + "</td>" +
"<td>" + r.unit + "</td>" +
"<td>" + typicalRange + "</td>" +
"<td>" + criticality + "</td>" +
"<td>" + r.meaning + "</td>" +
"<td><code>" + r.source + "</code></td>" +
"<td>" + conversionHints + "</td>" +
"<td>" + r.notes + "</td>";
tbody.appendChild(tr);
});
});
tableMeta.textContent = activeDir === "input"
? "Showing input topics/signals grouped by engineering function."
: "Showing output ports/signals/KPIs grouped by engineering function.";
}
btnInput.addEventListener("click", function () {
activeDir = "input";
btnInput.classList.add("active");
btnOutput.classList.remove("active");
renderRows();
});
btnOutput.addEventListener("click", function () {
activeDir = "output";
btnOutput.classList.add("active");
btnInput.classList.remove("active");
renderRows();
});
const themeToggle = document.getElementById("themeToggle");
const root = document.documentElement;
const storedTheme = localStorage.getItem("rm_anchor_theme");
if (storedTheme === "dark") {
root.setAttribute("data-theme", "dark");
}
themeToggle.addEventListener("click", function () {
const isDark = root.getAttribute("data-theme") === "dark";
const next = isDark ? "light" : "dark";
root.setAttribute("data-theme", next);
localStorage.setItem("rm_anchor_theme", next);
drawAllCharts();
});
function drawLineChart(svgId, options) {
const svg = document.getElementById(svgId);
const w = 520;
const h = 280;
const m = { top: 20, right: 16, bottom: 44, left: 56 };
const iw = w - m.left - m.right;
const ih = h - m.top - m.bottom;
const xMin = Math.min.apply(null, options.x);
const xMax = Math.max.apply(null, options.x);
const yMinRaw = Math.min.apply(null, options.y);
const yMaxRaw = Math.max.apply(null, options.y);
const yPad = (yMaxRaw - yMinRaw) * 0.12 || 1;
const yMin = Math.max(0, yMinRaw - yPad);
const yMax = yMaxRaw + yPad;
function sx(v) { return m.left + ((v - xMin) / (xMax - xMin || 1)) * iw; }
function sy(v) { return m.top + ih - ((v - yMin) / (yMax - yMin || 1)) * ih; }
const textColor = getComputedStyle(document.documentElement).getPropertyValue("--muted").trim() || "#627489";
const lineColor = options.color;
const axisColor = getComputedStyle(document.documentElement).getPropertyValue("--line-strong").trim() || "#96accb";
const gridColor = getComputedStyle(document.documentElement).getPropertyValue("--line").trim() || "#d7e0ee";
let path = "";
options.x.forEach(function (xv, i) {
const px = sx(xv);
const py = sy(options.y[i]);
path += (i === 0 ? "M" : " L") + px + " " + py;
});
const yTicks = 5;
let tickEls = "";
for (let i = 0; i <= yTicks; i++) {
const val = yMin + ((yMax - yMin) * i) / yTicks;
const py = sy(val);
tickEls += '<line x1="' + m.left + '" y1="' + py + '" x2="' + (w - m.right) + '" y2="' + py + '" stroke="' + gridColor + '" stroke-width="1" />';
tickEls += '<text x="' + (m.left - 8) + '" y="' + (py + 4) + '" text-anchor="end" fill="' + textColor + '" font-size="11">' + val.toFixed(2) + '</text>';
}
let pointEls = "";
options.x.forEach(function (xv, i) {
pointEls += '<circle cx="' + sx(xv) + '" cy="' + sy(options.y[i]) + '" r="3.8" fill="' + lineColor + '" />';
});
let xTickEls = "";
options.x.forEach(function (xv) {
xTickEls += '<line x1="' + sx(xv) + '" y1="' + (h - m.bottom) + '" x2="' + sx(xv) + '" y2="' + (h - m.bottom + 5) + '" stroke="' + axisColor + '"/>';
xTickEls += '<text x="' + sx(xv) + '" y="' + (h - m.bottom + 18) + '" text-anchor="middle" fill="' + textColor + '" font-size="11">' + xv + '</text>';
});
svg.innerHTML = '' +
'<rect x="0" y="0" width="520" height="280" fill="transparent" />' +
tickEls +
'<line x1="' + m.left + '" y1="' + (h - m.bottom) + '" x2="' + (w - m.right) + '" y2="' + (h - m.bottom) + '" stroke="' + axisColor + '" stroke-width="1.4" />' +
'<line x1="' + m.left + '" y1="' + m.top + '" x2="' + m.left + '" y2="' + (h - m.bottom) + '" stroke="' + axisColor + '" stroke-width="1.4" />' +
'<path d="' + path + '" fill="none" stroke="' + lineColor + '" stroke-width="2.3" />' +
pointEls +
xTickEls +
'<text x="260" y="272" text-anchor="middle" fill="' + textColor + '" font-size="12">' + options.xLabel + '</text>' +
'<text x="16" y="140" transform="rotate(-90 16 140)" text-anchor="middle" fill="' + textColor + '" font-size="12">' + options.yLabel + '</text>';
}
function drawModeBarChart(svgId) {
const svg = document.getElementById(svgId);
const modes = [
{ mode: "auto", count: 6 },
{ mode: "virtualControl", count: 6 },
{ mode: "fysicalControl", count: 4 }
];
const w = 520, h = 280;
const m = { top: 20, right: 16, bottom: 52, left: 52 };
const iw = w - m.left - m.right;
const ih = h - m.top - m.bottom;
const max = 6;
const barW = iw / modes.length * 0.58;
const gap = iw / modes.length * 0.42;
const textColor = getComputedStyle(document.documentElement).getPropertyValue("--muted").trim() || "#627489";
const axisColor = getComputedStyle(document.documentElement).getPropertyValue("--line-strong").trim() || "#96accb";
const blue = getComputedStyle(document.documentElement).getPropertyValue("--blue").trim() || "#2059d8";
const teal = getComputedStyle(document.documentElement).getPropertyValue("--teal").trim() || "#0d9f9e";
const amber = getComputedStyle(document.documentElement).getPropertyValue("--amber").trim() || "#cf8a11";
const colors = [blue, teal, amber];
let bars = "";
modes.forEach(function (d, i) {
const x = m.left + i * (barW + gap) + gap * 0.5;
const bh = (d.count / max) * ih;
const y = m.top + ih - bh;
bars += '<rect x="' + x + '" y="' + y + '" width="' + barW + '" height="' + bh + '" rx="6" fill="' + colors[i] + '" />';
bars += '<text x="' + (x + barW / 2) + '" y="' + (y - 6) + '" text-anchor="middle" fill="' + textColor + '" font-size="12">' + d.count + '</text>';
bars += '<text x="' + (x + barW / 2) + '" y="' + (h - m.bottom + 18) + '" text-anchor="middle" fill="' + textColor + '" font-size="11">' + d.mode + '</text>';
});
svg.innerHTML = '' +
'<line x1="' + m.left + '" y1="' + (h - m.bottom) + '" x2="' + (w - m.right) + '" y2="' + (h - m.bottom) + '" stroke="' + axisColor + '" stroke-width="1.4" />' +
'<line x1="' + m.left + '" y1="' + m.top + '" x2="' + m.left + '" y2="' + (h - m.bottom) + '" stroke="' + axisColor + '" stroke-width="1.4" />' +
bars +
'<text x="260" y="272" text-anchor="middle" fill="' + textColor + '" font-size="12">Mode</text>' +
'<text x="16" y="140" transform="rotate(-90 16 140)" text-anchor="middle" fill="' + textColor + '" font-size="12">Allowed actions (count)</text>';
}
function drawAllCharts() {
const ctrl = [0, 350, 550, 600, 1000];
const flow = [0, 0.9294287109, 0.941, 1.05, 1.9220000000];
const power = [5.0760000000, 39.1178710938, 64.8, 76.4, 169.2000000000];
const eff = flow.map(function (q, i) { return q / (power[i] || 1); });
drawLineChart("flowChart", {
x: ctrl,
y: flow,
xLabel: "Control setpoint",
yLabel: "Flow",
color: getComputedStyle(document.documentElement).getPropertyValue("--teal").trim() || "#0d9f9e"
});
drawLineChart("powerChart", {
x: ctrl,
y: power,
xLabel: "Control setpoint",
yLabel: "Power (kW)",
color: getComputedStyle(document.documentElement).getPropertyValue("--blue").trim() || "#2059d8"
});
drawLineChart("effChart", {
x: ctrl,
y: eff,
xLabel: "Control setpoint",
yLabel: "Efficiency index (flow/power)",
color: getComputedStyle(document.documentElement).getPropertyValue("--green").trim() || "#0fa57d"
});
drawModeBarChart("modeChart");
}
renderRows();
renderParameterSheet();
drawAllCharts();
})();
</script>
</body>
</html>