# generalFunctions ![code-ref](https://img.shields.io/badge/code--ref-48fa543-blue) ![kind](https://img.shields.io/badge/kind-Shared_Library-dddddd) ![status](https://img.shields.io/badge/status-stable-brightgreen) **generalFunctions** is the shared infrastructure every EVOLV node depends on. It provides the base classes all nodes extend (`BaseDomain`, `BaseNodeAdapter`), the command dispatch engine, the measurement store, unit-policy system, child-registration machinery, InfluxDB output formatting, and a set of domain utilities (PID, curve interpolation, prediction, statistics, coolprop). Nodes hold zero duplicated scaffolding — they only write the logic that differs. --- ## At a glance | Thing | Value | |:---|:---| | What it is | The shared library — not a Node-RED node, never placed in a flow | | Kind | Shared library (`require('generalFunctions')`) | | Consumed by | All 12 EVOLV nodes (rotatingMachine, MGC, pumpingStation, valve, VGC, reactor, settler, monster, measurement, diffuser, dashboardAPI) | | Import style | Package root only — `const { BaseDomain, UnitPolicy } = require('generalFunctions');` | | Side effects on a flow | None — the library has no editor form, no node registration | | Cross-node coupling | Through this library's API surface + Node-RED messages only — never direct imports between node packages | --- ## How it fits ```mermaid flowchart LR gf["generalFunctions
(shared library)"]:::lib rm["rotatingMachine
Equipment"]:::equip mgc["machineGroupControl
Unit"]:::unit ps["pumpingStation
Process Cell"]:::proc meas["measurement
Control Module"]:::ctrl valve["valve
Equipment"]:::equip vgc["valveGroupControl
Unit"]:::unit reactor["reactor
Unit"]:::unit settler["settler
Unit"]:::unit monster["monster
Unit"]:::unit diffuser["diffuser
Equipment"]:::equip dashAPI["dashboardAPI
utility"]:::util gf --> rm gf --> mgc gf --> ps gf --> meas gf --> valve gf --> vgc gf --> reactor gf --> settler gf --> monster gf --> diffuser gf --> dashAPI classDef lib fill:#222,color:#fff,stroke:#444 classDef proc fill:#0c99d9,color:#fff classDef unit fill:#50a8d9,color:#000 classDef equip fill:#86bbdd,color:#000 classDef ctrl fill:#a9daee,color:#000 classDef util fill:#dddddd,color:#000 ``` Every EVOLV node declares `generalFunctions` as a `dependencies` entry and imports from the package root only. The library has no S88 level of its own — it is the substrate the S88-classified nodes are built on. --- ## How to import Single root import, destructure what you need: ```js const { // Platform base classes BaseDomain, BaseNodeAdapter, ChildRouter, UnitPolicy, HealthStatus, LatestWinsGate, // Node-RED bridge createRegistry, CommandRegistry, statusBadge, StatusUpdater, // Measurement + config MeasurementContainer, configManager, configUtils, validation, // Output formatting + logging outputUtils, logger, // Child registration childRegistrationUtils, // Unit conversion + physics convert, Fysics, gravity, coolprop, // Control + prediction PIDController, CascadePIDController, createPidController, createCascadePidController, predict, interpolation, nrmse, stats, state, // Editor menus MenuManager, // Asset registry assetResolver, AssetResolver, FileBackend, HttpBackend, // Constants POSITIONS, POSITION_VALUES, isValidPosition, } = require('generalFunctions'); ``` > [!IMPORTANT] > Never import internal paths (`require('generalFunctions/src/domain/UnitPolicy')`). Only the package root is contractual; internal layout may move. For the full export list with signatures and stability tags, see [Reference — Contracts](Reference-Contracts). --- ## Module map — what lives where ```mermaid flowchart TB subgraph domain["src/domain/ — base classes"] BD["BaseDomain.js"] CR["ChildRouter.js"] UP["UnitPolicy.js"] LWG["LatestWinsGate.js"] HS["HealthStatus.js"] end subgraph nodered["src/nodered/ — Node-RED adapter layer"] BNA["BaseNodeAdapter.js"] CMR["commandRegistry.js"] SB["statusBadge.js"] SU["statusUpdater.js"] end subgraph measurements["src/measurements/ — measurement store"] MC["MeasurementContainer.js"] MB["MeasurementBuilder.js"] Meas["Measurement.js"] end subgraph helper["src/helper/ — shared utilities"] LOG["logger.js"] OU["outputUtils.js"] CRU["childRegistrationUtils.js"] CFG["configUtils.js"] VAL["validationUtils.js"] MU["menuUtils.js"] GR["gravity.js"] end subgraph predict_grp["src/predict/ — curve prediction"] PRED["predict_class.js"] INTERP["interpolation.js"] end subgraph configs["src/configs/ — schema registry"] CFGM["index.js (ConfigManager)"] JSON["*.json — per-node schemas"] end subgraph math["numeric & domain utilities"] PID["src/pid/"] NRMSE["src/nrmse/"] STATS["src/stats/"] OUT["src/outliers/"] STATE["src/state/"] CONV["src/convert/"] COOL["src/coolprop-node/"] FYS["src/convert/fysics.js"] end subgraph menu_grp["src/menu/"] MM["MenuManager"] end subgraph constants["src/constants/"] POS["positions.js"] end BD --> CR BD --> UP BD --> MC BD --> CRU BD --> LOG BNA --> BD BNA --> CMR BNA --> OU BNA --> SU ``` | Directory | Primary export | Read first if you're changing… | |:---|:---|:---| | `src/domain/` | `BaseDomain`, `ChildRouter`, `UnitPolicy`, `LatestWinsGate`, `HealthStatus` | Base class contracts, child routing, unit system | | `src/nodered/` | `BaseNodeAdapter`, `CommandRegistry`, `statusBadge`, `StatusUpdater` | Input dispatch, output loops, editor status | | `src/measurements/` | `MeasurementContainer` | Measurement storage, statistics, 4-segment key output | | `src/helper/` | `logger`, `outputUtils`, `childRegistrationUtils`, `configUtils`, `validationUtils`, `menuUtils`, `gravity` | Logging, InfluxDB formatting, child registration | | `src/configs/` | `ConfigManager` + per-node JSON schemas | Schema loading, config validation, default values | | `src/predict/` | `predict`, `interpolation` | Characteristic curve fitting + flow/power prediction | | `src/pid/` | `PIDController`, `CascadePIDController` | Closed-loop control | | `src/nrmse/` | `ErrorMetrics` (NRMSE) | Prediction quality scoring | | `src/stats/` | `stats` (mean, stddev, median) | Statistical reducers | | `src/outliers/` | `DynamicClusterDeviation` | Online outlier detection | | `src/state/` | `state`, `StateManager`, `MovementManager` | FSM for valve / machine state machines | | `src/convert/` | `convert`, `Fysics` | Unit conversion, physical constants | | `src/coolprop-node/` | `coolprop` | Thermodynamic property lookup | | `src/menu/` | `MenuManager` | Editor-form dropdown population | | `src/registry/` | `assetResolver`, `AssetResolver`, `FileBackend`, `HttpBackend` | Asset metadata lookup (replaces ad-hoc JSON readers) | | `src/constants/` | `POSITIONS`, `POSITION_VALUES`, `isValidPosition` | Canonical spatial position constants | --- ## What you'll send (the platform contract) This library doesn't accept `msg.topic` directly — nodes do. But every node's `nodeClass.js` and `specificClass.js` route through the same primitives: | Primitive | Role | |:---|:---| | `BaseNodeAdapter.input(msg)` | Routes incoming Node-RED messages through the node's `CommandRegistry`, applies unit normalisation, then dispatches to the handler. | | `CommandRegistry` | Topic + alias map. Handlers are pure functions; `units: {measure, default}` triggers automatic `convert` normalisation. | | `ChildRouter` | Declarative parent-side routing. `.onRegister(type, cb)`, `.onMeasurement(type, filter, cb)`, `.onPrediction(type, filter, cb)`. | | `MeasurementContainer.type().variant().position().value()` | Chainable write. Flattened output emits 4-segment keys `...`. | | `UnitPolicy.declare({canonical, output, curve?})` | The per-node unit triple. Used by `MeasurementContainer` (auto-convert on write) and by the output formatter (render in `output` units). | | `outputUtils.formatMsg(snapshot, config, mode)` | Delta-compresses successive snapshots. Returns `undefined` when nothing changed. | | `HealthStatus.ok / degraded / compose` | Frozen plain-object factory for prediction-quality state. | | `LatestWinsGate.fire(value)` | Serialises async dispatches; the latest call wins, intermediates are marked `SUPERSEDED`. | For full signatures and stability tags see [Reference — Contracts](Reference-Contracts). --- ## What you'll see come out A node that imports `BaseNodeAdapter` automatically gets the three EVOLV ports: | Port | Carries | Built by | |:---|:---|:---| | 0 (process) | Delta-compressed state snapshot (the `getOutput()` return) | `outputUtils.formatMsg(snapshot, config, 'process')` | | 1 (telemetry) | InfluxDB line-protocol payload (same fields) | `outputUtils.formatMsg(snapshot, config, 'influxdb')` | | 2 (register / control) | Parent-child handshake messages | `childRegistrationUtils` via `BaseNodeAdapter` | The 4-segment key shape **`...`** is the contractual output of `MeasurementContainer.getFlattenedOutput()`. Position labels are normalised to lowercase. Changing this shape is a forbidden breaking change — see [Reference — Limitations](Reference-Limitations#stability--versioning). --- ## Capability matrix | Capability | Status | Notes | |:---|:---|:---| | Base domain scaffolding (`BaseDomain`) | ✅ | Constructor, emitter, logger, measurements, child registry wired automatically | | Base Node-RED adapter (`BaseNodeAdapter`) | ✅ | Tick/event loop, status badge, input dispatch, Port 0/1/2 output | | Declarative command dispatch (`CommandRegistry`) | ✅ | Alias deprecation warnings, unit normalisation, `query.units` auto-topic | | Declarative child-registration routing (`ChildRouter`) | ✅ | Replaces per-node `registerChild` switch blocks | | Unit policy + conversion (`UnitPolicy`, `convert`) | ✅ | Canonical ↔ output ↔ curve unit sets; dual method/property access | | Measurement store (`MeasurementContainer`) | ✅ | Chainable, windowed, auto-convert, 4-segment key output | | InfluxDB + process output formatting (`outputUtils`) | ✅ | Delta-compressed; consumers must cache and merge | | Status badge helpers (`statusBadge`, `StatusUpdater`) | ✅ | Converged look-and-feel across all nodes | | Latest-wins async gate (`LatestWinsGate`) | ✅ | Extracted from MGC; shared by PS, VGC, MGC | | Prediction quality / drift tracking (`HealthStatus`) | ✅ | Frozen plain-object shape; composable | | Config schema registry (`configManager`) | ✅ | One JSON schema per node in `src/configs/` | | PID control (`PIDController`, `CascadePIDController`) | ✅ | Full-featured discrete PID with bumpless transfer | | Curve interpolation (`interpolation`, `predict`) | ✅ | Multidimensional characteristic-curve predictor | | Statistical helpers (`stats`, `nrmse`, `outliers`) | ✅ | Mean, stddev, median, NRMSE, dynamic-cluster outlier detection | | Thermodynamic properties (`coolprop`) | ✅ | CoolProp bindings for fluid/gas property lookup | | FSM for valve/machine states (`state`) | ✅ | StateManager + MovementManager | | Gravity calculations (`gravity`) | ✅ | WGS-84 model | | Physical constants (`Fysics`) | ✅ | Air density, viscosity, etc. | | Browser-side editor dropdowns (`MenuManager`, `menuUtils`) | ✅ | Node-RED editor form population | | Asset metadata registry (`assetResolver`) | ✅ | Replaces `loadCurve`, `AssetCategoryManager`, ad-hoc JSON readers | --- ## Need more? | Page | What you'll find | |:---|:---| | [Reference — Contracts](Reference-Contracts) | Full public API surface table — one row per export, with source file, stability tag, and signature | | [Reference — Architecture](Reference-Architecture) | Three-tier rules, `src/` directory tree, how 12 nodes consume the library, additive-only export discipline | | [Reference — Examples](Reference-Examples) | Usage patterns: extending `BaseDomain` and `BaseNodeAdapter`, registering commands, declaring child routes, `MeasurementContainer` chaining | | [Reference — Limitations](Reference-Limitations) | Known issues (deprecated `loadCurve`, `outlierDetection` logs to console, `configUtils` silent strip, …) and stability/versioning rules | [EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Platform CONTRACTS.md](https://gitea.wbd-rd.nl/RnD/EVOLV/src/branch/development/.claude/refactor/CONTRACTS.md) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)