From 14bf0896a7c4b058e773caddbe7e8f6a2715e8ff Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 10:30:56 +0530 Subject: [PATCH 01/11] feat: implement simulation analyzer for detailed simulation performance and flow analysis. --- .../modules/simulation/analyzer/analyzer.tsx | 100 ++++++++++-------- 1 file changed, 56 insertions(+), 44 deletions(-) diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index b5c6544..df1f4da 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -1,6 +1,6 @@ import { useEffect, useCallback, useRef } from "react"; import { useSceneContext } from "../../scene/sceneContext"; -import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; +import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; function Analyzer() { const { isPlaying } = usePlayButtonStore(); @@ -14,6 +14,7 @@ function Analyzer() { const { cranes } = craneStore(); const { storageUnits } = storageUnitStore(); const { materials, getMaterialsByModel } = materialStore(); + const { speed } = useAnimationPlaySpeed(); const { setAnalysis, setAnalyzing, analysis } = analysisStore(); @@ -222,6 +223,9 @@ function Analyzer() { useEffect(() => { if (!isPlaying) { resetAllRefs(); + } else { + // Reset start time when simulation starts + startTimeRef.current = new Date().toISOString(); } }, [isPlaying]); @@ -288,32 +292,35 @@ function Analyzer() { const calculateMaterialFlowMetricsForAsset = (assetId: string) => { 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 const wip = materialsOnAsset; - const throughput = (materialHistory.length / (Date.now() - new Date(startTimeRef.current).getTime())) * 1000; - // 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); + // Calculate throughput (items per second) + // Ensure we don't divide by zero + const durationMs = Date.now() - new Date(startTimeRef.current).getTime(); + + // Normalize by simulation speed to get "simulation time" throughput + const currentSpeed = Math.max(1, speed); + const throughput = durationMs > 1000 ? ((removals.length / durationMs) * 1000) / currentSpeed : 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; - // Calculate velocity - const velocity = materialHistory.length / Math.max(1, wip); + // Calculate velocity (turnover) + const velocity = removals.length / Math.max(1, wip); return { wip, throughput, avgLeadTime, - totalMaterialsProcessed: materialHistory.length, + totalMaterialsProcessed: removals.length, currentMaterials: materialsOnAsset, avgCycleTime: avgLeadTime / 1000, materialVelocity: velocity, @@ -670,32 +677,37 @@ function Analyzer() { /** * Update throughput snapshot for an asset */ - const updateThroughputSnapshot = useCallback((assetId: string) => { - const timestamp = Date.now(); - const timeWindow = 60; // 60 seconds + const updateThroughputSnapshot = useCallback( + (assetId: string) => { + const timestamp = Date.now(); + const timeWindow = 60; // 60 seconds - if (!throughputSnapshotsRef.current[assetId]) { - throughputSnapshotsRef.current[assetId] = []; - } + if (!throughputSnapshotsRef.current[assetId]) { + throughputSnapshotsRef.current[assetId] = []; + } - // Count items processed in the last time window - const removals = materialRemovalsRef.current[assetId] || []; - const recentRemovals = removals.filter((r) => timestamp - r.timestamp <= timeWindow * 1000); - const itemsProcessed = recentRemovals.length; - const rate = (itemsProcessed / timeWindow) * 3600; // items per hour + // Count items processed in the last time window + const removals = materialRemovalsRef.current[assetId] || []; + const recentRemovals = removals.filter((r) => timestamp - r.timestamp <= timeWindow * 1000); + const itemsProcessed = recentRemovals.length; + // Normalize by speed + const currentSpeed = Math.max(1, speed); + const rate = ((itemsProcessed / timeWindow) * 3600) / currentSpeed; // items per hour - throughputSnapshotsRef.current[assetId].push({ - timestamp, - itemsProcessed, - timeWindow, - rate, - }); + throughputSnapshotsRef.current[assetId].push({ + timestamp, + itemsProcessed, + timeWindow, + rate, + }); - // Keep only last 100 snapshots - if (throughputSnapshotsRef.current[assetId].length > 100) { - throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100); - } - }, []); + // Keep only last 100 snapshots + if (throughputSnapshotsRef.current[assetId].length > 100) { + throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100); + } + }, + [speed] + ); /** * Update performance snapshot for an asset @@ -948,7 +960,7 @@ function Analyzer() { historicalData: historicalDataRef.current[conveyor.modelUuid] || [], }; }, - [materials, analysis] + [materials, analysis, speed] ); // ============================================================================ @@ -1099,7 +1111,7 @@ function Analyzer() { historicalData: historicalDataRef.current[vehicle.modelUuid] || [], }; }, - [materials, analysis] + [materials, analysis, speed] ); // ============================================================================ @@ -1224,7 +1236,7 @@ function Analyzer() { historicalData: historicalDataRef.current[armBot.modelUuid] || [], }; }, - [materials, analysis] + [materials, analysis, speed] ); // ============================================================================ @@ -1349,7 +1361,7 @@ function Analyzer() { historicalData: historicalDataRef.current[machine.modelUuid] || [], }; }, - [materials, analysis] + [materials, analysis, speed] ); // ============================================================================ @@ -1486,7 +1498,7 @@ function Analyzer() { historicalData: historicalDataRef.current[storage.modelUuid] || [], }; }, - [analysis] + [analysis, speed] ); // ============================================================================ @@ -1624,7 +1636,7 @@ function Analyzer() { historicalData: historicalDataRef.current[human.modelUuid] || [], }; }, - [analysis] + [analysis, speed] ); // ============================================================================ @@ -1774,7 +1786,7 @@ function Analyzer() { historicalData: historicalDataRef.current[crane.modelUuid] || [], }; }, - [analysis] + [analysis, speed] ); // ============================================================================ From a032c47e62b1e0ebfc7eea09564567c95bc946b1 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 10:52:27 +0530 Subject: [PATCH 02/11] feat: implement a comprehensive simulation analyzer module for tracking and calculating performance metrics. --- .../modules/simulation/analyzer/analyzer.tsx | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index df1f4da..3c0e37e 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -184,6 +184,9 @@ function Analyzer() { > >({}); + // Track previous actions for ArmBots to detect cycle completion + const previousArmBotActionsRef = useRef>({}); + // Material lifecycle tracking const materialLifecycleRef = useRef< Record< @@ -215,6 +218,7 @@ function Analyzer() { performanceSnapshotsRef.current = {}; bottleneckEventsRef.current = {}; previousAssetStatesRef.current = {}; + previousArmBotActionsRef.current = {}; materialLifecycleRef.current = {}; setAnalysis(null); setAnalyzing(false); @@ -554,6 +558,14 @@ function Analyzer() { const trackStateChange = useCallback((assetId: string, fromState: string, toState: string) => { const timestamp = Date.now(); + // Increment error count if entering error state + if (toState === "error") { + if (!errorCountsRef.current[assetId]) { + errorCountsRef.current[assetId] = 0; + } + errorCountsRef.current[assetId]++; + } + if (!assetStateChangesRef.current[assetId]) { assetStateChangesRef.current[assetId] = []; } @@ -1126,7 +1138,7 @@ function Analyzer() { const materialFlow = calculateMaterialFlowMetricsForAsset(armBot.modelUuid); const cyclesCompleted = completedActionsRef.current[armBot.modelUuid] || 0; const pickAndPlaceCount = completedActionsRef.current[`${armBot.modelUuid}_pickplace`] || cyclesCompleted; - const defects = errorCountsRef.current[`${armBot.modelUuid}_defects`] || 0; + const defects = errorCount; // Use main error count directly const qualityMetrics = calculateQualityMetrics(armBot.modelUuid, pickAndPlaceCount, defects); @@ -1143,7 +1155,7 @@ function Analyzer() { const energyMetrics = calculateEnergyMetrics("roboticArm", armBot.activeTime || 0); // Calculate success rates - const pickAttempts = pickAndPlaceCount + (errorCountsRef.current[armBot.modelUuid] || 0); + const pickAttempts = pickAndPlaceCount + errorCount; const pickSuccessRate = pickAttempts > 0 ? (pickAndPlaceCount / pickAttempts) * 100 : 100; const placeAccuracy = pickSuccessRate * 0.98; // Assuming 98% of successful picks are placed correctly @@ -2297,6 +2309,33 @@ function Analyzer() { }); }, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, getMaterialsByModel, trackStateChange, trackBottleneckEvent, updatePreviousAssetState]); + // Monitor ArmBot action changes to track cycles + useEffect(() => { + if (!isPlaying) return; + + armBots.forEach((armBot) => { + const previousActionUuid = previousArmBotActionsRef.current[armBot.modelUuid]; + const currentActionUuid = armBot.currentAction?.actionUuid; + + // Check if action completed (transition from an action to no action or different action) + if (previousActionUuid && previousActionUuid !== currentActionUuid) { + // Action completed + if (!completedActionsRef.current[armBot.modelUuid]) { + completedActionsRef.current[armBot.modelUuid] = 0; + } + completedActionsRef.current[armBot.modelUuid]++; + + // Also update pick and place count which is used in analysis + if (!completedActionsRef.current[`${armBot.modelUuid}_pickplace`]) { + completedActionsRef.current[`${armBot.modelUuid}_pickplace`] = 0; + } + completedActionsRef.current[`${armBot.modelUuid}_pickplace`]++; + } + + previousArmBotActionsRef.current[armBot.modelUuid] = currentActionUuid; + }); + }, [armBots, isPlaying]); + // Periodic WIP and throughput snapshots useEffect(() => { if (!isPlaying) return; From 6ab4d1c0a923fe11c195f4e9908460eb51a5c420 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 10:55:39 +0530 Subject: [PATCH 03/11] feat: introduce comprehensive simulation analyzer component with detailed tracking and metric calculation capabilities. --- .../modules/simulation/analyzer/analyzer.tsx | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index 3c0e37e..ad86449 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -555,7 +555,7 @@ function Analyzer() { /** * Track asset state change */ - const trackStateChange = useCallback((assetId: string, fromState: string, toState: string) => { + const trackStateChange = useCallback((assetId: string, fromState: string, toState: string, context?: { actionName?: string }) => { const timestamp = Date.now(); // Increment error count if entering error state @@ -564,6 +564,18 @@ function Analyzer() { errorCountsRef.current[assetId] = 0; } errorCountsRef.current[assetId]++; + + // Granular error tracking based on action type + if (context?.actionName) { + const actionName = context.actionName.toLowerCase(); + if (actionName.includes("pick")) { + if (!errorCountsRef.current[`${assetId}_pick`]) errorCountsRef.current[`${assetId}_pick`] = 0; + errorCountsRef.current[`${assetId}_pick`]++; + } else if (actionName.includes("place")) { + if (!errorCountsRef.current[`${assetId}_place`]) errorCountsRef.current[`${assetId}_place`] = 0; + errorCountsRef.current[`${assetId}_place`]++; + } + } } if (!assetStateChangesRef.current[assetId]) { @@ -1155,9 +1167,23 @@ function Analyzer() { const energyMetrics = calculateEnergyMetrics("roboticArm", armBot.activeTime || 0); // Calculate success rates - const pickAttempts = pickAndPlaceCount + errorCount; - const pickSuccessRate = pickAttempts > 0 ? (pickAndPlaceCount / pickAttempts) * 100 : 100; - const placeAccuracy = pickSuccessRate * 0.98; // Assuming 98% of successful picks are placed correctly + // Calculate success rates + const pickSuccessCount = completedActionsRef.current[`${armBot.modelUuid}_pick`] || pickAndPlaceCount / 2; + const placeSuccessCount = completedActionsRef.current[`${armBot.modelUuid}_place`] || pickAndPlaceCount / 2; + + const pickErrors = errorCountsRef.current[`${armBot.modelUuid}_pick`] || 0; + const placeErrors = errorCountsRef.current[`${armBot.modelUuid}_place`] || 0; + + // If granular errors are 0 but main error count > 0, distribute them (fallback) + const remainingErrors = Math.max(0, errorCount - pickErrors - placeErrors); + const effectivePickErrors = pickErrors + (remainingErrors > 0 ? Math.ceil(remainingErrors / 2) : 0); + const effectivePlaceErrors = placeErrors + (remainingErrors > 0 ? Math.floor(remainingErrors / 2) : 0); + + const pickAttempts = pickSuccessCount + effectivePickErrors; + const placeAttempts = placeSuccessCount + effectivePlaceErrors; + + const pickSuccessRate = pickAttempts > 0 ? (pickSuccessCount / pickAttempts) * 100 : 100; + const placeAccuracy = placeAttempts > 0 ? (placeSuccessCount / placeAttempts) * 100 : 100; // Update historical data @@ -2279,7 +2305,7 @@ function Analyzer() { const allAssets = [ ...conveyors.map((c) => ({ id: c.modelUuid, state: c.state, isActive: !c.isPaused, type: "conveyor" as const })), ...machines.map((m) => ({ id: m.modelUuid, state: m.state, isActive: m.isActive, type: "machine" as const })), - ...armBots.map((a) => ({ id: a.modelUuid, state: a.state, isActive: a.isActive, type: "roboticArm" as const })), + ...armBots.map((a) => ({ id: a.modelUuid, state: a.state, isActive: a.isActive, type: "roboticArm" as const, currentAction: a.currentAction })), ...vehicles.map((v) => ({ id: v.modelUuid, state: v.state, isActive: v.isActive, type: "vehicle" as const })), ...humans.map((h) => ({ id: h.modelUuid, state: h.state, isActive: h.isActive, type: "human" as const })), ...cranes.map((c) => ({ id: c.modelUuid, state: c.state, isActive: c.isActive, type: "crane" as const })), @@ -2293,7 +2319,7 @@ function Analyzer() { if (previousState) { // Check for state change if (previousState.state !== asset.state) { - trackStateChange(asset.id, previousState.state, asset.state); + trackStateChange(asset.id, previousState.state, asset.state, asset.type === "roboticArm" ? { actionName: (asset as any).currentAction?.actionName } : undefined); } // Check for material count change (potential bottleneck) @@ -2330,6 +2356,24 @@ function Analyzer() { completedActionsRef.current[`${armBot.modelUuid}_pickplace`] = 0; } completedActionsRef.current[`${armBot.modelUuid}_pickplace`]++; + + // Granular pick/place tracking + if (previousActionUuid) { + // We need to look up what action this UUID corresponded to + // Since we don't store the action map history, we check the current config + // This assumes configuration hasn't changed, which is true for runtime + const action = armBot.point.actions.find((a) => a.actionUuid === previousActionUuid); + if (action) { + const actionName = action.actionName.toLowerCase(); + if (actionName.includes("pick")) { + if (!completedActionsRef.current[`${armBot.modelUuid}_pick`]) completedActionsRef.current[`${armBot.modelUuid}_pick`] = 0; + completedActionsRef.current[`${armBot.modelUuid}_pick`]++; + } else if (actionName.includes("place")) { + if (!completedActionsRef.current[`${armBot.modelUuid}_place`]) completedActionsRef.current[`${armBot.modelUuid}_place`] = 0; + completedActionsRef.current[`${armBot.modelUuid}_place`]++; + } + } + } } previousArmBotActionsRef.current[armBot.modelUuid] = currentActionUuid; From bcf2d01982f2e829bd1e110f2163c8df2f3e3d42 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 11:06:09 +0530 Subject: [PATCH 04/11] feat: Add animator components for manufacturer, operator, and worker human instances. --- .../instances/animator/manufacturerAnimator.tsx | 12 ++++++++++-- .../human/instances/animator/operatorAnimator.tsx | 12 ++++++++++-- .../human/instances/animator/workerAnimator.tsx | 12 ++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx b/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx index d7b0147..0524726 100644 --- a/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx @@ -15,7 +15,7 @@ interface ManufacturerAnimatorProps { function ManufacturerAnimator({ path, handleCallBack, human, reset }: Readonly) { const { humanStore, assetStore, productStore } = useSceneContext(); const { getActionByUuid, selectedProduct } = productStore(); - const { getHumanById } = humanStore(); + const { getHumanById, incrementDistanceTraveled } = humanStore(); const { setCurrentAnimation } = assetStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); @@ -108,7 +108,15 @@ function ManufacturerAnimator({ path, handleCallBack, human, reset }: Readonly 0) { + incrementDistanceTraveled(human.modelUuid, actualStep); + } + const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); diff --git a/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx index e2a7ab3..c5d7891 100644 --- a/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx @@ -15,7 +15,7 @@ interface WorkerAnimatorProps { function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly) { const { humanStore, assetStore, productStore } = useSceneContext(); const { getActionByUuid, selectedProduct } = productStore(); - const { getHumanById } = humanStore(); + const { getHumanById, incrementDistanceTraveled } = humanStore(); const { setCurrentAnimation } = assetStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); @@ -116,7 +116,15 @@ function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly 0) { + incrementDistanceTraveled(human.modelUuid, actualStep); + } + const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); diff --git a/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx b/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx index d076445..25a3fbb 100644 --- a/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx @@ -16,7 +16,7 @@ interface WorkerAnimatorProps { function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly) { const { humanStore, assetStore, productStore } = useSceneContext(); const { getActionByUuid, selectedProduct } = productStore(); - const { getHumanById } = humanStore(); + const { getHumanById, incrementDistanceTraveled } = humanStore(); const { setCurrentAnimation } = assetStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); @@ -118,7 +118,15 @@ function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProc const isAligned = angle < 0.01; if (isAligned) { - progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance); + const distanceStep = delta * (speed * human.speed); + const previousProgress = progressRef.current; + progressRef.current = Math.min(progressRef.current + distanceStep, totalDistance); + const actualStep = progressRef.current - previousProgress; + + if (actualStep > 0) { + incrementDistanceTraveled(human.modelUuid, actualStep); + } + const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); From 975253a1dc8d9b89ed5cedcf56c9af320749467a Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 11:15:18 +0530 Subject: [PATCH 05/11] feat: Add comprehensive simulation analyzer with detailed metric tracking and calculation. --- .../modules/simulation/analyzer/analyzer.tsx | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index ad86449..e45d64a 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -4,7 +4,7 @@ import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../store/ui/use function Analyzer() { const { isPlaying } = usePlayButtonStore(); - const { conveyorStore, machineStore, armBotStore, humanStore, vehicleStore, craneStore, storageUnitStore, materialStore, analysisStore } = useSceneContext(); + const { conveyorStore, machineStore, armBotStore, humanStore, vehicleStore, craneStore, storageUnitStore, materialStore, analysisStore, humanEventManagerRef } = useSceneContext(); const { conveyors } = conveyorStore(); const { machines } = machineStore(); @@ -187,6 +187,9 @@ function Analyzer() { // Track previous actions for ArmBots to detect cycle completion const previousArmBotActionsRef = useRef>({}); + // Track previous action counts for Humans to detect completion from EventManager + const previousHumanCountsRef = useRef>>({}); + // Material lifecycle tracking const materialLifecycleRef = useRef< Record< @@ -219,6 +222,7 @@ function Analyzer() { bottleneckEventsRef.current = {}; previousAssetStatesRef.current = {}; previousArmBotActionsRef.current = {}; + previousHumanCountsRef.current = {}; materialLifecycleRef.current = {}; setAnalysis(null); setAnalyzing(false); @@ -2380,6 +2384,53 @@ function Analyzer() { }); }, [armBots, isPlaying]); + // Monitor Human action changes from EventManager + useEffect(() => { + if (!isPlaying || !humanEventManagerRef.current) return; + + const interval = setInterval(() => { + if (!humanEventManagerRef.current) return; + + humanEventManagerRef.current.humanStates.forEach((humanState) => { + const humanId = humanState.humanId; + + // Initialize tracking for this human if needed + if (!previousHumanCountsRef.current[humanId]) { + previousHumanCountsRef.current[humanId] = {}; + } + + humanState.actionQueue.forEach((action) => { + let lastCount = previousHumanCountsRef.current[humanId][action.actionUuid] || 0; + const currentCount = action.count || 0; + + // Handle reset case (new action instance with same UUID) + if (currentCount < lastCount) { + lastCount = 0; + previousHumanCountsRef.current[humanId][action.actionUuid] = 0; + } + + const delta = currentCount - lastCount; + + if (delta > 0) { + // Update total completions for this human + if (!completedActionsRef.current[humanId]) completedActionsRef.current[humanId] = 0; + completedActionsRef.current[humanId] += delta; + + // Update granular action type completions (e.g., worker, manufacturer) + const typeKey = `${humanId}_${action.actionType}`; + if (!completedActionsRef.current[typeKey]) completedActionsRef.current[typeKey] = 0; + completedActionsRef.current[typeKey] += delta; + + // Update the last known count + previousHumanCountsRef.current[humanId][action.actionUuid] = currentCount; + } + }); + }); + }, 100); + + return () => clearInterval(interval); + }, [isPlaying, humanEventManagerRef]); + // Periodic WIP and throughput snapshots useEffect(() => { if (!isPlaying) return; From 8fefbe241988746695478411224364f30d4253c4 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 11:24:19 +0530 Subject: [PATCH 06/11] feat: Introduce BlockEditor component for managing simulation block styles and properties. --- .../components/block/BlockEditor.tsx | 12 ++++--- .../components/element/ElementDesign.tsx | 32 +++++++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx index 3ed46df..ae6b47c 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx @@ -241,7 +241,6 @@ const BlockEditor: React.FC = ({ setColor(e.target.value)} onChange={(e) => handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value)} /> @@ -251,12 +250,17 @@ const BlockEditor: React.FC = ({ handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))} + onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))} + /> + handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))} /> - diff --git a/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx b/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx index 21a0379..c57aa69 100644 --- a/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx +++ b/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx @@ -161,15 +161,6 @@ const ElementDesign: React.FC = ({ placeholder={"Layer"} onChange={(newValue: string) => updateElementZIndex(selectedBlock, selectedElement, Number(newValue))} /> - - updateElementStyle(selectedBlock, selectedElement, { backdropFilter: `blur(${Number(value)}px)` })} - /> - = ({
-
Element Color
+
Background
Color
@@ -253,6 +244,27 @@ const ElementDesign: React.FC = ({
{rgbaToHex(getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(50,50,50,1)")}
+ { + const currentBg = getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(0,0,0,1)"; + const currentHex = rgbaToHex(currentBg); + const newBg = hexToRgba(currentHex, Number(value) / 100); + updateElementStyle(selectedBlock, selectedElement, { backgroundColor: newBg }); + }} + /> + { + updateElementStyle(selectedBlock, selectedElement, { backdropFilter: `blur(${Number(value)}px)` }); + }} + />
Swap with Another Element
From d0e18d501055d5d8e9bec8393e2c50a6ad1fe590 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 11:52:49 +0530 Subject: [PATCH 07/11] feat: Introduce simulation analysis module with comprehensive performance tracking and a new element editor UI component. --- .../components/element/ElementEditor.tsx | 2 +- app/src/modules/simulation/analyzer/analyzer.tsx | 15 +++++++++------ app/src/types/simulationTypes.d.ts | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx index 2b43af4..b214f34 100644 --- a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx +++ b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx @@ -337,7 +337,7 @@ const ElementEditor: React.FC = ({ { id: "productivityMetrics.actionsPerHour", label: "Actions Per Hour", icon: }, { id: "efficiency.laborProductivity", label: "Labor Productivity", icon: }, { id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: }, - { id: "workloadDistribution", label: "Workload Distribution", icon: }, + { id: "workloadSummary", label: "Workload Distribution", icon: }, { id: "costMetrics.costPerHour", label: "Cost Per Hour", icon: }, { id: "productivityMetrics.distanceTraveled", label: "Distance Traveled", icon: }, ], diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index e45d64a..d696689 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -1571,13 +1571,15 @@ function Analyzer() { const totalActionTime = workerTime + manufacturerTime + operatorTime + assemblerTime; - const workloadDistribution = [ - { actionType: "worker", count: workerActions, totalTime: workerTime, percentage: totalActionTime > 0 ? (workerTime / totalActionTime) * 100 : 0 }, - { actionType: "manufacturer", count: manufacturerActions, totalTime: manufacturerTime, percentage: totalActionTime > 0 ? (manufacturerTime / totalActionTime) * 100 : 0 }, - { actionType: "operator", count: operatorActions, totalTime: operatorTime, percentage: totalActionTime > 0 ? (operatorTime / totalActionTime) * 100 : 0 }, - { actionType: "assembler", count: assemblerActions, totalTime: assemblerTime, percentage: totalActionTime > 0 ? (assemblerTime / totalActionTime) * 100 : 0 }, + const workloadDistributionData = [ + { actionType: "Worker", count: workerActions, totalTime: workerTime, percentage: totalActionTime > 0 ? (workerTime / totalActionTime) * 100 : 0 }, + { actionType: "Manufacturer", count: manufacturerActions, totalTime: manufacturerTime, percentage: totalActionTime > 0 ? (manufacturerTime / totalActionTime) * 100 : 0 }, + { actionType: "Operator", count: operatorActions, totalTime: operatorTime, percentage: totalActionTime > 0 ? (operatorTime / totalActionTime) * 100 : 0 }, + { actionType: "Assembler", count: assemblerActions, totalTime: assemblerTime, percentage: totalActionTime > 0 ? (assemblerTime / totalActionTime) * 100 : 0 }, ].filter((w) => w.count > 0); + const workloadDistribution = workloadDistributionData.map((d) => `${Math.round(d.percentage)}%`).join(" | "); + // Performance calculations const idealActionsPerHour = 60; // 60 actions per hour ideal const actualActionsPerHour = timeMetrics.totalTime > 0 ? (actionsCompleted / timeMetrics.totalTime) * 3600 : 0; @@ -1640,7 +1642,8 @@ function Analyzer() { loadEfficiency: loadUtilization, }, - workloadDistribution, + workloadDistribution: workloadDistributionData, + workloadSummary: workloadDistribution === "" ? "0%" : workloadDistribution, efficiency: { overallEffectiveness: calculateOEE(timeMetrics.uptime, performanceRate, qualityMetrics.firstPassYield), diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index e04ae4f..f3a2366 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -930,6 +930,7 @@ interface HumanAnalysis { totalTime: number; percentage: number; }[]; + workloadSummary: string; // Efficiency efficiency: EfficiencyMetrics & { From 036164155f38fce9c62a8ee72d27cf7c9fadbe18 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 12:29:12 +0530 Subject: [PATCH 08/11] feat: Introduce animation components for worker, manufacturer, and operator humans, model animators, and a new vehicle state management store. --- .../models/model/animator/modelAnimator.tsx | 10 ++-- .../animator/manufacturerAnimator.tsx | 11 +--- .../instances/animator/operatorAnimator.tsx | 11 +--- .../instances/animator/workerAnimator.tsx | 11 +--- app/src/store/simulation/useVehicleStore.ts | 58 ++++++++++--------- 5 files changed, 42 insertions(+), 59 deletions(-) diff --git a/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx index fb27fce..97ecc2a 100644 --- a/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx +++ b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx @@ -60,14 +60,16 @@ export function ModelAnimator({ asset, gltfScene }: ModelAnimatorProps) { if (isPlaying && currentAction && !isPaused) { blendFactor.current = 0; + Object.values(actions.current).forEach((action) => { + if (action !== currentAction) { + action.stop(); + } + }); + currentAction.reset(); currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1); currentAction.clampWhenFinished = true; - if (previousAction && previousAction !== currentAction) { - previousAction.crossFadeTo(currentAction, blendDuration, false); - } - currentAction.play(); mixerRef.current.addEventListener("finished", handleAnimationComplete); setPreviousAnimation(current); diff --git a/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx b/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx index 0524726..e6e335e 100644 --- a/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/manufacturerAnimator.tsx @@ -108,15 +108,8 @@ function ManufacturerAnimator({ path, handleCallBack, human, reset }: Readonly 0) { - incrementDistanceTraveled(human.modelUuid, actualStep); - } - + progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance); + incrementDistanceTraveled(human.modelUuid, delta * (speed * human.speed)); const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); diff --git a/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx index c5d7891..fc6d829 100644 --- a/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx @@ -116,15 +116,8 @@ function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly 0) { - incrementDistanceTraveled(human.modelUuid, actualStep); - } - + progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance); + incrementDistanceTraveled(human.modelUuid, delta * (speed * human.speed)); const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); diff --git a/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx b/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx index 25a3fbb..6e42eb8 100644 --- a/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/workerAnimator.tsx @@ -118,15 +118,8 @@ function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProc const isAligned = angle < 0.01; if (isAligned) { - const distanceStep = delta * (speed * human.speed); - const previousProgress = progressRef.current; - progressRef.current = Math.min(progressRef.current + distanceStep, totalDistance); - const actualStep = progressRef.current - previousProgress; - - if (actualStep > 0) { - incrementDistanceTraveled(human.modelUuid, actualStep); - } - + progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance); + incrementDistanceTraveled(human.modelUuid, delta * (speed * human.speed)); const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index e01bbce..f160e2d 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -6,10 +6,7 @@ interface VehiclesStore { addVehicle: (productUuid: string, event: VehicleEventSchema) => void; removeVehicle: (modelUuid: string) => void; - updateVehicle: ( - modelUuid: string, - updates: Partial> - ) => void; + updateVehicle: (modelUuid: string, updates: Partial>) => void; addPathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], point: VehicleAction["paths"]["initPickup"][number]) => VehicleAction["paths"]; updatePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string, updates: Partial) => VehicleAction["paths"]; deletePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string) => VehicleAction["paths"]; @@ -22,17 +19,15 @@ interface VehiclesStore { incrementVehicleLoad: (modelUuid: string, incrementBy: number) => void; decrementVehicleLoad: (modelUuid: string, decrementBy: number) => void; setVehicleLoad: (modelUuid: string, load: number) => void; - setVehicleState: ( - modelUuid: string, - newState: VehicleStatus["state"] - ) => void; + setVehicleState: (modelUuid: string, newState: VehicleStatus["state"]) => void; addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void; - setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string; }[]) => void; - removeLastMaterial: (modelUuid: string) => { materialId: string; materialType: string; } | undefined; - getLastMaterial: (modelUuid: string) => { materialId: string; materialType: string; } | undefined; + setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void; + removeLastMaterial: (modelUuid: string) => { materialId: string; materialType: string } | undefined; + getLastMaterial: (modelUuid: string) => { materialId: string; materialType: string } | undefined; clearCurrentMaterials: (modelUuid: string) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void; resetTime: (modelUuid: string) => void; getVehicleById: (modelUuid: string) => VehicleStatus | undefined; @@ -53,7 +48,7 @@ export const createVehicleStore = () => { state.vehicles.push({ ...event, productUuid, - currentPhase: 'stationed', + currentPhase: "stationed", isActive: false, isPicking: false, idleTime: 0, @@ -61,7 +56,7 @@ export const createVehicleStore = () => { currentLoad: 0, currentMaterials: [], distanceTraveled: 0, - state: 'idle' + state: "idle", }); } }); @@ -69,9 +64,7 @@ export const createVehicleStore = () => { removeVehicle: (modelUuid) => { set((state) => { - state.vehicles = state.vehicles.filter( - (v) => v.modelUuid !== modelUuid - ); + state.vehicles = state.vehicles.filter((v) => v.modelUuid !== modelUuid); }); }, @@ -87,7 +80,7 @@ export const createVehicleStore = () => { addPathPoint: (modelUuid, pathKey, point) => { let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] }; set((state) => { - const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); if (vehicle) { const path = vehicle.point.action.paths[pathKey]; path.push(point); @@ -100,10 +93,10 @@ export const createVehicleStore = () => { updatePathPoint: (modelUuid, pathKey, pointId, updates) => { let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] }; set((state) => { - const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); if (vehicle) { const path = vehicle.point.action.paths[pathKey]; - const index = path.findIndex(p => p.pointId === pointId); + const index = path.findIndex((p) => p.pointId === pointId); if (index !== -1) { Object.assign(path[index], updates); updatedPaths = vehicle.point.action.paths; @@ -116,10 +109,10 @@ export const createVehicleStore = () => { deletePathPoint: (modelUuid, pathKey, pointId) => { let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] }; set((state) => { - const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); if (vehicle) { const path = vehicle.point.action.paths[pathKey]; - vehicle.point.action.paths[pathKey] = path.filter(p => p.pointId !== pointId); + vehicle.point.action.paths[pathKey] = path.filter((p) => p.pointId !== pointId); updatedPaths = vehicle.point.action.paths; } }); @@ -134,7 +127,7 @@ export const createVehicleStore = () => { setCurrentPhase: (modelUuid, phase) => { set((state) => { - const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); if (vehicle) { vehicle.currentPhase = phase; } @@ -223,7 +216,7 @@ export const createVehicleStore = () => { }, removeLastMaterial: (modelUuid) => { - let removedMaterial: { materialId: string; materialType: string; } | undefined; + let removedMaterial: { materialId: string; materialType: string } | undefined; set((state) => { const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); if (vehicle) { @@ -239,14 +232,14 @@ export const createVehicleStore = () => { }, getLastMaterial: (modelUuid) => { - let removedMaterial: { materialId: string; materialType: string; } | undefined; + let removedMaterial: { materialId: string; materialType: string } | undefined; set((state) => { const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); if (vehicle) { if (vehicle.currentMaterials.length > 0) { removedMaterial = { materialId: vehicle.currentMaterials[vehicle.currentMaterials.length - 1].materialId, - materialType: vehicle.currentMaterials[vehicle.currentMaterials.length - 1].materialType + materialType: vehicle.currentMaterials[vehicle.currentMaterials.length - 1].materialType, }; } } @@ -281,6 +274,15 @@ export const createVehicleStore = () => { }); }, + incrementDistanceTraveled: (modelUuid, incrementBy) => { + set((state) => { + const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); + if (vehicle) { + vehicle.distanceTraveled += incrementBy; + } + }); + }, + resetTime: (modelUuid) => { set((state) => { const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); @@ -307,7 +309,7 @@ export const createVehicleStore = () => { return get().vehicles.filter((v) => v.isActive); }, })) - ) -} + ); +}; -export type VehicleStoreType = ReturnType; \ No newline at end of file +export type VehicleStoreType = ReturnType; From 40854a4bd76c64895f2692dc3c8ce26349ea53ab Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 12:38:53 +0530 Subject: [PATCH 09/11] feat: introduce `VehicleAnimator` component for animating 3D vehicles along defined paths with play/pause/reset functionality. --- .../simulation/vehicle/instances/animator/vehicleAnimator.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index b94a167..304ccdf 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -18,7 +18,7 @@ interface VehicleAnimatorProps { function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset, startUnloadingProcess }: Readonly) { const { vehicleStore } = useSceneContext(); - const { getVehicleById } = vehicleStore(); + const { getVehicleById, incrementDistanceTraveled } = vehicleStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); const { speed } = useAnimationPlaySpeed(); @@ -126,6 +126,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai if (isAligned) { progressRef.current += delta * (speed * agvDetail.speed); + incrementDistanceTraveled(agvUuid, delta * (speed * agvDetail.speed)); const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); From 4c857e0ba653f4d54347f0d8348c2b2f7100a0df Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 12:45:33 +0530 Subject: [PATCH 10/11] feat: Add simulation analyzer component for comprehensive simulation data tracking and performance metrics. --- .../modules/simulation/analyzer/analyzer.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index d696689..f28724f 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -190,6 +190,9 @@ function Analyzer() { // Track previous action counts for Humans to detect completion from EventManager const previousHumanCountsRef = useRef>>({}); + // Track previous vehicle phases to detect trip completion + const previousVehiclePhasesRef = useRef>({}); + // Material lifecycle tracking const materialLifecycleRef = useRef< Record< @@ -223,6 +226,7 @@ function Analyzer() { previousAssetStatesRef.current = {}; previousArmBotActionsRef.current = {}; previousHumanCountsRef.current = {}; + previousVehiclePhasesRef.current = {}; materialLifecycleRef.current = {}; setAnalysis(null); setAnalyzing(false); @@ -2457,6 +2461,26 @@ function Analyzer() { return () => clearInterval(snapshotInterval); }, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, updateWIPSnapshot]); + // Monitor Vehicle phase changes to track completed trips + useEffect(() => { + if (!isPlaying) return; + + vehicles.forEach((vehicle) => { + const previousPhase = previousVehiclePhasesRef.current[vehicle.modelUuid]; + const currentPhase = vehicle.currentPhase; + + // Check for transition from 'drop-pickup' to 'picking' (Trip completed) + if (previousPhase === "drop-pickup" && currentPhase === "picking") { + if (!completedActionsRef.current[vehicle.modelUuid]) { + completedActionsRef.current[vehicle.modelUuid] = 0; + } + completedActionsRef.current[vehicle.modelUuid]++; + } + + previousVehiclePhasesRef.current[vehicle.modelUuid] = currentPhase; + }); + }, [vehicles, isPlaying]); + return null; } From ddf9f30a41f593a84d8d4de2d2cbcab0ca9df23c Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 12:53:18 +0530 Subject: [PATCH 11/11] feat: Add simulation analyzer component for comprehensive performance tracking and metric calculation. --- app/src/modules/simulation/analyzer/analyzer.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index f28724f..9cb2273 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -1007,7 +1007,7 @@ function Analyzer() { 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 defects = errorCount; // Use main error count directly const qualityMetrics = calculateQualityMetrics(vehicle.modelUuid, totalLoadsDelivered, defects); // Performance calculations @@ -1123,7 +1123,7 @@ function Analyzer() { maintenanceCost: costMetrics.maintenanceCost, energyCost: energyMetrics.energyCost, totalCost: costMetrics.totalCost, - costPerMile: actualDistance > 0 ? costMetrics.totalCost / actualDistance : 0, + costPerMile: actualDistance > 0 ? costMetrics.totalCost / (actualDistance / 1609.34) : 0, // Convert meters to miles costPerTrip: tripsCompleted > 0 ? costMetrics.totalCost / tripsCompleted : 0, costPerLoad: totalLoadsDelivered > 0 ? costMetrics.totalCost / totalLoadsDelivered : 0, roi: costMetrics.roi, @@ -2475,6 +2475,12 @@ function Analyzer() { completedActionsRef.current[vehicle.modelUuid] = 0; } completedActionsRef.current[vehicle.modelUuid]++; + + // Track loads delivered (assuming 1 load per trip for now, or use vehicle.currentLoad if available/reliable at this point) + if (!completedActionsRef.current[`${vehicle.modelUuid}_loads`]) { + completedActionsRef.current[`${vehicle.modelUuid}_loads`] = 0; + } + completedActionsRef.current[`${vehicle.modelUuid}_loads`] += 1; } previousVehiclePhasesRef.current[vehicle.modelUuid] = currentPhase;