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,36 @@
# Function Anchors
This folder stores class-level anchor documents that define EVOLV logic truth for long-term maintainability.
## Standard
1. Start each anchor with a **Connection Map (At a Glance)**.
2. Then provide a **Unit Table** as the first data section.
3. Cover the class end-to-end: config, I/O contracts, mode/state logic, full function inventory, calculations, safeguards, tests, invariants, and known gaps.
4. Keep references tied to file/line evidence.
## Mandatory Architecture Rule
All EVOLV node anchors must use the same folder and artifact structure as `rotatingMachine`.
Required per node:
- `.agents/function-anchors/<nodeName>/ANCHOR-<nodeName>.md`
- `.agents/function-anchors/<nodeName>/ANCHOR-<nodeName>.html`
- `.agents/function-anchors/<nodeName>/EVIDENCE-<nodeName>-tests.md`
- `nodes/<nodeName>/test/basic/*.test.js`
- `nodes/<nodeName>/test/integration/*.test.js`
- `nodes/<nodeName>/test/edge/*.test.js`
Enforcement policy:
- Do not ship behavioral changes in `nodes/<nodeName>/` without updating the matching anchor and evidence files.
- New EVOLV nodes must be created with this structure from day one.
- Existing nodes missing this structure are considered incomplete and must be brought to parity.
## Files
- `TEMPLATE.md`: reusable format for all future anchor points.
- `rotatingMachine/ANCHOR-rotatingMachine.md`: current rotatingMachine anchor.
- `rotatingMachine/EVIDENCE-rotatingMachine-tests.md`: test-evidence companion.
- `pumpingStation/ANCHOR-pumpingStation.md`: pumpingStation anchor preparation baseline.
- `pumpingStation/ANCHOR-pumpingStation.html`: pumpingStation visual topology anchor baseline.
- `pumpingStation/EVIDENCE-pumpingStation-tests.md`: pumpingStation test plan/evidence baseline.
- `monster/ANCHOR-monster.md`: monster node anchor baseline with API/report integration context.
- `monster/ANCHOR-monster.html`: monster visual topology anchor baseline.
- `monster/EVIDENCE-monster-tests.md`: monster test evidence baseline.

View File

