Matches the 5-threshold convention (dryRunLevel, minLevel, startLevel,
maxLevel, overflowLevel) introduced in the pumpingStation wiki:
basin.heightInlet → basin.inflowLevel
basin.heightOutlet → basin.outflowLevel
basin.heightOverflow → basin.overflowLevel
control.levelbased.stopLevel → control.levelbased.minLevel
control.levelbased.maxFlowLevel → control.levelbased.maxLevel
control.levelbased.minFlowLevel → removed (redundant with startLevel)
control.levelbased.startLevel → unchanged
Description strings tightened to reference the semantic role instead
of generic "min level to scale flow" prose.
Breaking change for existing saved flows. Ties in with pumpingStation
commit a218945 which updates the consumer code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The unconditional transition to 'operational' after every movement abort
caused a bounce loop when MGC called abortActiveMovements on each demand
tick: abort→operational→new-flowmovement→abort→operational→... endlessly.
Pumps never reached their setpoint.
Fix: abortCurrentMovement now takes an options.returnToOperational flag
(default false). Routine MGC aborts leave the pump in accelerating/
decelerating — the pump continues its residual movement and reaches
operational naturally. Shutdown/emergency-stop paths pass
returnToOperational:true so the FSM unblocks for the stopping transition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The schema default for machineCurve.nq had a dummy pressure slice at
key "1" with x=[1..5] y=[10..50]. configUtils.updateConfig deep-merges
defaults into the real config, so this fake slice survived alongside the
real pressure slices (70000, 80000, ..., 390000 Pa). The predict class
then included it in its pressure-dimension spline, pulling all
interpolated y-values toward the dummy data at low pressures and
producing NEGATIVE flow predictions (e.g. -243 m³/h) where the real
curve is strictly positive.
Fix: default to empty objects {nq: {}, np: {}} so the deep merge adds
nothing. The validateMachineCurve function already returns the whole
default if the real curve is missing or invalid, so the empty default
doesn't break the no-curve-data path — it just stops poisoning the
real curve data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MGC and pumpingStation registerChild handlers dispatch on
softwareType === 'machine' / 'machinegroup' / 'pumpingstation' /
'measurement'. But buildConfig sets functionality.softwareType to the
lowercased node name, so in production rotatingMachine reports
'rotatingmachine' and machineGroupControl reports 'machinegroupcontrol'.
Result: the MGC <-> rotatingMachine and pumpingStation <-> MGC wiring
silently never hit the right branch in production, even though every
unit test passes (tests pass an already-aliased softwareType manually).
Fix: tiny SOFTWARE_TYPE_ALIASES map at the central registerChild
dispatcher in childRegistrationUtils. Real production names get
translated to the dispatch keys parents already check for, while tests
that pass already-aliased keys are unaffected (their values aren't in
the alias map and pass through unchanged).
rotatingmachine -> machine
machinegroupcontrol -> machinegroup
Verified end-to-end on Dockerized Node-RED: MGC now reports
'3 machine(s) connected' when wired to 3 rotatingMachine ports;
pumpingStation registers MGC as a machinegroup child and listens to
its predicted-flow stream.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reproduction (any node using assetMenu — measurement, rotatingMachine,
pumpingStation, monster, …):
open node -> pick Vega supplier -> pick Pressure type
-> model dropdown stays "Awaiting Type Selection"
Root cause: two interacting bugs in the chained dropdown wiring.
1. populate() inside both wireEvents() and loadData() auto-dispatched a
synthetic 'change' event whenever the value of the rebuilt <select>
differed from before the rebuild. That meant rebuilding 'type' inside
the supplier change handler could fire the *type* change handler
mid-way through, populate the model dropdown, and then return — only
for the supplier handler to continue and unconditionally call
populate(elems.model, [], '', undefined, 'Awaiting Type Selection'),
wiping the model dropdown back to empty.
2. loadData() ran the same auto-dispatch path, so on initial open of a
saved node the synthetic change cascaded through wireEvents listeners
AND loadData's own sequential populate calls double-populated each
level. The visible state depended on which path won the race.
Fix: convert the chain to an explicit downward cascade.
- populate() no longer dispatches change events. It simply rebuilds the
<select> with placeholder + options and assigns the requested value.
- New cascadeFromSupplier / cascadeFromType / cascadeFromModel helpers
read the *current DOM value* of each upstream <select>, look up the
matching item in menuData, and rebuild the next level — then call the
next cascade explicitly. Order is now deterministic and the parent
handler can never wipe the child after the child was populated.
- Each <select>'s native 'change' listener is just the corresponding
cascade function. Same code path runs for user picks AND for initial
load, so saved-node restore behaves identically to a fresh pick.
- The cascades are exposed under window.EVOLV.nodes.<name>.assetMenu._cascade
so loadData (or future sync code) can re-run them after async data
arrives without duplicating logic.
No new DOM dependencies, no test framework changes. Existing
generalFunctions tests still 52/61 (same 9 pre-existing failures
unrelated to this change).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MeasurementContainer.isUnitCompatible now short-circuits to accept any unit
when the measurement type is not in the built-in measureMap. Known types
(pressure, flow, power, temperature, volume, length, mass, energy) still
validate strictly. This unblocks user-defined types in the measurement
node's new digital/MQTT mode — e.g. 'humidity' with unit '%', 'co2' with
'ppm' — without forcing those units into the convert-module unit system.
measurement.json schema: add 'mode.current' (analog | digital) and
'channels' (array) so the validator stops stripping them from the runtime
config. Ignored in analog mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
state.js: When moveTo catches a 'Movement aborted' or 'Transition aborted'
error, transition the FSM back to 'operational'. This ensures a subsequent
shutdown or emergency-stop sequence is accepted — previously the FSM stayed
stuck in 'accelerating'/'decelerating' and rejected stopping/idle
transitions, silently dropping shutdown commands issued mid-ramp. Also
emits a 'movementAborted' event for observability.
rotatingMachine.json: Add schema entries for functionality.distance,
functionality.distanceUnit, functionality.distanceDescription, and top-level
output.{process,dbase}. These keys are produced by buildConfig / the HTML
editor but were previously stripped by the validator with an
'Unknown key' warning on every deploy.
configs/index.js: Trim buildConfig so it no longer unconditionally injects
distanceUnit/distanceDescription — those keys are rotatingMachine-specific
and would otherwise produce Unknown-key warnings on every other node.
Verified via Docker-hosted Node-RED E2E: shutdown from accelerating now
reaches idle; emergency stop from accelerating reaches off; 0 Unknown-key
warnings in container logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
At pressures 1600, 3200, and 3300 mbar, flow values had leaked into the
np (power) section. Replaced with linearly interpolated values from
adjacent pressure levels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
migrateConfig stamps a version string into config schemas. validateSchema
then iterates the string's character indices, causing infinite recursion.
Skip the 'version' key and guard against any non-object schema entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConfigManager.migrateConfig() was called but never defined — would crash at runtime.
Added config version checking, migration support, and fixed createEndpoint indentation.
New formatters module (csv, influxdb, json) for pluggable output formatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace hardcoded position strings with POSITIONS.* constants
- Prefix unused variables with _ to resolve no-unused-vars warnings
- Fix no-prototype-builtins with Object.prototype.hasOwnProperty.call()
- Extract menuUtils.js (543 lines) into 6 focused modules under menu/
- menuUtils.js now 35 lines, delegates via prototype mixin pattern
- Add 158 unit tests for ConfigManager, MeasurementContainer, ValidationUtils
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Break the 548-line monolith into focused modules:
- validators/typeValidators.js (number, integer, boolean, string, enum)
- validators/collectionValidators.js (array, set, object)
- validators/curveValidator.js (curve, machineCurve, dimensionStructure)
validationUtils.js now uses a VALIDATORS registry map and delegates to
extracted modules. Reduced from 548 to 217 lines.
Closes#2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ConfigManager.buildConfig() now lowercases softwareType
- Updated config JSON defaults to lowercase
- childRegistrationUtils lowercases softwareType on extraction
- Closes#8
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New baseConfig.json: shared schema for general/logging/functionality/asset sections
- ConfigManager.buildConfig(): builds runtime config from UI inputs + domain overrides
- Eliminates the need for each nodeClass to manually construct base config sections
- All nodes can now use: cfgMgr.buildConfig(name, uiConfig, nodeId, domainConfig)
Closes#1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove console.log('hello:') from Measurement.js (#22)
- Add bounds check for peakIndex in predict_class.js (#23)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New src/constants/positions.js: POSITIONS enum (upstream/downstream/atEquipment/delta)
- New src/configs/reactor.json: Full schema for CSTR/PFR reactor parameters and ASM3 initial state
- New src/configs/settler.json: Schema for settler node
- Export POSITIONS, POSITION_VALUES, isValidPosition from index.js
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>