import { useEffect, useCallback, useRef } from "react"; import { useSceneContext } from "../../scene/sceneContext"; import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; function Analyzer() { const { isPlaying } = usePlayButtonStore(); const { conveyorStore, machineStore, armBotStore, humanStore, vehicleStore, craneStore, storageUnitStore, materialStore, analysisStore } = useSceneContext(); const { conveyors } = conveyorStore(); const { machines } = machineStore(); const { armBots } = armBotStore(); const { humans } = humanStore(); const { vehicles } = vehicleStore(); const { cranes } = craneStore(); const { storageUnits } = storageUnitStore(); const { materials } = materialStore(); const { setAnalysis, setAnalyzing, analysis } = analysisStore(); const historicalDataRef = useRef>({}); const materialHistoryRef = useRef([]); const eventTimestampsRef = useRef>({}); const queueLengthsRef = useRef>({}); const performanceHistoryRef = useRef>({}); const analysisIntervalRef = useRef(null); const startTimeRef = useRef(new Date().toISOString()); const errorCountsRef = useRef>({}); const completedActionsRef = useRef>({}); const stateTransitionsRef = useRef>({}); // ============================================================================ // CALCULATION UTILITIES // ============================================================================ const calculateTimeMetrics = (idleTime: number, activeTime: number, assetId: string) => { const totalTime = idleTime + activeTime; const uptime = totalTime > 0 ? (activeTime / totalTime) * 100 : 0; const downtime = idleTime; const utilizationRate = uptime / 100; const errorCount = errorCountsRef.current[assetId] || 0; return { uptime, downtime, utilizationRate, mtbf: errorCount > 0 ? totalTime / errorCount : totalTime || 0, mttr: errorCount > 0 ? downtime / errorCount : 0, }; }; const calculateOEE = (availability: number, performance: number, quality: number) => { return (availability * performance * quality) / 10000; }; const trackStateTransition = (assetId: string, fromState: string, toState: string, time: number) => { if (!stateTransitionsRef.current[assetId]) { stateTransitionsRef.current[assetId] = []; } stateTransitionsRef.current[assetId].push({ fromState, toState, timestamp: Date.now(), duration: time, }); }; const getStateTransitions = (assetId: string) => { const transitions = stateTransitionsRef.current[assetId] || []; const grouped = transitions.reduce((acc, t) => { const key = `${t.fromState}-${t.toState}`; if (!acc[key]) { acc[key] = { fromState: t.fromState, toState: t.toState, count: 0, totalTime: 0 }; } acc[key].count++; acc[key].totalTime += t.duration; return acc; }, {} as Record); return Object.values(grouped).map((g: any) => ({ fromState: g.fromState, toState: g.toState, count: g.count, averageTime: g.totalTime / g.count, })); }; // Count materials currently on an asset const getMaterialsOnAsset = (assetId: string) => { return materials.filter((m) => m.current.modelUuid === assetId && m.isActive); }; // Calculate actual performance vs ideal const calculatePerformanceRate = (actualTime: number, idealTime: number) => { if (idealTime === 0 || actualTime === 0) return 100; return Math.min((idealTime / actualTime) * 100, 100); }; // ============================================================================ // ENHANCED UTILITY FUNCTIONS // ============================================================================ const calculateAdvancedTimeMetrics = (idleTime: number, activeTime: number, assetId: string, errorCount: number = 0) => { const totalTime = idleTime + activeTime; const uptime = totalTime > 0 ? (activeTime / totalTime) * 100 : 0; const downtime = idleTime; const utilizationRate = uptime / 100; // Enhanced reliability calculations const mtbf = errorCount > 0 ? totalTime / errorCount : totalTime; const mttr = errorCount > 0 ? downtime / errorCount : 0; const reliability = Math.exp(-errorCount / Math.max(1, totalTime / 3600)) * 100; // Reliability per hour // Calculate schedule adherence if scheduled data exists const scheduledTime = totalTime; // Could be enhanced with actual schedule data const scheduleAdherence = scheduledTime > 0 ? (activeTime / scheduledTime) * 100 : 100; return { uptime, downtime, utilizationRate, mtbf, mttr, reliability, totalTime, scheduleAdherence, }; }; const calculateQualityMetrics = (assetId: string, totalOperations: number, defects: number, historicalData: any[] = []) => { const errorCount = errorCountsRef.current[assetId] || 0; const firstPassYield = totalOperations > 0 ? ((totalOperations - defects) / totalOperations) * 100 : 100; const defectRate = totalOperations > 0 ? (defects / totalOperations) * 100 : 0; const scrapRate = defects > 0 ? defects * 0.1 : 0; // Assuming 10% scrap rate const reworkRate = defects > 0 ? defects * 0.9 : 0; // Assuming 90% rework // Calculate defect trends let defectTrend = "stable"; if (historicalData.length > 5) { const recentDefects = historicalData.slice(-5).filter((d) => d.defects > 0).length; const olderDefects = historicalData.slice(-10, -5).filter((d) => d.defects > 0).length; if (recentDefects > olderDefects * 1.5) defectTrend = "increasing"; else if (recentDefects < olderDefects * 0.5) defectTrend = "decreasing"; } return { errorRate: totalOperations > 0 ? (errorCount / totalOperations) * 100 : 0, errorFrequency: (errorCount / Math.max(1, totalOperations)) * 1000, // Errors per 1000 operations successRate: firstPassYield, firstPassYield, defectRate, scrapRate, reworkRate, defectTrend, defectsPerMillion: totalOperations > 0 ? (defects / totalOperations) * 1000000 : 0, stateTransitions: getStateTransitions(assetId), }; }; const calculateMaterialFlowMetricsForAsset = (assetId: string) => { const materialsOnAsset = getMaterialsOnAsset(assetId); const materialHistory = materialHistoryRef.current.filter((m) => m.material.current?.modelUuid === assetId || m.material.previous?.modelUuid === assetId); // Calculate flow metrics const wip = materialsOnAsset.length; const throughput = (materialHistory.length / (Date.now() - new Date(startTimeRef.current).getTime())) * 1000; // Calculate lead times const leadTimes = materialHistory .map((m) => { if (m.removedAt && m.material.startTime) { return new Date(m.removedAt).getTime() - m.material.startTime; } return 0; }) .filter((t) => t > 0); const avgLeadTime = leadTimes.length > 0 ? leadTimes.reduce((sum, t) => sum + t, 0) / leadTimes.length : 0; // Calculate velocity const velocity = materialHistory.length / Math.max(1, wip); return { wip, throughput, avgLeadTime, totalMaterialsProcessed: materialHistory.length, currentMaterials: materialsOnAsset.length, avgCycleTime: avgLeadTime / 1000, materialVelocity: velocity, inventoryTurns: throughput > 0 ? throughput / Math.max(1, wip) : 0, }; }; const calculatePerformanceMetrics = (actualOutput: number, idealOutput: number, actualTime: number, idealTime: number, assetType: string) => { if (idealTime === 0 || actualTime === 0) return { performanceRate: 100, timeEfficiency: 100, productivity: 0, outputPerHour: 0, }; const performanceRate = idealOutput > 0 ? Math.min((actualOutput / idealOutput) * 100, 100) : 100; const timeEfficiency = idealTime > 0 ? Math.min((idealTime / actualTime) * 100, 100) : 100; const productivity = actualTime > 0 ? actualOutput / actualTime : 0; const outputPerHour = actualTime > 0 ? (actualOutput / actualTime) * 3600 : 0; // Asset type specific adjustments const efficiencyAdjustment = { conveyor: 1.0, vehicle: 0.95, machine: 0.9, roboticArm: 0.92, human: 0.85, crane: 0.88, storage: 0.98, }[assetType] || 1.0; return { performanceRate: performanceRate * efficiencyAdjustment, timeEfficiency: timeEfficiency * efficiencyAdjustment, productivity, outputPerHour, efficiencyAdjustment, }; }; const calculateCostMetrics = (assetId: string, assetType: EventType, activeTime: number, totalOutput: number) => { // Cost parameters (hourly rates in $) const costParams = { conveyor: { hourlyRate: 50, maintenanceCost: 0.5, energyCost: 5 }, vehicle: { hourlyRate: 75, maintenanceCost: 2, energyCost: 10 }, machine: { hourlyRate: 100, maintenanceCost: 5, energyCost: 15 }, roboticArm: { hourlyRate: 120, maintenanceCost: 8, energyCost: 20 }, human: { hourlyRate: 35, maintenanceCost: 0.1, energyCost: 0 }, crane: { hourlyRate: 150, maintenanceCost: 10, energyCost: 25 }, storage: { hourlyRate: 25, maintenanceCost: 1, energyCost: 2 }, }; const params = costParams[assetType] || costParams.conveyor; const hoursOperated = activeTime / 3600; const errorCount = errorCountsRef.current[assetId] || 0; const operatingCost = hoursOperated * params.hourlyRate; const maintenanceCost = errorCount * params.maintenanceCost; const energyCost = hoursOperated * params.energyCost; const totalCost = operatingCost + maintenanceCost + energyCost; const costPerUnit = totalOutput > 0 ? totalCost / totalOutput : 0; const costPerHour = totalCost / Math.max(1, hoursOperated); const roi = totalOutput > 0 ? (totalOutput * 10) / totalCost : 0; // Assuming $10 value per unit return { operatingCost, maintenanceCost, energyCost, totalCost, costPerUnit, costPerHour, roi, valueAdded: totalOutput * 10 - totalCost, }; }; const calculateEnergyMetrics = (assetType: EventType, activeTime: number, distanceTraveled: number = 0) => { // Energy consumption rates in kW const energyRates = { conveyor: 7.5, vehicle: 15 + distanceTraveled * 0.1, // Base + distance-based machine: 20, roboticArm: 10, human: 0, crane: 25, storage: 2, }; const rate = energyRates[assetType] || 5; const hoursActive = activeTime / 3600; const energyConsumed = hoursActive * rate; // kWh const energyEfficiency = 85 - Math.random() * 10; // Simulated efficiency 75-85% const carbonFootprint = energyConsumed * 0.5; // kg CO2 per kWh (0.5 kg/kWh average) const energyCost = energyConsumed * 0.12; // $0.12 per kWh return { energyConsumed, energyEfficiency, carbonFootprint, powerUsage: rate, energyCost, energyPerUnit: 0, // Will be calculated with output }; }; // ============================================================================ // ENHANCED CONVEYOR ANALYSIS // ============================================================================ const analyzeConveyor = useCallback( (conveyor: ConveyorStatus): ConveyorAnalysis => { const errorCount = errorCountsRef.current[conveyor.modelUuid] || 0; const timeMetrics = calculateAdvancedTimeMetrics(conveyor.idleTime || 0, conveyor.activeTime || 0, conveyor.modelUuid, errorCount); const materialFlow = calculateMaterialFlowMetricsForAsset(conveyor.modelUuid); const materialsProcessed = materialFlow.totalMaterialsProcessed; const defects = errorCountsRef.current[`${conveyor.modelUuid}_defects`] || 0; const qualityMetrics = calculateQualityMetrics(conveyor.modelUuid, materialsProcessed, defects); // Performance calculations const conveyorLength = 10; // meters const idealSpeed = conveyor.speed; const actualSpeed = conveyor.isActive ? conveyor.speed : 0; const idealThroughput = idealSpeed > 0 ? (idealSpeed * 3600) / (conveyorLength / 2) : 0; // items per hour const actualThroughput = materialFlow.throughput * 3600; const performance = calculatePerformanceMetrics( materialsProcessed, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, materialsProcessed * 2, // 2 seconds per item ideal "conveyor" ); const costMetrics = calculateCostMetrics(conveyor.modelUuid, "conveyor", conveyor.activeTime || 0, materialsProcessed); const energyMetrics = calculateEnergyMetrics("conveyor", conveyor.activeTime || 0); // Update historical data const timestamp = new Date().toISOString(); if (!historicalDataRef.current[conveyor.modelUuid]) { historicalDataRef.current[conveyor.modelUuid] = []; } historicalDataRef.current[conveyor.modelUuid].push({ timestamp, isActive: conveyor.isActive, speed: conveyor.speed, state: conveyor.state, materialsCount: materialFlow.currentMaterials, throughput: actualThroughput, performance: performance.performanceRate, }); // Calculate queue if applicable const queueLength = materialFlow.wip; if (!queueLengthsRef.current[conveyor.modelUuid]) { queueLengthsRef.current[conveyor.modelUuid] = []; } queueLengthsRef.current[conveyor.modelUuid].push({ timestamp: Date.now(), length: queueLength, }); return { assetId: conveyor.modelUuid, assetName: conveyor.modelName, assetType: "conveyor", currentStatus: { isActive: conveyor.isActive, isPaused: conveyor.isPaused, state: conveyor.state, speed: conveyor.speed, currentProduct: conveyor.productUuid, currentMaterials: materialFlow.currentMaterials, queueLength, }, timeMetrics: { ...timeMetrics, idleTime: conveyor.idleTime || 0, activeTime: conveyor.activeTime || 0, totalTime: timeMetrics.totalTime, availability: timeMetrics.uptime, reliability: timeMetrics.reliability, scheduleAdherence: timeMetrics.scheduleAdherence, }, throughput: { itemsPerHour: actualThroughput, itemsPerDay: actualThroughput * 24, materialFlowRate: conveyor.speed * 60, capacityUtilization: timeMetrics.utilizationRate * 100, materialsProcessed, averageProcessingTime: materialFlow.avgCycleTime, wip: materialFlow.wip, throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, bottleneckIndex: timeMetrics.utilizationRate > 0.9 ? 1 : 0, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, performance.performanceRate, qualityMetrics.firstPassYield), availability: timeMetrics.uptime, performance: performance.performanceRate, quality: qualityMetrics.firstPassYield, timeEfficiency: performance.timeEfficiency, energyEfficiency: energyMetrics.energyEfficiency, costEfficiency: 100 - costMetrics.costPerUnit * 10, // Inverse relationship scheduleEfficiency: timeMetrics.scheduleAdherence, }, quality: qualityMetrics, // Add cost metrics costMetrics: { operatingCost: costMetrics.operatingCost, maintenanceCost: costMetrics.maintenanceCost, energyCost: energyMetrics.energyCost, totalCost: costMetrics.totalCost, costPerUnit: costMetrics.costPerUnit, costPerHour: costMetrics.costPerHour, roi: costMetrics.roi, valueAdded: costMetrics.valueAdded, }, // Add energy metrics energyMetrics: { energyConsumed: energyMetrics.energyConsumed, energyEfficiency: energyMetrics.energyEfficiency, carbonFootprint: energyMetrics.carbonFootprint, powerUsage: energyMetrics.powerUsage, energyCost: energyMetrics.energyCost, energyPerUnit: materialsProcessed > 0 ? energyMetrics.energyConsumed / materialsProcessed : 0, }, // Material flow metrics materialFlow: { wip: materialFlow.wip, throughput: materialFlow.throughput, avgCycleTime: materialFlow.avgCycleTime, materialVelocity: materialFlow.materialVelocity, inventoryTurns: materialFlow.inventoryTurns, leadTimeVariance: 0, // Could be calculated with more data }, historicalData: historicalDataRef.current[conveyor.modelUuid] || [], }; }, [materials, analysis] ); // ============================================================================ // ENHANCED VEHICLE ANALYSIS (apply similar enhancements to other assets) // ============================================================================ const analyzeVehicle = useCallback( (vehicle: VehicleStatus): VehicleAnalysis => { const errorCount = errorCountsRef.current[vehicle.modelUuid] || 0; const timeMetrics = calculateAdvancedTimeMetrics(vehicle.idleTime || 0, vehicle.activeTime || 0, vehicle.modelUuid, errorCount); const materialFlow = calculateMaterialFlowMetricsForAsset(vehicle.modelUuid); const tripsCompleted = completedActionsRef.current[vehicle.modelUuid] || 0; const totalLoadsDelivered = completedActionsRef.current[`${vehicle.modelUuid}_loads`] || 0; const defects = errorCountsRef.current[`${vehicle.modelUuid}_defects`] || 0; const qualityMetrics = calculateQualityMetrics(vehicle.modelUuid, totalLoadsDelivered, defects); // Performance calculations const loadCapacity = vehicle.point?.action?.loadCapacity || 1; const avgLoad = tripsCompleted > 0 ? totalLoadsDelivered / tripsCompleted : 0; const loadUtilization = (avgLoad / loadCapacity) * 100; const idealTripTime = 60; const actualTripTime = tripsCompleted > 0 ? timeMetrics.totalTime / tripsCompleted : 0; const idealThroughput = loadCapacity * (3600 / idealTripTime); const actualThroughput = materialFlow.throughput * 3600; const performance = calculatePerformanceMetrics(totalLoadsDelivered, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, idealTripTime * tripsCompleted, "vehicle"); // Route efficiency const optimalDistance = 100; const actualDistance = vehicle.distanceTraveled || 0; const routeEfficiency = actualDistance > 0 ? Math.min((optimalDistance / actualDistance) * 100, 100) : 100; const costMetrics = calculateCostMetrics(vehicle.modelUuid, "vehicle", vehicle.activeTime || 0, totalLoadsDelivered); const energyMetrics = calculateEnergyMetrics("vehicle", vehicle.activeTime || 0, actualDistance); // Update historical data const timestamp = new Date().toISOString(); if (!historicalDataRef.current[vehicle.modelUuid]) { historicalDataRef.current[vehicle.modelUuid] = []; } historicalDataRef.current[vehicle.modelUuid].push({ timestamp, phase: vehicle.currentPhase, load: vehicle.currentLoad, distanceTraveled: actualDistance, state: vehicle.state, performance: performance.performanceRate, speed: vehicle.speed, tripsCompleted, }); return { assetId: vehicle.modelUuid, assetName: vehicle.modelName, assetType: "vehicle", currentStatus: { isActive: vehicle.isActive, isPicking: vehicle.isPicking, currentPhase: vehicle.currentPhase, state: vehicle.state, speed: vehicle.speed, currentLoad: vehicle.currentLoad, currentMaterials: vehicle.currentMaterials, distanceTraveled: actualDistance, currentRouteEfficiency: routeEfficiency, }, timeMetrics: { ...timeMetrics, idleTime: vehicle.idleTime || 0, activeTime: vehicle.activeTime || 0, totalTime: timeMetrics.totalTime, averageTripTime: actualTripTime, availability: timeMetrics.uptime, reliability: timeMetrics.reliability, }, throughput: { itemsPerHour: actualThroughput, itemsPerDay: actualThroughput * 24, materialFlowRate: vehicle.speed, capacityUtilization: timeMetrics.utilizationRate * 100, tripsCompleted, averageLoadsPerTrip: avgLoad, totalLoadsDelivered, throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, wip: materialFlow.wip, bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, }, movementMetrics: { distanceTraveled: actualDistance, averageSpeedActual: timeMetrics.totalTime > 0 ? actualDistance / timeMetrics.totalTime : 0, fuelEfficiency: actualDistance > 0 ? totalLoadsDelivered / actualDistance : 0, routeEfficiency, idleDistance: vehicle.idleTime || 0, totalTrips: tripsCompleted, averageTripDistance: tripsCompleted > 0 ? actualDistance / tripsCompleted : 0, distanceEfficiency: routeEfficiency, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, performance.performanceRate, qualityMetrics.firstPassYield), availability: timeMetrics.uptime, performance: performance.performanceRate, quality: qualityMetrics.firstPassYield, loadUtilization, timeEfficiency: performance.timeEfficiency, energyEfficiency: energyMetrics.energyEfficiency, routeEfficiency, costEfficiency: 100 - costMetrics.costPerUnit * 5, }, quality: { ...qualityMetrics, onTimeDelivery: tripsCompleted > 0 ? ((tripsCompleted - defects) / tripsCompleted) * 100 : 100, damageRate: defects > 0 ? (defects / totalLoadsDelivered) * 100 : 0, accuracyRate: 100 - qualityMetrics.defectRate, }, costMetrics: { operatingCost: costMetrics.operatingCost, maintenanceCost: costMetrics.maintenanceCost, energyCost: energyMetrics.energyCost, totalCost: costMetrics.totalCost, costPerMile: actualDistance > 0 ? costMetrics.totalCost / actualDistance : 0, costPerTrip: tripsCompleted > 0 ? costMetrics.totalCost / tripsCompleted : 0, costPerLoad: totalLoadsDelivered > 0 ? costMetrics.totalCost / totalLoadsDelivered : 0, roi: costMetrics.roi, valueAdded: costMetrics.valueAdded, }, energyMetrics: { energyConsumed: energyMetrics.energyConsumed, energyEfficiency: energyMetrics.energyEfficiency, carbonFootprint: energyMetrics.carbonFootprint, powerUsage: energyMetrics.powerUsage, energyCost: energyMetrics.energyCost, energyPerMile: actualDistance > 0 ? energyMetrics.energyConsumed / actualDistance : 0, energyPerTrip: tripsCompleted > 0 ? energyMetrics.energyConsumed / tripsCompleted : 0, }, historicalData: historicalDataRef.current[vehicle.modelUuid] || [], }; }, [materials, analysis] ); // ============================================================================ // ROBOTIC ARM ANALYSIS // ============================================================================ const analyzeRoboticArm = useCallback( (armBot: ArmBotStatus): RoboticArmAnalysis => { const timeMetrics = calculateTimeMetrics(armBot.idleTime || 0, armBot.activeTime || 0, armBot.modelUuid); const totalTime = (armBot.idleTime || 0) + (armBot.activeTime || 0); const cyclesCompleted = completedActionsRef.current[armBot.modelUuid] || 0; // Calculate cycle time efficiency const idealCycleTime = 5; // 5 seconds ideal const actualCycleTime = cyclesCompleted > 0 ? totalTime / cyclesCompleted : 0; const cycleTimeEfficiency = calculatePerformanceRate(actualCycleTime, idealCycleTime); // Pick success rate based on errors const pickAttempts = cyclesCompleted + (errorCountsRef.current[armBot.modelUuid] || 0); const pickSuccessRate = pickAttempts > 0 ? (cyclesCompleted / pickAttempts) * 100 : 100; return { assetId: armBot.modelUuid, assetName: armBot.modelName, assetType: "roboticArm", currentStatus: { isActive: armBot.isActive, state: armBot.state, speed: armBot.speed, currentAction: armBot.currentAction || null, }, timeMetrics: { ...timeMetrics, idleTime: armBot.idleTime || 0, activeTime: armBot.activeTime || 0, totalTime, averageCycleTime: actualCycleTime, }, throughput: { itemsPerHour: totalTime > 0 ? (cyclesCompleted / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? (cyclesCompleted / totalTime) * 86400 : 0, materialFlowRate: armBot.speed, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, pickAndPlaceCount: cyclesCompleted, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, cycleTimeEfficiency, pickSuccessRate), availability: timeMetrics.uptime, performance: cycleTimeEfficiency, quality: pickSuccessRate, cycleTimeEfficiency, }, quality: { errorRate: pickAttempts > 0 ? (errorCountsRef.current[armBot.modelUuid] || 0) / pickAttempts : 0, errorFrequency: totalTime > 0 ? ((errorCountsRef.current[armBot.modelUuid] || 0) / totalTime) * 3600 : 0, successRate: pickSuccessRate, stateTransitions: getStateTransitions(armBot.modelUuid), pickSuccessRate, placeAccuracy: pickSuccessRate, }, historicalData: (analysis?.assets.find((a) => a.assetId === armBot.modelUuid)?.historicalData || []) as RoboticArmAnalysis["historicalData"], }; }, [analysis] ); // ============================================================================ // MACHINE ANALYSIS // ============================================================================ const analyzeMachine = useCallback( (machine: MachineStatus): MachineAnalysis => { const timeMetrics = calculateTimeMetrics(machine.idleTime || 0, machine.activeTime || 0, machine.modelUuid); const totalTime = (machine.idleTime || 0) + (machine.activeTime || 0); const cyclesCompleted = completedActionsRef.current[machine.modelUuid] || 0; const defects = errorCountsRef.current[`${machine.modelUuid}_defects`] || 0; // Get target process time from action const targetProcessTime = machine.point?.action?.processTime || 30; const actualProcessTime = cyclesCompleted > 0 ? totalTime / cyclesCompleted : 0; const targetVsActual = calculatePerformanceRate(actualProcessTime, targetProcessTime); // Quality metrics const totalParts = cyclesCompleted + defects; const qualityRate = totalParts > 0 ? ((cyclesCompleted - defects) / totalParts) * 100 : 100; const defectRate = totalParts > 0 ? (defects / totalParts) * 100 : 0; return { assetId: machine.modelUuid, assetName: machine.modelName, assetType: "machine", currentStatus: { isActive: machine.isActive, state: machine.state, currentAction: machine.currentAction || null, }, timeMetrics: { ...timeMetrics, idleTime: machine.idleTime || 0, activeTime: machine.activeTime || 0, totalTime, averageProcessTime: actualProcessTime, }, throughput: { itemsPerHour: totalTime > 0 ? (cyclesCompleted / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? (cyclesCompleted / totalTime) * 86400 : 0, materialFlowRate: cyclesCompleted > 0 ? cyclesCompleted / (totalTime / 60) : 0, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, partsProcessed: cyclesCompleted, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, targetVsActual, qualityRate), availability: timeMetrics.uptime, performance: targetVsActual, quality: qualityRate, targetVsActual, }, quality: { errorRate: totalParts > 0 ? (errorCountsRef.current[machine.modelUuid] || 0) / totalParts : 0, errorFrequency: totalTime > 0 ? ((errorCountsRef.current[machine.modelUuid] || 0) / totalTime) * 3600 : 0, successRate: qualityRate, stateTransitions: getStateTransitions(machine.modelUuid), defectRate, reworkRate: defectRate * 0.3, // Assume 30% of defects are reworked }, historicalData: (analysis?.assets.find((a) => a.assetId === machine.modelUuid)?.historicalData || []) as MachineAnalysis["historicalData"], }; }, [analysis] ); // ============================================================================ // STORAGE ANALYSIS // ============================================================================ const analyzeStorage = useCallback( (storage: StorageUnitStatus): StorageAnalysis => { const timeMetrics = calculateTimeMetrics(storage.idleTime || 0, storage.activeTime || 0, storage.modelUuid); const currentLoad = storage.currentLoad || 0; const capacity = storage.storageCapacity || 1; const utilizationRate = (currentLoad / capacity) * 100; const storeOps = completedActionsRef.current[`${storage.modelUuid}_store`] || 0; const retrieveOps = completedActionsRef.current[`${storage.modelUuid}_retrieve`] || 0; const totalOps = storeOps + retrieveOps; // Calculate turnover rate const totalTime = (storage.idleTime || 0) + (storage.activeTime || 0); const turnoverRate = totalTime > 0 ? (totalOps / totalTime) * 3600 : 0; // Operations per hour // Get peak occupancy from previous analysis const previousAnalysis = analysis?.assets.find((a) => a.assetId === storage.modelUuid); const previousPeak = previousAnalysis && "capacityMetrics" in previousAnalysis ? previousAnalysis.capacityMetrics.peakOccupancy : utilizationRate; const peakOccupancy = Math.max(previousPeak, utilizationRate); return { assetId: storage.modelUuid, assetName: storage.modelName, assetType: "storage", currentStatus: { isActive: storage.isActive, state: storage.state, currentLoad, storageCapacity: capacity, currentMaterials: storage.currentMaterials, }, timeMetrics: { ...timeMetrics, idleTime: storage.idleTime || 0, activeTime: storage.activeTime || 0, totalTime, }, capacityMetrics: { utilizationRate, averageOccupancy: utilizationRate, peakOccupancy, turnoverRate, }, throughput: { itemsPerHour: turnoverRate, itemsPerDay: turnoverRate * 24, materialFlowRate: turnoverRate / 60, capacityUtilization: utilizationRate, storeOperations: storeOps, retrieveOperations: retrieveOps, totalOperations: totalOps, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, utilizationRate, 100), availability: timeMetrics.uptime, performance: utilizationRate, quality: 100, spaceUtilization: utilizationRate, }, quality: { errorRate: totalOps > 0 ? (errorCountsRef.current[storage.modelUuid] || 0) / totalOps : 0, errorFrequency: totalTime > 0 ? ((errorCountsRef.current[storage.modelUuid] || 0) / totalTime) * 3600 : 0, successRate: 100, stateTransitions: getStateTransitions(storage.modelUuid), }, occupancyTrends: [], historicalData: (analysis?.assets.find((a) => a.assetId === storage.modelUuid)?.historicalData || []) as StorageAnalysis["historicalData"], }; }, [analysis] ); // ============================================================================ // HUMAN ANALYSIS // ============================================================================ const analyzeHuman = useCallback( (human: HumanStatus): HumanAnalysis => { const timeMetrics = calculateTimeMetrics(human.idleTime || 0, human.activeTime || 0, human.modelUuid); const totalTime = (human.idleTime || 0) + (human.activeTime || 0); const actionsCompleted = completedActionsRef.current[human.modelUuid] || 0; // Calculate workload distribution const workerActions = completedActionsRef.current[`${human.modelUuid}_worker`] || 0; const manufacturerActions = completedActionsRef.current[`${human.modelUuid}_manufacturer`] || 0; const operatorActions = completedActionsRef.current[`${human.modelUuid}_operator`] || 0; const assemblerActions = completedActionsRef.current[`${human.modelUuid}_assembler`] || 0; const workerTime = completedActionsRef.current[`${human.modelUuid}_worker_time`] || 0; const manufacturerTime = completedActionsRef.current[`${human.modelUuid}_manufacturer_time`] || 0; const operatorTime = completedActionsRef.current[`${human.modelUuid}_operator_time`] || 0; const assemblerTime = completedActionsRef.current[`${human.modelUuid}_assembler_time`] || 0; const workloadDistribution = [ { actionType: "worker", count: workerActions, totalTime: workerTime, percentage: totalTime > 0 ? (workerTime / totalTime) * 100 : 0 }, { actionType: "manufacturer", count: manufacturerActions, totalTime: manufacturerTime, percentage: totalTime > 0 ? (manufacturerTime / totalTime) * 100 : 0 }, { actionType: "operator", count: operatorActions, totalTime: operatorTime, percentage: totalTime > 0 ? (operatorTime / totalTime) * 100 : 0 }, { actionType: "assembler", count: assemblerActions, totalTime: assemblerTime, percentage: totalTime > 0 ? (assemblerTime / totalTime) * 100 : 0 }, ].filter((w) => w.count > 0); return { assetId: human.modelUuid, assetName: human.modelName, assetType: "human", currentStatus: { isActive: human.isActive, isScheduled: human.isScheduled, currentPhase: human.currentPhase, state: human.state, speed: human.speed, currentLoad: human.currentLoad, currentMaterials: human.currentMaterials, currentAction: human.currentAction || null, }, timeMetrics: { ...timeMetrics, idleTime: human.idleTime || 0, activeTime: human.activeTime || 0, totalTime, scheduledTime: totalTime, }, productivityMetrics: { actionsCompleted, actionsPerHour: totalTime > 0 ? (actionsCompleted / totalTime) * 3600 : 0, averageActionTime: actionsCompleted > 0 ? totalTime / actionsCompleted : 0, distanceTraveled: human.distanceTraveled || 0, }, workloadDistribution, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, 100, 100), availability: timeMetrics.uptime, performance: 100, quality: 100, laborProductivity: totalTime > 0 ? actionsCompleted / (totalTime / 3600) : 0, utilizationRate: timeMetrics.utilizationRate * 100, }, quality: { errorRate: actionsCompleted > 0 ? (errorCountsRef.current[human.modelUuid] || 0) / actionsCompleted : 0, errorFrequency: totalTime > 0 ? ((errorCountsRef.current[human.modelUuid] || 0) / totalTime) * 3600 : 0, successRate: 100, stateTransitions: getStateTransitions(human.modelUuid), }, historicalData: (analysis?.assets.find((a) => a.assetId === human.modelUuid)?.historicalData || []) as HumanAnalysis["historicalData"], }; }, [analysis] ); // ============================================================================ // CRANE ANALYSIS // ============================================================================ const analyzeCrane = useCallback((crane: CraneStatus): CraneAnalysis => { const timeMetrics = calculateTimeMetrics(crane.idleTime || 0, crane.activeTime || 0, crane.modelUuid); const totalTime = (crane.idleTime || 0) + (crane.activeTime || 0); const cyclesCompleted = completedActionsRef.current[crane.modelUuid] || 0; const loadsHandled = completedActionsRef.current[`${crane.modelUuid}_loads`] || 0; const totalLifts = completedActionsRef.current[`${crane.modelUuid}_lifts`] || 0; const totalLiftHeight = completedActionsRef.current[`${crane.modelUuid}_lift_height`] || 0; // Calculate cycle time const actualCycleTime = cyclesCompleted > 0 ? totalTime / cyclesCompleted : 0; const idealCycleTime = 20; // 20 seconds ideal const cycleTimeEfficiency = calculatePerformanceRate(actualCycleTime, idealCycleTime); // Calculate lift metrics const avgLiftHeight = totalLifts > 0 ? totalLiftHeight / totalLifts : 0; const avgLoadsPerCycle = cyclesCompleted > 0 ? loadsHandled / cyclesCompleted : 0; // Lift success rate const liftAttempts = totalLifts + (errorCountsRef.current[crane.modelUuid] || 0); const liftSuccessRate = liftAttempts > 0 ? (totalLifts / liftAttempts) * 100 : 100; // Load utilization based on max pick up count const maxPickUpCount = crane.point?.actions?.[0]?.maxPickUpCount || 1; const loadUtilization = (avgLoadsPerCycle / maxPickUpCount) * 100; return { assetId: crane.modelUuid, assetName: crane.modelName, assetType: "crane", currentStatus: { isActive: crane.isActive, isScheduled: crane.isScheduled, isCarrying: crane.isCarrying, currentPhase: crane.currentPhase, state: crane.state, currentLoad: crane.currentLoad, currentMaterials: crane.currentMaterials, currentAction: crane.currentAction || null, }, timeMetrics: { ...timeMetrics, idleTime: crane.idleTime || 0, activeTime: crane.activeTime || 0, totalTime, averageCycleTime: actualCycleTime, }, throughput: { itemsPerHour: totalTime > 0 ? (loadsHandled / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? (loadsHandled / totalTime) * 86400 : 0, materialFlowRate: cyclesCompleted > 0 ? cyclesCompleted / (totalTime / 60) : 0, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, loadsHandled, averageLoadsPerCycle: avgLoadsPerCycle, }, movementMetrics: { totalLifts, averageLiftHeight: avgLiftHeight, movementEfficiency: liftSuccessRate, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, cycleTimeEfficiency, liftSuccessRate), availability: timeMetrics.uptime, performance: cycleTimeEfficiency, quality: liftSuccessRate, loadUtilization, cycleTimeEfficiency, }, quality: { errorRate: liftAttempts > 0 ? (errorCountsRef.current[crane.modelUuid] || 0) / liftAttempts : 0, errorFrequency: totalTime > 0 ? ((errorCountsRef.current[crane.modelUuid] || 0) / totalTime) * 3600 : 0, successRate: 100, stateTransitions: getStateTransitions(crane.modelUuid), liftSuccessRate: liftSuccessRate, positioningAccuracy: liftSuccessRate, }, historicalData: [], }; }, []); // ============================================================================ // SYSTEM-WIDE ANALYSIS // ============================================================================ const analyzeSystem = useCallback((allAssets: AssetAnalysis[]): AnalysisSchema => { // Calculate system-wide metrics const totalAssets = allAssets.length; const activeAssets = allAssets.filter((a) => a.currentStatus.isActive).length; const assetsInError = allAssets.filter((a) => a.currentStatus.state === "error").length; const avgOEE = allAssets.reduce((sum, a) => sum + a.efficiency.overallEffectiveness, 0) / totalAssets; const avgUtilization = allAssets.reduce((sum, a) => sum + a.timeMetrics.utilizationRate, 0) / totalAssets; const avgIdleTime = allAssets.reduce((sum, a) => sum + a.timeMetrics.idleTime, 0) / totalAssets; const totalDowntime = allAssets.reduce((sum, a) => sum + a.timeMetrics.downtime, 0); // Helper to get throughput safely const getThroughput = (asset: AssetAnalysis): number => { if ("throughput" in asset) { return asset.throughput.itemsPerHour; } else if ("productivityMetrics" in asset) { return asset.productivityMetrics.actionsPerHour; } return 0; }; // Group by asset type const assetTypeGroups = allAssets.reduce((acc, asset) => { if (!acc[asset.assetType]) { acc[asset.assetType] = []; } acc[asset.assetType].push(asset); return acc; }, {} as Record); const assetTypePerformance = Object.entries(assetTypeGroups).map(([type, assets]) => ({ assetType: type, count: assets.length, averageOEE: assets.reduce((sum, a) => sum + a.efficiency.overallEffectiveness, 0) / assets.length, averageUtilization: (assets.reduce((sum, a) => sum + a.timeMetrics.utilizationRate, 0) / assets.length) * 100, totalThroughput: assets.reduce((sum, a) => sum + getThroughput(a), 0), })); // Identify bottlenecks (high utilization + low throughput) const bottlenecks = allAssets .filter((a) => a.timeMetrics.utilizationRate > 0.8) .map((a) => ({ assetId: a.assetId, assetName: a.assetName, severity: a.timeMetrics.utilizationRate > 0.95 ? ("critical" as const) : a.timeMetrics.utilizationRate > 0.9 ? ("high" as const) : a.timeMetrics.utilizationRate > 0.85 ? ("medium" as const) : ("low" as const), utilizationRate: a.timeMetrics.utilizationRate * 100, queueLength: 0, impactScore: a.timeMetrics.utilizationRate * 100, })) .sort((a, b) => b.impactScore - a.impactScore); return { assets: allAssets, systemPerformance: { overallOEE: avgOEE, systemThroughput: allAssets.reduce((sum, a) => sum + getThroughput(a), 0), systemUtilization: avgUtilization * 100, assetTypePerformance, criticalMetrics: { activeAssets, totalAssets, assetsInError, averageIdleTime: avgIdleTime, totalDowntime, }, }, materialFlow: { totalMaterialsInSystem: 0, materialsCompleted: 0, averageResidenceTime: 0, queueLengths: [], bottlenecks, flowContinuity: { overallFlowRate: 0, varianceCoefficient: 0, steadyStateDeviation: 0, }, }, predictiveInsights: { maintenanceAlerts: [], optimizationOpportunities: [], trendAnalysis: [], }, analysisTimeRange: { startTime: startTimeRef.current, endTime: new Date().toISOString(), duration: Date.now() - new Date(startTimeRef.current).getTime(), }, metadata: { lastUpdated: new Date().toISOString(), dataPoints: allAssets.length, analysisVersion: "1.0.0", }, }; }, []); // ============================================================================ // MAIN ANALYSIS FUNCTION // ============================================================================ const performAnalysis = useCallback(() => { setAnalyzing(true); try { const allAssets: AssetAnalysis[] = []; // Analyze all conveyors conveyors.forEach((conveyor) => { allAssets.push(analyzeConveyor(conveyor)); }); // Analyze all vehicles vehicles.forEach((vehicle) => { allAssets.push(analyzeVehicle(vehicle)); }); // Analyze all robotic arms armBots.forEach((armBot) => { allAssets.push(analyzeRoboticArm(armBot)); }); // Analyze all machines machines.forEach((machine) => { allAssets.push(analyzeMachine(machine)); }); // Analyze all storage units storageUnits.forEach((storage) => { allAssets.push(analyzeStorage(storage)); }); // Analyze all humans humans.forEach((human) => { allAssets.push(analyzeHuman(human)); }); // Analyze all cranes cranes.forEach((crane) => { allAssets.push(analyzeCrane(crane)); }); // Perform system-wide analysis const completeAnalysis = analyzeSystem(allAssets); setAnalysis(completeAnalysis); } catch (error) { console.error("Analysis error:", error); } finally { setAnalyzing(false); } }, [ conveyors, vehicles, armBots, machines, humans, cranes, analyzeConveyor, analyzeVehicle, analyzeRoboticArm, analyzeMachine, analyzeHuman, analyzeCrane, analyzeSystem, setAnalysis, setAnalyzing, ]); // ============================================================================ // EFFECTS // ============================================================================ // Perform initial analysis and set up interval useEffect(() => { if (!isPlaying) return; // Initial analysis performAnalysis(); // Set up periodic analysis (every 5 seconds) analysisIntervalRef.current = setInterval(() => { performAnalysis(); }, 5000); return () => { if (analysisIntervalRef.current) { clearInterval(analysisIntervalRef.current); } }; }, [performAnalysis, isPlaying]); return null; } export default Analyzer;