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 { useSceneContext } from "../../scene/sceneContext";
|
||||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
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() {
|
function Analyzer() {
|
||||||
const { isPaused } = usePauseButtonStore();
|
const { isPaused } = usePauseButtonStore();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
@@ -261,6 +405,10 @@ function Analyzer() {
|
|||||||
return getSimulationTime();
|
return getSimulationTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAssetKey = (assetType: string) => {
|
||||||
|
return assetType === "roboticArm" ? "ROBOTIC_ARM" : assetType.toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ENHANCED UTILITY FUNCTIONS
|
// ENHANCED UTILITY FUNCTIONS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -296,8 +444,8 @@ function Analyzer() {
|
|||||||
const errorCount = errorCountsRef.current[assetId] || 0;
|
const errorCount = errorCountsRef.current[assetId] || 0;
|
||||||
const firstPassYield = totalOperations > 0 ? ((totalOperations - defects) / totalOperations) * 100 : 0;
|
const firstPassYield = totalOperations > 0 ? ((totalOperations - defects) / totalOperations) * 100 : 0;
|
||||||
const defectRate = totalOperations > 0 ? (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 scrapRate = defects > 0 ? defects * QUALITY_CONSTANTS.DEFAULT_SCRAP_RATE : 0;
|
||||||
const reworkRate = defects > 0 ? defects * 0.9 : 0; // Assuming 90% rework
|
const reworkRate = defects > 0 ? defects * QUALITY_CONSTANTS.DEFAULT_REWORK_RATE : 0;
|
||||||
|
|
||||||
// Calculate defect trends
|
// Calculate defect trends
|
||||||
let defectTrend = "stable";
|
let defectTrend = "stable";
|
||||||
@@ -375,16 +523,8 @@ function Analyzer() {
|
|||||||
const outputPerHour = actualTime > 0 ? (actualOutput / actualTime) * 3600 : 0;
|
const outputPerHour = actualTime > 0 ? (actualOutput / actualTime) * 3600 : 0;
|
||||||
|
|
||||||
// Asset type specific adjustments
|
// Asset type specific adjustments
|
||||||
const efficiencyAdjustment =
|
const assetKey = getAssetKey(assetType);
|
||||||
{
|
const efficiencyAdjustment = EFFICIENCY_ADJUSTMENTS[assetKey] || EFFICIENCY_ADJUSTMENTS.DEFAULT;
|
||||||
conveyor: 1.0,
|
|
||||||
vehicle: 0.95,
|
|
||||||
machine: 0.9,
|
|
||||||
roboticArm: 0.92,
|
|
||||||
human: 0.85,
|
|
||||||
crane: 0.88,
|
|
||||||
storage: 0.98,
|
|
||||||
}[assetType] || 1.0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
performanceRate: performanceRate * efficiencyAdjustment,
|
performanceRate: performanceRate * efficiencyAdjustment,
|
||||||
@@ -396,29 +536,22 @@ function Analyzer() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const calculateCostMetrics = (assetId: string, assetType: EventType, activeTime: number, totalOutput: number) => {
|
const calculateCostMetrics = (assetId: string, assetType: EventType, activeTime: number, totalOutput: number) => {
|
||||||
// Cost parameters (hourly rates in $)
|
const assetKey = getAssetKey(assetType);
|
||||||
const costParams = {
|
const hourlyRate = HOURLY_RATES[assetKey] || HOURLY_RATES.CONVEYOR;
|
||||||
conveyor: { hourlyRate: 50, maintenanceCost: 0.5, energyCost: 5 },
|
const maintenanceCostParam = MAINTENANCE_COSTS[assetKey] || MAINTENANCE_COSTS.CONVEYOR;
|
||||||
vehicle: { hourlyRate: 75, maintenanceCost: 2, energyCost: 10 },
|
const energyCostParam = ENERGY_COSTS[assetKey] || ENERGY_COSTS.CONVEYOR;
|
||||||
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 params = costParams[assetType] || costParams.conveyor;
|
|
||||||
const hoursOperated = activeTime / 3600;
|
const hoursOperated = activeTime / 3600;
|
||||||
const errorCount = errorCountsRef.current[assetId] || 0;
|
const errorCount = errorCountsRef.current[assetId] || 0;
|
||||||
|
|
||||||
const operatingCost = hoursOperated * params.hourlyRate;
|
const operatingCost = hoursOperated * hourlyRate;
|
||||||
const maintenanceCost = errorCount * params.maintenanceCost;
|
const maintenanceCost = errorCount * maintenanceCostParam;
|
||||||
const energyCost = hoursOperated * params.energyCost;
|
const energyCost = hoursOperated * energyCostParam;
|
||||||
const totalCost = operatingCost + maintenanceCost + energyCost;
|
const totalCost = operatingCost + maintenanceCost + energyCost;
|
||||||
|
|
||||||
const costPerUnit = totalOutput > 0 ? totalCost / totalOutput : 0;
|
const costPerUnit = totalOutput > 0 ? totalCost / totalOutput : 0;
|
||||||
const costPerHour = totalCost / Math.max(1, hoursOperated);
|
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 {
|
return {
|
||||||
operatingCost,
|
operatingCost,
|
||||||
@@ -428,28 +561,21 @@ function Analyzer() {
|
|||||||
costPerUnit,
|
costPerUnit,
|
||||||
costPerHour,
|
costPerHour,
|
||||||
roi,
|
roi,
|
||||||
valueAdded: totalOutput * 10 - totalCost,
|
valueAdded: totalOutput * FINANCIAL_PARAMS.VALUE_PER_UNIT - totalCost,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateEnergyMetrics = (assetType: EventType, activeTime: number, distanceTraveled: number = 0) => {
|
const calculateEnergyMetrics = (assetType: EventType, activeTime: number, distanceTraveled: number = 0) => {
|
||||||
// Energy consumption rates in kW
|
const assetKey = getAssetKey(assetType);
|
||||||
const energyRates = {
|
let rate = ENERGY_CONSUMPTION_RATES[assetKey] || ENERGY_CONSUMPTION_RATES.DEFAULT;
|
||||||
conveyor: 7.5,
|
if (assetType === "vehicle") {
|
||||||
vehicle: 15 + distanceTraveled * 0.1, // Base + distance-based
|
rate = ENERGY_CONSUMPTION_RATES.VEHICLE + distanceTraveled * ENERGY_CONSUMPTION_RATES.VEHICLE_DISTANCE_FACTOR;
|
||||||
machine: 20,
|
}
|
||||||
roboticArm: 10,
|
|
||||||
human: 0,
|
|
||||||
crane: 25,
|
|
||||||
storage: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
const rate = energyRates[assetType] || 5;
|
|
||||||
const hoursActive = activeTime / 3600;
|
const hoursActive = activeTime / 3600;
|
||||||
const energyConsumed = hoursActive * rate; // kWh
|
const energyConsumed = hoursActive * rate; // kWh
|
||||||
const energyEfficiency = 85 - Math.random() * 10; // Simulated efficiency 75-85%
|
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 carbonFootprint = energyConsumed * ENERGY_CONSTANTS.CO2_PER_KWH;
|
||||||
const energyCost = energyConsumed * 0.12; // $0.12 per kWh
|
const energyCost = energyConsumed * ENERGY_CONSTANTS.COST_PER_KWH;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
energyConsumed,
|
energyConsumed,
|
||||||
@@ -696,7 +822,7 @@ function Analyzer() {
|
|||||||
|
|
||||||
// Limit history to last 100 actions
|
// Limit history to last 100 actions
|
||||||
if (actionCompletionTimesRef.current[assetId].length > 100) {
|
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
|
// Keep only last 100 snapshots
|
||||||
if (wipSnapshotsRef.current[assetId].length > 100) {
|
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]
|
[getMaterialsByModel]
|
||||||
@@ -732,7 +858,7 @@ function Analyzer() {
|
|||||||
const updateThroughputSnapshot = useCallback(
|
const updateThroughputSnapshot = useCallback(
|
||||||
(assetId: string) => {
|
(assetId: string) => {
|
||||||
const timestamp = getSimulationTime();
|
const timestamp = getSimulationTime();
|
||||||
const timeWindow = 60; // 60 seconds
|
const timeWindow = ANALYSIS_SETTINGS.THROUGHPUT_WINDOW;
|
||||||
|
|
||||||
if (!throughputSnapshotsRef.current[assetId]) {
|
if (!throughputSnapshotsRef.current[assetId]) {
|
||||||
throughputSnapshotsRef.current[assetId] = [];
|
throughputSnapshotsRef.current[assetId] = [];
|
||||||
@@ -754,7 +880,7 @@ function Analyzer() {
|
|||||||
|
|
||||||
// Keep only last 100 snapshots
|
// Keep only last 100 snapshots
|
||||||
if (throughputSnapshotsRef.current[assetId].length > 100) {
|
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]
|
[speed]
|
||||||
@@ -780,7 +906,7 @@ function Analyzer() {
|
|||||||
|
|
||||||
// Keep only last 100 snapshots
|
// Keep only last 100 snapshots
|
||||||
if (performanceSnapshotsRef.current[assetId].length > 100) {
|
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
|
// 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({
|
bottleneckEventsRef.current[assetId].push({
|
||||||
timestamp,
|
timestamp,
|
||||||
queueLength,
|
queueLength,
|
||||||
@@ -888,7 +1014,7 @@ function Analyzer() {
|
|||||||
const qualityMetrics = calculateQualityMetrics(conveyor.modelUuid, materialsProcessed, defects);
|
const qualityMetrics = calculateQualityMetrics(conveyor.modelUuid, materialsProcessed, defects);
|
||||||
|
|
||||||
// Performance calculations
|
// Performance calculations
|
||||||
const conveyorLength = 10; // meters
|
const conveyorLength = PERFORMANCE_BENCHMARKS.CONVEYOR_LENGTH;
|
||||||
const idealSpeed = conveyor.speed;
|
const idealSpeed = conveyor.speed;
|
||||||
const actualSpeed = conveyor.isActive ? conveyor.speed : 0;
|
const actualSpeed = conveyor.isActive ? conveyor.speed : 0;
|
||||||
const idealThroughput = idealSpeed > 0 ? (idealSpeed * 3600) / (conveyorLength / 2) : 0; // items per hour
|
const idealThroughput = idealSpeed > 0 ? (idealSpeed * 3600) / (conveyorLength / 2) : 0; // items per hour
|
||||||
@@ -919,12 +1045,12 @@ function Analyzer() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentData = historicalDataRef.current[conveyor.modelUuid] || [];
|
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
|
// Calculate queue if applicable
|
||||||
const queueLength = materialFlow.wip;
|
const queueLength = materialFlow.wip;
|
||||||
const currentQueueData = queueLengthsRef.current[conveyor.modelUuid] || [];
|
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 {
|
return {
|
||||||
assetId: conveyor.modelUuid,
|
assetId: conveyor.modelUuid,
|
||||||
@@ -960,7 +1086,7 @@ function Analyzer() {
|
|||||||
averageProcessingTime: materialFlow.avgCycleTime,
|
averageProcessingTime: materialFlow.avgCycleTime,
|
||||||
wip: materialFlow.wip,
|
wip: materialFlow.wip,
|
||||||
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
||||||
bottleneckIndex: timeMetrics.utilizationRate > 0.9 ? 1 : 0,
|
bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.HIGH_UTILIZATION ? 1 : 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
efficiency: {
|
efficiency: {
|
||||||
@@ -1034,7 +1160,7 @@ function Analyzer() {
|
|||||||
const avgLoad = tripsCompleted > 0 ? totalLoadsDelivered / tripsCompleted : 0;
|
const avgLoad = tripsCompleted > 0 ? totalLoadsDelivered / tripsCompleted : 0;
|
||||||
const loadUtilization = (avgLoad / loadCapacity) * 100;
|
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 actualTripTime = tripsCompleted > 0 ? timeMetrics.totalTime / tripsCompleted : 0;
|
||||||
const idealThroughput = loadCapacity * (3600 / idealTripTime);
|
const idealThroughput = loadCapacity * (3600 / idealTripTime);
|
||||||
const actualThroughput = materialFlow.throughput * 3600;
|
const actualThroughput = materialFlow.throughput * 3600;
|
||||||
@@ -1042,7 +1168,7 @@ function Analyzer() {
|
|||||||
const performance = calculatePerformanceMetrics(totalLoadsDelivered, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, idealTripTime * tripsCompleted, "vehicle");
|
const performance = calculatePerformanceMetrics(totalLoadsDelivered, idealThroughput * (timeMetrics.totalTime / 3600), timeMetrics.totalTime, idealTripTime * tripsCompleted, "vehicle");
|
||||||
|
|
||||||
// Route efficiency
|
// Route efficiency
|
||||||
const optimalDistance = 100;
|
const optimalDistance = PERFORMANCE_BENCHMARKS.VEHICLE_OPTIMAL_DISTANCE;
|
||||||
const actualDistance = vehicle.distanceTraveled || 0;
|
const actualDistance = vehicle.distanceTraveled || 0;
|
||||||
const routeEfficiency = actualDistance > 0 ? Math.min((optimalDistance / actualDistance) * 100, 100) : 100;
|
const routeEfficiency = actualDistance > 0 ? Math.min((optimalDistance / actualDistance) * 100, 100) : 100;
|
||||||
|
|
||||||
@@ -1065,7 +1191,7 @@ function Analyzer() {
|
|||||||
speed: vehicle.speed,
|
speed: vehicle.speed,
|
||||||
tripsCompleted,
|
tripsCompleted,
|
||||||
},
|
},
|
||||||
].slice(-100);
|
].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assetId: vehicle.modelUuid,
|
assetId: vehicle.modelUuid,
|
||||||
@@ -1104,7 +1230,7 @@ function Analyzer() {
|
|||||||
totalLoadsDelivered,
|
totalLoadsDelivered,
|
||||||
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
||||||
wip: materialFlow.wip,
|
wip: materialFlow.wip,
|
||||||
bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0,
|
bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
movementMetrics: {
|
movementMetrics: {
|
||||||
@@ -1182,7 +1308,7 @@ function Analyzer() {
|
|||||||
const qualityMetrics = calculateQualityMetrics(armBot.modelUuid, pickAndPlaceCount, defects);
|
const qualityMetrics = calculateQualityMetrics(armBot.modelUuid, pickAndPlaceCount, defects);
|
||||||
|
|
||||||
// Performance calculations
|
// 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 actualCycleTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0;
|
||||||
const idealThroughput = (3600 / idealCycleTime) * 1; // 1 item per cycle
|
const idealThroughput = (3600 / idealCycleTime) * 1; // 1 item per cycle
|
||||||
const actualThroughput = materialFlow.throughput * 3600;
|
const actualThroughput = materialFlow.throughput * 3600;
|
||||||
@@ -1228,7 +1354,7 @@ function Analyzer() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentData = historicalDataRef.current[armBot.modelUuid] || [];
|
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 {
|
return {
|
||||||
assetId: armBot.modelUuid,
|
assetId: armBot.modelUuid,
|
||||||
@@ -1261,7 +1387,7 @@ function Analyzer() {
|
|||||||
pickAndPlaceCount,
|
pickAndPlaceCount,
|
||||||
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
||||||
wip: materialFlow.wip,
|
wip: materialFlow.wip,
|
||||||
bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0,
|
bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
efficiency: {
|
efficiency: {
|
||||||
@@ -1321,7 +1447,7 @@ function Analyzer() {
|
|||||||
const qualityMetrics = calculateQualityMetrics(machine.modelUuid, partsProcessed, defects);
|
const qualityMetrics = calculateQualityMetrics(machine.modelUuid, partsProcessed, defects);
|
||||||
|
|
||||||
// Performance calculations
|
// 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 actualProcessTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0;
|
||||||
const idealThroughput = (3600 / targetProcessTime) * 1; // 1 part per process
|
const idealThroughput = (3600 / targetProcessTime) * 1; // 1 part per process
|
||||||
const actualThroughput = materialFlow.throughput * 3600;
|
const actualThroughput = materialFlow.throughput * 3600;
|
||||||
@@ -1335,8 +1461,8 @@ function Analyzer() {
|
|||||||
// Quality calculations
|
// Quality calculations
|
||||||
const totalParts = partsProcessed + defects;
|
const totalParts = partsProcessed + defects;
|
||||||
const defectRate = totalParts > 0 ? (defects / totalParts) * 100 : 0;
|
const defectRate = totalParts > 0 ? (defects / totalParts) * 100 : 0;
|
||||||
const reworkRate = defectRate * 0.3; // Assume 30% of defects are reworked
|
const reworkRate = defectRate * QUALITY_CONSTANTS.MACHINE_REWORK_RATIO;
|
||||||
const scrapRate = defectRate * 0.7; // Assume 70% are scrapped
|
const scrapRate = defectRate * QUALITY_CONSTANTS.MACHINE_SCRAP_RATIO;
|
||||||
|
|
||||||
// Update historical data
|
// Update historical data
|
||||||
const timestamp = getSimulationTime();
|
const timestamp = getSimulationTime();
|
||||||
@@ -1352,7 +1478,7 @@ function Analyzer() {
|
|||||||
defectRate,
|
defectRate,
|
||||||
performance: performance.performanceRate,
|
performance: performance.performanceRate,
|
||||||
},
|
},
|
||||||
].slice(-100);
|
].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assetId: machine.modelUuid,
|
assetId: machine.modelUuid,
|
||||||
@@ -1384,7 +1510,7 @@ function Analyzer() {
|
|||||||
partsProcessed,
|
partsProcessed,
|
||||||
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
||||||
wip: materialFlow.wip,
|
wip: materialFlow.wip,
|
||||||
bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0,
|
bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
efficiency: {
|
efficiency: {
|
||||||
@@ -1464,7 +1590,7 @@ function Analyzer() {
|
|||||||
totalOps,
|
totalOps,
|
||||||
state: storage.state,
|
state: storage.state,
|
||||||
},
|
},
|
||||||
].slice(-100);
|
].slice(-ANALYSIS_SETTINGS.HISTORY_LIMIT);
|
||||||
|
|
||||||
// Calculate peak occupancy from historical data
|
// Calculate peak occupancy from historical data
|
||||||
const occupancyData = historicalDataRef.current[storage.modelUuid] || [];
|
const occupancyData = historicalDataRef.current[storage.modelUuid] || [];
|
||||||
@@ -1519,7 +1645,7 @@ function Analyzer() {
|
|||||||
totalOperations: totalOps,
|
totalOperations: totalOps,
|
||||||
throughputEfficiency: (totalOps / Math.max(1, capacity * 24)) * 100,
|
throughputEfficiency: (totalOps / Math.max(1, capacity * 24)) * 100,
|
||||||
wip: currentLoad,
|
wip: currentLoad,
|
||||||
bottleneckIndex: utilizationRate > 0.9 ? 1 : 0,
|
bottleneckIndex: utilizationRate > ANALYSIS_SETTINGS.HIGH_UTILIZATION ? 1 : 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
efficiency: {
|
efficiency: {
|
||||||
@@ -1554,7 +1680,7 @@ function Analyzer() {
|
|||||||
energyPerUnit: totalOps > 0 ? energyMetrics.energyConsumed / totalOps : 0,
|
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,
|
timestamp: d.timestamp,
|
||||||
occupancy: d.currentLoad,
|
occupancy: d.currentLoad,
|
||||||
utilizationRate: d.utilizationRate,
|
utilizationRate: d.utilizationRate,
|
||||||
@@ -1604,7 +1730,7 @@ function Analyzer() {
|
|||||||
const workloadDistribution = workloadDistributionData.map((d) => `${Math.round(d.percentage)}%`).join(" | ");
|
const workloadDistribution = workloadDistributionData.map((d) => `${Math.round(d.percentage)}%`).join(" | ");
|
||||||
|
|
||||||
// Performance calculations
|
// 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 actualActionsPerHour = timeMetrics.totalTime > 0 ? (actionsCompleted / timeMetrics.totalTime) * 3600 : 0;
|
||||||
const performanceRate = Math.min((actualActionsPerHour / idealActionsPerHour) * 100, 100);
|
const performanceRate = Math.min((actualActionsPerHour / idealActionsPerHour) * 100, 100);
|
||||||
|
|
||||||
@@ -1627,7 +1753,7 @@ function Analyzer() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentData = historicalDataRef.current[human.modelUuid] || [];
|
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 {
|
return {
|
||||||
assetId: human.modelUuid,
|
assetId: human.modelUuid,
|
||||||
@@ -1724,7 +1850,7 @@ function Analyzer() {
|
|||||||
const qualityMetrics = calculateQualityMetrics(crane.modelUuid, loadsHandled, errorCount);
|
const qualityMetrics = calculateQualityMetrics(crane.modelUuid, loadsHandled, errorCount);
|
||||||
|
|
||||||
// Performance calculations
|
// 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 actualCycleTime = cyclesCompleted > 0 ? timeMetrics.totalTime / cyclesCompleted : 0;
|
||||||
const idealThroughput = (3600 / idealCycleTime) * 1; // 1 load per cycle
|
const idealThroughput = (3600 / idealCycleTime) * 1; // 1 load per cycle
|
||||||
const actualThroughput = cyclesCompleted > 0 ? (loadsHandled / timeMetrics.totalTime) * 3600 : 0;
|
const actualThroughput = cyclesCompleted > 0 ? (loadsHandled / timeMetrics.totalTime) * 3600 : 0;
|
||||||
@@ -1764,7 +1890,7 @@ function Analyzer() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentData = historicalDataRef.current[crane.modelUuid] || [];
|
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 {
|
return {
|
||||||
assetId: crane.modelUuid,
|
assetId: crane.modelUuid,
|
||||||
@@ -1803,7 +1929,7 @@ function Analyzer() {
|
|||||||
averageLoadsPerCycle: avgLoadsPerCycle,
|
averageLoadsPerCycle: avgLoadsPerCycle,
|
||||||
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
throughputEfficiency: (actualThroughput / Math.max(1, idealThroughput)) * 100,
|
||||||
wip: crane.currentLoad,
|
wip: crane.currentLoad,
|
||||||
bottleneckIndex: timeMetrics.utilizationRate > 0.85 ? 1 : 0,
|
bottleneckIndex: timeMetrics.utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD ? 1 : 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
movementMetrics: {
|
movementMetrics: {
|
||||||
@@ -2023,9 +2149,9 @@ function Analyzer() {
|
|||||||
const impactScore = utilizationRate * 100 + queueLength * 10;
|
const impactScore = utilizationRate * 100 + queueLength * 10;
|
||||||
|
|
||||||
let severity: "critical" | "high" | "medium" | "low" = "low";
|
let severity: "critical" | "high" | "medium" | "low" = "low";
|
||||||
if (utilizationRate > 0.95) severity = "critical";
|
if (utilizationRate > ANALYSIS_SETTINGS.CRITICAL_UTILIZATION) severity = "critical";
|
||||||
else if (utilizationRate > 0.9) severity = "high";
|
else if (utilizationRate > ANALYSIS_SETTINGS.HIGH_UTILIZATION) severity = "high";
|
||||||
else if (utilizationRate > 0.85) severity = "medium";
|
else if (utilizationRate > ANALYSIS_SETTINGS.BOTTLENECK_UTILIZATION_THRESHOLD) severity = "medium";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assetId: asset.assetId,
|
assetId: asset.assetId,
|
||||||
@@ -2138,7 +2264,7 @@ function Analyzer() {
|
|||||||
const calculateAdvancedAnalytics = (): AdvancedAnalytics => {
|
const calculateAdvancedAnalytics = (): AdvancedAnalytics => {
|
||||||
// Financials
|
// Financials
|
||||||
// Assume average revenue per completed item is $150 (since cost is ~10-100 range)
|
// 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 totalRevenue = completedMaterials * revenuePerUnit;
|
||||||
const grossProfit = totalRevenue - totalCost;
|
const grossProfit = totalRevenue - totalCost;
|
||||||
const netProfitMargin = totalRevenue > 0 ? (grossProfit / totalRevenue) * 100 : 0;
|
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);
|
const maintenanceCosts = allAssets.reduce((sum, a) => sum + (a.costMetrics?.maintenanceCost || 0), 0);
|
||||||
|
|
||||||
// Fixed vs Variable (Assumption: 40% fixed, 60% variable)
|
// Fixed vs Variable (Assumption: 40% fixed, 60% variable)
|
||||||
const fixedCosts = totalCost * 0.4;
|
const fixedCosts = totalCost * FINANCIAL_PARAMS.FIXED_COST_RATIO;
|
||||||
const variableCosts = totalCost * 0.6;
|
const variableCosts = totalCost * FINANCIAL_PARAMS.VARIABLE_COST_RATIO;
|
||||||
const breakEvenUnits = revenuePerUnit > variableCosts / Math.max(1, completedMaterials) ? fixedCosts / (revenuePerUnit - variableCosts / Math.max(1, completedMaterials)) : 0;
|
const breakEvenUnits = revenuePerUnit > variableCosts / Math.max(1, completedMaterials) ? fixedCosts / (revenuePerUnit - variableCosts / Math.max(1, completedMaterials)) : 0;
|
||||||
|
|
||||||
// Burn Rate
|
// Burn Rate
|
||||||
@@ -2175,15 +2301,15 @@ function Analyzer() {
|
|||||||
|
|
||||||
// Supply Chain
|
// Supply Chain
|
||||||
// Assign values to materials
|
// Assign values to materials
|
||||||
const avgMaterialValue = 25;
|
const avgMaterialValue = FINANCIAL_PARAMS.MATERIAL_VALUE;
|
||||||
const totalInventoryValue = totalMaterialsInSystem * avgMaterialValue;
|
const totalInventoryValue = totalMaterialsInSystem * avgMaterialValue;
|
||||||
const inventoryTurnoverRate = totalInventoryValue > 0 ? (totalCost * 0.6) / totalInventoryValue : 0; // standard formula COGS/AvgInv
|
const inventoryTurnoverRate = totalInventoryValue > 0 ? (totalCost * FINANCIAL_PARAMS.VARIABLE_COST_RATIO) / totalInventoryValue : 0; // standard formula COGS/AvgInv
|
||||||
const holdingCostRate = 0.25; // 25% annual holding cost
|
const holdingCostRate = FINANCIAL_PARAMS.HOLDING_COST_RATE;
|
||||||
const holdingCost = ((totalInventoryValue * holdingCostRate) / (365 * 24)) * analysisDurationHours; // prorated
|
const holdingCost = ((totalInventoryValue * holdingCostRate) / (365 * 24)) * analysisDurationHours; // prorated
|
||||||
|
|
||||||
// Scrap value
|
// Scrap value
|
||||||
const totalScrap = allAssets.reduce((sum, a) => sum + (a.quality?.scrapRate || 0), 0);
|
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 = {
|
const supplyChain: SupplyChainMetrics = {
|
||||||
totalInventoryValue,
|
totalInventoryValue,
|
||||||
@@ -2198,16 +2324,16 @@ function Analyzer() {
|
|||||||
|
|
||||||
// Sustainability
|
// Sustainability
|
||||||
// CO2 factors
|
// 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 wasteGenerationRate = analysisDurationHours > 0 ? totalWaste / analysisDurationHours : 0;
|
||||||
|
|
||||||
const sustainability: SustainabilityMetrics = {
|
const sustainability: SustainabilityMetrics = {
|
||||||
totalCarbonFootprint: totalEnergyConsumed * 0.5, // 0.5 kg/kWh
|
totalCarbonFootprint: totalEnergyConsumed * ENERGY_CONSTANTS.CO2_PER_KWH,
|
||||||
energyIntensity: completedMaterials > 0 ? totalEnergyConsumed / completedMaterials : 0,
|
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,
|
wasteGenerationRate,
|
||||||
recyclableRatio: 0.3, // 30% recyclable (simulated)
|
recyclableRatio: SUSTAINABILITY_CONSTANTS.RECYCLABLE_RATIO,
|
||||||
waterUsage: completedMaterials * 10, // 10L per unit (simulated)
|
waterUsage: completedMaterials * SUSTAINABILITY_CONSTANTS.WATER_USAGE_PER_UNIT,
|
||||||
ecoEfficiencyScore: Math.min(100, Math.max(0, 80 - totalEnergyConsumed * 0.1 + completedMaterials * 0.5)),
|
ecoEfficiencyScore: Math.min(100, Math.max(0, 80 - totalEnergyConsumed * 0.1 + completedMaterials * 0.5)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2282,8 +2408,8 @@ function Analyzer() {
|
|||||||
totalCost,
|
totalCost,
|
||||||
totalValueAdded,
|
totalValueAdded,
|
||||||
totalEnergyConsumed,
|
totalEnergyConsumed,
|
||||||
energyCost: totalEnergyConsumed * 0.12, // $0.12 per kWh
|
energyCost: totalEnergyConsumed * ENERGY_CONSTANTS.COST_PER_KWH,
|
||||||
carbonFootprint: totalEnergyConsumed * 0.5, // 0.5 kg CO2 per kWh
|
carbonFootprint: totalEnergyConsumed * ENERGY_CONSTANTS.CO2_PER_KWH,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -2694,7 +2820,7 @@ function Analyzer() {
|
|||||||
if (!completedActionsRef.current[`${crane.modelUuid}_lift_height`]) {
|
if (!completedActionsRef.current[`${crane.modelUuid}_lift_height`]) {
|
||||||
completedActionsRef.current[`${crane.modelUuid}_lift_height`] = 0;
|
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)
|
// Track loads handled when picking (each pick is a load)
|
||||||
if (!completedActionsRef.current[`${crane.modelUuid}_loads`]) {
|
if (!completedActionsRef.current[`${crane.modelUuid}_loads`]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user