#!/usr/bin/env python3 import json with open('/home/mdares/.node-red/flows.json', 'r') as f: flows = json.load(f) # Find Work Order buttons node work_order_buttons_node = None for node in flows: if node.get('id') == '9bbd4fade968036d': work_order_buttons_node = node break if not work_order_buttons_node: print("✗ Could not find Work Order buttons node") exit(1) # Get the current function code func_code = work_order_buttons_node.get('func', '') # Find the complete-work-order case and add high scrap detection # Insert the code BEFORE "node.warn('[COMPLETE] Cleared all state flags');" high_scrap_code = ''' // ============================================================ // HIGH SCRAP DETECTION // ============================================================ const targetQty = Number(activeOrder.target) || 0; const scrapCount = finalScrapParts; const scrapPercent = targetQty > 0 ? (scrapCount / targetQty) * 100 : 0; // Trigger: Scrap > 10% of target quantity let anomalyMsg = null; if (scrapPercent > 10 && targetQty > 0) { const severity = scrapPercent > 25 ? 'critical' : 'warning'; const highScrapAnomaly = { anomaly_type: 'high-scrap', severity: severity, title: `High Waste Detected`, description: `Work order completed with ${scrapCount} scrap parts (${scrapPercent.toFixed(1)}% of target ${targetQty}). Why is there so much waste?`, data: { scrap_count: scrapCount, target_quantity: targetQty, scrap_percent: Math.round(scrapPercent * 10) / 10, good_parts: finalGoodParts, total_cycles: finalCycleCount }, kpi_snapshot: { oee: (msg.kpis && msg.kpis.oee) || global.get("currentKPIs")?.oee || 0, availability: (msg.kpis && msg.kpis.availability) || global.get("currentKPIs")?.availability || 0, performance: (msg.kpis && msg.kpis.performance) || global.get("currentKPIs")?.performance || 0, quality: (msg.kpis && msg.kpis.quality) || global.get("currentKPIs")?.quality || 0 }, work_order_id: order.id, cycle_count: finalCycleCount, timestamp: Date.now(), status: 'active' }; node.warn(`[HIGH SCRAP] Detected ${scrapPercent.toFixed(1)}% scrap on work order ${order.id}`); // Send to Event Logger (output 5) anomalyMsg = { topic: "anomaly-detected", payload: [highScrapAnomaly] }; } ''' # Find the marker to insert before marker = "node.warn('[COMPLETE] Cleared all state flags');" if marker in func_code: # Insert the high scrap code before this marker func_code = func_code.replace(marker, high_scrap_code + '\\n ' + marker) print("✓ Injected high scrap detection code") else: print("✗ Could not find marker to inject code") exit(1) # Also need to change the return statement to include the anomaly message old_return = 'return [null, null, null, msg];' new_return = 'return [null, null, null, msg, anomalyMsg];' if old_return in func_code: # Only replace the one in complete-work-order case # Split by case first parts = func_code.split('case "complete-work-order":') before = parts[0] after_case = parts[1] # Split this case by the next case case_parts = after_case.split('case "get-current-state":', 1) this_case = case_parts[0] rest = case_parts[1] if len(case_parts) > 1 else '' # Replace return in this case only this_case = this_case.replace(old_return, new_return) # Reconstruct func_code = before + 'case "complete-work-order":' + this_case if rest: func_code += 'case "get-current-state":' + rest print("✓ Updated return statement to include anomaly message") else: print("⚠ Could not find return statement to update") work_order_buttons_node['func'] = func_code # Save flows.json with open('/home/mdares/.node-red/flows.json', 'w') as f: json.dump(flows, f, indent=4) print("✓ flows.json updated with high scrap detection")