#!/usr/bin/env node /** * Fix display issues: * 1. Set positionIcon on all nodes based on positionVsParent * 2. Switch reactor from CSTR to PFR with proper length/resolution * 3. Add missing default fields to all dashboard widgets (gauges, sliders, button-groups) */ const fs = require('fs'); const path = require('path'); const FLOW_PATH = path.join(__dirname, '..', 'docker', 'demo-flow.json'); const flow = JSON.parse(fs.readFileSync(FLOW_PATH, 'utf8')); const byId = (id) => flow.find(n => n.id === id); // ============================================= // FIX 1: positionIcon on all process nodes // ============================================= // Icon mapping from physicalPosition.js const positionIconMap = { 'upstream': '→', 'atEquipment': '⊥', 'downstream': '←', }; let iconFixed = 0; for (const node of flow) { if (node.positionVsParent !== undefined && node.positionVsParent !== '') { const icon = positionIconMap[node.positionVsParent]; if (icon && node.positionIcon !== icon) { node.positionIcon = icon; iconFixed++; } } // Also ensure positionIcon has a fallback if positionVsParent is set if (node.positionVsParent && !node.positionIcon) { node.positionIcon = positionIconMap[node.positionVsParent] || '⊥'; iconFixed++; } } console.log(`Fixed positionIcon on ${iconFixed} nodes`); // ============================================= // FIX 2: Switch reactor from CSTR to PFR // ============================================= const reactor = byId('demo_reactor'); if (reactor) { reactor.reactor_type = 'PFR'; reactor.length = 50; // 50m plug flow reactor reactor.resolution_L = 10; // 10 slices for spatial resolution reactor.alpha = 0; // Danckwerts BC (dispersive flow, more realistic) console.log(`Switched reactor to PFR: length=${reactor.length}m, resolution=${reactor.resolution_L} slices`); // Update influent measurements with positions along the reactor // FT-001 at inlet (position 0), DO-001 at 1/3, NH4-001 at 2/3 const measFlow = byId('demo_meas_flow'); if (measFlow) { measFlow.hasDistance = true; measFlow.distance = 0; // at inlet measFlow.distanceUnit = 'm'; measFlow.distanceDescription = 'reactor inlet'; measFlow.positionVsParent = 'upstream'; measFlow.positionIcon = '→'; console.log(' FT-001 positioned at reactor inlet (0m)'); } const measDo = byId('demo_meas_do'); if (measDo) { measDo.hasDistance = true; measDo.distance = 15; // 15m along the reactor (30% of length) measDo.distanceUnit = 'm'; measDo.distanceDescription = 'aeration zone'; measDo.positionVsParent = 'atEquipment'; measDo.positionIcon = '⊥'; console.log(' DO-001 positioned at 15m (aeration zone)'); } const measNh4 = byId('demo_meas_nh4'); if (measNh4) { measNh4.hasDistance = true; measNh4.distance = 35; // 35m along the reactor (70% of length) measNh4.distanceUnit = 'm'; measNh4.distanceDescription = 'post-aeration zone'; measNh4.positionVsParent = 'atEquipment'; measNh4.positionIcon = '⊥'; console.log(' NH4-001 positioned at 35m (post-aeration zone)'); } } // ============================================= // FIX 3: Add missing defaults to dashboard widgets // ============================================= // --- ui-gauge: add missing fields --- const gaugeDefaults = { value: 'payload', valueType: 'msg', sizeThickness: 16, sizeGap: 4, sizeKeyThickness: 8, styleRounded: true, styleGlow: false, alwaysShowTitle: false, floatingTitlePosition: 'top-left', icon: '', }; let gaugeFixed = 0; for (const node of flow) { if (node.type !== 'ui-gauge') continue; let changed = false; for (const [key, defaultVal] of Object.entries(gaugeDefaults)) { if (node[key] === undefined) { node[key] = defaultVal; changed = true; } } // Ensure className exists if (node.className === undefined) node.className = ''; // Ensure outputs (gauges have 1 output in newer versions) if (changed) gaugeFixed++; } console.log(`Fixed ${gaugeFixed} ui-gauge nodes with missing defaults`); // --- ui-button-group: add missing fields --- const buttonGroupDefaults = { rounded: true, useThemeColors: true, topic: 'topic', topicType: 'msg', className: '', }; let bgFixed = 0; for (const node of flow) { if (node.type !== 'ui-button-group') continue; let changed = false; for (const [key, defaultVal] of Object.entries(buttonGroupDefaults)) { if (node[key] === undefined) { node[key] = defaultVal; changed = true; } } // Ensure options have valueType if (node.options && Array.isArray(node.options)) { for (const opt of node.options) { if (!opt.valueType) opt.valueType = 'str'; } } if (changed) bgFixed++; } console.log(`Fixed ${bgFixed} ui-button-group nodes with missing defaults`); // --- ui-slider: add missing fields --- const sliderDefaults = { topic: 'topic', topicType: 'msg', thumbLabel: true, showTicks: 'always', className: '', iconPrepend: '', iconAppend: '', color: '', colorTrack: '', colorThumb: '', showTextField: false, }; let sliderFixed = 0; for (const node of flow) { if (node.type !== 'ui-slider') continue; let changed = false; for (const [key, defaultVal] of Object.entries(sliderDefaults)) { if (node[key] === undefined) { node[key] = defaultVal; changed = true; } } if (changed) sliderFixed++; } console.log(`Fixed ${sliderFixed} ui-slider nodes with missing defaults`); // --- ui-chart: add missing fields --- const chartDefaults = { className: '', }; let chartFixed = 0; for (const node of flow) { if (node.type !== 'ui-chart') continue; let changed = false; for (const [key, defaultVal] of Object.entries(chartDefaults)) { if (node[key] === undefined) { node[key] = defaultVal; changed = true; } } if (changed) chartFixed++; } console.log(`Fixed ${chartFixed} ui-chart nodes with missing defaults`); // --- ui-template: add missing fields --- for (const node of flow) { if (node.type !== 'ui-template') continue; if (node.templateScope === undefined) node.templateScope = 'local'; if (node.className === undefined) node.className = ''; } // --- ui-text: add missing fields --- for (const node of flow) { if (node.type !== 'ui-text') continue; if (node.className === undefined) node.className = ''; } // ============================================= // Validate // ============================================= const allIds = new Set(flow.map(n => n.id)); let issues = 0; for (const n of flow) { if (!n.wires) continue; for (const port of n.wires) { for (const target of port) { if (!allIds.has(target)) { console.warn(`BROKEN WIRE: ${n.id} → ${target}`); issues++; } } } } if (issues === 0) console.log('All wire references valid ✓'); // List all nodes with positionIcon to verify console.log('\nNodes with positionIcon:'); for (const n of flow) { if (n.positionIcon) { console.log(` ${n.positionIcon} ${n.name || n.id} (${n.positionVsParent})`); } } // Write fs.writeFileSync(FLOW_PATH, JSON.stringify(flow, null, 2) + '\n'); console.log(`\nWrote ${FLOW_PATH} (${flow.length} nodes)`);