feat: implement simulation analyzer for detailed simulation performance and flow analysis.

This commit is contained in:
2025-12-20 10:30:56 +05:30
parent e9d0a98a49
commit 14bf0896a7

View File

@@ -1,6 +1,6 @@
import { useEffect, useCallback, useRef } from "react"; import { useEffect, useCallback, useRef } from "react";
import { useSceneContext } from "../../scene/sceneContext"; import { useSceneContext } from "../../scene/sceneContext";
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
function Analyzer() { function Analyzer() {
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();
@@ -14,6 +14,7 @@ function Analyzer() {
const { cranes } = craneStore(); const { cranes } = craneStore();
const { storageUnits } = storageUnitStore(); const { storageUnits } = storageUnitStore();
const { materials, getMaterialsByModel } = materialStore(); const { materials, getMaterialsByModel } = materialStore();
const { speed } = useAnimationPlaySpeed();
const { setAnalysis, setAnalyzing, analysis } = analysisStore(); const { setAnalysis, setAnalyzing, analysis } = analysisStore();
@@ -222,6 +223,9 @@ function Analyzer() {
useEffect(() => { useEffect(() => {
if (!isPlaying) { if (!isPlaying) {
resetAllRefs(); resetAllRefs();
} else {
// Reset start time when simulation starts
startTimeRef.current = new Date().toISOString();
} }
}, [isPlaying]); }, [isPlaying]);
@@ -288,32 +292,35 @@ function Analyzer() {
const calculateMaterialFlowMetricsForAsset = (assetId: string) => { const calculateMaterialFlowMetricsForAsset = (assetId: string) => {
const materialsOnAsset = getMaterialsByModel(assetId).length; const materialsOnAsset = getMaterialsByModel(assetId).length;
const materialHistory = materialHistoryRef.current.filter((m) => m.material.current?.modelUuid === assetId || m.material.previous?.modelUuid === assetId);
// Use removals as the history of processed items
// This fixes the issue where items per hour was 0 because materialHistoryRef was empty
const removals = materialRemovalsRef.current[assetId] || [];
// Calculate flow metrics // Calculate flow metrics
const wip = materialsOnAsset; const wip = materialsOnAsset;
const throughput = (materialHistory.length / (Date.now() - new Date(startTimeRef.current).getTime())) * 1000;
// Calculate lead times // Calculate throughput (items per second)
const leadTimes = materialHistory // Ensure we don't divide by zero
.map((m) => { const durationMs = Date.now() - new Date(startTimeRef.current).getTime();
if (m.removedAt && m.material.startTime) {
return new Date(m.removedAt).getTime() - m.material.startTime; // Normalize by simulation speed to get "simulation time" throughput
} const currentSpeed = Math.max(1, speed);
return 0; const throughput = durationMs > 1000 ? ((removals.length / durationMs) * 1000) / currentSpeed : 0;
})
.filter((t) => t > 0); // Calculate lead times (processing times on this asset)
const leadTimes = removals.map((m) => m.processingTime || 0).filter((t) => t > 0);
const avgLeadTime = leadTimes.length > 0 ? leadTimes.reduce((sum, t) => sum + t, 0) / leadTimes.length : 0; const avgLeadTime = leadTimes.length > 0 ? leadTimes.reduce((sum, t) => sum + t, 0) / leadTimes.length : 0;
// Calculate velocity // Calculate velocity (turnover)
const velocity = materialHistory.length / Math.max(1, wip); const velocity = removals.length / Math.max(1, wip);
return { return {
wip, wip,
throughput, throughput,
avgLeadTime, avgLeadTime,
totalMaterialsProcessed: materialHistory.length, totalMaterialsProcessed: removals.length,
currentMaterials: materialsOnAsset, currentMaterials: materialsOnAsset,
avgCycleTime: avgLeadTime / 1000, avgCycleTime: avgLeadTime / 1000,
materialVelocity: velocity, materialVelocity: velocity,
@@ -670,7 +677,8 @@ function Analyzer() {
/** /**
* Update throughput snapshot for an asset * Update throughput snapshot for an asset
*/ */
const updateThroughputSnapshot = useCallback((assetId: string) => { const updateThroughputSnapshot = useCallback(
(assetId: string) => {
const timestamp = Date.now(); const timestamp = Date.now();
const timeWindow = 60; // 60 seconds const timeWindow = 60; // 60 seconds
@@ -682,7 +690,9 @@ function Analyzer() {
const removals = materialRemovalsRef.current[assetId] || []; const removals = materialRemovalsRef.current[assetId] || [];
const recentRemovals = removals.filter((r) => timestamp - r.timestamp <= timeWindow * 1000); const recentRemovals = removals.filter((r) => timestamp - r.timestamp <= timeWindow * 1000);
const itemsProcessed = recentRemovals.length; const itemsProcessed = recentRemovals.length;
const rate = (itemsProcessed / timeWindow) * 3600; // items per hour // Normalize by speed
const currentSpeed = Math.max(1, speed);
const rate = ((itemsProcessed / timeWindow) * 3600) / currentSpeed; // items per hour
throughputSnapshotsRef.current[assetId].push({ throughputSnapshotsRef.current[assetId].push({
timestamp, timestamp,
@@ -695,7 +705,9 @@ function Analyzer() {
if (throughputSnapshotsRef.current[assetId].length > 100) { if (throughputSnapshotsRef.current[assetId].length > 100) {
throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100); throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100);
} }
}, []); },
[speed]
);
/** /**
* Update performance snapshot for an asset * Update performance snapshot for an asset
@@ -948,7 +960,7 @@ function Analyzer() {
historicalData: historicalDataRef.current[conveyor.modelUuid] || [], historicalData: historicalDataRef.current[conveyor.modelUuid] || [],
}; };
}, },
[materials, analysis] [materials, analysis, speed]
); );
// ============================================================================ // ============================================================================
@@ -1099,7 +1111,7 @@ function Analyzer() {
historicalData: historicalDataRef.current[vehicle.modelUuid] || [], historicalData: historicalDataRef.current[vehicle.modelUuid] || [],
}; };
}, },
[materials, analysis] [materials, analysis, speed]
); );
// ============================================================================ // ============================================================================
@@ -1224,7 +1236,7 @@ function Analyzer() {
historicalData: historicalDataRef.current[armBot.modelUuid] || [], historicalData: historicalDataRef.current[armBot.modelUuid] || [],
}; };
}, },
[materials, analysis] [materials, analysis, speed]
); );
// ============================================================================ // ============================================================================
@@ -1349,7 +1361,7 @@ function Analyzer() {
historicalData: historicalDataRef.current[machine.modelUuid] || [], historicalData: historicalDataRef.current[machine.modelUuid] || [],
}; };
}, },
[materials, analysis] [materials, analysis, speed]
); );
// ============================================================================ // ============================================================================
@@ -1486,7 +1498,7 @@ function Analyzer() {
historicalData: historicalDataRef.current[storage.modelUuid] || [], historicalData: historicalDataRef.current[storage.modelUuid] || [],
}; };
}, },
[analysis] [analysis, speed]
); );
// ============================================================================ // ============================================================================
@@ -1624,7 +1636,7 @@ function Analyzer() {
historicalData: historicalDataRef.current[human.modelUuid] || [], historicalData: historicalDataRef.current[human.modelUuid] || [],
}; };
}, },
[analysis] [analysis, speed]
); );
// ============================================================================ // ============================================================================
@@ -1774,7 +1786,7 @@ function Analyzer() {
historicalData: historicalDataRef.current[crane.modelUuid] || [], historicalData: historicalDataRef.current[crane.modelUuid] || [],
}; };
}, },
[analysis] [analysis, speed]
); );
// ============================================================================ // ============================================================================