feat: implement simulation analyzer component with extensive configuration parameters and state tracking refs

This commit is contained in:
2025-12-22 15:44:31 +05:30
parent fc5c95d60f
commit 7a0b91a946

View File

@@ -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<string, number> = {
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<string, number> = {
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<string, number> = {
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<string, number> = {
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<string, number> = {
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`]) {