feat: implement simulation analyzer component with extensive configuration parameters and state tracking refs
This commit is contained in:
@@ -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`]) {
|
||||
|
||||
Reference in New Issue
Block a user