From 7a0b91a946224e2f5e68dc6a0f2ea53af5250a00 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 22 Dec 2025 15:44:31 +0530 Subject: [PATCH] feat: implement simulation analyzer component with extensive configuration parameters and state tracking refs --- .../modules/simulation/analyzer/analyzer.tsx | 306 ++++++++++++------ 1 file changed, 216 insertions(+), 90 deletions(-) diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index ad26ebf..e8e5a1c 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -2,6 +2,150 @@ import { useEffect, useCallback, useRef } from "react"; import { useSceneContext } from "../../scene/sceneContext"; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; +// ============================================================================ +// CONFIGURATION CONSTANTS +// ============================================================================ + +/** + * Hourly operational costs for different asset types (in currency units). + * These values represent the base cost of running an asset for one hour. + */ +const HOURLY_RATES: Record = { + CONVEYOR: 50, + VEHICLE: 75, + MACHINE: 100, + ROBOTIC_ARM: 120, + HUMAN: 35, + CRANE: 150, + STORAGE: 25, +}; + +/** + * Maintenance cost multipliers per error count. + * This cost is incurred for every error or defect associated with the asset. + */ +const MAINTENANCE_COSTS: Record = { + CONVEYOR: 0.5, + VEHICLE: 2, + MACHINE: 5, + ROBOTIC_ARM: 8, + HUMAN: 0.1, + CRANE: 10, + STORAGE: 1, +}; + +/** + * Energy cost factors per hour of operation. + * Used to calculate the financial impact of energy consumption. + */ +const ENERGY_COSTS: Record = { + CONVEYOR: 5, + VEHICLE: 10, + MACHINE: 15, + ROBOTIC_ARM: 20, + HUMAN: 0, + CRANE: 25, + STORAGE: 2, +}; + +/** + * Energy consumption rates in kW (Kilowatts). + * Defines how much power each asset consumes while active. + */ +const ENERGY_CONSUMPTION_RATES: Record = { + CONVEYOR: 7.5, + VEHICLE: 15, // Base consumption for vehicles + VEHICLE_DISTANCE_FACTOR: 0.1, // Additional consumption per unit of distance + MACHINE: 20, + ROBOTIC_ARM: 10, + HUMAN: 0, // Humans do not consume electrical energy directly + CRANE: 25, + STORAGE: 2, // Climate control or lighting for storage + DEFAULT: 5, +}; + +/** + * Global energy pricing and environmental impact constants. + */ +const ENERGY_CONSTANTS = { + COST_PER_KWH: 0.12, // Cost per Kilowatt-hour + CO2_PER_KWH: 0.5, // Carbon emissions (kg) per Kilowatt-hour +}; + +/** + * Efficiency adjustment factors to normalize performance metrics. + * Scales raw performance data to account for different asset capabilities. + */ +const EFFICIENCY_ADJUSTMENTS: Record = { + CONVEYOR: 1.0, + VEHICLE: 0.95, + MACHINE: 0.9, + ROBOTIC_ARM: 0.92, + HUMAN: 0.85, + CRANE: 0.88, + STORAGE: 0.98, + DEFAULT: 1.0, +}; + +/** + * Operational benchmarks for ideal performance comparisons. + * Used to calculate OEE and other efficiency probabilities. + */ +const PERFORMANCE_BENCHMARKS = { + CONVEYOR_LENGTH: 10, // Assumed length in meters + VEHICLE_IDEAL_TRIP_TIME: 60, // Seconds per ideal trip + VEHICLE_OPTIMAL_DISTANCE: 100, // Meters per ideal trip + ARM_IDEAL_CYCLE_TIME: 5, // Seconds per pick-place cycle + MACHINE_DEFAULT_PROCESS_TIME: 30, // Seconds default processing time + CRANE_IDEAL_CYCLE_TIME: 20, // Seconds per crane cycle + HUMAN_IDEAL_ACTIONS_PER_HOUR: 60, // Actions target per hour + CRANE_DEFAULT_LIFT_HEIGHT: 5, // Meters per lift +}; + +/** + * Quality control ratios and defect rates. + */ +const QUALITY_CONSTANTS = { + DEFAULT_SCRAP_RATE: 0.1, // 10% of defects are scrapped by default + DEFAULT_REWORK_RATE: 0.9, // 90% of defects can be reworked by default + MACHINE_SCRAP_RATIO: 0.7, // Machines possess higher scrap risk + MACHINE_REWORK_RATIO: 0.3, // Lower rework ability for machine defects +}; + +/** + * Settings controlling the sensitivity and scope of the analysis. + */ +const ANALYSIS_SETTINGS = { + HISTORY_LIMIT: 100, // Number of historical data points to keep + THROUGHPUT_WINDOW: 60, // Time window (seconds) for throughput calculation + BOTTLENECK_QUEUE_THRESHOLD: 2, // Queue size that triggers bottleneck detection + BOTTLENECK_UTILIZATION_THRESHOLD: 0.85, // Utilization % that triggers bottleneck warning + CRITICAL_UTILIZATION: 0.95, // Utilization % considered critical + HIGH_UTILIZATION: 0.9, // Utilization % considered high +}; + +/** + * Parameters for financial analysis and calculations. + */ +const FINANCIAL_PARAMS = { + REVENUE_PER_UNIT: 150, // Estimated revenue per completed unit + VALUE_PER_UNIT: 10, // Internal value assigned to WIP units + MATERIAL_VALUE: 25, // Base cost value of raw materials + HOLDING_COST_RATE: 0.25, // Annual holding cost percentage (25%) + SCRAP_RECOVERY_RATE: 0.1, // Value recovered from scrapped material (10%) + FIXED_COST_RATIO: 0.4, // Portion of total cost considered fixed + VARIABLE_COST_RATIO: 0.6, // Portion of total cost considered variable +}; + +/** + * Constants for sustainability and environmental impact analysis. + */ +const SUSTAINABILITY_CONSTANTS = { + WASTE_PER_SCRAP_KG: 5, // Waste generated (kg) per scrapped unit + RECYCLABLE_RATIO: 0.3, // Percentage of waste that is recyclable + WATER_USAGE_PER_UNIT: 10, // Liters of water used per unit produced +}; + function Analyzer() { const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); @@ -261,6 +405,10 @@ function Analyzer() { return getSimulationTime(); }; + const getAssetKey = (assetType: string) => { + return assetType === "roboticArm" ? "ROBOTIC_ARM" : assetType.toUpperCase(); + }; + // ============================================================================ // ENHANCED UTILITY FUNCTIONS // ============================================================================ @@ -296,8 +444,8 @@ function Analyzer() { const errorCount = errorCountsRef.current[assetId] || 0; const firstPassYield = totalOperations > 0 ? ((totalOperations - defects) / totalOperations) * 100 : 0; 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 + const scrapRate = defects > 0 ? defects * QUALITY_CONSTANTS.DEFAULT_SCRAP_RATE : 0; + const reworkRate = defects > 0 ? defects * QUALITY_CONSTANTS.DEFAULT_REWORK_RATE : 0; // Calculate defect trends let defectTrend = "stable"; @@ -375,16 +523,8 @@ function Analyzer() { 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; + const assetKey = getAssetKey(assetType); + const efficiencyAdjustment = EFFICIENCY_ADJUSTMENTS[assetKey] || EFFICIENCY_ADJUSTMENTS.DEFAULT; return { performanceRate: performanceRate * efficiencyAdjustment, @@ -396,29 +536,22 @@ function Analyzer() { }; 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 }, - }; + const assetKey = getAssetKey(assetType); + const hourlyRate = HOURLY_RATES[assetKey] || HOURLY_RATES.CONVEYOR; + const maintenanceCostParam = MAINTENANCE_COSTS[assetKey] || MAINTENANCE_COSTS.CONVEYOR; + const energyCostParam = ENERGY_COSTS[assetKey] || ENERGY_COSTS.CONVEYOR; - const params = costParams[assetType] || costParams.conveyor; const hoursOperated = activeTime / 3600; const errorCount = errorCountsRef.current[assetId] || 0; - const operatingCost = hoursOperated * params.hourlyRate; - const maintenanceCost = errorCount * params.maintenanceCost; - const energyCost = hoursOperated * params.energyCost; + const operatingCost = hoursOperated * hourlyRate; + const maintenanceCost = errorCount * maintenanceCostParam; + const energyCost = hoursOperated * energyCostParam; const totalCost = operatingCost + maintenanceCost + energyCost; 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 + const roi = totalOutput > 0 ? (totalOutput * FINANCIAL_PARAMS.VALUE_PER_UNIT) / totalCost : 0; return { operatingCost, @@ -428,28 +561,21 @@ function Analyzer() { costPerUnit, costPerHour, roi, - valueAdded: totalOutput * 10 - totalCost, + valueAdded: totalOutput * FINANCIAL_PARAMS.VALUE_PER_UNIT - totalCost, }; }; 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, - }; - - const rate = energyRates[assetType] || 5; + const assetKey = getAssetKey(assetType); + let rate = ENERGY_CONSUMPTION_RATES[assetKey] || ENERGY_CONSUMPTION_RATES.DEFAULT; + if (assetType === "vehicle") { + rate = ENERGY_CONSUMPTION_RATES.VEHICLE + distanceTraveled * ENERGY_CONSUMPTION_RATES.VEHICLE_DISTANCE_FACTOR; + } 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 + const carbonFootprint = energyConsumed * ENERGY_CONSTANTS.CO2_PER_KWH; + const energyCost = energyConsumed * ENERGY_CONSTANTS.COST_PER_KWH; return { energyConsumed, @@ -696,7 +822,7 @@ function Analyzer() { // Limit history to last 100 actions if (actionCompletionTimesRef.current[assetId].length > 100) { - actionCompletionTimesRef.current[assetId] = actionCompletionTimesRef.current[assetId].slice(-100); + actionCompletionTimesRef.current[assetId] = actionCompletionTimesRef.current[assetId].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); } }, []); @@ -720,7 +846,7 @@ function Analyzer() { // Keep only last 100 snapshots if (wipSnapshotsRef.current[assetId].length > 100) { - wipSnapshotsRef.current[assetId] = wipSnapshotsRef.current[assetId].slice(-100); + wipSnapshotsRef.current[assetId] = wipSnapshotsRef.current[assetId].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); } }, [getMaterialsByModel] @@ -732,7 +858,7 @@ function Analyzer() { const updateThroughputSnapshot = useCallback( (assetId: string) => { const timestamp = getSimulationTime(); - const timeWindow = 60; // 60 seconds + const timeWindow = ANALYSIS_SETTINGS.THROUGHPUT_WINDOW; if (!throughputSnapshotsRef.current[assetId]) { throughputSnapshotsRef.current[assetId] = []; @@ -754,7 +880,7 @@ function Analyzer() { // Keep only last 100 snapshots if (throughputSnapshotsRef.current[assetId].length > 100) { - throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100); + throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); } }, [speed] @@ -780,7 +906,7 @@ function Analyzer() { // Keep only last 100 snapshots if (performanceSnapshotsRef.current[assetId].length > 100) { - performanceSnapshotsRef.current[assetId] = performanceSnapshotsRef.current[assetId].slice(-100); + performanceSnapshotsRef.current[assetId] = performanceSnapshotsRef.current[assetId].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); } }, []); @@ -795,7 +921,7 @@ function Analyzer() { } // Only track if it's a significant bottleneck - if (queueLength > 2 || utilizationRate > 0.85) { + if (queueLength > ANALYSIS_SETTINGS.BOTTLENECK_QUEUE_THRESHOLD || utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD) { bottleneckEventsRef.current[assetId].push({ timestamp, queueLength, @@ -888,7 +1014,7 @@ function Analyzer() { const qualityMetrics = calculateQualityMetrics(conveyor.modelUuid, materialsProcessed, defects); // Performance calculations - const conveyorLength = 10; // meters + const conveyorLength = PERFORMANCE_BENCHMARKS.CONVEYOR_LENGTH; const idealSpeed = conveyor.speed; const actualSpeed = conveyor.isActive ? conveyor.speed : 0; const idealThroughput = idealSpeed > 0 ? (idealSpeed * 3600) / (conveyorLength / 2) : 0; // items per hour @@ -919,12 +1045,12 @@ function Analyzer() { }; const currentData = historicalDataRef.current[conveyor.modelUuid] || []; - historicalDataRef.current[conveyor.modelUuid] = [...currentData, newEntry].slice(-100); + historicalDataRef.current[conveyor.modelUuid] = [...currentData, newEntry].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); // Calculate queue if applicable const queueLength = materialFlow.wip; const currentQueueData = queueLengthsRef.current[conveyor.modelUuid] || []; - queueLengthsRef.current[conveyor.modelUuid] = [...currentQueueData, { timestamp: getSimulationTime(), length: queueLength }].slice(-100); + queueLengthsRef.current[conveyor.modelUuid] = [...currentQueueData, { timestamp: getSimulationTime(), length: queueLength }].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); return { assetId: conveyor.modelUuid, @@ -960,7 +1086,7 @@ function Analyzer() { averageProcessingTime: materialFlow.avgCycleTime, wip: materialFlow.wip, throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, - bottleneckIndex: timeMetrics.utilizationRate > 0.9 ? 1 : 0, + bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.HIGH_UTILIZATION ? 1 : 0, }, efficiency: { @@ -1034,7 +1160,7 @@ function Analyzer() { const avgLoad = tripsCompleted > 0 ? totalLoadsDelivered / tripsCompleted : 0; const loadUtilization = (avgLoad / loadCapacity) * 100; - const idealTripTime = 60; + const idealTripTime = PERFORMANCE_BENCHMARKS.VEHICLE_IDEAL_TRIP_TIME; const actualTripTime = tripsCompleted > 0 ? timeMetrics.totalTime / tripsCompleted : 0; const idealThroughput = loadCapacity * (3600 / idealTripTime); const actualThroughput = materialFlow.throughput * 3600; @@ -1042,7 +1168,7 @@ function Analyzer() { const performance = calculatePerformanceMetrics(totalLoadsDelivered, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, idealTripTime * tripsCompleted, "vehicle"); // Route efficiency - const optimalDistance = 100; + const optimalDistance = PERFORMANCE_BENCHMARKS.VEHICLE_OPTIMAL_DISTANCE; const actualDistance = vehicle.distanceTraveled || 0; const routeEfficiency = actualDistance > 0 ? Math.min((optimalDistance / actualDistance) * 100, 100) : 100; @@ -1065,7 +1191,7 @@ function Analyzer() { speed: vehicle.speed, tripsCompleted, }, - ].slice(-100); + ].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); return { assetId: vehicle.modelUuid, @@ -1104,7 +1230,7 @@ function Analyzer() { totalLoadsDelivered, throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, wip: materialFlow.wip, - bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, + bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0, }, movementMetrics: { @@ -1182,7 +1308,7 @@ function Analyzer() { const qualityMetrics = calculateQualityMetrics(armBot.modelUuid, pickAndPlaceCount, defects); // Performance calculations - const idealCycleTime = 5; // 5 seconds ideal + const idealCycleTime = PERFORMANCE_BENCHMARKS.ARM_IDEAL_CYCLE_TIME; const actualCycleTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0; const idealThroughput = (3600 / idealCycleTime) * 1; // 1 item per cycle const actualThroughput = materialFlow.throughput * 3600; @@ -1228,7 +1354,7 @@ function Analyzer() { }; const currentData = historicalDataRef.current[armBot.modelUuid] || []; - historicalDataRef.current[armBot.modelUuid] = [...currentData, newEntry].slice(-100); + historicalDataRef.current[armBot.modelUuid] = [...currentData, newEntry].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); return { assetId: armBot.modelUuid, @@ -1261,7 +1387,7 @@ function Analyzer() { pickAndPlaceCount, throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, wip: materialFlow.wip, - bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, + bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0, }, efficiency: { @@ -1321,7 +1447,7 @@ function Analyzer() { const qualityMetrics = calculateQualityMetrics(machine.modelUuid, partsProcessed, defects); // Performance calculations - const targetProcessTime = machine.point?.action?.processTime || 30; + const targetProcessTime = machine.point?.action?.processTime || PERFORMANCE_BENCHMARKS.MACHINE_DEFAULT_PROCESS_TIME; const actualProcessTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0; const idealThroughput = (3600 / targetProcessTime) * 1; // 1 part per process const actualThroughput = materialFlow.throughput * 3600; @@ -1335,8 +1461,8 @@ function Analyzer() { // Quality calculations const totalParts = partsProcessed + defects; const defectRate = totalParts > 0 ? (defects / totalParts) * 100 : 0; - const reworkRate = defectRate * 0.3; // Assume 30% of defects are reworked - const scrapRate = defectRate * 0.7; // Assume 70% are scrapped + const reworkRate = defectRate * QUALITY_CONSTANTS.MACHINE_REWORK_RATIO; + const scrapRate = defectRate * QUALITY_CONSTANTS.MACHINE_SCRAP_RATIO; // Update historical data const timestamp = getSimulationTime(); @@ -1352,7 +1478,7 @@ function Analyzer() { defectRate, performance: performance.performanceRate, }, - ].slice(-100); + ].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); return { assetId: machine.modelUuid, @@ -1384,7 +1510,7 @@ function Analyzer() { partsProcessed, throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, wip: materialFlow.wip, - bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, + bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0, }, efficiency: { @@ -1464,7 +1590,7 @@ function Analyzer() { totalOps, state: storage.state, }, - ].slice(-100); + ].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); // Calculate peak occupancy from historical data const occupancyData = historicalDataRef.current[storage.modelUuid] || []; @@ -1519,7 +1645,7 @@ function Analyzer() { totalOperations: totalOps, throughputEfficiency: (totalOps / Math.max(1, capacity * 24)) * 100, wip: currentLoad, - bottleneckIndex: utilizationRate > 0.9 ? 1 : 0, + bottleneckIndex: utilizationRate > ANALYSIS_SETTINGS.HIGH_UTILIZATION ? 1 : 0, }, efficiency: { @@ -1554,7 +1680,7 @@ function Analyzer() { energyPerUnit: totalOps > 0 ? energyMetrics.energyConsumed / totalOps : 0, }, - occupancyTrends: occupancyData.slice(-100).map((d) => ({ + occupancyTrends: occupancyData.slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT).map((d) => ({ timestamp: d.timestamp, occupancy: d.currentLoad, utilizationRate: d.utilizationRate, @@ -1604,7 +1730,7 @@ function Analyzer() { const workloadDistribution = workloadDistributionData.map((d) => `${Math.round(d.percentage)}%`).join(" | "); // Performance calculations - const idealActionsPerHour = 60; // 60 actions per hour ideal + const idealActionsPerHour = PERFORMANCE_BENCHMARKS.HUMAN_IDEAL_ACTIONS_PER_HOUR; const actualActionsPerHour = timeMetrics.totalTime > 0 ? (actionsCompleted / timeMetrics.totalTime) * 3600 : 0; const performanceRate = Math.min((actualActionsPerHour / idealActionsPerHour) * 100, 100); @@ -1627,7 +1753,7 @@ function Analyzer() { }; const currentData = historicalDataRef.current[human.modelUuid] || []; - historicalDataRef.current[human.modelUuid] = [...currentData, newEntry].slice(-100); + historicalDataRef.current[human.modelUuid] = [...currentData, newEntry].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); return { assetId: human.modelUuid, @@ -1724,7 +1850,7 @@ function Analyzer() { const qualityMetrics = calculateQualityMetrics(crane.modelUuid, loadsHandled, errorCount); // Performance calculations - const idealCycleTime = 20; // 20 seconds ideal + const idealCycleTime = PERFORMANCE_BENCHMARKS.CRANE_IDEAL_CYCLE_TIME; const actualCycleTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0; const idealThroughput = (3600 / idealCycleTime) * 1; // 1 load per cycle const actualThroughput = cyclesCompleted > 0 ? (loadsHandled / timeMetrics.totalTime) * 3600 : 0; @@ -1764,7 +1890,7 @@ function Analyzer() { }; const currentData = historicalDataRef.current[crane.modelUuid] || []; - historicalDataRef.current[crane.modelUuid] = [...currentData, newEntry].slice(-100); + historicalDataRef.current[crane.modelUuid] = [...currentData, newEntry].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT); return { assetId: crane.modelUuid, @@ -1803,7 +1929,7 @@ function Analyzer() { averageLoadsPerCycle: avgLoadsPerCycle, throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100, wip: crane.currentLoad, - bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0, + bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0, }, movementMetrics: { @@ -2023,9 +2149,9 @@ function Analyzer() { const impactScore = utilizationRate * 100 + queueLength * 10; let severity: "critical" | "high" | "medium" | "low" = "low"; - if (utilizationRate > 0.95) severity = "critical"; - else if (utilizationRate > 0.9) severity = "high"; - else if (utilizationRate > 0.85) severity = "medium"; + if (utilizationRate > ANALYSIS_SETTINGS.CRITICAL_UTILIZATION) severity = "critical"; + else if (utilizationRate > ANALYSIS_SETTINGS.HIGH_UTILIZATION) severity = "high"; + else if (utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD) severity = "medium"; return { assetId: asset.assetId, @@ -2138,7 +2264,7 @@ function Analyzer() { const calculateAdvancedAnalytics = (): AdvancedAnalytics => { // Financials // Assume average revenue per completed item is $150 (since cost is ~10-100 range) - const revenuePerUnit = 150; + const revenuePerUnit = FINANCIAL_PARAMS.REVENUE_PER_UNIT; const totalRevenue = completedMaterials * revenuePerUnit; const grossProfit = totalRevenue - totalCost; const netProfitMargin = totalRevenue > 0 ? (grossProfit / totalRevenue) * 100 : 0; @@ -2149,8 +2275,8 @@ function Analyzer() { const maintenanceCosts = allAssets.reduce((sum, a) => sum + (a.costMetrics?.maintenanceCost || 0), 0); // Fixed vs Variable (Assumption: 40% fixed, 60% variable) - const fixedCosts = totalCost * 0.4; - const variableCosts = totalCost * 0.6; + const fixedCosts = totalCost * FINANCIAL_PARAMS.FIXED_COST_RATIO; + const variableCosts = totalCost * FINANCIAL_PARAMS.VARIABLE_COST_RATIO; const breakEvenUnits = revenuePerUnit > variableCosts / Math.max(1, completedMaterials) ? fixedCosts / (revenuePerUnit - variableCosts / Math.max(1, completedMaterials)) : 0; // Burn Rate @@ -2175,15 +2301,15 @@ function Analyzer() { // Supply Chain // Assign values to materials - const avgMaterialValue = 25; + const avgMaterialValue = FINANCIAL_PARAMS.MATERIAL_VALUE; const totalInventoryValue = totalMaterialsInSystem * avgMaterialValue; - const inventoryTurnoverRate = totalInventoryValue > 0 ? (totalCost * 0.6) / totalInventoryValue : 0; // standard formula COGS/AvgInv - const holdingCostRate = 0.25; // 25% annual holding cost + const inventoryTurnoverRate = totalInventoryValue > 0 ? (totalCost * FINANCIAL_PARAMS.VARIABLE_COST_RATIO) / totalInventoryValue : 0; // standard formula COGS/AvgInv + const holdingCostRate = FINANCIAL_PARAMS.HOLDING_COST_RATE; const holdingCost = ((totalInventoryValue * holdingCostRate) / (365 * 24)) * analysisDurationHours; // prorated // Scrap value const totalScrap = allAssets.reduce((sum, a) => sum + (a.quality?.scrapRate || 0), 0); - const scrapValue = totalScrap * (avgMaterialValue * 0.1); // 10% recovery + const scrapValue = totalScrap * (avgMaterialValue * FINANCIAL_PARAMS.SCRAP_RECOVERY_RATE); const supplyChain: SupplyChainMetrics = { totalInventoryValue, @@ -2198,16 +2324,16 @@ function Analyzer() { // Sustainability // CO2 factors - const totalWaste = totalScrap * 5; // 5kg per scrap unit (simulated) + const totalWaste = totalScrap * SUSTAINABILITY_CONSTANTS.WASTE_PER_SCRAP_KG; const wasteGenerationRate = analysisDurationHours > 0 ? totalWaste / analysisDurationHours : 0; const sustainability: SustainabilityMetrics = { - totalCarbonFootprint: totalEnergyConsumed * 0.5, // 0.5 kg/kWh + totalCarbonFootprint: totalEnergyConsumed * ENERGY_CONSTANTS.CO2_PER_KWH, energyIntensity: completedMaterials > 0 ? totalEnergyConsumed / completedMaterials : 0, - carbonIntensity: completedMaterials > 0 ? (totalEnergyConsumed * 0.5) / completedMaterials : 0, + carbonIntensity: completedMaterials > 0 ? (totalEnergyConsumed * ENERGY_CONSTANTS.CO2_PER_KWH) / completedMaterials : 0, wasteGenerationRate, - recyclableRatio: 0.3, // 30% recyclable (simulated) - waterUsage: completedMaterials * 10, // 10L per unit (simulated) + recyclableRatio: SUSTAINABILITY_CONSTANTS.RECYCLABLE_RATIO, + waterUsage: completedMaterials * SUSTAINABILITY_CONSTANTS.WATER_USAGE_PER_UNIT, ecoEfficiencyScore: Math.min(100, Math.max(0, 80 - totalEnergyConsumed * 0.1 + completedMaterials * 0.5)), }; @@ -2282,8 +2408,8 @@ function Analyzer() { totalCost, totalValueAdded, totalEnergyConsumed, - energyCost: totalEnergyConsumed * 0.12, // $0.12 per kWh - carbonFootprint: totalEnergyConsumed * 0.5, // 0.5 kg CO2 per kWh + energyCost: totalEnergyConsumed * ENERGY_CONSTANTS.COST_PER_KWH, + carbonFootprint: totalEnergyConsumed * ENERGY_CONSTANTS.CO2_PER_KWH, }, }; }, @@ -2694,7 +2820,7 @@ function Analyzer() { if (!completedActionsRef.current[`${crane.modelUuid}_lift_height`]) { completedActionsRef.current[`${crane.modelUuid}_lift_height`] = 0; } - completedActionsRef.current[`${crane.modelUuid}_lift_height`] += 5; + completedActionsRef.current[`${crane.modelUuid}_lift_height`] += PERFORMANCE_BENCHMARKS.CRANE_DEFAULT_LIFT_HEIGHT; // Track loads handled when picking (each pick is a load) if (!completedActionsRef.current[`${crane.modelUuid}_loads`]) {