@@ -0,0 +1,94 @@
# Function Anchor Template
Use this template to document any EVOLV class as a stable "logic truth" anchor.
## Mandatory File Layout (Required For Every Node)
- `.agents/function-anchors/<nodeName>/ANCHOR-<nodeName>.md`
- `.agents/function-anchors/<nodeName>/ANCHOR-<nodeName>.html`
- `.agents/function-anchors/<nodeName>/EVIDENCE-<nodeName>-tests.md`
- `nodes/<nodeName>/test/basic/*.test.js`
- `nodes/<nodeName>/test/integration/*.test.js`
- `nodes/<nodeName>/test/edge/*.test.js`
Any deviation from this layout must be treated as technical debt and resolved before closing the work item.
## 1) Connection Map (At a Glance)
- **Node type**:
- **Consumes from EVOLV nodes/topics**:
- **Publishes to EVOLV nodes/topics**:
- **Registers as child to**:
- **Accepts child registration from**:
- **Admin/UI endpoints**:
## 2) Unit Table (Always First Data Section)
| Signal/Field | Represents | Asset Type | Default Unit | Accepted Units | Source of Truth (file:line) | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|---|---|
## 3) Class Identity
- **Class**:
- **Primary files**:
- **Runtime responsibility**:
- **Editor responsibility**:
## 4) Configuration Contract
| UI Field | Runtime Path | Default | Validation/Coercion | Behavior Impact | Source |
|---|---|---|---|---|---|
## 5) Input/Output Contract
### Input topics
| Topic | Payload schema | Handler | Side effects | Source |
|---|---|---|---|---|
### Output ports
| Port | Message type | Producer method | Typical consumers | Source |
|---|---|---|---|---|
### Admin endpoints
| Endpoint | Method | Purpose | Source |
|---|---|---|---|
## 6) Mode, State, and Control Model
- **Modes**:
- **Allowed actions by mode**:
- **Allowed sources by mode**:
- **Operational states for prediction**:
- **Sequence definitions**:
## 7) End-to-End Execution Flow
1. Constructor and initialization flow.
2. Registration and child wiring flow.
3. Input routing flow.
4. Tick/output emission flow.
5. Status update flow.
## 8) Full Function Inventory
| Function | Purpose | Reads | Writes | Calls | Emits/Returns | Failure/Fallback | Source | Covered by tests |
|---|---|---|---|---|---|---|---|---|
## 9) Calculations and Physical Semantics
- **Prediction paths** (flow, power, control).
- **Pressure selection order**.
- **Efficiency, CoG, and BEP distance calculations**.
- **Assumptions and plausibility constraints**.
## 10) Error Handling and Safeguards
- Validation guards.
- Warning/error paths.
- Availability-first behavior.
## 11) Test Evidence Matrix
| Test file | What is covered | Methods/contracts anchored |
|---|---|---|
## 12) Invariants (Anchor Truth)
- Non-negotiable behaviors this class must preserve.
## 13) Known Gaps / Risks
- Mismatches, TODOs, or technical debt observed in current implementation.
## 14) Change Checklist
- Required updates when logic changes:
- Code sections
- Contract docs
- Tests
- Example flows

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>dashboardAPI Anchor</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; background: #f7f8fa; color: #1f2937; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; }
</style>
</head>
<body>
<h1>dashboardAPI Function Anchor</h1>
<div class="card">Baseline topology placeholder. Expand during functional changes.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
# dashboardAPI Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- Node type: dashboardAPI
- Scope: baseline anchor scaffold to satisfy EVOLV required architecture.
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| TBD | TBD | TBD | nodes/dashboardAPI/src/* | TBD | TBD | TBD |
## 2) Class Identity
- Runtime registration: nodes/dashboardAPI
- Node-RED wrapper: nodes/dashboardAPI/src/nodeClass.js (when present)
- Domain logic: nodes/dashboardAPI/src/specificClass.js (when present)
- Editor UI: nodes/dashboardAPI/*.html (when present)
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
- Replace placeholder sections with full contract mapping on first functional change.
## 4) Standardization Plan
1. Maintain this anchor and evidence docs with behavior changes.
2. Maintain tests under test/basic, test/integration, test/edge.
3. Maintain examples package (README, basic.flow.json, integration.flow.json, edge.flow.json).
## 5) Acceptance Criteria For Completion
- Anchor/evidence artifacts exist.
- Test structure exists.
- Example structure exists.

View File

@@ -0,0 +1,15 @@
# dashboardAPI Test Evidence
Status: baseline structure scaffolded.
## Required Test Layout
- nodes/dashboardAPI/test/basic/*.test.js
- nodes/dashboardAPI/test/integration/*.test.js
- nodes/dashboardAPI/test/edge/*.test.js
## Baseline Mapping
| Test file | Scope |
|---|---|
| nodes/dashboardAPI/test/basic/structure-module-load.basic.test.js | module load smoke |
| nodes/dashboardAPI/test/integration/structure-examples.integration.test.js | examples package integrity |
| nodes/dashboardAPI/test/edge/structure-examples-node-type.edge.test.js | node-type presence in basic example |

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>diffuser Anchor</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; background: #f7f8fa; color: #1f2937; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; }
</style>
</head>
<body>
<h1>diffuser Function Anchor</h1>
<div class="card">Baseline topology placeholder. Expand during functional changes.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
# diffuser Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- Node type: diffuser
- Scope: baseline anchor scaffold to satisfy EVOLV required architecture.
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| TBD | TBD | TBD | nodes/diffuser/src/* | TBD | TBD | TBD |
## 2) Class Identity
- Runtime registration: nodes/diffuser
- Node-RED wrapper: nodes/diffuser/src/nodeClass.js (when present)
- Domain logic: nodes/diffuser/src/specificClass.js (when present)
- Editor UI: nodes/diffuser/*.html (when present)
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
- Replace placeholder sections with full contract mapping on first functional change.
## 4) Standardization Plan
1. Maintain this anchor and evidence docs with behavior changes.
2. Maintain tests under test/basic, test/integration, test/edge.
3. Maintain examples package (README, basic.flow.json, integration.flow.json, edge.flow.json).
## 5) Acceptance Criteria For Completion
- Anchor/evidence artifacts exist.
- Test structure exists.
- Example structure exists.

View File

@@ -0,0 +1,15 @@
# diffuser Test Evidence
Status: baseline structure scaffolded.
## Required Test Layout
- nodes/diffuser/test/basic/*.test.js
- nodes/diffuser/test/integration/*.test.js
- nodes/diffuser/test/edge/*.test.js
## Baseline Mapping
| Test file | Scope |
|---|---|
| nodes/diffuser/test/basic/structure-module-load.basic.test.js | module load smoke |
| nodes/diffuser/test/integration/structure-examples.integration.test.js | examples package integrity |
| nodes/diffuser/test/edge/structure-examples-node-type.edge.test.js | node-type presence in basic example |

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>machineGroupControl Anchor</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; background: #f7f8fa; color: #1f2937; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; }
</style>
</head>
<body>
<h1>machineGroupControl Function Anchor</h1>
<div class="card">Baseline topology placeholder. Expand during functional changes.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
# machineGroupControl Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- Node type: machineGroupControl
- Scope: baseline anchor scaffold to satisfy EVOLV required architecture.
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| TBD | TBD | TBD | nodes/machineGroupControl/src/* | TBD | TBD | TBD |
## 2) Class Identity
- Runtime registration: nodes/machineGroupControl
- Node-RED wrapper: nodes/machineGroupControl/src/nodeClass.js (when present)
- Domain logic: nodes/machineGroupControl/src/specificClass.js (when present)
- Editor UI: nodes/machineGroupControl/*.html (when present)
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
- Replace placeholder sections with full contract mapping on first functional change.
## 4) Standardization Plan
1. Maintain this anchor and evidence docs with behavior changes.
2. Maintain tests under test/basic, test/integration, test/edge.
3. Maintain examples package (README, basic.flow.json, integration.flow.json, edge.flow.json).
## 5) Acceptance Criteria For Completion
- Anchor/evidence artifacts exist.
- Test structure exists.
- Example structure exists.

View File

@@ -0,0 +1,15 @@
# machineGroupControl Test Evidence
Status: baseline structure scaffolded.
## Required Test Layout
- nodes/machineGroupControl/test/basic/*.test.js
- nodes/machineGroupControl/test/integration/*.test.js
- nodes/machineGroupControl/test/edge/*.test.js
## Baseline Mapping
| Test file | Scope |
|---|---|
| nodes/machineGroupControl/test/basic/structure-module-load.basic.test.js | module load smoke |
| nodes/machineGroupControl/test/integration/structure-examples.integration.test.js | examples package integrity |
| nodes/machineGroupControl/test/edge/structure-examples-node-type.edge.test.js | node-type presence in basic example |

View File

@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Measurement Anchor Topology</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; color: #1f2937; background: #f7f8fa; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; margin-bottom: 12px; }
.topic { font-family: monospace; color: #0f52a5; }
ul { margin: 8px 0 0 20px; }
</style>
</head>
<body>
<h1>Measurement Function Anchor</h1>
<div class="card">
<h2>Topology</h2>
<ul>
<li><strong>Node:</strong> <code>measurement</code></li>
<li><strong>Runtime:</strong> <code>nodes/measurement/src/nodeClass.js</code></li>
<li><strong>Domain:</strong> <code>nodes/measurement/src/specificClass.js</code></li>
<li><strong>Admin endpoints:</strong> <code>/measurement/menu.js</code>, <code>/measurement/configData.js</code>, <code>/measurement/asset-reg</code></li>
</ul>
</div>
<div class="card">
<h2>Input Topics</h2>
<ul>
<li><span class="topic">measurement</span> -> set input value when payload is numeric</li>
<li><span class="topic">simulator</span> -> toggle simulation mode</li>
<li><span class="topic">outlierDetection</span> -> toggle outlier mode flag</li>
<li><span class="topic">calibrate</span> -> run calibration logic</li>
</ul>
</div>
<div class="card">
<h2>Output Ports</h2>
<ul>
<li>Port 0: process message</li>
<li>Port 1: influx message</li>
<li>Port 2: parent registration (<code>registerChild</code>)</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,53 @@
# Measurement Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- **Node type**: `measurement` (`nodes/measurement/measurement.js:1`, `nodes/measurement/measurement.html:14`)
- **Consumes topics**: `measurement`, `simulator`, `outlierDetection`, `calibrate` (`nodes/measurement/src/nodeClass.js:147`)
- **Publishes periodic outputs**:
- Output `0`: process payload (`nodes/measurement/src/nodeClass.js:137`)
- Output `1`: influx payload (`nodes/measurement/src/nodeClass.js:138`)
- Output `2`: parent registration (`registerChild`) (`nodes/measurement/src/nodeClass.js:118`)
- **Cross-node integrations (direct observed)**:
- Registers as child to parent with `positionVsParent` and optional `distance` (`nodes/measurement/src/nodeClass.js:118`)
- Emits measurement updates through `MeasurementContainer` in `specificClass` (`nodes/measurement/src/specificClass.js:479`)
- **Admin/UI endpoints**:
- `GET /measurement/menu.js` (`nodes/measurement/measurement.js:23`)
- `GET /measurement/configData.js` (`nodes/measurement/measurement.js:33`)
- `POST /measurement/asset-reg` (`nodes/measurement/measurement.js:43`)
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| `inputValue` | raw measurement input | asset-dependent | `nodes/measurement/src/specificClass.js:30` | `measurement` topic or simulator | scaling/smoothing pipeline | defaults to `0` |
| `outputAbs` (`mAbs`) | processed absolute output | `config.asset.unit` | `nodes/measurement/src/specificClass.js:472` | `updateOutputAbs()` | process/influx outputs + event emitter | constrained to configured abs range |
| `outputPercent` (`mPercent`) | normalized percent-like output | `%` semantic | `nodes/measurement/src/specificClass.js:483` | `updateOutputPercent()` | process/influx outputs | interpolated from abs range or observed min/max |
| `storedValues` | smoothing window values | same as processed value | `nodes/measurement/src/specificClass.js:24` | `applySmoothing()` | smoothing and repeatability checks | capped to `smoothWindow` length |
| `simulation.enabled` | internal simulated signal mode | boolean | `nodes/measurement/src/specificClass.js:52` | config/topic toggle | `tick()` behavior | off by default |
## 2) Class Identity
- **Runtime registration + endpoints**: `nodes/measurement/measurement.js`
- **Node-RED wrapper/routing**: `nodes/measurement/src/nodeClass.js`
- **Domain measurement logic**: `nodes/measurement/src/specificClass.js`
- **Editor UI/defaults**: `nodes/measurement/measurement.html`
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
1. Node label precedence can hide fallback text due to expression order:
- `return this.positionIcon + " " + this.assetType || "Measurement";` (`nodes/measurement/measurement.html:63`)
2. `success` variable is assigned without declaration in editor save path:
- `success = window.EVOLV.nodes.measurement.assetMenu.saveEditor(this);` (`nodes/measurement/measurement.html:131`)
3. `toggleOutlierDetection()` mutates config object to boolean:
- `this.config.outlierDetection = !this.config.outlierDetection;` (`nodes/measurement/src/specificClass.js:503`)
4. Input handler ignores numeric strings for `measurement` topic:
- accepts only `typeof msg.payload === 'number'` (`nodes/measurement/src/nodeClass.js:152`)
## 4) Standardization Plan (Mirror RotatingMachine)
1. Keep this anchor pair (`.md` + `.html`) and evidence file maintained with behavior changes.
2. Maintain test layout under `nodes/measurement/test/`:
- `basic/`, `integration/`, `edge/`, `helpers/`
3. Maintain examples package under `nodes/measurement/examples/`:
- `README.md`, `basic.flow.json`, `integration.flow.json`, `edge.flow.json`
## 5) Acceptance Criteria For Completion
- Required anchor artifacts exist and map to current behavior.
- Test suite runs with node-level command.
- Example flow files exist and pass flow-structure tests.

View File

@@ -0,0 +1,32 @@
# Measurement Test Evidence
Status: baseline suite created and executed.
## Required Test Layout
- `nodes/measurement/test/basic/*.test.js`
- `nodes/measurement/test/integration/*.test.js`
- `nodes/measurement/test/edge/*.test.js`
- `nodes/measurement/test/helpers/*.js`
## Test-to-Contract Mapping
| Test file | Scope | Primary contracts anchored |
|---|---|---|
| `nodes/measurement/test/basic/specific-constructor.basic.test.js` | constructor baseline and range derivation | `Measurement` constructor |
| `nodes/measurement/test/basic/scaling-and-output.basic.test.js` | scaling constraint and output update path | `calculateInput`, `updateOutputAbs`, `getOutput` |
| `nodes/measurement/test/basic/nodeclass-routing.basic.test.js` | topic routing and registration output shape | `nodeClass._attachInputHandler`, `_registerChild` |
| `nodes/measurement/test/integration/examples-flows.integration.test.js` | example package integrity and expected topic drivers | `nodes/measurement/examples/*.flow.json` |
| `nodes/measurement/test/integration/measurement-event.integration.test.js` | measurement container event emission contract | `updateOutputAbs`, measurement emitter wiring |
| `nodes/measurement/test/edge/invalid-payload.edge.test.js` | non-numeric input payload ignored behavior | `nodeClass._attachInputHandler` measurement branch |
| `nodes/measurement/test/edge/outlier-toggle.edge.test.js` | current outlier toggle behavior capture | `toggleOutlierDetection` |
## Executed
- Command:
- `cd nodes/measurement && npm test`
- Result:
- `pass: baseline suite` (see latest run in session)
- Date:
- February 19, 2026
## Known Gaps Captured by Tests
- Outlier toggle currently converts config object to boolean.
- Measurement topic currently ignores numeric strings.

View File

@@ -0,0 +1,74 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Monster Anchor Map</title>
<style>
:root {
--bg: #f5f7fb;
--panel: #ffffff;
--line: #9ab0d9;
--text: #14233d;
--muted: #4d6084;
--monster: #4f8582;
--sensor: #eef8e8;
--api: #fff5e5;
--ops: #e9f1ff;
}
body { margin: 0; font-family: "Segoe UI", sans-serif; background: var(--bg); color: var(--text); }
.wrap { max-width: 1100px; margin: 24px auto; padding: 0 16px 24px; }
.panel { background: var(--panel); border: 1px solid #dde5f5; border-radius: 12px; padding: 16px; }
h1 { margin: 0 0 8px; font-size: 24px; }
p { margin: 0 0 12px; color: var(--muted); }
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.chip { border: 1px solid #d1ddf5; border-radius: 999px; padding: 4px 10px; background: #f7faff; font-size: 12px; }
svg { width: 100%; height: auto; border-radius: 10px; background: #f9fbff; }
</style>
</head>
<body>
<div class="wrap">
<div class="panel">
<h1>Monster Function Anchor</h1>
<p>External APIs are orchestrated by surrounding flows; the `monster` node computes sampling state and report fields.</p>
<div class="chips">
<span class="chip">input: input_q / i_start / monsternametijden / rain_data</span>
<span class="chip">output: pulse, m3Total, m3PerPuls, bucketVol, running</span>
<span class="chip">report path: Z-Info import + operator m3/pulse reference</span>
</div>
<svg viewBox="0 0 980 380" role="img" aria-label="Monster 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="390" y="150" width="190" height="80" rx="10" fill="var(--monster)"></rect>
<text x="485" y="192" text-anchor="middle" fill="#fff" font-size="16">monster</text>
<rect x="40" y="45" width="220" height="56" rx="9" fill="var(--sensor)" stroke="#b7d89e"></rect>
<text x="150" y="79" text-anchor="middle" fill="#2e5a22" font-size="13">PLC/measurement flow input</text>
<rect x="40" y="290" width="220" height="56" rx="9" fill="var(--api)" stroke="#e9c589"></rect>
<text x="150" y="324" text-anchor="middle" fill="#634319" font-size="13">Open-Meteo + Aquon schedule</text>
<rect x="720" y="45" width="220" height="56" rx="9" fill="var(--ops)" stroke="#aac0ef"></rect>
<text x="830" y="79" text-anchor="middle" fill="#244271" font-size="13">Dashboard / Influx / Grafana</text>
<rect x="720" y="290" width="220" height="56" rx="9" fill="#e7faf5" stroke="#9edcca"></rect>
<text x="830" y="324" text-anchor="middle" fill="#1e5244" font-size="13">PLC pulse + Z-Info report tooling</text>
<line x1="260" y1="73" x2="390" y2="160" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<line x1="260" y1="318" x2="390" y2="220" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<line x1="580" y1="160" x2="720" y2="75" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<line x1="580" y1="220" x2="720" y2="318" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<text x="294" y="125" fill="var(--muted)" font-size="12">input_q / i_start / registerChild</text>
<text x="285" y="277" fill="var(--muted)" font-size="12">rain_data / monsternametijden</text>
<text x="610" y="124" fill="var(--muted)" font-size="12">process + influx streams</text>
<text x="607" y="278" fill="var(--muted)" font-size="12">pulse + m3Total + m3PerPuls</text>
</svg>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,85 @@
# Monster Function Anchor (Baseline)
## 0) Connection Map (At a Glance)
- **Node type**: `monster` (`nodes/monster/monster.js:1`, `nodes/monster/monster.html:5`)
- **Consumes control/input topics**: `input_q`, `i_start`, `monsternametijden`, `rain_data`, `registerChild` (`nodes/monster/src/nodeClass.js:202`)
- **Publishes periodic outputs**:
- Output `0`: process payload (`nodes/monster/src/nodeClass.js:185`)
- Output `1`: influx payload (`nodes/monster/src/nodeClass.js:186`)
- Output `2`: parent registration (`registerChild`) (`nodes/monster/src/nodeClass.js:158`)
- **Cross-node integrations**:
- Accepts measurement children of type `flow` (`nodes/monster/src/specificClass.js:300`)
- Common external orchestration pattern around this node:
- Open-Meteo -> `rain_data`
- Aquon schedule feed -> `monsternametijden`
- PLC/MQTT pulse sink fed by `output.pulse`
- Z-Info/report tooling fed by `m3Total` + `m3PerPuls`
- Dashboard API/Grafana and Influx consumers
- **Admin/UI endpoints**:
- `GET /monster/menu.js`
- `GET /monster/configData.js` (`nodes/monster/monster.js:17`, `nodes/monster/monster.js:27`)
## 1) Unit Table (Always First Data Section)
| Signal/Field | Represents | Asset Type | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|---|
| `input_q.payload.value` | influent flow command | manual/control input | `m3/h` (normalized in wrapper) | `nodes/monster/src/nodeClass.js:216` | upstream control flow | sampling calculation loop | invalid/unit conversion failure is warned and ignored |
| `flow.measured.*` | measured flow from child sensors | measurement child | `m3/h` | `nodes/monster/src/specificClass.js:300` | measurement nodes | effective flow selection | if missing, manual flow or 0 is used |
| `q` | effective flow used by model | derived | `m3/h` | `nodes/monster/src/specificClass.js:775` | `tick()` | pulse and volume progression | defaults to `0` if no measured/manual flow |
| `m3PerPuls` | reporting conversion factor for sampler pulse | derived/report field | `m3/pulse` | `nodes/monster/src/specificClass.js:660` | `sampling_program()` | Z-Info/report tooling, operations | `0` when not running |
| `m3Total` | accumulated volume during active run | derived/report field | `m3` | `nodes/monster/src/specificClass.js:687` | `sampling_program()` | Z-Info/report tooling | reset to `0` when sampling window ends |
| `pulse` | pulse command signal | control output | boolean | `nodes/monster/src/specificClass.js:707` | `sampling_program()` | PLC/MQTT pulse output paths | forced `false` under cooldown/capacity/end-of-run |
| `bucketVol` | sampled bucket fill volume | derived/state | `L` | `nodes/monster/src/specificClass.js:712` | pulse accumulation | dashboard/operator checks | reset to `0` after run |
| `predictedRateM3h` | rain-scaled prediction reference | derived | `m3/h` | `nodes/monster/src/specificClass.js:367` | `getOutput()` | dashboards/diagnostics | falls back to measured/manual effective rate |
## 2) Class Identity
- **Runtime registration + endpoints**: `nodes/monster/monster.js`
- **Node-RED wrapper/routing**: `nodes/monster/src/nodeClass.js`
- **Domain sampling logic**: `nodes/monster/src/specificClass.js`
- **Editor UI/defaults**: `nodes/monster/monster.html`
- **Default config schema**: `nodes/generalFunctions/src/configs/monster.json`
## 3) Configuration Contract (Key)
| UI Field | Runtime Path | Default | Behavior Impact | Source |
|---|---|---|---|---|
| `samplingtime` | `constraints.samplingtime` | `0` | sampling window hours | `nodes/monster/monster.html:16`, `nodes/monster/src/nodeClass.js:68` |
| `minvolume` | `constraints.minVolume` | `5` | min valid sample volume | `nodes/monster/monster.html:17`, `nodes/monster/src/nodeClass.js:69` |
| `maxweight` | `constraints.maxWeight` | `22` | max bucket load before invalid sample | `nodes/monster/monster.html:18`, `nodes/monster/src/nodeClass.js:70` |
| `nominalFlowMin` / `flowMax` | `constraints.nominalFlowMin` / `constraints.flowMax` | `0` / `0` | prediction bounds and start guard | `nodes/monster/monster.html:19`, `nodes/monster/src/specificClass.js:226` |
| `minSampleIntervalSec` | `constraints.minSampleIntervalSec` | `60` | pulse cooldown protection | `nodes/monster/monster.html:22`, `nodes/monster/src/specificClass.js:693` |
| `emptyWeightBucket` | `asset.emptyWeightBucket` | `3` | max bucket volume derivation | `nodes/monster/monster.html:23`, `nodes/monster/src/specificClass.js:378` |
| `aquon_sample_name` | `aquonSampleName` | `"112100"` internal default | schedule selector key | `nodes/monster/monster.html:24`, `nodes/monster/src/nodeClass.js:96` |
## 4) I/O and Integration Notes
- Node-level output is process/influx/parent only.
- External APIs are normally handled by surrounding flows, not by the node class itself.
- Report tooling integration should read from process payload fields:
- `m3Total`
- `m3PerPuls`
- `running`
- `pulse`
- Reference examples:
- dashboard baseline: `nodes/monster/examples/monster-dashboard.flow.json`
- full API + dashboard template: `nodes/monster/examples/monster-api-dashboard.flow.json`
## 5) Current Gaps / Risks
1. Wrapper exposes topics (`setMode`, `execSequence`, `execMovement`, `flowMovement`, `emergencystop`) that are not implemented in `Monster.handleInput` contract.
2. `showWorkingCurves`/`CoG` routes in wrapper call methods that are not present in `Monster`.
3. Existing legacy tests were not organized in required `basic/integration/edge` folders before this update.
4. External API credentials/tokens must remain outside committed example flows.
## 6) Test Evidence Matrix (Current Baseline)
| Test file | What is covered | Methods/contracts anchored |
|---|---|---|
| `nodes/monster/test/basic/constructor.basic.test.js` | constructor + output field contract | `constructor`, `set_boundries_and_targets`, `getOutput` |
| `nodes/monster/test/integration/flow-and-schedule.integration.test.js` | flow averaging, rain/schedule ingestion | `registerChild`, `handleInput`, `tick`, `updateRainData`, `regNextDate` |
| `nodes/monster/test/edge/sampling-guards.edge.test.js` | invalid-bound guard + cooldown behavior | `validateFlowBounds`, `sampling_program`, cooldown gate |
## 7) Change Checklist
- When `monster` behavior changes, update:
- `nodes/monster/src/nodeClass.js`
- `nodes/monster/src/specificClass.js`
- `nodes/monster/monster.html`
- `nodes/monster/test/basic|integration|edge/*`
- `.agents/function-anchors/monster/ANCHOR-monster.md`
- `.agents/function-anchors/monster/ANCHOR-monster.html`
- `.agents/function-anchors/monster/EVIDENCE-monster-tests.md`

View File

@@ -0,0 +1,30 @@
# Monster Test Evidence
## Executed Baseline
- Command:
- `node --test test/basic/*.test.js test/integration/*.test.js test/edge/*.test.js`
- Working directory:
- `nodes/monster`
- Result:
- `pass: 6`, `fail: 0`
## Test Matrix
| Test file | Scope | Contracts anchored |
|---|---|---|
| `nodes/monster/test/basic/constructor.basic.test.js` | initialization and output field contract | constructor, boundary setup, report output fields |
| `nodes/monster/test/basic/structure-module-load.basic.test.js` | required structure/module load guard | baseline architecture compliance |
| `nodes/monster/test/integration/flow-and-schedule.integration.test.js` | flow aggregation + rain/schedule ingestion | measured/manual flow merge, `handleInput`, schedule update path |
| `nodes/monster/test/integration/structure-examples.integration.test.js` | required examples contract guard | example flow presence/shape |
| `nodes/monster/test/edge/sampling-guards.edge.test.js` | sampling safety guards | invalid flow bounds guard, cooldown pulse throttling |
| `nodes/monster/test/edge/structure-examples-node-type.edge.test.js` | node-type structure guard | example includes `monster` node usage |
## Coverage Notes
- Structure guards now require both dashboard examples:
- `nodes/monster/examples/monster-dashboard.flow.json`
- `nodes/monster/examples/monster-api-dashboard.flow.json`
- Focused on the most operationally critical report fields:
- `m3Total`
- `m3PerPuls`
- `pulse`
- `running`
- Additional contract tests are still recommended for wrapper topic routes that currently map to unsupported domain handlers.

View File

@@ -0,0 +1,75 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PumpingStation Anchor Map</title>
<style>
:root {
--bg: #f5f7fb;
--panel: #ffffff;
--line: #9ab0d9;
--text: #14233d;
--muted: #4d6084;
--pump: #0c99d9;
--child: #e7faf5;
--sensor: #eef8e8;
--dash: #fff5e5;
}
body { margin: 0; font-family: "Segoe UI", sans-serif; background: var(--bg); color: var(--text); }
.wrap { max-width: 1100px; margin: 24px auto; padding: 0 16px 24px; }
.panel { background: var(--panel); border: 1px solid #dde5f5; border-radius: 12px; padding: 16px; }
h1 { margin: 0 0 8px; font-size: 24px; }
p { margin: 0 0 12px; color: var(--muted); }
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.chip { border: 1px solid #d1ddf5; border-radius: 999px; padding: 4px 10px; background: #f7faff; font-size: 12px; }
svg { width: 100%; height: auto; border-radius: 10px; background: #f9fbff; }
</style>
</head>
<body>
<div class="wrap">
<div class="panel">
<h1>PumpingStation Function Anchor</h1>
<p>Preparation baseline map. Keep this topology in sync with `ANCHOR-pumpingStation.md` and runtime contracts.</p>
<div class="chips">
<span class="chip">input: registerChild / calibrate* / q_in / changemode</span>
<span class="chip">output[0]: process</span>
<span class="chip">output[1]: influx</span>
<span class="chip">output[2]: registerChild</span>
</div>
<svg viewBox="0 0 900 360" role="img" aria-label="PumpingStation integration map">
<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="360" y="140" width="180" height="72" rx="10" fill="var(--pump)"></rect>
<text x="450" y="182" text-anchor="middle" fill="#fff" font-size="16">pumpingStation</text>
<rect x="40" y="40" width="210" height="56" rx="9" fill="var(--child)" stroke="#9edcca"></rect>
<text x="145" y="74" text-anchor="middle" fill="#1e5244" font-size="13">machine / machineGroupControl</text>
<rect x="40" y="250" width="210" height="56" rx="9" fill="var(--sensor)" stroke="#b7d89e"></rect>
<text x="145" y="284" text-anchor="middle" fill="#2e5a22" font-size="13">measurement (level/flow/pressure)</text>
<rect x="650" y="40" width="210" height="56" rx="9" fill="var(--dash)" stroke="#e9c589"></rect>
<text x="755" y="74" text-anchor="middle" fill="#634319" font-size="13">dashboard / manual control</text>
<rect x="650" y="250" width="210" height="56" rx="9" fill="#e9f1ff" stroke="#aac0ef"></rect>
<text x="755" y="284" text-anchor="middle" fill="#244271" font-size="13">parent process / orchestrator</text>
<line x1="250" y1="68" x2="360" y2="152" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<line x1="250" y1="278" x2="360" y2="198" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<line x1="650" y1="68" x2="540" y2="150" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<line x1="540" y1="202" x2="650" y2="278" stroke="var(--line)" stroke-width="2" marker-end="url(#arr)"></line>
<text x="268" y="128" fill="var(--muted)" font-size="12">flow.predicted.* / control handoff</text>
<text x="260" y="240" fill="var(--muted)" font-size="12">*.measured.&lt;position&gt;</text>
<text x="566" y="128" fill="var(--muted)" font-size="12">q_in / calibrate / mode</text>
<text x="560" y="240" fill="var(--muted)" font-size="12">registerChild + process/influx consumers</text>
</svg>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,69 @@
# Pumping Station Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- **Node type**: `pumpingStation` (`nodes/pumpingStation/pumpingStation.js:1`, `nodes/pumpingStation/pumpingStation.html:15`)
- **Consumes parent/control topics**: `changemode`, `registerChild`, `calibratePredictedVolume`, `calibratePredictedLevel`, `q_in` (`nodes/pumpingStation/src/nodeClass.js:209`)
- **Publishes periodic outputs**:
- Output `0`: process payload (`nodes/pumpingStation/src/nodeClass.js:197`)
- Output `1`: influx payload (`nodes/pumpingStation/src/nodeClass.js:198`)
- Output `2`: parent registration/control plumbing (`registerChild`) (`nodes/pumpingStation/src/nodeClass.js:114`)
- **Cross-node integrations (direct observed)**:
- Registers `measurement` children and listens for `*.measured.<position>` events (`nodes/pumpingStation/src/specificClass.js:73`)
- Registers `machine`, `machinegroup`, `pumpingstation` children and listens for predicted flow (`nodes/pumpingStation/src/specificClass.js:59`)
- Commands child machines/stations/groups during control/safety transitions (`nodes/pumpingStation/src/specificClass.js:258`, `nodes/pumpingStation/src/specificClass.js:528`)
- **Admin/UI endpoints**:
- `GET /pumpingStation/menu.js`
- `GET /pumpingStation/configData.js` (`nodes/pumpingStation/pumpingStation.js:22`, `nodes/pumpingStation/pumpingStation.js:33`)
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| `flow.measured.*` / `flow.predicted.*` | inflow/outflow streams | `m3/s` preferred | `nodes/pumpingStation/src/specificClass.js:24` | measurement/machine/machinegroup children | net-flow selection + predicted volume integration | falls back to level-rate estimate when unavailable (`nodes/pumpingStation/src/specificClass.js:458`) |
| `level.measured.*` / `level.predicted.*` | wet well level | `m` | `nodes/pumpingStation/src/specificClass.js:24` | measurement or pressure conversion path | control decisions + remaining-time estimate | if no level available, remaining time becomes null (`nodes/pumpingStation/src/specificClass.js:487`) |
| `volume.predicted.atequipment` | integrated basin volume | `m3` | `nodes/pumpingStation/src/specificClass.js:393` | tick-based integration | safety + status + output | if volume unreadable, station shuts down machines availability-first (`nodes/pumpingStation/src/specificClass.js:503`) |
| `volumePercent.*.atequipment` | normalized fill percentage | `%` | `nodes/pumpingStation/src/specificClass.js:424` | level/volume conversion | status + dashboards | not emitted until level/volume is known |
| `netFlowRate.*.atequipment` | selected net flow | measured unit or `m3/s` | `nodes/pumpingStation/src/specificClass.js:454` | `_selectBestNetFlow()` | status + remaining-time + safety | defaults to `0` with `steady` direction (`nodes/pumpingStation/src/specificClass.js:466`) |
| `timeleft` | estimated seconds to empty/full limit | `s` | `nodes/pumpingStation/src/specificClass.js:470` | `_computeRemainingTime()` | safety logic + output | null if insufficient data |
## 2) Class Identity
- **Runtime registration + endpoints**: `nodes/pumpingStation/pumpingStation.js`
- **Node-RED wrapper/routing**: `nodes/pumpingStation/src/nodeClass.js`
- **Domain/station logic**: `nodes/pumpingStation/src/specificClass.js`
- **Editor UI/defaults**: `nodes/pumpingStation/pumpingStation.html`
- **Default config schema/validation rules**: `nodes/generalFunctions/src/configs/pumpingStation.json`
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
1. Topic/mode mismatch:
- UI default uses `controlMode: "none"` (`nodes/pumpingStation/pumpingStation.html:59`)
- runtime switch expects `manual` not `none` (`nodes/pumpingStation/src/specificClass.js:234`)
2. Position token mismatch risk:
- code mixes `atEquipment` and `atequipment` variants (`nodes/pumpingStation/src/nodeClass.js:122`, `nodes/pumpingStation/src/specificClass.js:103`)
3. Child softwareType mismatch risk:
- checks for `'pumpingstation'`/`'machinegroup'` lowercase (`nodes/pumpingStation/src/specificClass.js:61`, `nodes/pumpingStation/src/specificClass.js:63`)
- other configs generally use camelCase (`nodes/generalFunctions/src/configs/pumpingStation.json:48`)
4. Missing guards in input registration path:
- no null check after `RED.nodes.getNode` (`nodes/pumpingStation/src/nodeClass.js:217`)
5. Test baseline exists but is not yet full parity:
- basic/edge/integration scaffolding is present; additional safety/control math coverage is still pending.
## 4) Standardization Plan (Mirror RotatingMachine)
1. Create `ANCHOR-pumpingStation.html` with:
- always-visible topology map
- unit/signal catalog table
- control and safety flow diagram
- known invariants and risk list
2. Expand the current unit/integration/edge test suite under `nodes/pumpingStation/test/`:
- config defaults/overrides
- topic routing and child registration
- predicted volume integration and remaining-time math
- safety triggers and control actions
- regression for string casing mismatches and missing child node IDs
3. Add evidence companion doc:
- `EVIDENCE-pumpingStation-tests.md` with fail-before/pass-after references.
4. Keep this anchor and tests updated on every pumpingStation behavior change.
## 5) Acceptance Criteria For Completion
- Anchor markdown complete to template parity with rotatingMachine.
- Anchor HTML visualization added and aligned with actual contracts.
- Test suite runnable with `node --test nodes/pumpingStation/test/**/*.test.js`.
- Evidence file links each test file to anchored behavior.

View File

@@ -0,0 +1,34 @@
# PumpingStation Test Evidence (Preparation)
Status: baseline suite created and executed.
## Executed
- Command:
- `node --test test/basic/*.test.js test/edge/*.test.js test/integration/*.test.js`
- Working directory:
- `nodes/pumpingStation`
- Result:
- `pass: 4`, `fail: 0`
## Planned Test Matrix
| Planned test file | Scope | Primary contracts anchored |
|---|---|---|
| `nodes/pumpingStation/test/basic/constructor.basic.test.js` | config initialization, basin property derivation | constructor, `initBasinProperties`, config defaults |
| `nodes/pumpingStation/test/basic/nodeClass-routing.basic.test.js` | topic routing and registration handling | `nodeClass._attachInputHandler`, `registerChild`, calibration topics, `q_in` parsing |
| `nodes/pumpingStation/test/integration/registration-normalization.integration.test.js` | softwareType/position normalization and listener dedupe | `registerChild`, `_registerPredictedFlowChild`, `_registerMeasurementChild` |
| `nodes/pumpingStation/test/edge/mode-alias.edge.test.js` | mode alias normalization | `_normalizeMode`, `changeMode` compatibility path |
| `nodes/pumpingStation/test/integration/flow-balance.integration.test.js` | inflow/outflow aggregation and predicted volume update | `_updatePredictedVolume`, `_selectBestNetFlow`, `_computeRemainingTime` |
| `nodes/pumpingStation/test/integration/measurement.integration.test.js` | level/pressure measurement handling and conversions | `_onLevelMeasurement`, `_onPressureMeasurement` |
| `nodes/pumpingStation/test/integration/safety.integration.test.js` | dry-run/overfill/time threshold behavior | `_safetyController` |
| `nodes/pumpingStation/test/integration/control-levelbased.integration.test.js` | level-based machine command dispatch behavior | `_controlLevelBased`, `_applyMachineLevelControl` |
| `nodes/pumpingStation/test/edge/status.edge.test.js` | status output formatting under sparse data | `_updateNodeStatus` |
## Execution Target
- Preferred command (after suite exists): `node --test nodes/pumpingStation/test/**/*.test.js`
## Coverage Goal
- Match rotatingMachine discipline:
- config contract coverage
- topic routing coverage
- control/safety path coverage
- regression cases for known risk patterns

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>reactor Anchor</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; background: #f7f8fa; color: #1f2937; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; }
</style>
</head>
<body>
<h1>reactor Function Anchor</h1>
<div class="card">Baseline topology placeholder. Expand during functional changes.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
# reactor Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- Node type: reactor
- Scope: baseline anchor scaffold to satisfy EVOLV required architecture.
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| TBD | TBD | TBD | nodes/reactor/src/* | TBD | TBD | TBD |
## 2) Class Identity
- Runtime registration: nodes/reactor
- Node-RED wrapper: nodes/reactor/src/nodeClass.js (when present)
- Domain logic: nodes/reactor/src/specificClass.js (when present)
- Editor UI: nodes/reactor/*.html (when present)
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
- Replace placeholder sections with full contract mapping on first functional change.
## 4) Standardization Plan
1. Maintain this anchor and evidence docs with behavior changes.
2. Maintain tests under test/basic, test/integration, test/edge.
3. Maintain examples package (README, basic.flow.json, integration.flow.json, edge.flow.json).
## 5) Acceptance Criteria For Completion
- Anchor/evidence artifacts exist.
- Test structure exists.
- Example structure exists.

View File

@@ -0,0 +1,15 @@
# reactor Test Evidence
Status: baseline structure scaffolded.
## Required Test Layout
- nodes/reactor/test/basic/*.test.js
- nodes/reactor/test/integration/*.test.js
- nodes/reactor/test/edge/*.test.js
## Baseline Mapping
| Test file | Scope |
|---|---|
| nodes/reactor/test/basic/structure-module-load.basic.test.js | module load smoke |
| nodes/reactor/test/integration/structure-examples.integration.test.js | examples package integrity |
| nodes/reactor/test/edge/structure-examples-node-type.edge.test.js | node-type presence in basic example |

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>

View File

@@ -0,0 +1,226 @@
# Rotating Machine Function Anchor
## 0) Connection Map (At a Glance)
- **Node type**: `rotatingMachine` (`nodes/rotatingMachine/rotatingMachine.js:1`, `nodes/rotatingMachine/rotatingMachine.html:16`)
- **Consumes parent/control topics**: `setMode`, `execSequence`, `execMovement`, `flowMovement`, `emergencystop`, `simulateMeasurement`, `registerChild`, `showWorkingCurves`, `CoG` (`nodes/rotatingMachine/src/nodeClass.js:267`)
- **Publishes periodic outputs**:
- Output `0`: process payload (`nodes/rotatingMachine/src/nodeClass.js:249`)
- Output `1`: influx payload (`nodes/rotatingMachine/src/nodeClass.js:251`)
- Output `2`: registration/control plumbing (`registerChild`) (`nodes/rotatingMachine/src/nodeClass.js:222`)
- **Cross-node integrations (direct observed)**:
- Registered/managed by `machineGroupControl` as `machine`, which then commands each machine via `handleInput('parent', ...)` (`nodes/machineGroupControl/src/specificClass.js:50`, `nodes/machineGroupControl/src/specificClass.js:711`, `nodes/machineGroupControl/src/specificClass.js:1028`)
- Can be orchestrated by `pumpingStation` via `execSequence` and movement commands (`nodes/pumpingStation/src/specificClass.js:296`, `nodes/pumpingStation/src/specificClass.js:297`)
- Dashboard/test flows inject `simulateMeasurement` and consume process output topics (`nodes/rotatingMachine/examples/basic.flow.json:380`, `nodes/rotatingMachine/examples/basic.flow.json:412`)
- **Admin/UI endpoints**:
- `GET /rotatingMachine/menu.js`
- `GET /rotatingMachine/configData.js` (`nodes/rotatingMachine/rotatingMachine.js:17`, `nodes/rotatingMachine/rotatingMachine.js:27`)
## 1) Unit Table (Anchor Starts Here)
| Signal/Field | Represents | Asset Type | Default Unit | Accepted Units | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|---|---|
| `pressure.measured.*` | pressure input (upstream/downstream) | measurement child (`pressure`) | `mbar` | any convertible via `MeasurementContainer` | `nodes/rotatingMachine/src/specificClass.js:48`, `nodes/rotatingMachine/src/specificClass.js:708` | real child sensors and virtual dashboard child | pressure dimension for curve selection (`predict*.fDimension`) | if missing, pressure dimension forced to minimum (`0`) (`nodes/rotatingMachine/src/specificClass.js:552`) |
| `flow.predicted.downstream` | predicted flow at discharge | rotating machine | `general.unit` from config | convertible (`m3/h`, `l/s`, etc.) | `nodes/rotatingMachine/src/specificClass.js:53`, `nodes/rotatingMachine/src/specificClass.js:423` | `calcFlow()` | output formatting, status text, parent/group logic | forced `0` if non-operational or no curve (`nodes/rotatingMachine/src/specificClass.js:415`, `nodes/rotatingMachine/src/specificClass.js:429`) |
| `flow.predicted.atEquipment` | same flow at equipment point | rotating machine | `general.unit` | convertible | `nodes/rotatingMachine/src/specificClass.js:53`, `nodes/rotatingMachine/src/specificClass.js:424` | `calcFlow()` | efficiency calculations | forced `0` if non-operational/no curve |
| `power.predicted.atEquipment` | predicted power draw | rotating machine | `kW` | convertible (`W`, `kW`) | `nodes/rotatingMachine/src/specificClass.js:54`, `nodes/rotatingMachine/src/specificClass.js:448` | `calcPower()` | efficiency calculations, status text | forced `0` if non-operational/no curve |
| `ctrl.predicted.atEquipment` | predicted control position for requested flow | rotating machine | unitless (%) semantic | numeric | `nodes/rotatingMachine/src/specificClass.js:482` | `calcCtrl()` | `flowmovement` command path | returns `0` if no curve |
| `temperature.measured.atEquipment` | process temp for density lookup | machine fluid context | `C` default, converted to `K` when used | convertible | init at `15 C` (`nodes/rotatingMachine/src/specificClass.js:149`) | init + measurement updates | CoolProp density input | if missing conversion, efficiency can degrade silently |
| `atmPressure.measured.atEquipment` | atmospheric pressure for density lookup | machine fluid context | `Pa` | convertible | init at `101325 Pa` (`nodes/rotatingMachine/src/specificClass.js:151`) | init | CoolProp density input | fallback density `1000 kg/m3` on CoolProp error (`nodes/rotatingMachine/src/specificClass.js:867`) |
| `efficiency.*` | specific flow (`flow/power`) | derived metric | implicit unitless ratio | numeric | `nodes/rotatingMachine/src/specificClass.js:878`, `nodes/rotatingMachine/src/specificClass.js:881` | `calcEfficiency()` | output and BEP distance metrics | unchanged if power/flow are zero |
| `specificEnergyConsumption.*` | power per flow | derived metric | implicit | numeric | `nodes/rotatingMachine/src/specificClass.js:879`, `nodes/rotatingMachine/src/specificClass.js:882` | `calcEfficiency()` | output consumers | unchanged if power/flow zero |
| `nHydraulicEfficiency.*` | hydraulic-efficiency-like metric | derived metric | unitless | numeric | `nodes/rotatingMachine/src/specificClass.js:884` | `calcEfficiency()` | diagnostic output | skipped if pressure/flow/power conversions unavailable |
| `cog`, `NCog`, `NCogPercent` | efficiency-curve peak indicators | derived curve metrics | unitless | numeric | `nodes/rotatingMachine/src/specificClass.js:796`, `nodes/rotatingMachine/src/specificClass.js:948` | `calcCog()`, `getOutput()` | group optimization, dashboards | retains last computed values |
| `effDistFromPeak`, `effRelDistFromPeak` | distance from best-efficiency point | derived | unitless | numeric | `nodes/rotatingMachine/src/specificClass.js:924`, `nodes/rotatingMachine/src/specificClass.js:962` | `calcDistanceBEP()` | output consumers | remains last computed value |
| `runtime`, `maintenanceTime`, `moveTimeleft`, `state` | movement/state telemetry | state machine | `h`/`s`/enum | numeric/string | `nodes/rotatingMachine/src/specificClass.js:943` | `state` module | output/status/parent control | depends on `state` module behavior |
## 2) Class Identity
- **Runtime registration + endpoints**: `nodes/rotatingMachine/rotatingMachine.js`
- **Node-RED wrapper/routing**: `nodes/rotatingMachine/src/nodeClass.js`
- **Domain/mechanical logic**: `nodes/rotatingMachine/src/specificClass.js`
- **Editor UI/defaults**: `nodes/rotatingMachine/rotatingMachine.html`
- **Default config schema/validation rules**: `nodes/generalFunctions/src/configs/rotatingMachine.json`
## 3) Configuration Contract
| UI Field | Runtime Path | Default | Validation/Coercion | Behavior Impact | Source |
|---|---|---|---|---|---|
| `speed` | `stateConfig.movement.speed` | `1` | `Number(uiConfig.speed)` | movement progression speed | `nodes/rotatingMachine/rotatingMachine.html:22`, `nodes/rotatingMachine/src/nodeClass.js:91` |
| `startup/warmup/shutdown/cooldown` | `stateConfig.time.*` | `0` | `Number(...)` | sequence transition durations | `nodes/rotatingMachine/rotatingMachine.html:23`, `nodes/rotatingMachine/src/nodeClass.js:95` |
| `movementMode` | `stateConfig.movement.mode` | `staticspeed` | raw string | state movement model selection | `nodes/rotatingMachine/rotatingMachine.html:27`, `nodes/rotatingMachine/src/nodeClass.js:92` |
| `unit` | `config.general.unit` + `config.asset.unit` | UI empty, config default `l/s` | direct assign then config init | base flow unit for measurements and outputs | `nodes/rotatingMachine/src/nodeClass.js:50`, `nodes/generalFunctions/src/configs/rotatingMachine.json:18` |
| `model` | `config.asset.model` | UI empty, config default `Unknown` | direct assign | curve loading via `loadCurve(model)` | `nodes/rotatingMachine/src/nodeClass.js:62`, `nodes/rotatingMachine/src/specificClass.js:18` |
| logging fields | `config.general.logging.*` | `enableLog=false`, `logLevel=error` in UI; config default enabled/info | direct assign | runtime verbosity | `nodes/rotatingMachine/rotatingMachine.html:39`, `nodes/rotatingMachine/src/nodeClass.js:51` |
| `positionVsParent` | `config.functionality.positionVsParent` | UI empty, config default `atEquipment` | direct assign + default in schema | registration topology to parent | `nodes/rotatingMachine/src/nodeClass.js:66`, `nodes/generalFunctions/src/configs/rotatingMachine.json:74` |
| Mode/action/source rules | `config.mode.*` | schema defaults | configUtils validation into `Set` semantics | command gating | `nodes/generalFunctions/src/configs/rotatingMachine.json:231`, `nodes/rotatingMachine/src/specificClass.js:269` |
| Sequences | `config.sequences.*` | schema defaults | configUtils validation | machine state transitions | `nodes/generalFunctions/src/configs/rotatingMachine.json:360`, `nodes/rotatingMachine/src/specificClass.js:363` |
## 4) Input/Output Contract
### 4.1 Input topics (`nodeClass`)
| Topic | Payload schema | Handler | Side effects |
|---|---|---|---|
| `registerChild` | `payload=<nodeId>`, optional `positionVsParent` | registers child source via `childRegistrationUtils` | starts measurement event wiring (`nodes/rotatingMachine/src/nodeClass.js:268`) |
| `setMode` | `payload=<mode>` | `setMode()` | updates command policy mode |
| `execSequence` | `{source, action, parameter}` | `handleInput()` | executes state sequence |
| `execMovement` | `{source, action, setpoint}` | `handleInput()` | moves position |
| `flowMovement` | `{source, action, setpoint}` | `handleInput()` | converts flow->ctrl then moves |
| `emergencystop` | `{source, action}` | `handleInput()` | emergency sequence attempt |
| `simulateMeasurement` | `{type, position, value, unit, timestamp?}` | measurement update handlers | updates virtual pressure or measured values |
| `showWorkingCurves` | any | immediate response on output 0 | emits curve/cog debug payload |
| `CoG` | any | immediate response on output 0 | calls `m.showCoG()` (method currently not defined in `specificClass`) |
### 4.2 Output ports
| Port | Message type | Source |
|---|---|---|
| `0` | formatted process message from flattened measurements + state fields | `nodes/rotatingMachine/src/nodeClass.js:250` |
| `1` | formatted influxdb message | `nodes/rotatingMachine/src/nodeClass.js:251` |
| `2` | registration to parent: `{topic:'registerChild', payload:id, positionVsParent}` | `nodes/rotatingMachine/src/nodeClass.js:222` |
### 4.3 Admin endpoints
| Endpoint | Purpose | Source |
|---|---|---|
| `/rotatingMachine/menu.js` | dynamic editor menu script (`asset`, `logger`, `position`) | `nodes/rotatingMachine/rotatingMachine.js:17` |
| `/rotatingMachine/configData.js` | dynamic default/config script | `nodes/rotatingMachine/rotatingMachine.js:27` |
## 5) Mode, State, and Control Model
- **Modes**: `auto`, `virtualControl`, `fysicalControl` (`nodes/generalFunctions/src/configs/rotatingMachine.json:233`)
- **Mode gate enforcement**:
- `isValidActionForMode(action, mode)` (`nodes/rotatingMachine/src/specificClass.js:279`)
- `isValidSourceForMode(source, mode)` (`nodes/rotatingMachine/src/specificClass.js:269`)
- **Actions supported by handler**: `execsequence`, `execmovement`, `flowmovement`, `entermaintenance`, `exitmaintenance`, `emergencystop`, `statuscheck` (`nodes/rotatingMachine/src/specificClass.js:303`)
- **Operational states for active prediction**: `operational`, `warmingup`, `accelerating`, `decelerating` (`nodes/rotatingMachine/src/specificClass.js:739`)
- **Sequence defaults**: startup/shutdown/emergencystop/maintenance flows defined in config schema (`nodes/generalFunctions/src/configs/rotatingMachine.json:365`)
## 6) End-to-End Execution Flow
1. Node registration instantiates `nodeClass`, then `Specific` (`Machine`).
2. `Machine` constructor loads model curve, initializes predictors/state/measurements, creates virtual pressure children, and subscribes to state events.
3. `nodeClass` starts delayed child registration (`output 2`) and 1-second tick/status loops.
4. Incoming topics route through `switch(msg.topic)` to mode changes, movement/sequence commands, child registration, and simulated measurements.
5. Child measurement events update parent measurement container and dispatch typed handlers.
6. Pressure updates set predictor dimension, recompute flow/power/efficiency/CoG/BEP metrics.
7. Each tick emits formatted process + influx messages.
## 7) Full Function Inventory
### 7.1 `nodes/rotatingMachine/rotatingMachine.js`
| Function | Purpose | Source |
|---|---|---|
| module export init | register Node-RED node type and admin endpoints | `nodes/rotatingMachine/rotatingMachine.js:5` |
### 7.2 `nodes/rotatingMachine/src/nodeClass.js`
| Function | Purpose | Key effects | Source |
|---|---|---|---|
| `constructor` | boot wrapper lifecycle | load config, create source, start loops/handlers | `nodes/rotatingMachine/src/nodeClass.js:16` |
| `_loadConfig` | map UI config to runtime config | builds `general/asset/functionality`; builds `outputUtils` | `nodes/rotatingMachine/src/nodeClass.js:44` |
| `_setupSpecificClass` | build `Machine` with movement/time state config | instantiates `Specific`; stores on `node.source` | `nodes/rotatingMachine/src/nodeClass.js:77` |
| `_bindEvents` | placeholder | no-op currently | `nodes/rotatingMachine/src/nodeClass.js:112` |
| `_updateNodeStatus` | compose Node-RED status icon/text | warns once for missing pressure init; includes flow/power/state | `nodes/rotatingMachine/src/nodeClass.js:116` |
| `_registerChild` | announce self to parent | sends `registerChild` on output 2 after 100ms | `nodes/rotatingMachine/src/nodeClass.js:217` |
| `_startTickLoop` | periodic work | 1s `_tick`; 1s status refresh | `nodes/rotatingMachine/src/nodeClass.js:230` |
| `_tick` | periodic output generation | `formatMsg(process)` + `formatMsg(influxdb)` send to outputs 0/1 | `nodes/rotatingMachine/src/nodeClass.js:246` |
| `_attachInputHandler` | route inbound topics | dispatches all command/simulation/register/show topics | `nodes/rotatingMachine/src/nodeClass.js:260` |
| `_attachCloseHandler` | shutdown cleanup | clears intervals | `nodes/rotatingMachine/src/nodeClass.js:357` |
### 7.3 `nodes/rotatingMachine/src/specificClass.js`
| Function | Purpose | Key effects | Source |
|---|---|---|---|
| `constructor` | initialize machine domain object | config/curve/predictors/state/measurement/events/virtual children | `nodes/rotatingMachine/src/specificClass.js:7` |
| `_initVirtualPressureChildren` | create simulated upstream/downstream pressure children | registers virtual measurement children | `nodes/rotatingMachine/src/specificClass.js:104` |
| `_init` | seed base measurements and min/max flow | sets temperature, atmPressure, curve min/max | `nodes/rotatingMachine/src/specificClass.js:147` |
| `_updateState` | enforce non-operational flow = 0 | overwrites predicted flow when inactive | `nodes/rotatingMachine/src/specificClass.js:163` |
| `registerChild` | subscribe to child measurement events | stores real pressure child IDs, updates measurements, calls handlers | `nodes/rotatingMachine/src/specificClass.js:173` |
| `_callMeasurementHandler` | typed measurement dispatch | pressure/flow/temp handlers + fallback | `nodes/rotatingMachine/src/specificClass.js:212` |
| `assessDrift` | compare measured vs predicted windows | delegates to `nrmse.assessDrift` | `nodes/rotatingMachine/src/specificClass.js:237` |
| `reverseCurve` | flip x/y arrays | used for flow->ctrl predictor | `nodes/rotatingMachine/src/specificClass.js:252` |
| `updateConfig` | apply validated config patch | merges via `configUtils` | `nodes/rotatingMachine/src/specificClass.js:264` |
| `isValidSourceForMode` | mode source gate | checks configured allowed set | `nodes/rotatingMachine/src/specificClass.js:269` |
| `isValidActionForMode` | mode action gate | checks configured allowed set | `nodes/rotatingMachine/src/specificClass.js:279` |
| `handleInput` | main command dispatcher | executes sequence/movement/status commands | `nodes/rotatingMachine/src/specificClass.js:289` |
| `abortMovement` | cancel current movement | delegates to state abort if available | `nodes/rotatingMachine/src/specificClass.js:345` |
| `setMode` | update current mode | validates against schema enum values | `nodes/rotatingMachine/src/specificClass.js:351` |
| `executeSequence` | run state sequence | transitions through configured states; updatePosition at end | `nodes/rotatingMachine/src/specificClass.js:363` |
| `setpoint` | move to numeric target | validates non-negative number then `state.moveTo` | `nodes/rotatingMachine/src/specificClass.js:394` |
| `calcFlow` | predict flow from current curve and ctrl position | writes predicted flow measurements | `nodes/rotatingMachine/src/specificClass.js:413` |
| `calcPower` | predict power from current curve and ctrl position | writes predicted power measurement | `nodes/rotatingMachine/src/specificClass.js:438` |
| `inputFlowCalcPower` | estimate power from requested flow | flow->ctrl->power chained prediction | `nodes/rotatingMachine/src/specificClass.js:460` |
| `calcCtrl` | estimate ctrl for desired flow | writes predicted ctrl | `nodes/rotatingMachine/src/specificClass.js:478` |
| `getMeasuredPressure` | choose pressure basis for prediction | differential preferred; then downstream; then upstream; else 0 | `nodes/rotatingMachine/src/specificClass.js:496` |
| `_getPreferredPressureValue` | pressure source priority resolver | real child > virtual child > aggregated position value | `nodes/rotatingMachine/src/specificClass.js:570` |
| `getPressureInitializationStatus` | pressure readiness status model | upstream/downstream/differential flags | `nodes/rotatingMachine/src/specificClass.js:600` |
| `updateSimulatedMeasurement` | write dashboard-sim values | pressure route via virtual child; others dispatch typed handler | `nodes/rotatingMachine/src/specificClass.js:617` |
| `handleMeasuredFlow` | reconcile measured flow availability/consistency | returns matched/single measurement or null | `nodes/rotatingMachine/src/specificClass.js:644` |
| `handleMeasuredPower` | read measured power | returns value or null with error | `nodes/rotatingMachine/src/specificClass.js:685` |
| `updateMeasuredTemperature` | temp update hook | currently log-only | `nodes/rotatingMachine/src/specificClass.js:698` |
| `updateMeasuredPressure` | pressure update hook | stores pressure, recomputes pressure basis and position metrics | `nodes/rotatingMachine/src/specificClass.js:703` |
| `updateMeasuredFlow` | flow update hook | stores measured flow if operational; mirrors predicted flow value | `nodes/rotatingMachine/src/specificClass.js:718` |
| `_isOperationalState` | operational predicate | active states used by prediction guards | `nodes/rotatingMachine/src/specificClass.js:737` |
| `updatePosition` | core recompute pipeline on movement/state changes | calc flow/power -> efficiency -> cog -> BEP distance | `nodes/rotatingMachine/src/specificClass.js:745` |
| `calcDistanceFromPeak` | abs distance metric | absolute efficiency delta | `nodes/rotatingMachine/src/specificClass.js:767` |
| `calcRelativeDistanceFromPeak` | normalized distance metric | interpolation to [0,1] | `nodes/rotatingMachine/src/specificClass.js:771` |
| `showWorkingCurves` | debugging snapshot of current curve context | returns current curves + metrics | `nodes/rotatingMachine/src/specificClass.js:779` |
| `calcCog` | compute peak efficiency point on current curve | updates `cog`, `NCog`, indexes, minEfficiency | `nodes/rotatingMachine/src/specificClass.js:796` |
| `calcEfficiencyCurve` | derive efficiency curve and peak/min | from aligned power/flow arrays | `nodes/rotatingMachine/src/specificClass.js:817` |
| `calcFlowPower` | convenience combined prediction | calls `calcFlow` and `calcPower` | `nodes/rotatingMachine/src/specificClass.js:845` |
| `calcEfficiency` | compute efficiency family metrics | CoolProp density path + fallback + writes derived metrics | `nodes/rotatingMachine/src/specificClass.js:854` |
| `updateCurve` | replace machine curve at runtime | validates config and updates predictors | `nodes/rotatingMachine/src/specificClass.js:897` |
| `getCompleteCurve` | return full loaded curves | power+flow input curves | `nodes/rotatingMachine/src/specificClass.js:910` |
| `getCurrentCurves` | return currently selected pressure curve slices | current flow/power curves | `nodes/rotatingMachine/src/specificClass.js:916` |
| `calcDistanceBEP` | write BEP distance metrics | updates `absDistFromPeak`, `relDistFromPeak` | `nodes/rotatingMachine/src/specificClass.js:924` |
| `getOutput` | flatten and enrich output object | adds state/runtime/ctrl/mode/cog/drift/eff-distance | `nodes/rotatingMachine/src/specificClass.js:936` |
## 8) Calculations and Capability Matrix
- **Curve-backed capabilities**:
- flow prediction (`nq`) via `predictFlow`
- power prediction (`np`) via `predictPower`
- control inversion (flow->ctrl) via reversed `nq`
- **Pressure basis selection order**:
1. real differential (`downstream - upstream`)
2. real/virtual downstream only
3. real/virtual upstream only
4. fallback `0` (minimum pressure behavior)
- **Availability-first behavior**:
- missing pressure does not stop operation; it degrades predictions and warns.
- CoolProp failure does not stop operation; density fallback is used.
- non-operational states force predicted flow/power to zero.
- **BEP indicators**:
- computes peak efficiency index and normalized CoG (`NCog`) for optimization usage by parent/group controllers.
## 9) Error Handling and Safeguards
- Invalid actions/sources are rejected by mode gates, with warnings (`nodes/rotatingMachine/src/specificClass.js:297`).
- Invalid setpoint (`<0` or non-number) is rejected in `setpoint()` (`nodes/rotatingMachine/src/specificClass.js:398`).
- Missing curve model disables predictor objects but keeps class alive (`nodes/rotatingMachine/src/specificClass.js:27`).
- Missing pressure initialization surfaces Node-RED warning ring status (`nodes/rotatingMachine/src/nodeClass.js:126`).
- `simulateMeasurement` rejects non-finite values and unsupported types (`nodes/rotatingMachine/src/nodeClass.js:312`).
## 10) Test Evidence Matrix
| Test file | Covered behavior |
|---|---|
| `nodes/rotatingMachine/test/basic/constructor.basic.test.js` | constructor curve/no-curve behavior and output shape |
| `nodes/rotatingMachine/test/basic/mode-and-input.basic.test.js` | `setMode`, `handleInput` validation, operational-state predicate |
| `nodes/rotatingMachine/test/edge/error-paths.edge.test.js` | negative setpoint resilience, status failure path |
| `nodes/rotatingMachine/test/edge/nodeClass-routing.edge.test.js` | input-topic routing, pressure initialization status warning, curve/CoG reply routing |
| `nodes/rotatingMachine/test/integration/sequences.integration.test.js` | startup sequence and movement execution |
| `nodes/rotatingMachine/test/integration/registration.integration.test.js` | child registration and pressure event propagation |
| `nodes/rotatingMachine/test/integration/pressure-initialization.integration.test.js` | explicit pressure init combinations and real-vs-virtual pressure priority |
| `nodes/rotatingMachine/test/integration/coolprop.integration.test.js` | efficiency path with CoolProp and medium-pressure initialization behavior |
| `nodes/rotatingMachine/test/integration/basic-flow-dashboard.integration.test.js` | example-flow parser/wiring contracts for dashboard topics |
## 11) Invariants (Anchor Truth)
- `specificClass` is the mechanical/logic source of truth; `nodeClass` is routing/lifecycle only.
- Prediction calculations must be curve-backed when curve exists, and availability-first fallback when it does not.
- Pressure selection priority is **real sensor > virtual dashboard > aggregated fallback**.
- Command execution must remain mode-gated by both action and source.
- Output shape must keep process/influx separation and parent registration on output port 2.
- Operational-state gating must continue to prevent active prediction outputs in inactive states.
## 12) Known Gaps / Risks (Current Implementation)
- `nodeClass` routes topic `CoG` to `m.showCoG()`, but `showCoG` is not present in `specificClass` (runtime risk on that topic): `nodes/rotatingMachine/src/nodeClass.js:340`.
- `handleInput('emergencystop')` calls sequence `"emergencyStop"`, but config default key is `"emergencystop"` (case/name mismatch risk): `nodes/rotatingMachine/src/specificClass.js:327`, `nodes/generalFunctions/src/configs/rotatingMachine.json:381`.
- `_setupSpecificClass` uses `machineConfig.eneableLog` (typo) for state logging config; likely not intended: `nodes/rotatingMachine/src/nodeClass.js:86`.
- Label expression can evaluate unexpectedly because `+` and `||` precedence are mixed: `nodes/rotatingMachine/rotatingMachine.html:58`.
## 13) Change Checklist
When changing rotatingMachine logic, update all of:
1. Runtime logic in `nodes/rotatingMachine/src/specificClass.js`.
2. Node-RED routing/lifecycle in `nodes/rotatingMachine/src/nodeClass.js`.
3. UI defaults/fields in `nodes/rotatingMachine/rotatingMachine.html`.
4. Config schema and mode/action/source/sequence defaults in `nodes/generalFunctions/src/configs/rotatingMachine.json`.
5. Example flow contracts in `nodes/rotatingMachine/examples/*.flow.json`.
6. Tests under `nodes/rotatingMachine/test/` (basic, edge, integration).

View File

@@ -0,0 +1,23 @@
# Rotating Machine Test Evidence
## Scope
Evidence source for `ANCHOR-rotatingMachine.md`.
## Test-to-Contract Mapping
| Test file | Contract/Behavior Anchored |
|---|---|
| `nodes/rotatingMachine/test/basic/constructor.basic.test.js` | Constructor should tolerate missing model curve and still return output object with core fields. |
| `nodes/rotatingMachine/test/basic/mode-and-input.basic.test.js` | Mode validation, source/action gating behavior, and active-state definition (`warmingup` active). |
| `nodes/rotatingMachine/test/edge/error-paths.edge.test.js` | Error path resilience in `setpoint()` and status update exception fallback (`Status Error`). |
| `nodes/rotatingMachine/test/edge/nodeClass-routing.edge.test.js` | Topic routing for control and simulation commands, pressure init warning behavior, and debug topic reply routing. |
| `nodes/rotatingMachine/test/integration/sequences.integration.test.js` | End-to-end state transitions for startup and movement command paths. |
| `nodes/rotatingMachine/test/integration/registration.integration.test.js` | Child measurement registration pipeline stores measured pressure in parent container. |
| `nodes/rotatingMachine/test/integration/pressure-initialization.integration.test.js` | Pressure initialization matrix (none/upstream/downstream/both) and preference for real child pressure over virtual dashboard pressure. |
| `nodes/rotatingMachine/test/integration/coolprop.integration.test.js` | Efficiency calculation path passes through CoolProp logic and verifies pressure dimension initialization behavior. |
| `nodes/rotatingMachine/test/integration/basic-flow-dashboard.integration.test.js` | Example dashboard parser wiring and topic/index contracts for flow/power/pressure charts. |
## Remaining Coverage Gaps
- No direct test proves `handleInput('emergencystop')` sequence-name alignment with config key.
- No direct test for `CoG` input topic when `showCoG` is absent.
- No direct test for UI label precedence behavior in `rotatingMachine.html`.
- No direct test for typo path `machineConfig.eneableLog` in `_setupSpecificClass`.

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>settler Anchor</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; background: #f7f8fa; color: #1f2937; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; }
</style>
</head>
<body>
<h1>settler Function Anchor</h1>
<div class="card">Baseline topology placeholder. Expand during functional changes.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
# settler Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- Node type: settler
- Scope: baseline anchor scaffold to satisfy EVOLV required architecture.
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| TBD | TBD | TBD | nodes/settler/src/* | TBD | TBD | TBD |
## 2) Class Identity
- Runtime registration: nodes/settler
- Node-RED wrapper: nodes/settler/src/nodeClass.js (when present)
- Domain logic: nodes/settler/src/specificClass.js (when present)
- Editor UI: nodes/settler/*.html (when present)
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
- Replace placeholder sections with full contract mapping on first functional change.
## 4) Standardization Plan
1. Maintain this anchor and evidence docs with behavior changes.
2. Maintain tests under test/basic, test/integration, test/edge.
3. Maintain examples package (README, basic.flow.json, integration.flow.json, edge.flow.json).
## 5) Acceptance Criteria For Completion
- Anchor/evidence artifacts exist.
- Test structure exists.
- Example structure exists.

View File

@@ -0,0 +1,15 @@
# settler Test Evidence
Status: baseline structure scaffolded.
## Required Test Layout
- nodes/settler/test/basic/*.test.js
- nodes/settler/test/integration/*.test.js
- nodes/settler/test/edge/*.test.js
## Baseline Mapping
| Test file | Scope |
|---|---|
| nodes/settler/test/basic/structure-module-load.basic.test.js | module load smoke |
| nodes/settler/test/integration/structure-examples.integration.test.js | examples package integrity |
| nodes/settler/test/edge/structure-examples-node-type.edge.test.js | node-type presence in basic example |

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>valve Anchor</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; background: #f7f8fa; color: #1f2937; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; }
</style>
</head>
<body>
<h1>valve Function Anchor</h1>
<div class="card">Baseline topology placeholder. Expand during functional changes.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
# valve Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- Node type: valve
- Scope: baseline anchor scaffold to satisfy EVOLV required architecture.
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| TBD | TBD | TBD | nodes/valve/src/* | TBD | TBD | TBD |
## 2) Class Identity
- Runtime registration: nodes/valve
- Node-RED wrapper: nodes/valve/src/nodeClass.js (when present)
- Domain logic: nodes/valve/src/specificClass.js (when present)
- Editor UI: nodes/valve/*.html (when present)
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
- Replace placeholder sections with full contract mapping on first functional change.
## 4) Standardization Plan
1. Maintain this anchor and evidence docs with behavior changes.
2. Maintain tests under test/basic, test/integration, test/edge.
3. Maintain examples package (README, basic.flow.json, integration.flow.json, edge.flow.json).
## 5) Acceptance Criteria For Completion
- Anchor/evidence artifacts exist.
- Test structure exists.
- Example structure exists.

View File

@@ -0,0 +1,15 @@
# valve Test Evidence
Status: baseline structure scaffolded.
## Required Test Layout
- nodes/valve/test/basic/*.test.js
- nodes/valve/test/integration/*.test.js
- nodes/valve/test/edge/*.test.js
## Baseline Mapping
| Test file | Scope |
|---|---|
| nodes/valve/test/basic/structure-module-load.basic.test.js | module load smoke |
| nodes/valve/test/integration/structure-examples.integration.test.js | examples package integrity |
| nodes/valve/test/edge/structure-examples-node-type.edge.test.js | node-type presence in basic example |

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>valveGroupControl Anchor</title>
<style>
body { font-family: Arial, sans-serif; margin: 24px; background: #f7f8fa; color: #1f2937; }
.card { background: #fff; border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; }
</style>
</head>
<body>
<h1>valveGroupControl Function Anchor</h1>
<div class="card">Baseline topology placeholder. Expand during functional changes.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
# valveGroupControl Function Anchor (Preparation Baseline)
## 0) Connection Map (At a Glance)
- Node type: valveGroupControl
- Scope: baseline anchor scaffold to satisfy EVOLV required architecture.
## 1) Unit Table (Initial Baseline)
| Signal/Field | Represents | Default Unit | Source of Truth | Produced By | Consumed By | Fallback/Degraded Behavior |
|---|---|---|---|---|---|---|
| TBD | TBD | TBD | nodes/valveGroupControl/src/* | TBD | TBD | TBD |
## 2) Class Identity
- Runtime registration: nodes/valveGroupControl
- Node-RED wrapper: nodes/valveGroupControl/src/nodeClass.js (when present)
- Domain logic: nodes/valveGroupControl/src/specificClass.js (when present)
- Editor UI: nodes/valveGroupControl/*.html (when present)
## 3) Current Gaps To Resolve Before Declaring Anchor Complete
- Replace placeholder sections with full contract mapping on first functional change.
## 4) Standardization Plan
1. Maintain this anchor and evidence docs with behavior changes.
2. Maintain tests under test/basic, test/integration, test/edge.
3. Maintain examples package (README, basic.flow.json, integration.flow.json, edge.flow.json).
## 5) Acceptance Criteria For Completion
- Anchor/evidence artifacts exist.
- Test structure exists.
- Example structure exists.

View File

@@ -0,0 +1,15 @@
# valveGroupControl Test Evidence
Status: baseline structure scaffolded.
## Required Test Layout
- nodes/valveGroupControl/test/basic/*.test.js
- nodes/valveGroupControl/test/integration/*.test.js
- nodes/valveGroupControl/test/edge/*.test.js
## Baseline Mapping
| Test file | Scope |
|---|---|
| nodes/valveGroupControl/test/basic/structure-module-load.basic.test.js | module load smoke |
| nodes/valveGroupControl/test/integration/structure-examples.integration.test.js | examples package integrity |
| nodes/valveGroupControl/test/edge/structure-examples-node-type.edge.test.js | node-type presence in basic example |