Waterschap Brabantse Delta · R&D-lab · uitleg
generalFunctions is géén Node-RED-procesnode met een input→output-reis,
maar de gedeelde bibliotheek waarop elke andere EVOLV-node leunt. De eigen
CONTRACT.md zegt het letterlijk: de publieke API is "wat
require('generalFunctions') exporteert" (CONTRACT.md:1-5), en de
superproject-regel voegt toe: "generalFunctions is shared by ALL 13 nodes. Any change here
can break any node" (.claude/rules/general-functions.md:3-4). Er is dus geen
9-staps-pijplijn om te volgen; deze pagina toont in plaats daarvan een kaart-grid per
hoofdmodule plus twee gerichte, node-geteste demo's van de contracten die álle nodes delen:
de meet-adressering en het poort 0/1/2-contract. Omdat dit de meest-
opgezochte node is, staat correctheid van die gedeelde contracten voorop.
generalFunctions, geanalyseerd tegen
origin/main @ 70dd2180f9d0 (peildatum 2026-07-17). Het
feitendossier is geschreven op werkkopie 14edf25941da (branch
feat/child-router-speed-ctrl); alle contracten in dit artifact zijn
opnieuw tegen origin/main geverifieerd (zie verantwoording).CONTRACT.md en de per-node-artifacts op het EVOLV-spoor
in HELIX.Eén kaart per hoofdmodule van de barrel (index.js:65-115 — het
contractuele toegangspunt; "if this file and the barrel disagree, the barrel wins",
CONTRACT.md:13-16,116-117). Feitelijke afnemers, geteld via imports in
nodes/*/src: coresync, dashboardAPI, diffuser, edgeIO, machineGroupControl,
measurement, monster, pumpingStation, reactor, rotatingMachine, settler, valve,
valveGroupControl (13 nodes; rotatingMachine met 14 importerende bestanden de grootste
afnemer). Elke kaart draagt een pad:regel-verwijzing.
ConfigManager.buildConfig, instantieert de static DomainClass,
maakt een OutputUtils (met static alwaysEmitFields) en de
command-registry, bedraadt de tick- óf event-gedreven outputloop, drijft
de StatusUpdater en plant de child-registratie op poort 2. Injecteert
impliciet een query.units-commando.MeasurementContainer → ChildRegistrationUtils →
ChildRouter. Default getOutput() =
measurements.getFlattenedOutput(); notifyOutputChanged() emit
'output-changed' — het signaal waarop de adapter output stuurt.measurements[type][variant][position][childId] — de childId-laag is er
altijd ('default' zonder .child()). Elke
value() emit <type>.<variant>.<position>
(positie lowercase); Measurement is een rolling window (10 samples).
Zie demo I1.Predict (tabulaire curve-predictor, y(x),
getLocalPeak), Interpolation (1-D/3-D; monotone-cubic-spline —
zie bug 5) en AffinityFlow (affiniteitswet
ψ=ΔP/N², φ=Q/N; vereist gemeten astoerental).{default, rules}). buildConfig
voert een bewuste deep merge van de domainConfig uit (een eerdere shallow merge
vaagde general.id weg). Validatie zit in
ValidationUtils.validateSchema: onbekende keys eruit met warning, defaults
erin. Versie-migratie stempelt 0.0.0→1.0.0.formatMsg(output, config, format) met delta-compressie
(alleen gewijzigde keys; niets gewijzigd → null) en tag-flattening.
Pluggable formatters: influxdb, json, csv,
process, frost. alwaysEmit omzeilt de compressie
per veld (bv. pomp-ctrl).msg.command-verb-envelope —
vervangt de per-node switch (msg.topic). cmd(verb)-builder
waarvan de enumerable properties zélf de envelope zijn (geen .build();
methods niet-enumerable zodat de Node-RED-clone naar een plat object collapt).state is een EventEmitter-gebaseerde S88-achtige
toestandsmachine; stateManager en movementManager sturen
bewegingen: staticspeed (lineair) vs dynspeed (ease-in/out),
met sequences (startup/shutdown/emergencystop) uit het config-schema.AssetResolver met namespaces (curves,
assetSpec, menu, monsterSamples,
monsterSpecs, units); sync resolve()
(case-insensitive, warmt de hele namespace). Backends FileBackend/
HttpBackend; ensureModelCurve (edge refresh-on-select);
loadCurve = deprecated shim.child.register: valideert payload, normaliseert
softwareType via SOFTWARE_TYPE_ALIASES
(rotatingmachine→machine), ondersteunt meerdere parents, verrijkt de
child-container (setChildId/Name/ParentRef) en slaat op onder
mainClass.child[type][categorie]. Géén unregister-methode.ChildRouter: declaratieve parent-side routing
(onRegister/onMeasurement/onPrediction) die per concreet event
<type>.<variant>.<position> op de child-emitter abonneert;
wildcards enumereren KNOWN_TYPES. UnitPolicy declareert de
canonieke unit per domein en levert containerOptions().vm-context met nagemaakte browseromgeving. API: PropsSI,
verzadiging, dichtheid. EVOLV-uitbreiding: 'wastewater[:tss=g/L]'-correctie;
dichtheid-fallback 1000. Exact 2 call-sites in heel EVOLV (pumpingStation,
rotatingMachine).convert-units: fluent
convert(v).from(u).to(u), 24 maatfamilies (o.a. pressure,
volumeFlowRate, power, rotationalSpeed met anker
rad/s). Fysics levert vloeistof/hydraulische helpers.MenuManager genereert één client-side JS-string die
window.EVOLV.nodes.<node> injecteert (menuData, HTML-injecties,
initEditor). Geregistreerde factories: asset,
logger, position, aquon. Legacy laag:
menuUtils/endpointUtils.gravity (WGS-84/Somigliana; standaard 9.80665),
stats (mean/stdDev n−1/median; 1
sample→0 i.p.v. NaN), PIDController/CascadePIDController,
ErrorMetrics (NRMSE), assertNoNaN. logger is een
dunne console-wrapper (geen structured logging).type.variant.position.childIdDit adresseringsschema deelt élke node. Kies de vier assen; de pagina bouwt
live de event-naam die de container emit én de flattened output-key die op
poort 0/1 verschijnt. Let op de twee valkuilen die de code expliciet benoemt: de
positie wordt gelowercased (atEquipment → atequipment) en de
.default-childId-laag hoort er altijd bij — hem weglaten is "the #1
footgun for new code". De keys hieronder zijn met node tegen de echte
MeasurementContainer.js geverifieerd (zie testgevallen in de pagina-JS).
container.emitterChildRouter._attachVariantListeners op abonneert —
zo stromen child-metingen naar de parent zónder handmatige bedrading
(src/domain/ChildRouter.js:129-145).Elke node erft dit poortcontract van BaseNodeAdapter:
poort 0 procesdata (delta-gecomprimeerd), poort 1 dbase
(Influx/FROST/…), poort 2 parent — eenmalig, 100 ms na constructie, het
child.register-bericht. De outputloop wordt op twéé manieren bedraad. Kies
hieronder de modus en zie welke draad actief is; de valkuil zit in de tick-modus.
BaseNodeAdapter + outputUtils het
voor álle nodes afdwingen. measurement = config.general.name of
fallback softwareType_id (src/helper/outputUtils.js:52-53); de
formatter per kanaal is instelbaar via config.output.process/dbase.| Poort | Kanaal | Inhoud & belangrijkste keys |
|---|---|---|
| 0 | process |
{topic: <naam>, payload} — delta-gecomprimeerd, alleen
gewijzigde keys; keys in de flattened-vorm
type.variant.position.childId (incl. .default). Object-
velden ontlopen de delta-compressie (zie aandachtspunten).
(src/helper/formatters/processFormatter.js:5-7;
src/helper/outputUtils.js:17-41) |
| 1 | dbase |
payload = {measurement, fields:<gewijzigd>, tags, timestamp}
(default influxdb-formatter). Tags: id, softwareType, role,
positionVsParent, uuid, tagcode, geoLocation, category, type, model, unit;
geneste objecten geflattened tot key_childKey, lege waarden gedropt
tegen tag-cardinaliteit. Omzetbaar naar frost (ISO-timestamp +
source-blok voor coresync-SDT) / json / csv.
(src/helper/formatters/influxdbFormatter.js:12-20;
outputUtils.js:87-133; frostFormatter.js:8-28) |
| 2 | parent |
Eenmalig, 100 ms na constructie:
{topic:'child.register', payload:node.id, positionVsParent, distance}
(positionVsParent default 'atEquipment'). De parent-handler
roept childRegistrationUtils.registerChild(…) aan.
(src/nodered/BaseNodeAdapter.js:117-131;
src/helper/childRegistrationUtils.js:22-72) |
GEFIXT 2026-07-17 De predict-engine interpoleert
curves met een monotone_cubic_spline. Tot 2026-07-17 was de Fritsch-Carlson-
monotoniciteitslimiter gegate op method == "fritschcarlson", terwijl de type-string
"monotone_cubic_spline" is: de limiter was dode code en er bleef een gewone
cubic-Hermite over die op steile cliffs overshootte. De fix (c5f6224,
PR #29, merge 6eaef76) laat monotone_cubic_spline nu wél door de
Fritsch-Carlson-tangens-init én de limiter lopen (gate op twee plekken uitgebreid,
src/predict/interpolation.js). Onderstaande figuur zet het oude gedrag
(vóór de fix — overshoot naar 10,15 boven het plateau en −0,29 onder de vloer) naast de
nu gedeployde monotone curve, die netjes binnen [0, 10] blijft — beide met
node voorberekend (zie pagina-JS).
Alle punten zijn code-observaties d.d. 2026-07-17, statisch tegen
origin/main @ 70dd218. De testsuite draait groen (342 tests, 0 fail;
zelf uitgevoerd 2026-07-17); CI draait npm test op node:20 maar heeft
ondanks de jobnaam lint-and-test géén lint-stap.
origin/main op 2026-07-17 (PR #29,
commit c5f6224, merge 6eaef76). Was: de limiter gegate op
method=="fritschcarlson" terwijl het type "monotone_cubic_spline"
heet → dode code → cubic-Hermite met overshoot. De fix breidt de gate op twee plekken uit
met || method=="monotone_cubic_spline", zodat de spline nu de
Fritsch-Carlson-tangens-init én de limiter doorloopt; er is een
interpolation-monotonic.test.js toegevoegd. Zie de vóór/ná-figuur hierboven.atEquipment-casusmismatchorigin/main):
position() lowercased naar 'atequipment' (:287),
maar _convertPositionStr2Num switcht op
POSITIONS.AT_EQUIPMENT === 'atEquipment' (:839-853;
src/constants/positions.js:8). Gevolg: .distance(null) na
.position('atEquipment') valt in de default-branch (error-log,
undefined) in plaats van 0. upstream/downstream
matchen wél (al lowercase). aanname: mogelijk onbedoeld.JSON.stringify'd, de verse waarde
een object; !== is dus altijd waar en object-velden worden elke emissie
opnieuw gestuurd (afgeleid randgeval; aanname dat dit
onbedoeld is).index.js:6-7 belooft menuUtils als
export, maar menuUtils, endpointUtils en
nodeTemplates zitten níét in module.exports — alleen bereikbaar
via een subpath/helper-barrel.package.json exporteert "./mathUtils" naar een
niet-bestaand bestand. CONTRACT.md beschrijft formatMsg(payload, mode)
(werkelijk: formatMsg(output, config, format)) en een niet-bestaand
assetApiConfig. CONTRACT.md zegt zelf: de barrel wint (:116-117).KNOWN_TYPES handmatig synchroonChildRouter moet meebewegen met
MeasurementContainer.measureMap: "Keep in sync … if new types land there".
Bij speed is een eerder incident gedocumenteerd — zonder de sync "silently
drops every speed event" voor een wildcard-filter.ChildRegistrationUtils heeft géén unregister-methode, en
herregistratie pusht dezelfde child nogmaals in
mainClass.child[type][category] (geen dedupe in _storeChild).
De registeredChildren-Map dedupet wél op id. Hoe elke parent
unregisterChild afhandelt is niet vastgesteld (per node; hypothese).logger is een dunne console-wrapper (geen timestamps/JSON),
ondanks het "structured logging"-label; MenuManager gebruikt rauwe
console.warn/error. Repo-LICENSE is EUPL-1.2, maar
validationUtils.js/configUtils.js dragen een eigen
"no copying"-header, fysics.js een MIT-achtige, coolprop MIT;
package.json zegt "SEE LICENSE".| Module | Pad | Rol |
|---|---|---|
MeasurementContainer | src/measurements/ |
Chainable meetopslag met events; Measurement is een rolling window
(default 10); builder met verplichte type/variant/position. Twee unit-lagen:
defaultUnits (ingress: pressure mbar, flow m3/h, power kW, temp C) en
canonicalUnits (Pa, m3/s, W, K, speed rad/s). |
outputUtils | src/helper/outputUtils.js |
Berichtopbouw per kanaal met delta-compressie en pluggable formatters (influxdb/json/csv/process/frost). |
childRegistrationUtils | src/helper/childRegistrationUtils.js |
Parent-child-handshake: valideren, softwareType-aliasing, meerdere parents,
opslag onder mainClass.child[type][categorie]. |
configManager | src/configs/index.js |
Schema-gedreven configopbouw en versie-migratie (13 schema-JSON's);
deep-merge domainConfig; validatie in ValidationUtils. |
MenuManager + menuUtils | src/menu/, src/helper/menuUtils.js |
Genereert client-side editor-JS met factories asset/logger/position/aquon. |
convert + Fysics | src/convert/ |
Gevendorde convert-units-fork (24 maatfamilies, rotationalSpeed
anker rad/s) plus hydraulische helpers. |
coolprop | src/coolprop-node/ |
Gevendorde CoolProp Emscripten/WASM-build (~4,2 MB) met afvalwater-correctie en tabel-refrigerants. |
predict | src/predict/ |
Predict, Interpolation (1-D/3-D),
AffinityFlow (affiniteitswet). |
pid, nrmse, stats, state |
src/pid/, src/nrmse/, src/stats/, src/state/ |
Regelaars, foutmetrieken, statistiek, S88-toestandsmachine. |
| Platformklassen | src/domain/, src/nodered/ |
BaseDomain, BaseNodeAdapter, ChildRouter,
UnitPolicy, LatestWinsGate, HealthStatus,
statusBadge/StatusUpdater, CommandRegistry,
cmd-builder. |
| Asset-register | src/registry/ |
AssetResolver (namespaces assetSpec/curves/menu/monsterSamples/
monsterSpecs/units), FileBackend/HttpBackend,
frostMeasuredCurve, loadCurve (deprecated shim). |
| Overig | src/helper/, src/outliers/ |
configUtils, validation, assertNoNaN,
gravity (WGS-84 Somigliana), logger;
DynamicClusterDeviation alleen via subpath "./outliers". |
index.js:65-115) is het contractuele toegangspunt.Parent-child (detail). De handshake loopt in drie stappen: het child stuurt
(100 ms vertraagd, zodat siblings klaar zijn) child.register op
poort 2; de flow-bedrading brengt dat bij de parent-input; de parent roept
registerChild aan (bv. nodes/monster/src/commands/handlers.js:47-60).
Daarbij normaliseert SOFTWARE_TYPE_ALIASES
(rotatingmachine→machine, machinegroupcontrol→machinegroup).
Parents in de praktijk: pumpingStation, machineGroupControl, valveGroupControl, monster,
reactor, dashboardAPI.
CoolProp/WASM (detail). src/cp.js draait de Emscripten-glue in een
vm-context met nagemaakte browseromgeving (mock XMLHttpRequest
leest de .wasm van schijf) en pollt onRuntimeInitialized met
5 s timeout. Exact twee call-sites in heel EVOLV:
pumpingStation/src/measurement/measurementRouter.js:5,71 (waterdichtheid) en
rotatingMachine/src/prediction/efficiencyMath.js:21,98 (dichtheid voor
rendement); de refrigerant-functies worden door geen enkele node aangeroepen.
docs/measurement-container.html en
docs/command-envelope.html; het API-contract staat in CONTRACT.md.generalFunctions. Het feitendossier is geschreven op werkkopie
14edf25941da (branch feat/child-router-speed-ctrl; super-repo
RnD/EVOLV 74c4089, 2026-07-03). Analysedatum 2026-07-17.git fetch
gedraaid; de aandachtspunten zijn tegen origin/main gecontroleerd (baseline
70dd2180f9d0). Bug 5 is daarna gemerged: PR #29
(fix/monotone-cubic-overshoot, commit c5f6224) is via merge
6eaef76 in origin/main geland (CI lint-and-test +
gitleaks groen), geverifieerd met
git merge-base --is-ancestor c5f6224 origin/main → ancestor. De
atEquipment-casusmismatch is nog bevestigd aanwezig in
origin/main.npm test); elk feit draagt een
pad:regel-verwijzing. De interactieve stukken zijn getrouwe herimplementaties:
het adresserings-keyformaat (I1) is met node tegen de echte
MeasurementContainer.js geverifieerd en de spline-figuur draait door de echte
Interpolation-engine — testgevallen staan als commentaar in de pagina-JS.