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