- Update all submodule URLs from gitea.centraal.wbd-rd.nl to gitea.wbd-rd.nl - Add settler as proper submodule in .gitmodules - Add agent skills, function anchors, decisions, and improvements - Add Docker configuration and scripts - Add manuals and third_party docs - Update .gitignore with secrets and build artifacts - Remove stale .tgz build artifact Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
244 lines
7.4 KiB
JavaScript
244 lines
7.4 KiB
JavaScript
#!/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)`);
|