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, analysisStore } = useSceneContext(); const { conveyors } = conveyorStore(); const { machines } = machineStore(); const { armBots } = armBotStore(); const { humans } = humanStore(); const { vehicles } = vehicleStore(); const { cranes } = craneStore(); const { storageUnits } = storageUnitStore(); const { setAnalysis, setAnalyzing } = analysisStore(); const analysisIntervalRef = useRef(null); const startTimeRef = useRef(new Date().toISOString()); // ============================================================================ // CALCULATION UTILITIES // ============================================================================ const calculateTimeMetrics = (idleTime: number, activeTime: number, totalErrors: number = 0) => { const totalTime = idleTime + activeTime; const uptime = totalTime > 0 ? (activeTime / totalTime) * 100 : 0; const downtime = idleTime; const utilizationRate = uptime / 100; return { uptime, downtime, utilizationRate, mtbf: totalErrors > 0 ? totalTime / totalErrors : totalTime, mttr: totalErrors > 0 ? downtime / totalErrors : 0, }; }; const calculateOEE = (availability: number, performance: number, quality: number) => { return (availability * performance * quality) / 10000; // All in percentages }; // ============================================================================ // CONVEYOR ANALYSIS // ============================================================================ const analyzeConveyor = useCallback((conveyor: any): ConveyorAnalysis => { const timeMetrics = calculateTimeMetrics(conveyor.idleTime || 0, conveyor.activeTime || 0); const totalTime = (conveyor.idleTime || 0) + (conveyor.activeTime || 0); const materialsProcessed = conveyor.materialsProcessed || 0; return { assetId: conveyor.modelUuid, assetName: conveyor.modelName, assetType: "conveyor", currentStatus: { isActive: conveyor.isActive || false, isPaused: conveyor.isPaused || false, state: conveyor.state || "idle", speed: conveyor.speed || 0, currentProduct: conveyor.productUuid || null, }, timeMetrics: { ...timeMetrics, idleTime: conveyor.idleTime || 0, activeTime: conveyor.activeTime || 0, totalTime, }, throughput: { itemsPerHour: totalTime > 0 ? (materialsProcessed / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? (materialsProcessed / totalTime) * 86400 : 0, materialFlowRate: conveyor.speed || 0, capacityUtilization: timeMetrics.utilizationRate * 100, materialsProcessed, averageProcessingTime: materialsProcessed > 0 ? totalTime / materialsProcessed : 0, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, 100, 100), availability: timeMetrics.uptime, performance: 100, quality: 100, }, quality: { errorRate: 0, errorFrequency: 0, successRate: 100, stateTransitions: [], }, historicalData: [], }; }, []); // ============================================================================ // VEHICLE ANALYSIS // ============================================================================ const analyzeVehicle = useCallback((vehicle: any): VehicleAnalysis => { const timeMetrics = calculateTimeMetrics(vehicle.idleTime || 0, vehicle.activeTime || 0); const totalTime = (vehicle.idleTime || 0) + (vehicle.activeTime || 0); const tripsCompleted = vehicle.tripsCompleted || 0; return { assetId: vehicle.modelUuid, assetName: vehicle.modelName, assetType: "vehicle", currentStatus: { isActive: vehicle.isActive || false, isPicking: vehicle.isPicking || false, currentPhase: vehicle.currentPhase || "idle", state: vehicle.state || "idle", speed: vehicle.speed || 0, currentLoad: vehicle.currentLoad || 0, currentMaterials: vehicle.currentMaterials || [], }, timeMetrics: { ...timeMetrics, idleTime: vehicle.idleTime || 0, activeTime: vehicle.activeTime || 0, totalTime, averageTripTime: tripsCompleted > 0 ? totalTime / tripsCompleted : 0, }, throughput: { itemsPerHour: totalTime > 0 ? ((vehicle.currentLoad || 0) / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? ((vehicle.currentLoad || 0) / totalTime) * 86400 : 0, materialFlowRate: vehicle.speed || 0, capacityUtilization: timeMetrics.utilizationRate * 100, tripsCompleted, averageLoadsPerTrip: tripsCompleted > 0 ? (vehicle.totalLoadsDelivered || 0) / tripsCompleted : 0, totalLoadsDelivered: vehicle.totalLoadsDelivered || 0, }, movementMetrics: { distanceTraveled: vehicle.distanceTraveled || 0, averageSpeedActual: totalTime > 0 ? (vehicle.distanceTraveled || 0) / totalTime : 0, fuelEfficiency: 0, routeEfficiency: 100, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, 100, 100), availability: timeMetrics.uptime, performance: 100, quality: 100, loadUtilization: vehicle.point?.action?.loadCapacity > 0 ? ((vehicle.currentLoad || 0) / vehicle.point.action.loadCapacity) * 100 : 0, }, quality: { errorRate: 0, errorFrequency: 0, successRate: 100, stateTransitions: [], }, historicalData: [], }; }, []); // ============================================================================ // ROBOTIC ARM ANALYSIS // ============================================================================ const analyzeRoboticArm = useCallback((armBot: any): RoboticArmAnalysis => { const timeMetrics = calculateTimeMetrics(armBot.idleTime || 0, armBot.activeTime || 0); const totalTime = (armBot.idleTime || 0) + (armBot.activeTime || 0); const cyclesCompleted = armBot.cyclesCompleted || 0; return { assetId: armBot.modelUuid, assetName: armBot.modelName, assetType: "roboticArm", currentStatus: { isActive: armBot.isActive || false, state: armBot.state || "idle", speed: armBot.speed || 0, currentAction: armBot.currentAction || null, }, timeMetrics: { ...timeMetrics, idleTime: armBot.idleTime || 0, activeTime: armBot.activeTime || 0, totalTime, averageCycleTime: cyclesCompleted > 0 ? totalTime / cyclesCompleted : 0, }, throughput: { itemsPerHour: totalTime > 0 ? (cyclesCompleted / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? (cyclesCompleted / totalTime) * 86400 : 0, materialFlowRate: armBot.speed || 0, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, pickAndPlaceCount: cyclesCompleted, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, 100, 100), availability: timeMetrics.uptime, performance: 100, quality: 100, cycleTimeEfficiency: 100, }, quality: { errorRate: 0, errorFrequency: 0, successRate: 100, stateTransitions: [], pickSuccessRate: 100, placeAccuracy: 100, }, historicalData: [], }; }, []); // ============================================================================ // MACHINE ANALYSIS // ============================================================================ const analyzeMachine = useCallback((machine: any): MachineAnalysis => { const timeMetrics = calculateTimeMetrics(machine.idleTime || 0, machine.activeTime || 0); const totalTime = (machine.idleTime || 0) + (machine.activeTime || 0); const cyclesCompleted = machine.cyclesCompleted || 0; return { assetId: machine.modelUuid, assetName: machine.modelName, assetType: "machine", currentStatus: { isActive: machine.isActive || false, state: machine.state || "idle", currentAction: machine.currentAction || null, }, timeMetrics: { ...timeMetrics, idleTime: machine.idleTime || 0, activeTime: machine.activeTime || 0, totalTime, averageProcessTime: cyclesCompleted > 0 ? totalTime / cyclesCompleted : 0, }, throughput: { itemsPerHour: totalTime > 0 ? (cyclesCompleted / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? (cyclesCompleted / totalTime) * 86400 : 0, materialFlowRate: 1, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, partsProcessed: cyclesCompleted, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, 100, 100), availability: timeMetrics.uptime, performance: 100, quality: 100, targetVsActual: 100, }, quality: { errorRate: 0, errorFrequency: 0, successRate: 100, stateTransitions: [], defectRate: 0, reworkRate: 0, }, historicalData: [], }; }, []); // ============================================================================ // STORAGE ANALYSIS // ============================================================================ const analyzeStorage = useCallback((storage: any): StorageAnalysis => { const timeMetrics = calculateTimeMetrics(storage.idleTime || 0, storage.activeTime || 0); const utilizationRate = storage.storageCapacity > 0 ? (storage.currentLoad / storage.storageCapacity) * 100 : 0; return { assetId: storage.modelUuid, assetName: storage.modelName, assetType: "storage", currentStatus: { isActive: storage.isActive || false, state: storage.state || "idle", currentLoad: storage.currentLoad || 0, storageCapacity: storage.storageCapacity || 0, currentMaterials: storage.currentMaterials || [], }, timeMetrics: { ...timeMetrics, idleTime: storage.idleTime || 0, activeTime: storage.activeTime || 0, totalTime: (storage.idleTime || 0) + (storage.activeTime || 0), }, capacityMetrics: { utilizationRate, averageOccupancy: utilizationRate, peakOccupancy: storage.peakOccupancy || utilizationRate, turnoverRate: 0, }, throughput: { itemsPerHour: 0, itemsPerDay: 0, materialFlowRate: 0, capacityUtilization: utilizationRate, storeOperations: storage.storeOperations || 0, retrieveOperations: storage.retrieveOperations || 0, totalOperations: (storage.storeOperations || 0) + (storage.retrieveOperations || 0), }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, utilizationRate, 100), availability: timeMetrics.uptime, performance: utilizationRate, quality: 100, spaceUtilization: utilizationRate, }, quality: { errorRate: 0, errorFrequency: 0, successRate: 100, stateTransitions: [], }, occupancyTrends: [], historicalData: [], }; }, []); // ============================================================================ // HUMAN ANALYSIS // ============================================================================ const analyzeHuman = useCallback((human: any): HumanAnalysis => { const timeMetrics = calculateTimeMetrics(human.idleTime || 0, human.activeTime || 0); const totalTime = (human.idleTime || 0) + (human.activeTime || 0); const actionsCompleted = human.actionsCompleted || 0; return { assetId: human.modelUuid, assetName: human.modelName, assetType: "human", currentStatus: { isActive: human.isActive || false, isScheduled: human.isScheduled || false, currentPhase: human.currentPhase || "idle", state: human.state || "idle", speed: human.speed || 0, currentLoad: human.currentLoad || 0, 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: 0, errorFrequency: 0, successRate: 100, stateTransitions: [], }, historicalData: [], }; }, []); // ============================================================================ // CRANE ANALYSIS // ============================================================================ const analyzeCrane = useCallback((crane: any): CraneAnalysis => { const timeMetrics = calculateTimeMetrics(crane.idleTime || 0, crane.activeTime || 0); const totalTime = (crane.idleTime || 0) + (crane.activeTime || 0); const cyclesCompleted = crane.cyclesCompleted || 0; return { assetId: crane.modelUuid, assetName: crane.modelName, assetType: "crane", currentStatus: { isActive: crane.isActive || false, isScheduled: crane.isScheduled || false, isCarrying: crane.isCarrying || false, currentPhase: crane.currentPhase || "idle", state: crane.state || "idle", currentLoad: crane.currentLoad || 0, currentMaterials: crane.currentMaterials || [], currentAction: crane.currentAction || null, }, timeMetrics: { ...timeMetrics, idleTime: crane.idleTime || 0, activeTime: crane.activeTime || 0, totalTime, averageCycleTime: cyclesCompleted > 0 ? totalTime / cyclesCompleted : 0, }, throughput: { itemsPerHour: totalTime > 0 ? (cyclesCompleted / totalTime) * 3600 : 0, itemsPerDay: totalTime > 0 ? (cyclesCompleted / totalTime) * 86400 : 0, materialFlowRate: 1, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, loadsHandled: crane.loadsHandled || 0, averageLoadsPerCycle: cyclesCompleted > 0 ? (crane.loadsHandled || 0) / cyclesCompleted : 0, }, movementMetrics: { totalLifts: crane.totalLifts || 0, averageLiftHeight: crane.averageLiftHeight || 0, movementEfficiency: 100, }, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, 100, 100), availability: timeMetrics.uptime, performance: 100, quality: 100, loadUtilization: 0, cycleTimeEfficiency: 100, }, quality: { errorRate: 0, errorFrequency: 0, successRate: 100, stateTransitions: [], liftSuccessRate: 100, positioningAccuracy: 100, }, 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;