Waterschap Brabantse Delta · R&D-lab · uitleg

EVOLV · measurement — één sample door de kanaal-pipeline

eigenaar R&D-lab laatst getest 2026-07-17 versie 2.0 concept
measurement is de sensorlaag van EVOLV: hij zet ruwe input — een analoge scalar of een digitaal multi-kanaals object — om naar geschaalde, gladgestreken en outlier-gefilterde meetwaarden, en publiceert die in de gedeelde MeasurementContainer waar parent-equipmentnodes op meelezen. Elk kanaal draait één pipeline: outlier → offset → scaling → smoothing → output. De node is tick-gedreven (vaste 1 s-tick), niet event-gedreven. Deze pagina volgt letterlijk wat de code doet aan de hand van één voorbeeldsample en een interactieve figuur die de pipeline getrouw naspeelt.

Doel & rol in de keten

measurement is een S88-Control Module (lane L2, kleur #a9daee; CLAUDE.md §"S88 classification"; CONTRACT.md:83-88) en daarmee een blad in de hiërarchie: hij accepteert zelf géén kinderen. Als child registreert hij zich via Port 2 child.register bij een parent-equipmentnode — rotatingMachine, pumpingStation, reactor of monster (CONTRACT.md:83-88). Parents consumeren de meetwaarden niet via de poorten maar via events op de gedeelde MeasurementContainer (README.md:5).

De node is tick-gedrevenstatic tickInterval = 1000 (src/nodeClass.js:12): elke seconde draait de adapter tick() en daarna _emitOutputs() (BaseNodeAdapter.js:134-148); de statusbadge ververst óók elke 1000 ms. Er is geen state-machine, alleen de configuratieschakelaar mode.current = analog | digital (runtime niet omschakelbaar, specificClass.js:16-23).

Rode draad door de hele pagina: één analoog drukkanaal — scaling [4, 20] mA[0, 250] mbar, offset 0, smoothing mean/10, outlier zScore/3. Eén sample {value: 12, unit:'mA'} loopt door de pipeline en levert 12 mA → 125,0 mbar. Alle getallen zijn met node tegen de echte src/channel.js doorgerekend (2400 fuzz-samples matchten exact); de reeks in de interactieve figuur is synthetisch en zo gelabeld.

Praat met deze node

Het I/O-contract in één oogopslag: kies een verb, stel de velden in, kopieer het exacte injecteerbare msg, en klik Injecteer ▸ om te zien wat er uit de drie poorten komt — met live doorgerekende waarden op het rode-draadkanaal. Elke bewering draagt een pad:regel-chip. Poortlabels ["process","dbase","parent"].

Stuurinput · 1 poort
Bericht
Poortenoutput · 3 poorten
Kern-inzicht · tick-only output (dode event-route)
De poort-emissies hierboven verschijnen alleen op het 1 s-tickritme. Omdat tickInterval = 1000 > 0 retourneert _wireOutputs() vóór het aankoppelen van de output-changed-listener (BaseNodeAdapter.js:137-153); de notifyOutputChanged()-emits uit de analoge setter en calculateInput (specificClass.js:140,189) hebben dus geen luisteraar — een dode event-route. Latentie tot 1 s. Het commentaar in nodeClass.js:10-11 ("event-driven via de output-changed emit") beschrijft de feitelijke draad niet.
Volledige poort-referentie
Poortcontract (labels ["process","dbase","parent"], measurement.html:79-82). Poort 0/1 zijn delta-gecomprimeerd: een tick zonder wijziging levert null → géén bericht (outputUtils.js:17-67).
PoortLabelBelangrijkste keys
0process Delta-gecomprimeerde snapshot. Analoog (specificClass.js:217-227): mAbs, mPercent, totalMinValue (Infinity→0), totalMaxValue (−Infinity→0), totalMinSmooth, totalMaxSmooth. Digitaal: genest {channels:{<key>:{key,type,position,unit,mAbs,mPercent,totalMin/MaxValue,totalMin/MaxSmooth}}} (specificClass.js:128-132; channel.js:90-103) — het geneste object wordt door de delta-compressor ge-JSON-stringified (outputUtils.js:29-33). msg.topic = general.name. Formaat process|json|csv.
1dbase Zelfde snapshot, formatter uit output.dbase: influxdb (default) | frost | json | csv. Influx-vorm {measurement, fields, tags, timestamp} (influxdbFormatter.js:12-20) met laag-cardinale tags (id, softwareType, role, positionVsParent, uuid, tagcode, geoLocation_x/y/z, category, type, model, unit; lege waarden gedropt, outputUtils.js:87-133).
2parent Eenmalig, 100 ms na start: { topic:'child.register', payload:<node.id>, positionVsParent, distance } — bewust legacy msg.topic-pad (transitional handshake, BaseNodeAdapter.js:120-131; CONTRACT.md:51-55).

Wie praat met wie

measurement is een blad: geen kinderen. Hij registreert zich omhoog bij één parent (via draad, poort 2); de meetwaarden bereiken de parent níet via een draad maar als in-process events op de gedeelde MeasurementContainer-emitter. De echte sensor (edgeIO/veld) zit buiten deze repo: veldwaarden komen binnen als measurement-sample of via de simulator.

rotatingMachine / pumpingStation / reactor / monster parent-equipment measurement deze node · S88 Control Module · BLAD echte sensor / edgeIO buiten deze repo geen kinderen (blad) p2 child.register <type>.measured.<position> · in-process sample / simulate
registratie · via draad (poort 2) meetwaarde · in-process event (MeasurementContainer) veld → node · buiten deze repo

Bij elke afgeronde output-wijziging voedt het kanaal de container met type(<type>).variant('measured').position(<position>).value(outputAbs, …, unit) (channel.js:215-220); de container emit <type>.measured.<position> (positie lowercase), bv. pressure.measured.upstream (MeasurementContainer.js:361) — dát is het in-process pad waarover de parent meeleest.

De reis van één sample

Klik een stap (of gebruik ◀ ▶). Boven zie je waar in de pipeline je bent; rechts wat er gebeurt, de waarde van het rode-draadvoorbeeld op dat punt, en het codepad. Let op de volgorde: de echte runtime-pipeline is outlier → offset → scaling → smoothing (channel.js:69-88) — niet de volgorde uit de editor-hulptekst (zie het kern-inzicht verderop).

Signaalconditionering, interactief

Deze figuur is de kern van het artifact: een getrouwe herimplementatie van src/channel.js (node-getest, 2400 fuzz-samples matchten exact) op een synthetische ruwe reeks (sinus + ruis + een paar spikes). Kies een smoothing-methode (alle 12), zet outlierdetectie aan met een methode + drempel, schakel scaling en verschuif de offset. Geplot: de ruwe reeks, de verwerkte reeks en de verworpen outliers. De pipeline draait in de echte volgorde outlier → offset → scaling → smoothing; het rolling window bevat gesmoothde waarden.

3,0
0,0
10

Kern-inzichten & modellen

kern
Runtime-volgorde ≠ hulptekst
De editor-hulptekst noemt de pipeline offset → scaling → smoothing → outlier (measurement.html:864), maar de echte runtime-volgorde is outlier → offset → scaling → smoothing (channel.js:69-88). Gevolg: outlierdetectie kijkt naar de ruwe sample, vóór offset/scaling. Het pipeline-diagram in de editor toont de runtime-volgorde overigens wél correct (measurement.html:436-439).
kern
Tick-only output / dode event-route
Door tickInterval=1000 wordt de output-changed-listener nooit aangekoppeld (BaseNodeAdapter.js:137-153); de emits in specificClass.js:140,189 hebben geen luisteraar. Outputs verschijnen uitsluitend op het 1 s-tickritme, latentie tot 1 s. aanname: het commentaar in nodeClass.js:10-11 is mogelijk onbedoeld achtergebleven.
Outlier op smoothed-window
Het rolling window wordt gevuld in _applySmoothing (channel.js:164) — het bevat dus gesmoothde waarden. De outlier-check toetst een nieuwe raw sample tegen die smoothed-historie (channel.js:72). Methodisch discutabel, maar zo gecodeerd.
zScore σ=0 ⇒ ∞ (bewust)
Bij een perfect vlakke baseline is σ=0. De code doet bewust géén early-return: z = (val===μ ? 0 : Infinity) (channel.js:125-129) — elke afwijking wordt dan een outlier (z=∞ > thr). Zie de preset in de figuur hierboven. modifiedZScore doet het omgekeerde: MAD=0 ⇒ nooit outlier (channel.js:142).
Analoog vs digitaal
Analoog: één kanaal met type = asset.type, unit uit asset.unit (specificClass.js:54-68). Digitaal: één kanaal per channels[]-entry, elk met eigen {key,type,position,unit,scaling?,smoothing?, outlierDetection?}; ontbrekende sub-secties vallen terug op de analoge topniveau-config (specificClass.js:87-90). mode is config-only, runtime niet omschakelbaar.
Contract-drift
README.md:38 claimt mode-keuze "via msg.mode" — er is geen msg.mode-verwerking in de runtime. CONTRACT.md:76-79 beschrijft een legacy 'mAbs'-emitter die nergens meer ge-emit wordt. En de envelope-migratietabel (.claude/rules/command-envelope.md §4) noemt measurement niet, terwijl de code feitelijk al op het envelope-pad zit (src/commands/index.js:15-59).

Lineaire schaling (indien scaling.enabled; channel.js:150-159), met v eerst geclampt op [inputMin, inputMax]:

out= absmin+ (vinmin) · absmaxabsmin |inmaxinmin|
SymboolBetekenisEenheidHerkomst
vge-offsette, geclampte raw inputraw-eenheidchannel.js:77,157
inmin, inmaxraw-inputbereikraw-eenheidscaling.inputMin/inputMax
absmin, absmaxprocesbereik na schalingkanaal-unitscaling.absMin/absMax
outgeschaalde waardekanaal-unitchannel.js:158

Rode draad: v = 12, [4,20]→[0,250]out = 0 + (12−4)·250/16 = 125,0 mbar (node-geverifieerd). Ongeldig inputbereik (≤ 0) → warn + fallback naar [0,1] (channel.js:151-156).

Smoothing: rolling window (max smoothWindow) met 12 methodes (channel.js:163-189): none, mean, min, max, sd (n−1), median, weightedMovingAverage (gewichten 1..n), lowPass (EMA α=0,2), highPass (α=0,8), bandPass (lp+hp−x), kalman (vaste gain g≈0,0909) en savitzkyGolay (5-punts [−3,12,17,12,−3]/35). Methode-namen worden lowercased gematcht; onbekend → error-log + raw value door. Kalibratie (zero-set, alleen bij stabiel window): offset = baseline − outputAbs (calibrator.js:50-66). Er is geen alarm-/limietlogica in deze node.

Configuratie & meettypes

Kernvelden (editor-defaults measurement.html:29-76; schema nodes/generalFunctions/src/configs/measurement.json). * = required. UI→domein-mapping in nodeClass.buildDomainConfig() (nodeClass.js:15-40).
Sectie.veldDefaultBetekenis / enum
mode.currentanaloganalog / digital (config-only)
asset.typepressuremeettype = as in de MeasurementContainer
scaling.enabledfalselineaire schaling aan/uit
scaling.inputMin/inputMax0 / 1 *raw-inputbereik
scaling.absMin/absMax50 / 100 *procesbereik na schaling
scaling.offset0additieve bias/zero (raw-eenheden)
smoothing.smoothMethodmean none / mean / min / max / sd / lowPass / highPass / weightedMovingAverage / bandPass / median / kalman / savitzkyGolay
smoothing.smoothWindow10 *venstergrootte (min 1)
outlierDetection.enabledfalseoutlierfilter aan/uit
outlierDetection.methodzScorezScore / iqr / modifiedZScore
outlierDetection.threshold3drempel bij gekozen methode
interpolation.percentMin/percentMax0 / 100percentagebereik mPercent
calibration.stabilityThreshold0.01absolute stdDev-grens "stabiel venster"
channels[]digitale kanaaldefinities {key,type,position,unit,…}; genegeerd in analoog
Meettypes & eenheden voor de digitale kanaal-editor (src/editor/digital-channels.js:28-50). Canonieke types hebben unit-conversie; custom types (humidity, co2, voc en elk ander onbekend type) gebruiken vrije unit-tekst zonder conversie.
TypeUnit-suggesties
pressurePa, kPa, MPa, hPa, bar, mbar, torr, psi
flowm³/s, m³/h, L/s, L/min, gpm
powerW, kW, MW, hp
temperatureC, K, F, R
volumemL, L, m³, gal
lengthmm, cm, m, km, in, ft
massmg, g, kg, t, oz, lb
energyWh, kWh, J, kJ, MJ
humidity / co2 / voc + overigcustom — vrije unit-tekst, geen conversie

Aandachtspunten uit de code-analyse

Alle punten zijn code-observaties d.d. 2026-07-17 (statische analyse, niet in runtime geverifieerd). Elk punt draagt een pad:regel-verwijzing; duiding is expliciet als aanname gemarkeerd.

Verder lezen

Bron statische code-analyse van de werkkopie RnD/EVOLV (super-repo 74c4089, 2026-07-03) — submodule measurement geanalyseerd @ c368bf8b2ce7, plus het feitendossier van deze analyse. Analysedatum 2026-07-17.
Versheids-herverificatie op 2026-07-17 is git fetch gedraaid: measurement origin/main = c368bf8b2ce7 — gelijk aan de geanalyseerde revisie (geen drift).
Methode statische code-analyse (geen runtime-verificatie); elk feit draagt een pad:regel-verwijzing. De interactieve figuur en de console zijn getrouwe herimplementaties, vooraf met node tegen de echte src/channel.js doorgerekend (testgevallen als commentaar in de pagina-JS): scaling 12 mA op [4,20]→[0,250] = 125,0 mbar; mean/median/kalman op een vaste reeks; zScore σ=0 = outlier; 2400 fuzz-samples matchten channel.js exact. De reeks in F1 is synthetisch en zo gelabeld.
Beperking beschrijft uitsluitend de geanalyseerde revisie; gedrag op andere branches of deployments kan afwijken.
Contact R&D-lab · lab.wbd-rd.nl