+
#${i + 1} · ${h.time}
+
${h.msg.replace(/
+
`;
+ }).join('');
+ body.scrollTop = body.scrollHeight;
+ }
+
+ function applyToTopology(compId, dx, dy) {
+ const meta = findCompMeta(compId);
+ if (!meta) return false;
+ const c = meta.comp;
+ if (c.anchor && c.anchor.absolute) {
+ c.anchor.absolute.x = (c.anchor.absolute.x || 0) + dx;
+ c.anchor.absolute.y = (c.anchor.absolute.y || 0) + dy;
+ return true;
+ }
+ if (c.anchor && !c.anchor.absolute) {
+ c.anchor.dx = (c.anchor.dx ?? 0) + dx;
+ c.anchor.dy = (c.anchor.dy ?? 0) + dy;
+ return true;
+ }
+ if (typeof c.x === 'number' && typeof c.y === 'number') {
+ c.x += dx;
+ c.y += dy;
+ return true;
+ }
+ return false;
+ }
+
+ function rebuildScene() {
+ const sceneEl = document.getElementById('scene-content');
+ if (!sceneEl || !window.INVYONE_TOPO) return;
+ // anchor 기반 컴포넌트의 _resolved 캐시 리셋 (dx/dy/at 등 변경 사항 재계산되도록)
+ const T = window.INVYONE_TOPO;
+ ['TANKS','PUMPS','VALVES','FILTERS','MODULES','GAUGES','SENSORS','LEDS'].forEach(b => {
+ (T[b] || []).forEach(c => {
+ if (c.anchor) {
+ c._resolved = false;
+ if (!c.anchor.absolute) { c.x = undefined; c.y = undefined; }
+ }
+ });
+ });
+ sceneEl.innerHTML = window.INVYONE_TOPO.buildScene();
+ if (window.INVYONE_ENGINE && typeof window.INVYONE_ENGINE.init === 'function') {
+ try { window.INVYONE_ENGINE.init(); } catch (err) { console.warn('[DRAG] engine.init re-call failed:', err); }
+ }
+ }
+
+ function findCompMeta(compId) {
+ if (!window.INVYONE_TOPO) return null;
+ const T = window.INVYONE_TOPO;
+ const buckets = ['TANKS', 'PUMPS', 'GAUGES', 'SENSORS', 'FILTERS', 'MODULES', 'LEDS', 'VALVES', 'TILES', 'PANELS'];
+ for (const b of buckets) {
+ const arr = T[b] || [];
+ const c = arr.find((x) => x.id === compId);
+ if (c) return { bucket: b, comp: c };
+ }
+ return null;
+ }
+
+ function showResult(compId, compType, dx, dy, origX, origY) {
+ const meta = findCompMeta(compId);
+ const isAnchor = !!(meta && meta.comp && meta.comp.anchor && !meta.comp.anchor.absolute);
+
+ // 게이지는 transform 안에 (x-20, y-20) 적용돼 있으니 anchor 절대 좌표는 +20 보정
+ const isGauge = compType === 'gauge';
+ const baseX = isGauge ? origX + 20 : origX;
+ const baseY = isGauge ? origY + 20 : origY;
+ const newAnchorX = baseX + dx;
+ const newAnchorY = baseY + dy;
+
+ const dxStr = (dx >= 0 ? '+' : '') + dx.toFixed(1);
+ const dyStr = (dy >= 0 ? '+' : '') + dy.toFixed(1);
+
+ let lines = [`▶ ${compId} (${compType || '-'})`, `이동량: dx=${dxStr}, dy=${dyStr}`];
+
+ if (isAnchor) {
+ const a = meta.comp.anchor;
+ const oldDx = a.dx ?? 0;
+ const oldDy = a.dy ?? 0;
+ lines.push(`anchor 기반 → 기존 dx:${oldDx}, dy:${oldDy}`);
+ lines.push(`수정 후: dx:${(oldDx + dx).toFixed(1)}, dy:${(oldDy + dy).toFixed(1)}`);
+ } else {
+ lines.push(`절대 좌표 → 새 위치: x=${newAnchorX.toFixed(0)}, y=${newAnchorY.toFixed(0)}`);
+ }
+
+ const msg = lines.join('\n');
+ console.log('%c[DRAG]\n' + msg, 'color:#5af9ff;font-weight:700;');
+ appendToPanel(msg, '#5af9ff');
+ }
+
+ function init() {
+ svg = document.getElementById('scene');
+ if (!svg) {
+ setTimeout(init, 100);
+ return;
+ }
+ window.addEventListener('mousedown', onMouseDown, true);
+ window.addEventListener('mousemove', onMouseMove, true);
+ window.addEventListener('mouseup', onMouseUp, true);
+ console.log('%c[DEV] Drag mode 사용 가능: Shift+D 로 토글', 'color:#5af9ff;font-weight:700;');
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/frontend/public/scada-demo/js/topology.js b/frontend/public/scada-demo/js/topology.js
index 59e26096..077127d6 100644
--- a/frontend/public/scada-demo/js/topology.js
+++ b/frontend/public/scada-demo/js/topology.js
@@ -5,14 +5,14 @@
(function (global) {
// ===== TANKS =====
const TANKS = [
- { id: 'sand', x: 340, y: 120, w: 96, h: 150, type: 'ac', label: 'SAND TANK', capacity: 5000, level: 62.0, color: '#5af9ff', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
- { id: 'ac', x: 500, y: 120, w: 96, h: 150, type: 'ac', label: 'AC TANK', capacity: 5000, level: 58.4, color: '#5af9ff', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
- { id: 'raw', x: 660, y: 90, w: 120, h: 230, type: 'tank', label: 'RAW WATER TANK', capacity: 30000, level: 75.0, color: '#3aa3ff', alarms: { hh: 95, hl: 85, ll: 20, lh: 10 } },
- { id: 'chem', x: 895, y: 110, w: 50, h: 70, type: 'dosing', label: 'Chem', capacity: 500, level: 71.0, color: '#7cff3a', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
- { id: 'water-filter', x: 1600, y: 90, w: 120, h: 230, type: 'tank', label: 'WATER FILTER TANK', capacity: 25000, level: 48.0, color: '#3aa3ff', alarms: { hh: 95, hl: 85, ll: 20, lh: 10 }, nozzles: [{ side: 'left', pos: 0.30 }, { side: 'bot', pos: 0.5 }] },
- { id: 'chem-cep-1', x: 80, y: 545, w: 50, h: 70, type: 'dosing', label: 'CEP-1', capacity: 500, level: 64.2, color: '#ff8a3a', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
- { id: 'chem-cep-2', x: 150, y: 545, w: 50, h: 70, type: 'dosing', label: 'CEP-2', capacity: 500, level: 55.8, color: '#ff4f9a', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
- { id: 'cip-tank', x: 80, y: 765, w: 60, h: 80, type: 'dosing', label: 'CIP TANK', capacity: 1500, level: 80.5, color: '#5af9ff', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
+ { id: 'sand', x: 340, y: 90, w: 96, h: 150, type: 'ac', label: 'SAND TANK', capacity: 5000, level: 62.0, color: '#5af9ff', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
+ { id: 'ac', x: 500, y: 90, w: 96, h: 150, type: 'ac', label: 'AC TANK', capacity: 5000, level: 58.4, color: '#5af9ff', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
+ { id: 'raw', x: 661, y: 51, w: 120, h: 230, type: 'tank', label: 'RAW WATER TANK', capacity: 30000, level: 75.0, color: '#3aa3ff', alarms: { hh: 95, hl: 85, ll: 20, lh: 10 } },
+ { id: 'chem', x: 895, y: 80, w: 50, h: 70, type: 'dosing', label: 'Chem', capacity: 500, level: 71.0, color: '#7cff3a', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
+ { id: 'water-filter', x: 1600, y: 60, w: 120, h: 230, type: 'tank', label: 'WATER FILTER TANK', capacity: 25000, level: 48.0, color: '#3aa3ff', alarms: { hh: 95, hl: 85, ll: 20, lh: 10 }, nozzles: [{ side: 'left', pos: 0.30 }, { side: 'bot', pos: 0.5 }] },
+ { id: 'chem-cep-1', x: 60, y: 545, w: 50, h: 70, type: 'dosing', label: 'CEP-1', capacity: 500, level: 64.2, color: '#ff8a3a', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
+ { id: 'chem-cep-2', x: 130, y: 545, w: 50, h: 70, type: 'dosing', label: 'CEP-2', capacity: 500, level: 55.8, color: '#ff4f9a', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
+ { id: 'cip-tank', x: 80, y: 845, w: 60, h: 80, type: 'dosing', label: 'CIP TANK', capacity: 1500, level: 80.5, color: '#5af9ff', alarms: { hh: 95, hl: 85, ll: 15, lh: 5 } },
{ id: 'mbr', x: 760, y: 600, w: 500, h: 200, type: 'mbr', label: 'MBR TANK', capacity: 40000, level: 52.0, color: '#ffd682', alarms: { hh: 92, hl: 80, ll: 20, lh: 10 } },
{ id: 'dp', x: 1320, y: 545, w: 90, h: 180, type: 'tank', label: 'DP TANK', capacity: 8000, level: 87.0, color: '#3aa3ff', alarms: { hh: 95, hl: 85, ll: 20, lh: 10 } },
{ id: 'regulating', x: 1580, y: 640, w: 100, h: 180, type: 'tank', label: 'REGULATING TANK', capacity: 12000, level: 54.06, color: '#ff8a3a', alarms: { hh: 95, hl: 85, ll: 20, lh: 10 } },
@@ -26,7 +26,7 @@
];
const MODULES = [
- { id: 'uf-system', anchor: { to: 'uf-filter.right', myPort: 'topLeft', dx: 62, dy: -122 }, type: 'membrane', count: 5, label: 'UF SYSTEM', accent: '#5af9ff', orientation: 'vertical' },
+ { id: 'uf-system', anchor: { to: 'uf-filter.right', myPort: 'topLeft', dx: 82, dy: -87 }, type: 'membrane', count: 5, label: 'UF SYSTEM', accent: '#5af9ff', orientation: 'vertical' },
{ id: 'ro-system', anchor: { to: 'ro-press.out', myPort: 'in', dx: 55, dy: -47 }, type: 'membrane', count: 5, label: 'RO SYSTEM', accent: '#5af9ff', orientation: 'horizontal' },
{ id: 'mbr-carb-l', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: -183, dy: 106 }, type: 'carbon', count: 5, label: 'CARBON L', accent: '#5af9ff' },
{ id: 'mbr-carb-r', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: 77, dy: 106 }, type: 'carbon', count: 5, label: 'CARBON R', accent: '#5af9ff' },
@@ -34,8 +34,8 @@
const PUMPS = [
// Root pumps (no natural upstream tank — keep absolute)
- { id: 'bw-a1', x: 78, y: 220, label: 'FEED-A', source: null, dest: 'sand', lpm: 80, valves: ['v-input'], pipes: ['p-input-bwa1', 'p-bwa1-pf', 'p-pf-sand'] },
- { id: 'air-blower', x: 380, y: 785, label: 'AIR BLOWER', source: null, dest: 'mbr', lpm: 0, valves: [], pipes: ['p-blower-mbr'] },
+ { id: 'bw-a1', x: 78, y: 190, label: 'FEED-A', source: null, dest: 'sand', lpm: 80, valves: ['v-input'], pipes: ['p-input-bwa1', 'p-bwa1-pf', 'p-pf-sand'] },
+ { id: 'air-blower', x: 380, y: 865, label: 'AIR BLOWER', source: null, dest: 'mbr', lpm: 0, valves: [], pipes: ['p-blower-mbr'] },
// Tank-foot pumps (anchor to tank bottom, my top inlet aligns)
{ id: 'sand-out', anchor: { to: 'sand.bot', myPort: 'in', dy: 14 }, label: 'SAND PUMP', source: 'sand', dest: 'ac', lpm: 80, valves: ['v-sand-out'], pipes: ['p-sand-down', 'p-sand-ac'], orient: 'v' },
@@ -61,11 +61,11 @@
// All valves placed on their gating pipe — onPipe + at fraction
{ id: 'v-input', anchor: { onPipe: 'p-bwa1-pf', at: 0.30 }, label: 'V-IN', open: false },
{ id: 'v-sand-out', anchor: { onPipe: 'p-sand-ac', at: 0.55 }, label: '', open: false },
- { id: 'v-ac-out', anchor: { onPipe: 'p-ac-raw', at: 0.78 }, label: '', open: false },
+ { id: 'v-ac-out', anchor: { onPipe: 'p-ac-raw', at: 0.78, dx: -46, dy: -1 }, label: '', open: false },
{ id: 'v-raw-out', anchor: { onPipe: 'p-raw-bw2', at: 0.78 }, label: '', open: false },
{ id: 'v-chem-uf', anchor: { onPipe: 'p-chem-uf', at: 0.5 }, label: '', open: false },
{ id: 'v-uf-in', anchor: { onPipe: 'p-bw2-uf', at: 0.75 }, label: 'V-UF-IN', open: false },
- { id: 'v-uf-out', anchor: { onPipe: 'p-uf-wf', at: 0.20 }, label: 'V-UF-OUT', open: false },
+ { id: 'v-uf-out', anchor: { onPipe: 'p-uf-wf', at: 0.20, dx: -24, dy: 0 }, label: 'V-UF-OUT', open: false },
{ id: 'v-ro-in', anchor: { onPipe: 'p-rofeed-filter', at: 0.5 }, label: '', open: false },
{ id: 'v-ro-press-out', anchor: { onPipe: 'p-ropress-ro', at: 0.4 }, label: '', open: false },
{ id: 'v-cip-out', anchor: { onPipe: 'p-cip-ro', at: 0.4 }, label: '', open: false },
@@ -433,18 +433,18 @@
{ id: 'p-bwa1-pf', from: 'bw-a1.out', to: 'pump-filter.left' },
{ id: 'p-pf-sand', from: 'pump-filter.right', to: 'sand.left', via: [{ y: 200 }] },
{ id: 'p-sand-down', from: 'sand.bot', to: 'sand-out.top' },
- { id: 'p-sand-ac', from: 'sand-out.out', to: 'ac-out.in', via: [{ y: 290 }] },
+ { id: 'p-sand-ac', from: 'sand-out.out', to: 'ac-out.in', via: [{ y: 279 }] },
{ id: 'p-ac-down', from: 'ac.bot', to: 'ac-out.top' },
{ id: 'p-ac-raw', from: 'ac-out.out', to: 'raw.top', via: [{ x: 605 }, { y: 90 }] },
- { id: 'p-raw-bw2', from: 'raw.right', to: 'bw-a2.in', via: [{ y: 250 }] },
+ { id: 'p-raw-bw2', from: 'raw.right', to: 'bw-a2.in', via: [{ y: 211 }] },
{ id: 'p-bw2-uf', from: 'bw-a2.out', to: 'uf-pump-a.in' },
{ id: 'p-chem-uf', from: 'chem.bot', to: 'chem-uf.in', hint: 'v-first' },
- { id: 'p-chemuf-out', from: 'chem-uf.out', to: { x: 970, y: 250 } },
- { id: 'p-raw-uf', from: 'raw.rightLow', to: 'uf-pump-b.in', via: [{ x: 990 }] },
+ { id: 'p-chemuf-out', from: 'chem-uf.out', to: { x: 980, y: 209 } },
+ { id: 'p-raw-uf', from: 'raw.rightLow', to: 'uf-pump-b.in', via: [{ x: 999 }] },
{ id: 'p-uf-filter', from: 'uf-pump-a.out', to: 'uf-filter.left' },
{ id: 'p-ufpb-filter',from: 'uf-pump-b.out', to: 'uf-filter.left' },
{ id: 'p-filter-uf', from: 'uf-filter.right', to: 'uf-system.topLeft' },
- { id: 'p-uf-wf', from: 'uf-system.botRight', to: 'water-filter.leftHigh', via: [{ x: 1490 }, { y: 159 }] },
+ { id: 'p-uf-wf', from: 'uf-system.botRight', to: 'water-filter.leftHigh', via: [{ x: 1490 }, { y: 130 }] },
// Bottom row — RO / MBR / DP / REG
{ id: 'p-wf-ro', from: 'water-filter.bot', to: 'ro-feed.in', via: [{ y: 420 }, { x: 200 }, { y: 660 }] },
@@ -452,13 +452,13 @@
{ id: 'p-filter-ropress',from: 'ro-filter.right', to: 'ro-press.in' },
{ id: 'p-ropress-ro', from: 'ro-press.out', to: 'ro-system.in' },
{ id: 'p-ro-mbr', from: 'ro-system.out', to: 'mbr.leftHigh' },
- { id: 'p-cip-ro', from: 'cip-pump.out', to: 'ro-system.bot', via: [{ y: 730 }] },
- { id: 'p-ciptank-pump', from: 'cip-tank.bot', to: 'cip-pump.in', via: [{ y: 820 }] },
+ { id: 'p-cip-ro', from: 'cip-pump.out', to: 'ro-system.bot', via: [{ y: 834 }] },
+ { id: 'p-ciptank-pump', from: 'cip-tank.bot', to: 'cip-pump.in', via: [{ y: 900 }] },
{ id: 'p-blower-mbr', from: 'air-blower.out', to: 'mbr.bot', via: [{ x: 700 }] },
// MBR → AERATION DRAIN → DP TANK
{ id: 'p-mbr-aer', from: 'mbr.botRight', to: 'aer-drain.in' },
- { id: 'p-aer-dp', from: 'aer-drain.out', to: 'dp.bot', via: [{ x: 1290 }, { y: 760 }, { x: 1365 }] },
+ { id: 'p-aer-dp', from: 'aer-drain.out', to: 'dp.bot', via: [{ x: 1289 }, { y: 760 }, { x: 1365 }] },
// DP TANK → DP PUMP → REGULATING
{ id: 'p-dp-down', from: 'dp.bot', to: 'dp-out.in' },
@@ -468,23 +468,23 @@
const GAUGES = [
{ id: 'p-in-1', anchor: { to: 'bw-a1.top', myPort: 'center', dx: -23, dy: -110 }, label: 'P-IN', max: 8, unit: 'bar', source: 'bw-a1' },
{ id: 'p-in-2', anchor: { to: 'bw-a1.top', myPort: 'center', dx: 87, dy: -110 }, label: 'P-PF', max: 8, unit: 'bar', source: 'bw-a1' },
- { id: 'p-raw', anchor: { to: 'raw.bot', myPort: 'center', dx: -35, dy: 60 }, label: 'P-RAW', max: 8, unit: 'bar', source: 'bw-a2' },
- { id: 'p-uf-1', anchor: { to: 'uf-pump-a.bot', myPort: 'center', dx: 210, dy: 75 }, label: 'P-UF-IN', max: 10, unit: 'bar', source: 'uf-pump-a' },
- { id: 'p-uf-2', anchor: { to: 'uf-pump-a.bot', myPort: 'center', dx: 515, dy: 75 }, label: 'P-UF-OUT', max: 10, unit: 'bar', source: 'uf-pump-a' },
+ { id: 'p-raw', anchor: { to: 'raw.bot', myPort: 'center', dx: -31, dy: 64 }, label: 'P-RAW', max: 8, unit: 'bar', source: 'bw-a2' },
+ { id: 'p-uf-1', anchor: { to: 'uf-pump-a.bot', myPort: 'center', dx: 218, dy: 100 }, label: 'P-UF-IN', max: 10, unit: 'bar', source: 'uf-pump-a' },
+ { id: 'p-uf-2', anchor: { to: 'uf-pump-a.bot', myPort: 'center', dx: 497, dy: 100 }, label: 'P-UF-OUT', max: 10, unit: 'bar', source: 'uf-pump-a' },
{ id: 'p-wf', anchor: { to: 'water-filter.right', myPort: 'center', dx: 90, dy: -45 }, label: 'P-WF', max: 8, unit: 'bar', source: 'bw-a2' },
- { id: 'p-cep-1', anchor: { to: 'chem-cep-1.bot', myPort: 'center', dx: -15, dy: 65 }, label: 'P1', max: 8, unit: 'bar', source: 'ro-feed' },
- { id: 'p-cep-2', anchor: { to: 'chem-cep-2.bot', myPort: 'center', dx: -5, dy: 65 }, label: 'P2', max: 8, unit: 'bar', source: 'ro-feed' },
- { id: 'p-ro-in', anchor: { to: 'ro-feed.top', myPort: 'center', dx: 20, dy: -45 }, label: 'P-RO-IN', max: 16, unit: 'bar', source: 'ro-feed' },
- { id: 'p-ro-out', anchor: { to: 'ro-press.top', myPort: 'center', dx: 315, dy: -45 }, label: 'P-RO-OUT', max: 16, unit: 'bar', source: 'ro-press' },
- { id: 'p-dp-1', anchor: { to: 'dp.top', myPort: 'center', dx: 0, dy: -50 }, label: 'P-DP', max: 8, unit: 'bar', source: 'aer-drain' },
+ { id: 'p-cep-1', anchor: { to: 'chem-cep-1.bot', myPort: 'center', dx: -15, dy: 95 }, label: 'P1', max: 8, unit: 'bar', source: 'ro-feed' },
+ { id: 'p-cep-2', anchor: { to: 'chem-cep-2.bot', myPort: 'center', dx: -5, dy: 95 }, label: 'P2', max: 8, unit: 'bar', source: 'ro-feed' },
+ { id: 'p-ro-in', anchor: { to: 'ro-feed.top', myPort: 'center', dx: 20, dy: -57 }, label: 'P-RO-IN', max: 16, unit: 'bar', source: 'ro-feed' },
+ { id: 'p-ro-out', anchor: { to: 'ro-press.top', myPort: 'center', dx: 315, dy: -130 }, label: 'P-RO-OUT', max: 16, unit: 'bar', source: 'ro-press' },
+ { id: 'p-dp-1', anchor: { to: 'dp.top', myPort: 'center', dx: 95, dy: -50 }, label: 'P-DP', max: 8, unit: 'bar', source: 'aer-drain' },
];
const SENSORS = [
- { id: 'do', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: -250, dy: -80 }, label: 'DO', value: 8.5, unit: 'ppm', min: 4, max: 12, alarms: { lo: 5, hi: 11 } },
- { id: 'temp', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: -130, dy: -80 }, label: 'Temp', value: 25.0, unit: 'C', min: 0, max: 50, alarms: { lo: 10, hi: 35 } },
- { id: 'tss', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: -10, dy: -80 }, label: 'TSS', value: 0.28, unit: 'mg/L', min: 0, max: 5, alarms: { lo: 0, hi: 2 } },
- { id: 'ph', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: 110, dy: -80 }, label: 'pH', value: 8.5, unit: 'pH', min: 0, max: 14, alarms: { lo: 6.5, hi: 8.5 } },
- { id: 'tss-raw', anchor: { to: 'bw-a1.bot', myPort: 'topLeft', dx: -68, dy: 75 }, label: 'TSS-RAW', value: 1.42, unit: 'mg/L', min: 0, max: 5, alarms: { lo: 0, hi: 3 } },
+ { id: 'do', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: -250, dy: -90 }, label: 'DO', value: 8.5, unit: 'ppm', min: 4, max: 12, alarms: { lo: 5, hi: 11 } },
+ { id: 'temp', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: -110, dy: -90 }, label: 'Temp', value: 25.0, unit: 'C', min: 0, max: 50, alarms: { lo: 10, hi: 35 } },
+ { id: 'tss', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: 30, dy: -90 }, label: 'TSS', value: 0.28, unit: 'mg/L', min: 0, max: 5, alarms: { lo: 0, hi: 2 } },
+ { id: 'ph', anchor: { to: 'mbr.top', myPort: 'topLeft', dx: 170, dy: -90 }, label: 'pH', value: 8.5, unit: 'pH', min: 0, max: 14, alarms: { lo: 6.5, hi: 8.5 } },
+ { id: 'tss-raw', anchor: { to: 'bw-a1.bot', myPort: 'topLeft', dx: -68, dy: 105 }, label: 'TSS-RAW', value: 1.42, unit: 'mg/L', min: 0, max: 5, alarms: { lo: 0, hi: 3 } },
];
const LEDS = [
@@ -493,15 +493,15 @@
];
const TILES = [
- { id: 'flow-in', x: 1830, y: 520, label: 'FLOW IN', value: '0.0', unit: 'L/min', w: 130, h: 50 },
- { id: 'flow-out', x: 1830, y: 580, label: 'FLOW OUT', value: '0.0', unit: 'L/min', w: 130, h: 50 },
- { id: 'recovery', x: 1830, y: 640, label: 'RECOVERY', value: '0.0', unit: '%', w: 130, h: 50 },
- { id: 'alarms', x: 1830, y: 700, label: 'ACTIVE ALARMS', value: '0', unit: '', w: 130, h: 50 },
+ { id: 'flow-in', x: 1830, y: 520, label: 'FLOW IN', value: '0.0', unit: 'L/min', w: 150, h: 64 },
+ { id: 'flow-out', x: 1830, y: 594, label: 'FLOW OUT', value: '0.0', unit: 'L/min', w: 150, h: 64 },
+ { id: 'recovery', x: 1830, y: 668, label: 'RECOVERY', value: '0.0', unit: '%', w: 150, h: 64 },
+ { id: 'alarms', x: 1830, y: 742, label: 'ACTIVE ALARMS', value: '0', unit: '', w: 150, h: 64 },
];
const PANELS = [
- { id: 'cep', x: 40, y: 510, w: 410, h: 210, label: 'CEP SYSTEM' },
- { id: 'cip', x: 40, y: 740, w: 470, h: 125, label: 'CIP SYSTEM' },
+ { id: 'cep', x: 40, y: 510, w: 410, h: 300, label: 'CEP SYSTEM' },
+ { id: 'cip', x: 40, y: 820, w: 470, h: 125, label: 'CIP SYSTEM' },
{ id: 'mbr-frame', x: 760, y: 600, w: 500, h: 200, label: '' },
];
@@ -569,7 +569,7 @@
const bubbles = bubbleSpots.map((bx, i) => `
`).join('\n ');
out.push(`
- ${t.label}
+ ${t.label}
diff --git a/package-lock.json b/package-lock.json
index e6ada6f1..8ec08b36 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,10 +1,11 @@
{
- "name": "ERP-node",
+ "name": "invyone",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
+ "@anthropic-ai/claude-code": "^2.1.132",
"@prisma/client": "^6.16.2",
"@react-three/drei": "^10.7.6",
"@react-three/fiber": "^9.4.0",
@@ -21,6 +22,133 @@
"playwright": "^1.58.2"
}
},
+ "node_modules/@anthropic-ai/claude-code": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.132.tgz",
+ "integrity": "sha512-y0Exs+/jST92n0Hr+GLVCxQ54TYoLnphnwnXztgGAgkrOVMiGJ1uzvmqOds69eO7SQwKwBc7RiLzMZKQjvoiQA==",
+ "hasInstallScript": true,
+ "license": "SEE LICENSE IN README.md",
+ "bin": {
+ "claude": "bin/claude.exe"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "@anthropic-ai/claude-code-darwin-arm64": "2.1.132",
+ "@anthropic-ai/claude-code-darwin-x64": "2.1.132",
+ "@anthropic-ai/claude-code-linux-arm64": "2.1.132",
+ "@anthropic-ai/claude-code-linux-arm64-musl": "2.1.132",
+ "@anthropic-ai/claude-code-linux-x64": "2.1.132",
+ "@anthropic-ai/claude-code-linux-x64-musl": "2.1.132",
+ "@anthropic-ai/claude-code-win32-arm64": "2.1.132",
+ "@anthropic-ai/claude-code-win32-x64": "2.1.132"
+ }
+ },
+ "node_modules/@anthropic-ai/claude-code-darwin-arm64": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-arm64/-/claude-code-darwin-arm64-2.1.132.tgz",
+ "integrity": "sha512-imBlTl4dJ+IqGPMLTbLjSefBSd7c3iUjHGkz7/Q3RVDQJvcb2G83LFsaQGR3JwZ+fEwC7T6E6B8Mprwe9En08g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@anthropic-ai/claude-code-darwin-x64": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-x64/-/claude-code-darwin-x64-2.1.132.tgz",
+ "integrity": "sha512-CnsFT78zXkcR2wcEVivP6aqOMcIKSnegPZZierlSQ/zaXWCVNoW+3OHloMnDm+7BX6BT41K2bz7GckCr0pHiyg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@anthropic-ai/claude-code-linux-arm64": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64/-/claude-code-linux-arm64-2.1.132.tgz",
+ "integrity": "sha512-VJgVycbS6u/lD05vGKmd+mMFypYt098jYt6yYGibqIvFPQcNkmMZJQfPIJBuVB/+XeySNlI4Pplo6jhwRM0HUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@anthropic-ai/claude-code-linux-arm64-musl": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64-musl/-/claude-code-linux-arm64-musl-2.1.132.tgz",
+ "integrity": "sha512-RhGYFnGf6IE3PpOG8IWkrYoW1uS1i23cqvGKBHxlLhWsbvxxWUSOHd5IlzCS7udG7L4/EQU2UEAcdLaFEF8ZdQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@anthropic-ai/claude-code-linux-x64": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64/-/claude-code-linux-x64-2.1.132.tgz",
+ "integrity": "sha512-ElubH7haoKIXy9SNn8dTGMym0n+zy6A70wWXhhcdAFo7P/M8bdLT6zRyp5TttrkhAZcA8xPVxEB/zNU3sPCMLg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@anthropic-ai/claude-code-linux-x64-musl": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64-musl/-/claude-code-linux-x64-musl-2.1.132.tgz",
+ "integrity": "sha512-Xu6kajnzX5igVtjuqTdohGoat8bXMccs9TsS3n14Ith+3mgB0IX+zTxiR/0V9TCv7nkYe1tRrO2DHEOl9GzR4A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@anthropic-ai/claude-code-win32-arm64": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-arm64/-/claude-code-win32-arm64-2.1.132.tgz",
+ "integrity": "sha512-FuNMUO6tERKJZrayqDf1ScAcFhzhs2joQ9PE8POc2Y7Bv2E+aq/bmaj1Nxff+RKgpYSnoGfru9Sk3lSLtIUzGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@anthropic-ai/claude-code-win32-x64": {
+ "version": "2.1.132",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-x64/-/claude-code-win32-x64-2.1.132.tgz",
+ "integrity": "sha512-prA22ML+CCHsMI0jj9BD5EVjzhZ38vPc+Udz/GvPs6h+HPH+EeNSjGDNzn9YvkiYpjUun2HYF24ztEXMJigGVw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "SEE LICENSE IN LICENSE.md",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@azure-rest/core-client": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.5.1.tgz",
@@ -471,7 +599,6 @@
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.4.0.tgz",
"integrity": "sha512-k4iu1R6e5D54918V4sqmISUkI5OgTw3v7/sDRKEC632Wd5g2WBtUS5gyG63X0GJO/HZUj1tsjSXfyzwrUHZl1g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.17.8",
"@types/react-reconciler": "^0.32.0",
@@ -712,7 +839,6 @@
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.180.0.tgz",
"integrity": "sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@dimforge/rapier3d-compat": "~0.12.0",
"@tweenjs/tween.js": "~23.1.3",
@@ -1079,7 +1205,8 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/d3-color": {
"version": "3.1.0",
@@ -1138,7 +1265,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -2166,7 +2292,6 @@
"integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==",
"hasInstallScript": true,
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@prisma/config": "6.16.2",
"@prisma/engines": "6.16.2"
@@ -2382,7 +2507,8 @@
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/semver": {
"version": "7.7.2",
@@ -2501,8 +2627,7 @@
"version": "0.180.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz",
"integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/three-mesh-bvh": {
"version": "0.8.3",
@@ -2598,7 +2723,6 @@
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
- "peer": true,
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
diff --git a/package.json b/package.json
index 16b3b1e8..785c4aec 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,6 @@
{
"dependencies": {
+ "@anthropic-ai/claude-code": "^2.1.132",
"@prisma/client": "^6.16.2",
"@react-three/drei": "^10.7.6",
"@react-three/fiber": "^9.4.0",