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>