#!/usr/bin/env node /** * Comprehensive runtime analysis of the WWTP demo flow. * Captures process debug output, pumping station state, measurements, * and analyzes filling/draining behavior over time. */ const http = require('http'); const NR_URL = 'http://localhost:1880'; function fetchJSON(url) { return new Promise((resolve, reject) => { http.get(url, res => { const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => { try { resolve(JSON.parse(Buffer.concat(chunks))); } catch (e) { reject(new Error('Parse error from ' + url + ': ' + e.message)); } }); }).on('error', reject); }); } // Inject a debug-capture subflow to intercept process messages async function injectDebugCapture() { const flows = await fetchJSON(NR_URL + '/flows'); // Find all nodes on WWTP tab const wwtp = flows.filter(n => n.z === 'demo_tab_wwtp'); console.log('=== WWTP Node Inventory ==='); const byType = {}; wwtp.forEach(n => { if (!byType[n.type]) byType[n.type] = []; byType[n.type].push(n); }); Object.entries(byType).sort().forEach(([type, nodes]) => { console.log(type + ' (' + nodes.length + '):'); nodes.forEach(n => { const extra = []; if (n.simulator) extra.push('sim=ON'); if (n.model) extra.push('model=' + n.model); if (n.basinVolume) extra.push('basin=' + n.basinVolume + 'm3'); if (n.basinHeight) extra.push('h=' + n.basinHeight + 'm'); if (n.positionVsParent) extra.push('pos=' + n.positionVsParent); if (n.control) extra.push('ctrl=' + JSON.stringify(n.control)); console.log(' ' + n.id + ' "' + (n.name || '') + '" ' + (extra.length ? '[' + extra.join(', ') + ']' : '')); }); }); // Analyze pumping station configurations console.log('\n=== Pumping Station Configs ==='); const pss = wwtp.filter(n => n.type === 'pumpingStation'); pss.forEach(ps => { console.log('\n' + ps.id + ' "' + ps.name + '"'); console.log(' Basin: vol=' + ps.basinVolume + 'm3, h=' + ps.basinHeight + 'm'); console.log(' Inlet: h=' + ps.heightInlet + 'm, Outlet: h=' + ps.heightOutlet + 'm'); console.log(' Simulator: ' + ps.simulator); console.log(' Control mode: ' + (ps.controlMode || 'not set')); // Check q_in inject wiring const qinInject = wwtp.find(n => n.id === 'demo_inj_' + ps.id.replace('demo_ps_', '') + '_flow'); if (qinInject) { console.log(' q_in inject: repeat=' + qinInject.repeat + 's, wired to ' + JSON.stringify(qinInject.wires)); } // Check what's wired to this PS (port 2 = parent registration) const children = wwtp.filter(n => { if (!n.wires) return false; return n.wires.some(portWires => portWires && portWires.includes(ps.id)); }); console.log(' Children wired to it: ' + children.map(c => c.id + '(' + c.type + ')').join(', ')); }); // Analyze inject timers console.log('\n=== Active Inject Timers ==='); const injects = wwtp.filter(n => n.type === 'inject'); injects.forEach(inj => { const targets = (inj.wires || []).flat(); console.log(inj.id + ' "' + (inj.name || '') + '"'); console.log(' topic=' + inj.topic + ' payload=' + inj.payload); console.log(' once=' + inj.once + ' repeat=' + (inj.repeat || 'none')); console.log(' → ' + targets.join(', ')); }); // Analyze q_in function nodes console.log('\n=== q_in Flow Simulation Functions ==='); const fnNodes = wwtp.filter(n => n.type === 'function' && n.name && n.name.includes('Flow')); fnNodes.forEach(fn => { console.log(fn.id + ' "' + fn.name + '"'); console.log(' func: ' + (fn.func || '').substring(0, 200)); const targets = (fn.wires || []).flat(); console.log(' → ' + targets.join(', ')); }); // Analyze measurement nodes console.log('\n=== Measurement Nodes ==='); const meas = wwtp.filter(n => n.type === 'measurement'); meas.forEach(m => { console.log(m.id + ' "' + (m.name || '') + '"'); console.log(' type=' + m.assetType + ' sim=' + m.simulator + ' range=[' + m.o_min + ',' + m.o_max + '] unit=' + m.unit); console.log(' pos=' + (m.positionVsParent || 'none')); // Check port 2 wiring (parent registration) const port2 = m.wires && m.wires[2] ? m.wires[2] : []; console.log(' port2→ ' + (port2.length ? port2.join(', ') : 'none')); }); // Analyze rotating machines console.log('\n=== Rotating Machine Nodes ==='); const machines = wwtp.filter(n => n.type === 'rotatingMachine'); machines.forEach(m => { console.log(m.id + ' "' + (m.name || '') + '"'); console.log(' model=' + m.model + ' mode=' + m.movementMode); console.log(' pos=' + m.positionVsParent + ' supplier=' + m.supplier); console.log(' speed=' + m.speed + ' startup=' + m.startup + ' shutdown=' + m.shutdown); const port2 = m.wires && m.wires[2] ? m.wires[2] : []; console.log(' port2→ ' + (port2.length ? port2.join(', ') : 'none')); }); // Check wiring integrity console.log('\n=== Wiring Analysis ==='); pss.forEach(ps => { const psPort0 = ps.wires && ps.wires[0] ? ps.wires[0] : []; const psPort1 = ps.wires && ps.wires[1] ? ps.wires[1] : []; const psPort2 = ps.wires && ps.wires[2] ? ps.wires[2] : []; console.log(ps.id + ' wiring:'); console.log(' port0 (process): ' + psPort0.join(', ')); console.log(' port1 (influx): ' + psPort1.join(', ')); console.log(' port2 (parent): ' + psPort2.join(', ')); }); } injectDebugCapture().catch(err => { console.error('Analysis failed:', err); process.exit(1); });