Files
EVOLV/scripts/fix-display-issues.js
znetsixe 6a6c04d34b Migrate to new Gitea instance (gitea.wbd-rd.nl)
- 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>
2026-03-04 21:07:04 +01:00

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)`);