701 lines
26 KiB
TypeScript
701 lines
26 KiB
TypeScript
|
|
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;
|