diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index b23f8ef..5e71e51 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -13,89 +13,19 @@ function Analyzer() { const { vehicles } = vehicleStore(); const { cranes } = craneStore(); const { storageUnits } = storageUnitStore(); - const { materials } = materialStore(); + const { materials, getMaterialsByModel } = 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 // ============================================================================ @@ -158,11 +88,11 @@ function Analyzer() { }; const calculateMaterialFlowMetricsForAsset = (assetId: string) => { - const materialsOnAsset = getMaterialsOnAsset(assetId); + const materialsOnAsset = getMaterialsByModel(assetId).length; const materialHistory = materialHistoryRef.current.filter((m) => m.material.current?.modelUuid === assetId || m.material.previous?.modelUuid === assetId); // Calculate flow metrics - const wip = materialsOnAsset.length; + const wip = materialsOnAsset; const throughput = (materialHistory.length / (Date.now() - new Date(startTimeRef.current).getTime())) * 1000; // Calculate lead times @@ -185,7 +115,7 @@ function Analyzer() { throughput, avgLeadTime, totalMaterialsProcessed: materialHistory.length, - currentMaterials: materialsOnAsset.length, + currentMaterials: materialsOnAsset, avgCycleTime: avgLeadTime / 1000, materialVelocity: velocity, inventoryTurns: throughput > 0 ? throughput / Math.max(1, wip) : 0, @@ -293,6 +223,30 @@ function Analyzer() { }; }; + const calculateOEE = (availability: number, performance: number, quality: number) => { + return (availability * performance * quality) / 10000; + }; + + 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, + })); + }; + // ============================================================================ // ENHANCED CONVEYOR ANALYSIS // ============================================================================ @@ -328,10 +282,7 @@ function Analyzer() { // Update historical data const timestamp = new Date().toISOString(); - if (!historicalDataRef.current[conveyor.modelUuid]) { - historicalDataRef.current[conveyor.modelUuid] = []; - } - historicalDataRef.current[conveyor.modelUuid].push({ + const newEntry = { timestamp, isActive: conveyor.isActive, speed: conveyor.speed, @@ -339,17 +290,15 @@ function Analyzer() { materialsCount: materialFlow.currentMaterials, throughput: actualThroughput, performance: performance.performanceRate, - }); + }; + + const currentData = historicalDataRef.current[conveyor.modelUuid] || []; + historicalDataRef.current[conveyor.modelUuid] = [...currentData, newEntry].slice(-100); // 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, - }); + const currentQueueData = queueLengthsRef.current[conveyor.modelUuid] || []; + queueLengthsRef.current[conveyor.modelUuid] = [...currentQueueData, { timestamp: Date.now(), length: queueLength }].slice(-100); return { assetId: conveyor.modelUuid, @@ -588,25 +537,57 @@ function Analyzer() { }, [materials, analysis] ); + // ============================================================================ - // ROBOTIC ARM ANALYSIS + // ENHANCED ROBOTIC ARM ANALYSIS // ============================================================================ const analyzeRoboticArm = useCallback( (armBot: ArmBotStatus): RoboticArmAnalysis => { - const timeMetrics = calculateTimeMetrics(armBot.idleTime || 0, armBot.activeTime || 0, armBot.modelUuid); + const errorCount = errorCountsRef.current[armBot.modelUuid] || 0; + const timeMetrics = calculateAdvancedTimeMetrics(armBot.idleTime || 0, armBot.activeTime || 0, armBot.modelUuid, errorCount); - const totalTime = (armBot.idleTime || 0) + (armBot.activeTime || 0); + const materialFlow = calculateMaterialFlowMetricsForAsset(armBot.modelUuid); const cyclesCompleted = completedActionsRef.current[armBot.modelUuid] || 0; + const pickAndPlaceCount = completedActionsRef.current[`${armBot.modelUuid}_pickplace`] || cyclesCompleted; + const defects = errorCountsRef.current[`${armBot.modelUuid}_defects`] || 0; - // Calculate cycle time efficiency + const qualityMetrics = calculateQualityMetrics(armBot.modelUuid, pickAndPlaceCount, defects); + + // Performance calculations const idealCycleTime = 5; // 5 seconds ideal - const actualCycleTime = cyclesCompleted > 0 ? totalTime / cyclesCompleted : 0; - const cycleTimeEfficiency = calculatePerformanceRate(actualCycleTime, idealCycleTime); + const actualCycleTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0; + const idealThroughput = (3600 / idealCycleTime) * 1; // 1 item per cycle + const actualThroughput = materialFlow.throughput * 3600; - // Pick success rate based on errors - const pickAttempts = cyclesCompleted + (errorCountsRef.current[armBot.modelUuid] || 0); - const pickSuccessRate = pickAttempts > 0 ? (cyclesCompleted / pickAttempts) * 100 : 100; + const performance = calculatePerformanceMetrics(pickAndPlaceCount, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, idealCycleTime * cyclesCompleted, "roboticArm"); + + const costMetrics = calculateCostMetrics(armBot.modelUuid, "roboticArm", armBot.activeTime || 0, pickAndPlaceCount); + + const energyMetrics = calculateEnergyMetrics("roboticArm", armBot.activeTime || 0); + + // Calculate success rates + const pickAttempts = pickAndPlaceCount + (errorCountsRef.current[armBot.modelUuid] || 0); + const pickSuccessRate = pickAttempts > 0 ? (pickAndPlaceCount / pickAttempts) * 100 : 100; + const placeAccuracy = pickSuccessRate * 0.98; // Assuming 98% of successful picks are placed correctly + + // Update historical data + + const timestamp = new Date().toISOString(); + const newEntry = { + timestamp, + cycleTime: actualCycleTime, + actionType: armBot.currentAction?.actionName || "unknown", + isActive: armBot.isActive, + state: armBot.state, + speed: armBot.speed, + cyclesCompleted, + successRate: pickSuccessRate, + performance: performance.performanceRate, + }; + + const currentData = historicalDataRef.current[armBot.modelUuid] || []; + historicalDataRef.current[armBot.modelUuid] = [...currentData, newEntry].slice(-100); return { assetId: armBot.modelUuid, @@ -624,63 +605,112 @@ function Analyzer() { ...timeMetrics, idleTime: armBot.idleTime || 0, activeTime: armBot.activeTime || 0, - totalTime, + totalTime: timeMetrics.totalTime, averageCycleTime: actualCycleTime, + availability: timeMetrics.uptime, + reliability: timeMetrics.reliability, }, throughput: { - itemsPerHour: totalTime > 0 ? (cyclesCompleted / totalTime) * 3600 : 0, - itemsPerDay: totalTime > 0 ? (cyclesCompleted / totalTime) * 86400 : 0, + itemsPerHour: actualThroughput, + itemsPerDay: actualThroughput * 24, materialFlowRate: armBot.speed, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, - pickAndPlaceCount: cyclesCompleted, + pickAndPlaceCount, + throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, + wip: materialFlow.wip, + bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, }, efficiency: { - overallEffectiveness: calculateOEE(timeMetrics.uptime, cycleTimeEfficiency, pickSuccessRate), + overallEffectiveness: calculateOEE(timeMetrics.uptime, performance.performanceRate, qualityMetrics.firstPassYield), availability: timeMetrics.uptime, - performance: cycleTimeEfficiency, - quality: pickSuccessRate, - cycleTimeEfficiency, + performance: performance.performanceRate, + quality: qualityMetrics.firstPassYield, + cycleTimeEfficiency: performance.timeEfficiency, }, 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), + ...qualityMetrics, pickSuccessRate, - placeAccuracy: pickSuccessRate, + placeAccuracy, }, - historicalData: (analysis?.assets.find((a) => a.assetId === armBot.modelUuid)?.historicalData || []) as RoboticArmAnalysis["historicalData"], + // Add cost metrics + costMetrics: { + operatingCost: costMetrics.operatingCost, + maintenanceCost: costMetrics.maintenanceCost, + energyCost: energyMetrics.energyCost, + totalCost: costMetrics.totalCost, + 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: pickAndPlaceCount > 0 ? energyMetrics.energyConsumed / pickAndPlaceCount : 0, + }, + + historicalData: historicalDataRef.current[armBot.modelUuid] || [], }; }, - [analysis] + [materials, analysis] ); // ============================================================================ - // MACHINE ANALYSIS + // ENHANCED MACHINE ANALYSIS // ============================================================================ const analyzeMachine = useCallback( (machine: MachineStatus): MachineAnalysis => { - const timeMetrics = calculateTimeMetrics(machine.idleTime || 0, machine.activeTime || 0, machine.modelUuid); + const errorCount = errorCountsRef.current[machine.modelUuid] || 0; + const timeMetrics = calculateAdvancedTimeMetrics(machine.idleTime || 0, machine.activeTime || 0, machine.modelUuid, errorCount); - const totalTime = (machine.idleTime || 0) + (machine.activeTime || 0); + const materialFlow = calculateMaterialFlowMetricsForAsset(machine.modelUuid); const cyclesCompleted = completedActionsRef.current[machine.modelUuid] || 0; + const partsProcessed = completedActionsRef.current[`${machine.modelUuid}_parts`] || cyclesCompleted; 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); + const qualityMetrics = calculateQualityMetrics(machine.modelUuid, partsProcessed, defects); - // Quality metrics - const totalParts = cyclesCompleted + defects; - const qualityRate = totalParts > 0 ? ((cyclesCompleted - defects) / totalParts) * 100 : 100; + // Performance calculations + const targetProcessTime = machine.point?.action?.processTime || 30; + const actualProcessTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0; + const idealThroughput = (3600 / targetProcessTime) * 1; // 1 part per process + const actualThroughput = materialFlow.throughput * 3600; + + const performance = calculatePerformanceMetrics(partsProcessed, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, targetProcessTime * cyclesCompleted, "machine"); + + const costMetrics = calculateCostMetrics(machine.modelUuid, "machine", machine.activeTime || 0, partsProcessed); + + const energyMetrics = calculateEnergyMetrics("machine", machine.activeTime || 0); + + // Quality calculations + const totalParts = partsProcessed + defects; const defectRate = totalParts > 0 ? (defects / totalParts) * 100 : 0; + const reworkRate = defectRate * 0.3; // Assume 30% of defects are reworked + const scrapRate = defectRate * 0.7; // Assume 70% are scrapped + + // Update historical data + const timestamp = new Date().toISOString(); + if (!historicalDataRef.current[machine.modelUuid]) { + historicalDataRef.current[machine.modelUuid] = []; + } + historicalDataRef.current[machine.modelUuid].push({ + timestamp, + processTime: actualProcessTime, + partsProcessed, + isActive: machine.isActive, + state: machine.state, + defectRate, + performance: performance.performanceRate, + }); return { assetId: machine.modelUuid, @@ -697,49 +727,74 @@ function Analyzer() { ...timeMetrics, idleTime: machine.idleTime || 0, activeTime: machine.activeTime || 0, - totalTime, + totalTime: timeMetrics.totalTime, averageProcessTime: actualProcessTime, + availability: timeMetrics.uptime, + reliability: timeMetrics.reliability, }, throughput: { - itemsPerHour: totalTime > 0 ? (cyclesCompleted / totalTime) * 3600 : 0, - itemsPerDay: totalTime > 0 ? (cyclesCompleted / totalTime) * 86400 : 0, - materialFlowRate: cyclesCompleted > 0 ? cyclesCompleted / (totalTime / 60) : 0, + itemsPerHour: actualThroughput, + itemsPerDay: actualThroughput * 24, + materialFlowRate: partsProcessed > 0 ? partsProcessed / (timeMetrics.totalTime / 60) : 0, capacityUtilization: timeMetrics.utilizationRate * 100, cyclesCompleted, - partsProcessed: cyclesCompleted, + partsProcessed, + throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, + wip: materialFlow.wip, + bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, }, efficiency: { - overallEffectiveness: calculateOEE(timeMetrics.uptime, targetVsActual, qualityRate), + overallEffectiveness: calculateOEE(timeMetrics.uptime, performance.performanceRate, qualityMetrics.firstPassYield), availability: timeMetrics.uptime, - performance: targetVsActual, - quality: qualityRate, - targetVsActual, + performance: performance.performanceRate, + quality: qualityMetrics.firstPassYield, + targetVsActual: performance.timeEfficiency, }, 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), + ...qualityMetrics, defectRate, - reworkRate: defectRate * 0.3, // Assume 30% of defects are reworked + reworkRate, + scrapRate, }, - historicalData: (analysis?.assets.find((a) => a.assetId === machine.modelUuid)?.historicalData || []) as MachineAnalysis["historicalData"], + // Add cost metrics + costMetrics: { + operatingCost: costMetrics.operatingCost, + maintenanceCost: costMetrics.maintenanceCost, + energyCost: energyMetrics.energyCost, + totalCost: costMetrics.totalCost, + roi: costMetrics.roi, + valueAdded: costMetrics.valueAdded, + costPerUnit: partsProcessed > 0 ? costMetrics.totalCost / partsProcessed : 0, + }, + + // Add energy metrics + energyMetrics: { + energyConsumed: energyMetrics.energyConsumed, + energyEfficiency: energyMetrics.energyEfficiency, + carbonFootprint: energyMetrics.carbonFootprint, + powerUsage: energyMetrics.powerUsage, + energyCost: energyMetrics.energyCost, + energyPerUnit: partsProcessed > 0 ? energyMetrics.energyConsumed / partsProcessed : 0, + }, + + historicalData: historicalDataRef.current[machine.modelUuid] || [], }; }, - [analysis] + [materials, analysis] ); // ============================================================================ - // STORAGE ANALYSIS + // ENHANCED STORAGE ANALYSIS // ============================================================================ const analyzeStorage = useCallback( (storage: StorageUnitStatus): StorageAnalysis => { - const timeMetrics = calculateTimeMetrics(storage.idleTime || 0, storage.activeTime || 0, storage.modelUuid); + const errorCount = errorCountsRef.current[storage.modelUuid] || 0; + const timeMetrics = calculateAdvancedTimeMetrics(storage.idleTime || 0, storage.activeTime || 0, storage.modelUuid, errorCount); const currentLoad = storage.currentLoad || 0; const capacity = storage.storageCapacity || 1; @@ -749,14 +804,37 @@ function Analyzer() { 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 + const qualityMetrics = calculateQualityMetrics(storage.modelUuid, totalOps, errorCount); - // 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); + // Calculate turnover rate + const turnoverRate = timeMetrics.totalTime > 0 ? (totalOps / timeMetrics.totalTime) * 3600 : 0; + + // Occupancy trends + const timestamp = new Date().toISOString(); + if (!historicalDataRef.current[storage.modelUuid]) { + historicalDataRef.current[storage.modelUuid] = []; + } + historicalDataRef.current[storage.modelUuid].push({ + timestamp, + currentLoad, + utilizationRate, + operation: storeOps > retrieveOps ? "store" : "retrieve", + totalOps, + state: storage.state, + }); + + // Calculate peak occupancy from historical data + const occupancyData = historicalDataRef.current[storage.modelUuid] || []; + const peakOccupancy = occupancyData.length > 0 ? Math.max(...occupancyData.map((d) => d.utilizationRate)) : utilizationRate; + const averageOccupancy = occupancyData.length > 0 ? occupancyData.reduce((sum, d) => sum + d.utilizationRate, 0) / occupancyData.length : utilizationRate; + + // Calculate occupancy trends for the last hour + const hourAgo = Date.now() - 3600000; + const recentOccupancy = occupancyData.filter((d) => new Date(d.timestamp).getTime() > hourAgo).map((d) => d.utilizationRate); + const occupancyTrend = recentOccupancy.length > 1 ? ((recentOccupancy[recentOccupancy.length - 1] - recentOccupancy[0]) / recentOccupancy[0]) * 100 : 0; + + const costMetrics = calculateCostMetrics(storage.modelUuid, "storage", storage.activeTime || 0, totalOps); + const energyMetrics = calculateEnergyMetrics("storage", storage.activeTime || 0); return { assetId: storage.modelUuid, @@ -775,14 +853,17 @@ function Analyzer() { ...timeMetrics, idleTime: storage.idleTime || 0, activeTime: storage.activeTime || 0, - totalTime, + totalTime: timeMetrics.totalTime, + availability: timeMetrics.uptime, + reliability: timeMetrics.reliability, }, capacityMetrics: { utilizationRate, - averageOccupancy: utilizationRate, + averageOccupancy, peakOccupancy, turnoverRate, + occupancyTrend, }, throughput: { @@ -793,42 +874,71 @@ function Analyzer() { storeOperations: storeOps, retrieveOperations: retrieveOps, totalOperations: totalOps, + throughputEfficiency: (totalOps / Math.max(1, capacity * 24)) * 100, + wip: currentLoad, + bottleneckIndex: utilizationRate > 0.9 ? 1 : 0, }, efficiency: { - overallEffectiveness: calculateOEE(timeMetrics.uptime, utilizationRate, 100), + overallEffectiveness: calculateOEE(timeMetrics.uptime, utilizationRate, qualityMetrics.firstPassYield), availability: timeMetrics.uptime, performance: utilizationRate, - quality: 100, + quality: qualityMetrics.firstPassYield, 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), + quality: qualityMetrics, + + // Add cost metrics + costMetrics: { + operatingCost: costMetrics.operatingCost, + maintenanceCost: costMetrics.maintenanceCost, + energyCost: energyMetrics.energyCost, + totalCost: costMetrics.totalCost, + roi: costMetrics.roi, + valueAdded: costMetrics.valueAdded, + costPerUnit: totalOps > 0 ? costMetrics.totalCost / totalOps : 0, + costPerStorageHour: capacity > 0 ? costMetrics.totalCost / (capacity * (timeMetrics.totalTime / 3600)) : 0, }, - occupancyTrends: [], - historicalData: (analysis?.assets.find((a) => a.assetId === storage.modelUuid)?.historicalData || []) as StorageAnalysis["historicalData"], + // Add energy metrics + energyMetrics: { + energyConsumed: energyMetrics.energyConsumed, + energyEfficiency: energyMetrics.energyEfficiency, + carbonFootprint: energyMetrics.carbonFootprint, + powerUsage: energyMetrics.powerUsage, + energyCost: energyMetrics.energyCost, + energyPerUnit: totalOps > 0 ? energyMetrics.energyConsumed / totalOps : 0, + }, + + occupancyTrends: occupancyData.slice(-100).map((d) => ({ + timestamp: d.timestamp, + occupancy: d.currentLoad, + utilizationRate: d.utilizationRate, + })), + + historicalData: historicalDataRef.current[storage.modelUuid] || [], }; }, [analysis] ); // ============================================================================ - // HUMAN ANALYSIS + // ENHANCED HUMAN ANALYSIS // ============================================================================ const analyzeHuman = useCallback( (human: HumanStatus): HumanAnalysis => { - const timeMetrics = calculateTimeMetrics(human.idleTime || 0, human.activeTime || 0, human.modelUuid); + const errorCount = errorCountsRef.current[human.modelUuid] || 0; + const timeMetrics = calculateAdvancedTimeMetrics(human.idleTime || 0, human.activeTime || 0, human.modelUuid, errorCount); - const totalTime = (human.idleTime || 0) + (human.activeTime || 0); const actionsCompleted = completedActionsRef.current[human.modelUuid] || 0; + const distanceTraveled = human.distanceTraveled || 0; + const currentLoad = human.currentLoad || 0; + const loadCapacity = human.point?.actions?.[0]?.loadCapacity || 1; + const loadUtilization = (currentLoad / loadCapacity) * 100; - // Calculate workload distribution + // Workload distribution calculations const workerActions = completedActionsRef.current[`${human.modelUuid}_worker`] || 0; const manufacturerActions = completedActionsRef.current[`${human.modelUuid}_manufacturer`] || 0; const operatorActions = completedActionsRef.current[`${human.modelUuid}_operator`] || 0; @@ -839,13 +949,41 @@ function Analyzer() { const operatorTime = completedActionsRef.current[`${human.modelUuid}_operator_time`] || 0; const assemblerTime = completedActionsRef.current[`${human.modelUuid}_assembler_time`] || 0; + const totalActionTime = workerTime + manufacturerTime + operatorTime + assemblerTime; + 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 }, + { actionType: "worker", count: workerActions, totalTime: workerTime, percentage: totalActionTime > 0 ? (workerTime / totalActionTime) * 100 : 0 }, + { actionType: "manufacturer", count: manufacturerActions, totalTime: manufacturerTime, percentage: totalActionTime > 0 ? (manufacturerTime / totalActionTime) * 100 : 0 }, + { actionType: "operator", count: operatorActions, totalTime: operatorTime, percentage: totalActionTime > 0 ? (operatorTime / totalActionTime) * 100 : 0 }, + { actionType: "assembler", count: assemblerActions, totalTime: assemblerTime, percentage: totalActionTime > 0 ? (assemblerTime / totalActionTime) * 100 : 0 }, ].filter((w) => w.count > 0); + // Performance calculations + const idealActionsPerHour = 60; // 60 actions per hour ideal + const actualActionsPerHour = timeMetrics.totalTime > 0 ? (actionsCompleted / timeMetrics.totalTime) * 3600 : 0; + const performanceRate = Math.min((actualActionsPerHour / idealActionsPerHour) * 100, 100); + + const qualityMetrics = calculateQualityMetrics(human.modelUuid, actionsCompleted, errorCount); + + const costMetrics = calculateCostMetrics(human.modelUuid, "human", human.activeTime || 0, actionsCompleted); + const energyMetrics = calculateEnergyMetrics("human", human.activeTime || 0); + + // Update historical data + const timestamp = new Date().toISOString(); + const newEntry = { + timestamp, + actionType: human.currentAction?.actionName || "unknown", + duration: timeMetrics.totalTime, + distanceTraveled, + isActive: human.isActive, + state: human.state, + load: currentLoad, + performance: performanceRate, + }; + + const currentData = historicalDataRef.current[human.modelUuid] || []; + historicalDataRef.current[human.modelUuid] = [...currentData, newEntry].slice(-100); + return { assetId: human.modelUuid, assetName: human.modelName, @@ -857,253 +995,542 @@ function Analyzer() { currentPhase: human.currentPhase, state: human.state, speed: human.speed, - currentLoad: human.currentLoad, + currentLoad, currentMaterials: human.currentMaterials, currentAction: human.currentAction || null, + loadUtilization, }, timeMetrics: { ...timeMetrics, idleTime: human.idleTime || 0, activeTime: human.activeTime || 0, - totalTime, - scheduledTime: totalTime, + totalTime: timeMetrics.totalTime, + scheduledTime: timeMetrics.totalTime, + availability: timeMetrics.uptime, + reliability: timeMetrics.reliability, }, productivityMetrics: { actionsCompleted, - actionsPerHour: totalTime > 0 ? (actionsCompleted / totalTime) * 3600 : 0, - averageActionTime: actionsCompleted > 0 ? totalTime / actionsCompleted : 0, - distanceTraveled: human.distanceTraveled || 0, + actionsPerHour: actualActionsPerHour, + averageActionTime: actionsCompleted > 0 ? timeMetrics.totalTime / actionsCompleted : 0, + distanceTraveled, + averageSpeed: timeMetrics.totalTime > 0 ? distanceTraveled / timeMetrics.totalTime : 0, + loadEfficiency: loadUtilization, }, workloadDistribution, efficiency: { - overallEffectiveness: calculateOEE(timeMetrics.uptime, 100, 100), + overallEffectiveness: calculateOEE(timeMetrics.uptime, performanceRate, qualityMetrics.firstPassYield), availability: timeMetrics.uptime, - performance: 100, - quality: 100, - laborProductivity: totalTime > 0 ? actionsCompleted / (totalTime / 3600) : 0, + performance: performanceRate, + quality: qualityMetrics.firstPassYield, + laborProductivity: actualActionsPerHour, 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), + quality: qualityMetrics, + + // Add cost metrics + costMetrics: { + operatingCost: costMetrics.operatingCost, + maintenanceCost: costMetrics.maintenanceCost, + energyCost: energyMetrics.energyCost, + totalCost: costMetrics.totalCost, + roi: costMetrics.roi, + valueAdded: costMetrics.valueAdded, + costPerAction: actionsCompleted > 0 ? costMetrics.totalCost / actionsCompleted : 0, + costPerHour: costMetrics.costPerHour, }, - historicalData: (analysis?.assets.find((a) => a.assetId === human.modelUuid)?.historicalData || []) as HumanAnalysis["historicalData"], + // Add energy metrics + energyMetrics: { + energyConsumed: energyMetrics.energyConsumed, + energyEfficiency: energyMetrics.energyEfficiency, + carbonFootprint: energyMetrics.carbonFootprint, + powerUsage: energyMetrics.powerUsage, + energyCost: energyMetrics.energyCost, + energyPerAction: actionsCompleted > 0 ? energyMetrics.energyConsumed / actionsCompleted : 0, + }, + + historicalData: historicalDataRef.current[human.modelUuid] || [], }; }, [analysis] ); // ============================================================================ - // CRANE ANALYSIS + // ENHANCED CRANE ANALYSIS // ============================================================================ - const analyzeCrane = useCallback((crane: CraneStatus): CraneAnalysis => { - const timeMetrics = calculateTimeMetrics(crane.idleTime || 0, crane.activeTime || 0, crane.modelUuid); + const analyzeCrane = useCallback( + (crane: CraneStatus): CraneAnalysis => { + const errorCount = errorCountsRef.current[crane.modelUuid] || 0; + const timeMetrics = calculateAdvancedTimeMetrics(crane.idleTime || 0, crane.activeTime || 0, crane.modelUuid, errorCount); - 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; + 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); + const qualityMetrics = calculateQualityMetrics(crane.modelUuid, loadsHandled, errorCount); - // Calculate lift metrics - const avgLiftHeight = totalLifts > 0 ? totalLiftHeight / totalLifts : 0; - const avgLoadsPerCycle = cyclesCompleted > 0 ? loadsHandled / cyclesCompleted : 0; + // Performance calculations + const idealCycleTime = 20; // 20 seconds ideal + const actualCycleTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0; + const idealThroughput = (3600 / idealCycleTime) * 1; // 1 load per cycle + const actualThroughput = cyclesCompleted > 0 ? (loadsHandled / timeMetrics.totalTime) * 3600 : 0; - // Lift success rate - const liftAttempts = totalLifts + (errorCountsRef.current[crane.modelUuid] || 0); - const liftSuccessRate = liftAttempts > 0 ? (totalLifts / liftAttempts) * 100 : 100; + const performance = calculatePerformanceMetrics(loadsHandled, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, idealCycleTime * cyclesCompleted, "crane"); - // Load utilization based on max pick up count - const maxPickUpCount = crane.point?.actions?.[0]?.maxPickUpCount || 1; - const loadUtilization = (avgLoadsPerCycle / maxPickUpCount) * 100; + // Lift metrics + const avgLiftHeight = totalLifts > 0 ? totalLiftHeight / totalLifts : 0; + const avgLoadsPerCycle = cyclesCompleted > 0 ? loadsHandled / cyclesCompleted : 0; - return { - assetId: crane.modelUuid, - assetName: crane.modelName, - assetType: "crane", + // Success rates + const liftAttempts = totalLifts + errorCount; + const liftSuccessRate = liftAttempts > 0 ? (totalLifts / liftAttempts) * 100 : 100; + const positioningAccuracy = liftSuccessRate * 0.98; // Slightly lower than success rate - 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, - }, + // Load utilization + const maxPickUpCount = crane.point?.actions?.[0]?.maxPickUpCount || 1; + const loadUtilization = (avgLoadsPerCycle / maxPickUpCount) * 100; - timeMetrics: { - ...timeMetrics, - idleTime: crane.idleTime || 0, - activeTime: crane.activeTime || 0, - totalTime, - averageCycleTime: actualCycleTime, - }, + const costMetrics = calculateCostMetrics(crane.modelUuid, "crane", crane.activeTime || 0, loadsHandled); + const energyMetrics = calculateEnergyMetrics("crane", crane.activeTime || 0); - 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, + // Movement efficiency calculation + const movementEfficiency = liftSuccessRate * 0.8 + positioningAccuracy * 0.2; // Weighted average + + // Update historical data + const timestamp = new Date().toISOString(); + const newEntry = { + timestamp, + cycleTime: actualCycleTime, loadsHandled, - averageLoadsPerCycle: avgLoadsPerCycle, - }, - - movementMetrics: { - totalLifts, - averageLiftHeight: avgLiftHeight, - movementEfficiency: liftSuccessRate, - }, - - efficiency: { - overallEffectiveness: calculateOEE(timeMetrics.uptime, cycleTimeEfficiency, liftSuccessRate), - availability: timeMetrics.uptime, - performance: cycleTimeEfficiency, - quality: liftSuccessRate, + isActive: crane.isActive, + state: crane.state, + isCarrying: crane.isCarrying, loadUtilization, - cycleTimeEfficiency, - }, + performance: performance.performanceRate, + }; - 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, - }, + const currentData = historicalDataRef.current[crane.modelUuid] || []; + historicalDataRef.current[crane.modelUuid] = [...currentData, newEntry].slice(-100); - historicalData: [], - }; - }, []); + 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, + loadUtilization, + }, + + timeMetrics: { + ...timeMetrics, + idleTime: crane.idleTime || 0, + activeTime: crane.activeTime || 0, + totalTime: timeMetrics.totalTime, + averageCycleTime: actualCycleTime, + availability: timeMetrics.uptime, + reliability: timeMetrics.reliability, + }, + + throughput: { + itemsPerHour: actualThroughput, + itemsPerDay: actualThroughput * 24, + materialFlowRate: loadsHandled > 0 ? loadsHandled / (timeMetrics.totalTime / 60) : 0, + capacityUtilization: timeMetrics.utilizationRate * 100, + cyclesCompleted, + loadsHandled, + averageLoadsPerCycle: avgLoadsPerCycle, + throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, + wip: crane.currentLoad, + bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, + }, + + movementMetrics: { + totalLifts, + averageLiftHeight: avgLiftHeight, + movementEfficiency, + loadEfficiency: loadUtilization, + cycleEfficiency: performance.timeEfficiency, + }, + + efficiency: { + overallEffectiveness: calculateOEE(timeMetrics.uptime, performance.performanceRate, qualityMetrics.firstPassYield), + availability: timeMetrics.uptime, + performance: performance.performanceRate, + quality: qualityMetrics.firstPassYield, + loadUtilization, + cycleTimeEfficiency: performance.timeEfficiency, + }, + + quality: { + ...qualityMetrics, + liftSuccessRate, + positioningAccuracy, + }, + + // Add cost metrics + costMetrics: { + operatingCost: costMetrics.operatingCost, + maintenanceCost: costMetrics.maintenanceCost, + energyCost: energyMetrics.energyCost, + totalCost: costMetrics.totalCost, + roi: costMetrics.roi, + valueAdded: costMetrics.valueAdded, + costPerLift: totalLifts > 0 ? costMetrics.totalCost / totalLifts : 0, + costPerCycle: cyclesCompleted > 0 ? costMetrics.totalCost / cyclesCompleted : 0, + }, + + // Add energy metrics + energyMetrics: { + energyConsumed: energyMetrics.energyConsumed, + energyEfficiency: energyMetrics.energyEfficiency, + carbonFootprint: energyMetrics.carbonFootprint, + powerUsage: energyMetrics.powerUsage, + energyCost: energyMetrics.energyCost, + energyPerLift: totalLifts > 0 ? energyMetrics.energyConsumed / totalLifts : 0, + }, + + historicalData: historicalDataRef.current[crane.modelUuid] || [], + }; + }, + [analysis] + ); // ============================================================================ - // SYSTEM-WIDE ANALYSIS + // COMPREHENSIVE 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 analyzeSystem = useCallback( + (allAssets: AssetAnalysis[]): AnalysisSchema => { + // System-wide calculations + const totalAssets = allAssets.length; + const activeAssets = allAssets.filter((a) => a.currentStatus.isActive).length; + const assetsInError = allAssets.filter((a) => a.currentStatus.state === "error").length; + const assetsIdle = allAssets.filter((a) => !a.currentStatus.isActive && a.currentStatus.state === "idle").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); + // Calculate system OEE (weighted average by throughput) + const totalThroughput = allAssets.reduce((sum, asset) => { + if ("throughput" in asset) { + return sum + asset.throughput.itemsPerHour; + } else if ("productivityMetrics" in asset) { + return sum + asset.productivityMetrics.actionsPerHour; + } + return sum; + }, 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; - }; + const weightedOEE = allAssets.reduce((sum, asset) => { + const weight = ("throughput" in asset ? asset.throughput.itemsPerHour : "productivityMetrics" in asset ? asset.productivityMetrics.actionsPerHour : 1) / Math.max(1, totalThroughput); + return sum + asset.efficiency.overallEffectiveness * weight; + }, 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); + // Calculate system utilization (average of all assets) + const avgUtilization = + allAssets.reduce((sum, a) => { + if ("timeMetrics" in a) { + return sum + a.timeMetrics.utilizationRate; + } + return sum; + }, 0) / totalAssets; - 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), - })); + // Calculate total idle and active time + const totalIdleTime = allAssets.reduce((sum, a) => { + if ("timeMetrics" in a) { + return sum + a.timeMetrics.idleTime; + } + return sum; + }, 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); + const totalActiveTime = allAssets.reduce((sum, a) => { + if ("timeMetrics" in a) { + return sum + a.timeMetrics.activeTime; + } + return sum; + }, 0); - return { - assets: allAssets, + const totalSystemTime = totalIdleTime + totalActiveTime; + const systemUptime = totalSystemTime > 0 ? (totalActiveTime / totalSystemTime) * 100 : 0; - systemPerformance: { - overallOEE: avgOEE, - systemThroughput: allAssets.reduce((sum, a) => sum + getThroughput(a), 0), - systemUtilization: avgUtilization * 100, - assetTypePerformance, - criticalMetrics: { - activeAssets, - totalAssets, - assetsInError, - averageIdleTime: avgIdleTime, - totalDowntime, + // Calculate total costs and value + const totalCost = allAssets.reduce((sum, a) => { + if ("costMetrics" in a) { + return sum + a.costMetrics.totalCost; + } + return sum; + }, 0); + + const totalValueAdded = allAssets.reduce((sum, a) => { + if ("costMetrics" in a) { + return sum + a.costMetrics.valueAdded; + } + return sum; + }, 0); + + const totalEnergyConsumed = allAssets.reduce((sum, a) => { + if ("energyMetrics" in a) { + return sum + a.energyMetrics.energyConsumed; + } + return sum; + }, 0); + + // Group by asset type for detailed analysis + 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]) => { + const typeThroughput = assets.reduce((sum, a) => { + if ("throughput" in a) { + return sum + a.throughput.itemsPerHour; + } else if ("productivityMetrics" in a) { + return sum + a.productivityMetrics.actionsPerHour; + } + return sum; + }, 0); + + const typeOEE = assets.reduce((sum, a) => sum + a.efficiency.overallEffectiveness, 0) / assets.length; + const typeUtilization = + assets.reduce((sum, a) => { + if ("timeMetrics" in a) { + return sum + a.timeMetrics.utilizationRate; + } + return sum; + }, 0) / assets.length; + + const typeCost = assets.reduce((sum, a) => { + if ("costMetrics" in a) { + return sum + a.costMetrics.totalCost; + } + return sum; + }, 0); + + return { + assetType: type, + count: assets.length, + averageOEE: typeOEE, + averageUtilization: typeUtilization * 100, + totalThroughput: typeThroughput, + totalCost: typeCost, + throughputPerAsset: typeThroughput / assets.length, + costPerAsset: typeCost / assets.length, + }; + }); + + // Material Flow Analysis + const totalMaterialsInSystem = materials.filter((m) => m.isActive).length; + const completedMaterials = materialHistoryRef.current.length; + const averageResidenceTime = + materialHistoryRef.current.length > 0 + ? materialHistoryRef.current.reduce((sum, entry) => { + const residenceTime = new Date(entry.removedAt).getTime() - (entry.material.startTime || 0); + return sum + (residenceTime || 0); + }, 0) / materialHistoryRef.current.length + : 0; + + // Bottleneck Identification + const bottlenecks = allAssets + .map((asset) => { + let utilizationRate = 0; + let queueLength = 0; + + if ("timeMetrics" in asset) { + utilizationRate = asset.timeMetrics.utilizationRate; + } + + if ("currentStatus" in asset && "queueLength" in asset.currentStatus) { + queueLength = asset.currentStatus.queueLength as number; + } + + const impactScore = utilizationRate * 100 + queueLength * 10; + + let severity: "critical" | "high" | "medium" | "low" = "low"; + if (utilizationRate > 0.95) severity = "critical"; + else if (utilizationRate > 0.9) severity = "high"; + else if (utilizationRate > 0.85) severity = "medium"; + + return { + assetId: asset.assetId, + assetName: asset.assetName, + severity, + utilizationRate: utilizationRate * 100, + queueLength, + impactScore, + }; + }) + .filter((b) => b.utilizationRate > 80 || b.queueLength > 5) + .sort((a, b) => b.impactScore - a.impactScore); + + // Queue Analysis + const queueLengths = allAssets + .filter((asset) => "currentStatus" in asset && "queueLength" in asset.currentStatus) + .map((asset) => ({ + assetId: asset.assetId, + assetName: asset.assetName, + queueLength: (asset.currentStatus as any).queueLength || 0, + averageWaitTime: 0, // Could be calculated with more data + })) + .filter((q) => q.queueLength > 0); + + // Flow Continuity Analysis + const throughputOverTime = Object.values(historicalDataRef.current) + .flat() + .filter((d) => d.timestamp) + .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + + const flowRates = throughputOverTime.map((d) => d.throughput || d.actionsPerHour || 0); + const meanFlowRate = flowRates.length > 0 ? flowRates.reduce((sum, rate) => sum + rate, 0) / flowRates.length : 0; + const variance = flowRates.length > 0 ? flowRates.reduce((sum, rate) => sum + Math.pow(rate - meanFlowRate, 2), 0) / flowRates.length : 0; + const varianceCoefficient = meanFlowRate > 0 ? Math.sqrt(variance) / meanFlowRate : 0; + + // Predictive Insights + const maintenanceAlerts = allAssets + .filter((asset) => { + const errorCount = errorCountsRef.current[asset.assetId] || 0; + const uptime = "timeMetrics" in asset ? asset.timeMetrics.uptime : 100; + return uptime < 95 || errorCount > 3; + }) + .map((asset) => ({ + assetId: asset.assetId, + assetName: asset.assetName, + assetType: asset.assetType, + alertType: + (errorCountsRef.current[asset.assetId] || 0) > 5 ? ("critical" as const) : (errorCountsRef.current[asset.assetId] || 0) > 3 ? ("predictive" as const) : ("preventive" as const), + estimatedTimeToFailure: Math.max(0, 100 - ((asset.timeMetrics as any)?.reliability || 100)) * 1000, + confidence: Math.min(90 + (errorCountsRef.current[asset.assetId] || 0) * 2, 100), + recommendation: (errorCountsRef.current[asset.assetId] || 0) > 5 ? "Immediate maintenance required" : "Schedule preventive maintenance", + })); + + // Optimization Opportunities + const optimizationOpportunities = [ + ...bottlenecks + .filter((b) => b.severity === "critical" || b.severity === "high") + .map((b) => ({ + area: "throughput" as const, + description: `High utilization bottleneck at ${b.assetName} (${b.utilizationRate.toFixed(1)}% utilization)`, + potentialImprovement: Math.min(100 - b.utilizationRate, 20), + priority: b.severity === "critical" ? ("high" as const) : ("medium" as const), + })), + { + area: "efficiency" as const, + description: `System OEE at ${weightedOEE.toFixed(1)}% - target improvements available`, + potentialImprovement: Math.max(0, 85 - weightedOEE), + priority: weightedOEE < 80 ? ("high" as const) : ("medium" as const), }, - }, - - materialFlow: { - totalMaterialsInSystem: 0, - materialsCompleted: 0, - averageResidenceTime: 0, - queueLengths: [], - bottlenecks, - flowContinuity: { - overallFlowRate: 0, - varianceCoefficient: 0, - steadyStateDeviation: 0, + { + area: "cost" as const, + description: `Total system cost: $${totalCost.toFixed(2)}`, + potentialImprovement: Math.min(20, totalValueAdded > 0 ? (totalCost / totalValueAdded) * 100 : 0), + priority: totalValueAdded < totalCost ? ("high" as const) : ("medium" as const), }, - }, + ]; - predictiveInsights: { - maintenanceAlerts: [], - optimizationOpportunities: [], - trendAnalysis: [], - }, + // Trend Analysis + const trendAnalysis = assetTypePerformance.map((type) => { + const historicalTypeData = allAssets + .filter((a) => a.assetType === type.assetType) + .flatMap((a) => historicalDataRef.current[a.assetId] || []) + .filter((d) => d.performance !== undefined); - analysisTimeRange: { - startTime: startTimeRef.current, - endTime: new Date().toISOString(), - duration: Date.now() - new Date(startTimeRef.current).getTime(), - }, + const recentPerformance = historicalTypeData.slice(-10).map((d) => d.performance); + const olderPerformance = historicalTypeData.slice(-20, -10).map((d) => d.performance); - metadata: { - lastUpdated: new Date().toISOString(), - dataPoints: allAssets.length, - analysisVersion: "1.0.0", - }, - }; - }, []); + const recentAvg = recentPerformance.length > 0 ? recentPerformance.reduce((sum, p) => sum + p, 0) / recentPerformance.length : 0; + const olderAvg = olderPerformance.length > 0 ? olderPerformance.reduce((sum, p) => sum + p, 0) / olderPerformance.length : 0; + + let trend: "increasing" | "decreasing" | "stable" = "stable"; + let changeRate = 0; + + if (olderAvg > 0 && recentAvg > 0) { + changeRate = ((recentAvg - olderAvg) / olderAvg) * 100; + if (Math.abs(changeRate) > 5) { + trend = changeRate > 0 ? "increasing" : "decreasing"; + } + } + + return { + metric: `${type.assetType} Performance`, + trend, + changeRate, + forecast: [recentAvg * 1.02, recentAvg * 1.01, recentAvg], // Simple forecast + }; + }); + + return { + assets: allAssets, + + systemPerformance: { + overallOEE: weightedOEE, + systemThroughput: totalThroughput, + systemUtilization: avgUtilization * 100, + assetTypePerformance, + criticalMetrics: { + activeAssets, + totalAssets, + assetsInError, + assetsIdle, + averageIdleTime: totalIdleTime / totalAssets, + totalDowntime: totalIdleTime, + systemUptime, + }, + }, + + materialFlow: { + totalMaterialsInSystem, + materialsCompleted: completedMaterials, + averageResidenceTime, + queueLengths, + bottlenecks: bottlenecks.slice(0, 10), // Top 10 bottlenecks + flowContinuity: { + overallFlowRate: meanFlowRate, + varianceCoefficient, + steadyStateDeviation: varianceCoefficient > 0.3 ? varianceCoefficient * 100 : 0, + }, + }, + + predictiveInsights: { + maintenanceAlerts: maintenanceAlerts.slice(0, 5), // Top 5 alerts + optimizationOpportunities: optimizationOpportunities.slice(0, 5), // Top 5 opportunities + trendAnalysis: trendAnalysis.filter((t) => t.trend !== "stable" || Math.abs(t.changeRate) > 1), + }, + + analysisTimeRange: { + startTime: startTimeRef.current, + endTime: new Date().toISOString(), + duration: Date.now() - new Date(startTimeRef.current).getTime(), + }, + + metadata: { + lastUpdated: new Date().toISOString(), + dataPoints: allAssets.length + materials.length + materialHistoryRef.current.length, + analysisVersion: "1.0.0", + totalCost, + totalValueAdded, + totalEnergyConsumed, + energyCost: totalEnergyConsumed * 0.12, // $0.12 per kWh + carbonFootprint: totalEnergyConsumed * 0.5, // 0.5 kg CO2 per kWh + }, + }; + }, + [materials] + ); // ============================================================================ // MAIN ANALYSIS FUNCTION @@ -1197,7 +1624,7 @@ function Analyzer() { clearInterval(analysisIntervalRef.current); } }; - }, [performAnalysis, isPlaying]); + }, [isPlaying, conveyors, vehicles, armBots, machines, humans, cranes, materials]); return null; } diff --git a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx index e8d41d3..23be664 100644 --- a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx +++ b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx @@ -1,6 +1,8 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { useFrame } from "@react-three/fiber"; import { useSceneContext } from "../../../../scene/sceneContext"; +import { usePauseButtonStore } from "../../../../../store/ui/usePlayButtonStore"; +import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore"; // import { findConveyorSubsequence } from '../../../simulator/functions/getConveyorSequencesInProduct'; @@ -8,7 +10,70 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { const { materialStore, conveyorStore, productStore } = useSceneContext(); const { getProductById, selectedProduct } = productStore(); const { getMaterialsByCurrentModelUuid } = materialStore(); - const { setConveyorPaused } = conveyorStore(); + const { setConveyorPaused, conveyors, incrementActiveTime, incrementIdleTime } = conveyorStore(); + const { isPlaying } = usePlayButtonStore(); + + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const isSpeedRef = useRef(0); + const isPausedRef = useRef(false); + + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + + if (!conveyor.isPaused) { + if (!isPausedRef.current) { + activeTimeRef.current += deltaTime * isSpeedRef.current; + } + } else { + if (!isPausedRef.current) { + idleTimeRef.current += deltaTime * isSpeedRef.current; + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return; + if (!conveyor.isPaused) { + const roundedActiveTime = Math.round(activeTimeRef.current); + incrementActiveTime(conveyor.modelUuid, roundedActiveTime); + activeTimeRef.current = 0; + } else { + const roundedIdleTime = Math.round(idleTimeRef.current); + incrementIdleTime(conveyor.modelUuid, roundedIdleTime); + idleTimeRef.current = 0; + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } + }; + }, [conveyor, conveyors, isPlaying]); useFrame(() => { const product = getProductById(selectedProduct.productUuid); diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index ce8ed51..e1da240 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,6 +1,6 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import * as THREE from "three"; -import { usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore"; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore"; import { useSceneContext } from "../../../../scene/sceneContext"; import { useTriggerHandler } from "../../../triggers/triggerHandler/useTriggerHandler"; @@ -13,7 +13,7 @@ function PillarJibInstance({ crane }: { readonly crane: CraneStatus }) { const { craneStore, productStore, humanStore, assetStore } = useSceneContext(); const { triggerPointActions } = useTriggerHandler(); const { getActionByUuid, selectedProduct } = productStore(); - const { setCurrentPhase, setCraneActive, setIsCaryying, removeCurrentAction, removeLastMaterial, decrementCraneLoad } = craneStore(); + const { cranes, setCurrentPhase, setCraneActive, setIsCaryying, removeCurrentAction, removeLastMaterial, decrementCraneLoad, incrementActiveTime, incrementIdleTime } = craneStore(); const { setCurrentPhase: setCurrentPhaseHuman, setHumanActive, setHumanState, getHumanById } = humanStore(); const { setCurrentAnimation, getAssetById } = assetStore(); const [animationPhase, setAnimationPhase] = useState("idle"); @@ -24,6 +24,68 @@ function PillarJibInstance({ crane }: { readonly crane: CraneStatus }) { const humanAsset = getAssetById(humanId || ""); const humanAction = getActionByUuid(selectedProduct.productUuid, actionTriggers?.[0]?.triggeredAsset?.triggeredAction?.actionUuid ?? ""); + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const isSpeedRef = useRef(0); + const isPausedRef = useRef(false); + + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + + if (crane.isActive) { + if (!isPausedRef.current) { + activeTimeRef.current += deltaTime * isSpeedRef.current; + } + } else { + if (!isPausedRef.current) { + idleTimeRef.current += deltaTime * isSpeedRef.current; + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return; + if (crane.isActive) { + const roundedActiveTime = Math.round(activeTimeRef.current); + incrementActiveTime(crane.modelUuid, roundedActiveTime); + activeTimeRef.current = 0; + } else { + const roundedIdleTime = Math.round(idleTimeRef.current); + incrementIdleTime(crane.modelUuid, roundedIdleTime); + idleTimeRef.current = 0; + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } + }; + }, [crane, cranes, isPlaying]); + useEffect(() => { if (isPlaying) { const human = getHumanById(humanId || ""); @@ -42,7 +104,13 @@ function PillarJibInstance({ crane }: { readonly crane: CraneStatus }) { setHumanState(humanId, "running"); setCurrentAnimation(humanId, "working_standing", true, false, false); } - } else if (crane.currentPhase === "dropping" && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && crane.isCarrying && human.currentPhase === "hooking") { + } else if ( + crane.currentPhase === "dropping" && + crane.currentMaterials.length > 0 && + action.maxPickUpCount <= crane.currentMaterials.length && + crane.isCarrying && + human.currentPhase === "hooking" + ) { setCurrentPhaseHuman(humanId, "loadPoint-unloadPoint"); } else if (human.state === "running" && human.currentPhase === "unhooking") { if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted) { @@ -83,7 +151,15 @@ function PillarJibInstance({ crane }: { readonly crane: CraneStatus }) { return ( <> - + diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index c3fb1dd..ca9647f 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -11,7 +11,7 @@ function HumanInstance({ human }: { readonly human: HumanStatus }) { const { isPlaying } = usePlayButtonStore(); const { humanStore, productStore } = useSceneContext(); const { getActionByUuid, selectedProduct } = productStore(); - const { incrementIdleTime, incrementActiveTime } = humanStore(); + const { incrementIdleTime, incrementActiveTime, humans } = humanStore(); const idleTimeRef = useRef(0); const activeTimeRef = useRef(0); @@ -73,7 +73,7 @@ function HumanInstance({ human }: { readonly human: HumanStatus }) { animationFrameIdRef.current = null; } }; - }, [human, isPlaying]); + }, [human, humans, isPlaying]); return ( <> diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 9710618..8b697ce 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -19,7 +19,7 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, assetStore } = useSceneContext(); const { resetAsset } = assetStore(); - const { setArmBotActive, setArmBotState, removeCurrentAction, incrementActiveTime, incrementIdleTime } = armBotStore(); + const { setArmBotActive, setArmBotState, removeCurrentAction, incrementActiveTime, incrementIdleTime, armBots } = armBotStore(); const { decrementVehicleLoad, removeLastMaterial } = vehicleStore(); const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = storageUnitStore(); const { getMaterialById, setIsVisible } = materialStore(); @@ -186,7 +186,7 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { animationFrameIdRef.current = null; // Reset the animation frame ID } }; - }, [armBot, currentPhase, isPlaying]); + }, [armBot, armBots, currentPhase, isPlaying]); useEffect(() => { const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone); @@ -320,7 +320,15 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { {!isReset && isPlaying && ( <> - + )} diff --git a/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx b/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx index 4fcc093..fc4eb19 100644 --- a/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx +++ b/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx @@ -1,15 +1,80 @@ -import { useEffect } from 'react' -import MaterialAnimator from '../animator/MaterialAnimator' +import { useEffect, useRef } from "react"; +import MaterialAnimator from "../animator/MaterialAnimator"; +import { useSceneContext } from "../../../../scene/sceneContext"; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore"; function StorageUnitInstance({ storageUnit }: Readonly<{ storageUnit: StorageUnitStatus }>) { + const { storageUnitStore } = useSceneContext(); + const { storageUnits, incrementActiveTime, incrementIdleTime } = storageUnitStore(); + const { isPlaying } = usePlayButtonStore(); + + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const isSpeedRef = useRef(0); + const isPausedRef = useRef(false); + + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + + if (storageUnit.isActive) { + if (!isPausedRef.current) { + activeTimeRef.current += deltaTime * isSpeedRef.current; + } + } else { + if (!isPausedRef.current) { + idleTimeRef.current += deltaTime * isSpeedRef.current; + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return; + if (storageUnit.isActive) { + const roundedActiveTime = Math.round(activeTimeRef.current); + incrementActiveTime(storageUnit.modelUuid, roundedActiveTime); + activeTimeRef.current = 0; + } else { + const roundedIdleTime = Math.round(idleTimeRef.current); + incrementIdleTime(storageUnit.modelUuid, roundedIdleTime); + idleTimeRef.current = 0; + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } + }; + }, [storageUnit, storageUnits, isPlaying]); useEffect(() => { // console.log('storageUnit: ', storageUnit); - }, [storageUnit]) + }, [storageUnit]); - return ( - - ) + return ; } -export default StorageUnitInstance \ No newline at end of file +export default StorageUnitInstance; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index e84ba61..e04ae4f 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -736,6 +736,14 @@ interface RoboticArmAnalysis { placeAccuracy: number; }; + // Cost + costMetrics: CostMetrics; + + // Energy + energyMetrics: EnergyMetrics & { + energyPerUnit: number; // Energy per item processed + }; + // Historical Data historicalData: { timestamp: string; @@ -784,6 +792,17 @@ interface MachineAnalysis { quality: QualityMetrics & { defectRate: number; reworkRate: number; + scrapRate: number; + }; + + // Cost + costMetrics: CostMetrics & { + costPerUnit: number; // Cost per item processed + }; + + // Energy + energyMetrics: EnergyMetrics & { + energyPerUnit: number; // Energy per item processed }; // Historical Data @@ -821,6 +840,7 @@ interface StorageAnalysis { averageOccupancy: number; peakOccupancy: number; turnoverRate: number; // Store/retrieve frequency + occupancyTrend: number; // Change in occupancy over time }; // Throughput @@ -838,6 +858,17 @@ interface StorageAnalysis { // Quality quality: QualityMetrics; + // Cost + costMetrics: CostMetrics & { + costPerUnit: number; // Cost per item processed + costPerStorageHour: number; // Cost per hour of storage + }; + + // Energy + energyMetrics: EnergyMetrics & { + energyPerUnit: number; // Energy per item processed + }; + // Occupancy Trends occupancyTrends: { timestamp: string; @@ -871,6 +902,7 @@ interface HumanAnalysis { actionUuid: string; actionName: string; } | null; + loadUtilization: number; }; // Time Metrics @@ -887,6 +919,8 @@ interface HumanAnalysis { actionsPerHour: number; averageActionTime: number; distanceTraveled: number; + averageSpeed: number; + loadEfficiency: number; // Current load / max capacity }; // Workload Distribution @@ -906,6 +940,17 @@ interface HumanAnalysis { // Quality quality: QualityMetrics; + // Cost + costMetrics: CostMetrics & { + costPerAction: number; // Cost per action performed + costPerHour: number; // Cost per hour of labor + }; + + // Energy + energyMetrics: EnergyMetrics & { + energyPerAction: number; // Energy per action performed + }; + // Historical Data historicalData: { timestamp: string; @@ -935,6 +980,7 @@ interface CraneAnalysis { materialType: string | null; materialId: string | null; } | null; + loadUtilization: number; }; // Time Metrics @@ -957,6 +1003,8 @@ interface CraneAnalysis { totalLifts: number; averageLiftHeight: number; movementEfficiency: number; + loadEfficiency: number; + cycleEfficiency: number; }; // Efficiency @@ -971,6 +1019,17 @@ interface CraneAnalysis { positioningAccuracy: number; }; + // Cost + costMetrics: CostMetrics & { + costPerLift: number; // Cost per lift operation + costPerCycle: number; // Cost per operational cycle + }; + + // Energy + energyMetrics: EnergyMetrics & { + energyPerLift: number; // Energy per lift operation + }; + // Historical Data historicalData: { timestamp: string; @@ -1033,8 +1092,10 @@ interface SystemPerformance { activeAssets: number; totalAssets: number; assetsInError: number; + assetsIdle: number; averageIdleTime: number; totalDowntime: number; + systemUptime: number; }; } @@ -1095,5 +1156,10 @@ interface AnalysisSchema { lastUpdated: string; dataPoints: number; analysisVersion: string; + totalCost: number; + totalValueAdded: number; + totalEnergyConsumed: number; + energyCost: number; + carbonFootprint: number; }; }