Files
Dwinzo_Demo/app/src/modules/simulation/analyzer/analyzer.tsx

701 lines
26 KiB
TypeScript
Raw Normal View History

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<NodeJS.Timeout | null>(null);
const startTimeRef = useRef<string>(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<string, AssetAnalysis[]>);
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;