From 6ab4d1c0a923fe11c195f4e9908460eb51a5c420 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 10:55:39 +0530 Subject: [PATCH] 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;