#!/usr/bin/env node /** * Patch demo-flow.json: * 1. Fix NH4 chart — remove demo_link_meas_dash from new NH4 nodes * 2. Update parse function — use "NH4 @ Xm" label format * 3. Reorganize entire treatment tab — logical left-to-right layout */ const fs = require('fs'); const path = require('path'); const flowPath = path.join(__dirname, '..', 'docker', 'demo-flow.json'); const flow = JSON.parse(fs.readFileSync(flowPath, 'utf8')); const find = (id) => flow.find(n => n.id === id); // ============================================================ // 1. FIX NH4 CHART WIRING // Remove demo_link_meas_dash from the 4 new NH4 nodes. // They should only go to process link + NH4 profile link. // ============================================================ const newNh4Ids = ['demo_meas_nh4_in', 'demo_meas_nh4_a', 'demo_meas_nh4_b', 'demo_meas_nh4_c']; for (const id of newNh4Ids) { const n = find(id); if (n) { n.wires[0] = n.wires[0].filter(w => w !== 'demo_link_meas_dash'); console.log(` ${id} Port 0 wires: ${JSON.stringify(n.wires[0])}`); } } console.log('1. Fixed: removed demo_link_meas_dash from new NH4 nodes'); // ============================================================ // 2. UPDATE PARSE FUNCTION — "NH4 @ Xm" format // Also make it generic: read distance from payload metadata // if available, fall back to topic matching. // ============================================================ const parseFn = find('demo_fn_nh4_profile_parse'); if (parseFn) { parseFn.func = `const p = msg.payload || {}; const topic = msg.topic || ''; const now = Date.now(); const val = Number(p.mAbs); if (!Number.isFinite(val)) return null; // Build label from distance metadata if available, else match by tag const dist = p.distance; const tag = p.assetTagNumber || topic; let label; if (dist !== undefined && dist !== null) { label = 'NH4 @ ' + dist + 'm'; } else if (tag.includes('NH4-IN')) label = 'NH4 @ 0m'; else if (tag.includes('NH4-A')) label = 'NH4 @ 10m'; else if (tag.includes('NH4-B')) label = 'NH4 @ 25m'; else if (tag.includes('NH4-001')) label = 'NH4 @ 35m'; else if (tag.includes('NH4-C')) label = 'NH4 @ 45m'; else label = 'NH4 @ ?m'; return { topic: label, payload: Math.round(val * 100) / 100, timestamp: now };`; console.log('2. Updated NH4 profile parse function to "NH4 @ Xm" format'); } // ============================================================ // 3. REORGANIZE TREATMENT TAB LAYOUT // // Logical left-to-right process flow: // // Col 1 (x=80): Comments / section headers // Col 2 (x=200): Injects (reactor tick, monster flow) // Col 3 (x=420): Inlet measurements (flow, DO, NH4 profile) // Col 4 (x=640): Link outs (meas dash, NH4 profile dash) // Col 5 (x=820): Reactor // Col 6 (x=1060): Settler // Col 7 (x=1280): Effluent measurements // Col 8 (x=1500): Effluent link outs // // Row zones (y): // Row A (y=40): Section comment // Row B (y=100-440): Main process: reactor measurements → reactor → settler // Row C (y=500-700): Effluent measurements (downstream of settler) // Row D (y=760-900): RAS recycle loop (below main flow) // Row E (y=960-1120): Merge collection / influent composition // // ============================================================ const layout = { // ── SECTION COMMENT ── 'demo_comment_treatment': { x: 80, y: 40 }, // ── INJECTS ── 'demo_inj_reactor_tick': { x: 200, y: 120 }, 'demo_inj_monster_flow': { x: 200, y: 560 }, // ── INLET MEASUREMENTS (column, spaced 60px) ── 'demo_meas_flow': { x: 420, y: 100 }, // FT-001 flow 'demo_meas_do': { x: 420, y: 160 }, // DO-001 'demo_meas_nh4_in': { x: 420, y: 220 }, // NH4-IN 0m 'demo_meas_nh4_a': { x: 420, y: 280 }, // NH4-A 10m 'demo_meas_nh4': { x: 420, y: 340 }, // NH4-001 35m (existing, keep between A & B for distance order — wait, 25m < 35m) 'demo_meas_nh4_b': { x: 420, y: 400 }, // NH4-B 25m 'demo_meas_nh4_c': { x: 420, y: 460 }, // NH4-C 45m // ── LINK OUTS (from measurements) ── 'demo_link_meas_dash': { x: 640, y: 130 }, 'demo_link_nh4_profile_dash': { x: 640, y: 340 }, // ── REACTOR ── 'demo_reactor': { x: 820, y: 220 }, // ── REACTOR LINK OUTS ── 'demo_link_reactor_dash': { x: 1020, y: 180 }, 'demo_link_overview_reactor_out': { x: 1020, y: 220 }, // ── SETTLER ── 'demo_settler': { x: 1060, y: 320 }, // ── SHARED LINK OUTS (process + influx) ── 'demo_link_influx_out_treatment': { x: 1020, y: 260 }, 'demo_link_process_out_treatment': { x: 1020, y: 300 }, // ── EFFLUENT SECTION ── 'demo_comment_effluent_meas': { x: 80, y: 520 }, 'demo_meas_eff_flow': { x: 1280, y: 320 }, 'demo_meas_eff_do': { x: 1280, y: 380 }, 'demo_meas_eff_nh4': { x: 1280, y: 440 }, 'demo_meas_eff_no3': { x: 1280, y: 500 }, 'demo_meas_eff_tss': { x: 1280, y: 560 }, 'demo_link_eff_meas_dash': { x: 1500, y: 440 }, 'demo_link_overview_eff_out': { x: 1500, y: 500 }, // ── MONSTER (downstream of settler, parallel to effluent meas) ── 'demo_monster': { x: 1060, y: 440 }, 'demo_fn_monster_flow': { x: 400, y: 560 }, // ── RAS RECYCLE LOOP (below main process) ── 'demo_fn_ras_filter': { x: 1060, y: 760 }, 'demo_pump_ras': { x: 1280, y: 760 }, 'demo_meas_ft_ras': { x: 1500, y: 760 }, 'demo_inj_ras_mode': { x: 1280, y: 820 }, 'demo_inj_ras_speed': { x: 1280, y: 880 }, 'demo_comment_pressure': { x: 80, y: 740 }, // ── MERGE COLLECTION (bottom section) ── 'demo_comment_merge': { x: 80, y: 960 }, 'demo_link_merge_west_in': { x: 100, y: 1000 }, 'demo_link_merge_north_in': { x: 100, y: 1060 }, 'demo_link_merge_south_in': { x: 100, y: 1120 }, 'demo_fn_tag_west': { x: 300, y: 1000 }, 'demo_fn_tag_north': { x: 300, y: 1060 }, 'demo_fn_tag_south': { x: 300, y: 1120 }, 'demo_fn_merge_collect': { x: 520, y: 1060 }, 'demo_link_merge_dash': { x: 720, y: 1020 }, 'demo_fn_influent_compose': { x: 720, y: 1100 }, }; // Sort NH4 measurements by distance for visual order // NH4-IN=0m, NH4-A=10m, NH4-B=25m, NH4-001=35m, NH4-C=45m // Adjust y to be in distance order: layout['demo_meas_nh4_in'] = { x: 420, y: 220 }; // 0m layout['demo_meas_nh4_a'] = { x: 420, y: 280 }; // 10m layout['demo_meas_nh4_b'] = { x: 420, y: 340 }; // 25m layout['demo_meas_nh4'] = { x: 420, y: 400 }; // 35m layout['demo_meas_nh4_c'] = { x: 420, y: 460 }; // 45m let moved = 0; for (const [id, pos] of Object.entries(layout)) { const n = find(id); if (n) { n.x = pos.x; n.y = pos.y; moved++; } else { console.warn(` WARN: node ${id} not found`); } } console.log(`3. Repositioned ${moved} nodes on treatment tab`); // ============================================================ // WRITE OUTPUT // ============================================================ fs.writeFileSync(flowPath, JSON.stringify(flow, null, 2) + '\n', 'utf8'); console.log(`\nDone. Wrote ${flow.length} nodes to ${flowPath}`);