feat: Add comprehensive simulation analyzer module with extensive tracking and metric calculation capabilities.
This commit is contained in:
@@ -17,15 +17,192 @@ function Analyzer() {
|
||||
|
||||
const { setAnalysis, setAnalyzing, analysis } = analysisStore();
|
||||
|
||||
// ============================================================================
|
||||
// COMPREHENSIVE TRACKING REFS FOR PERFORMANCE METRICS
|
||||
// ============================================================================
|
||||
|
||||
// Historical data tracking
|
||||
const historicalDataRef = useRef<Record<string, any[]>>({});
|
||||
const materialHistoryRef = useRef<MaterialHistoryEntry[]>([]);
|
||||
const queueLengthsRef = useRef<Record<string, { timestamp: number; length: number }[]>>({});
|
||||
|
||||
// Timing and intervals
|
||||
const analysisIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const startTimeRef = useRef<string>(new Date().toISOString());
|
||||
|
||||
// Error and action tracking
|
||||
const errorCountsRef = useRef<Record<string, number>>({});
|
||||
const completedActionsRef = useRef<Record<string, number>>({});
|
||||
const stateTransitionsRef = useRef<Record<string, any[]>>({});
|
||||
|
||||
// Material flow tracking - tracks materials added/removed per asset
|
||||
const materialAdditionsRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
materialId: string;
|
||||
materialType: string;
|
||||
timestamp: number;
|
||||
fromAsset?: string;
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
const materialRemovalsRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
materialId: string;
|
||||
materialType: string;
|
||||
timestamp: number;
|
||||
toAsset?: string;
|
||||
processingTime?: number;
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Asset state change tracking with detailed timestamps
|
||||
const assetStateChangesRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
fromState: string;
|
||||
toState: string;
|
||||
timestamp: number;
|
||||
duration?: number;
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Cycle tracking for each asset
|
||||
const assetCyclesRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
cycleId: string;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
cycleType: string; // 'processing', 'transport', 'pick-place', etc.
|
||||
materialsInvolved: string[];
|
||||
success: boolean;
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Action completion times for performance analysis
|
||||
const actionCompletionTimesRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
actionId: string;
|
||||
actionType: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
duration: number;
|
||||
success: boolean;
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Material processing times per asset
|
||||
const materialProcessingTimesRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
materialId: string;
|
||||
entryTime: number;
|
||||
exitTime?: number;
|
||||
processingDuration?: number;
|
||||
waitTime?: number;
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// WIP (Work In Progress) tracking per asset over time
|
||||
const wipSnapshotsRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
timestamp: number;
|
||||
wipCount: number;
|
||||
materialIds: string[];
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Throughput snapshots for trend analysis
|
||||
const throughputSnapshotsRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
timestamp: number;
|
||||
itemsProcessed: number;
|
||||
timeWindow: number; // in seconds
|
||||
rate: number; // items per hour
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Asset performance snapshots
|
||||
const performanceSnapshotsRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
timestamp: number;
|
||||
utilization: number;
|
||||
efficiency: number;
|
||||
quality: number;
|
||||
oee: number;
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Bottleneck detection tracking
|
||||
const bottleneckEventsRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
timestamp: number;
|
||||
queueLength: number;
|
||||
utilizationRate: number;
|
||||
waitingMaterials: string[];
|
||||
}[]
|
||||
>
|
||||
>({});
|
||||
|
||||
// Previous state tracking for delta calculations
|
||||
const previousAssetStatesRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
state: string;
|
||||
isActive: boolean;
|
||||
materialCount: number;
|
||||
timestamp: number;
|
||||
}
|
||||
>
|
||||
>({});
|
||||
|
||||
// Material lifecycle tracking
|
||||
const materialLifecycleRef = useRef<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
materialId: string;
|
||||
createdAt: number;
|
||||
completedAt?: number;
|
||||
path: {
|
||||
assetId: string;
|
||||
assetType: string;
|
||||
entryTime: number;
|
||||
exitTime?: number;
|
||||
}[];
|
||||
totalProcessingTime?: number;
|
||||
totalWaitTime?: number;
|
||||
}
|
||||
>
|
||||
>({});
|
||||
|
||||
// ============================================================================
|
||||
// ENHANCED UTILITY FUNCTIONS
|
||||
// ============================================================================
|
||||
@@ -247,6 +424,370 @@ function Analyzer() {
|
||||
}));
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TRACKING HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Track material addition to an asset
|
||||
*/
|
||||
const trackMaterialAddition = useCallback((assetId: string, materialId: string, materialType: string, fromAsset?: string) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
if (!materialAdditionsRef.current[assetId]) {
|
||||
materialAdditionsRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
materialAdditionsRef.current[assetId].push({
|
||||
materialId,
|
||||
materialType,
|
||||
timestamp,
|
||||
fromAsset,
|
||||
});
|
||||
|
||||
// Track in material processing times
|
||||
if (!materialProcessingTimesRef.current[assetId]) {
|
||||
materialProcessingTimesRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
materialProcessingTimesRef.current[assetId].push({
|
||||
materialId,
|
||||
entryTime: timestamp,
|
||||
});
|
||||
|
||||
// Update material lifecycle
|
||||
if (!materialLifecycleRef.current[materialId]) {
|
||||
materialLifecycleRef.current[materialId] = {
|
||||
materialId,
|
||||
createdAt: timestamp,
|
||||
path: [],
|
||||
};
|
||||
}
|
||||
|
||||
materialLifecycleRef.current[materialId].path.push({
|
||||
assetId,
|
||||
assetType: "", // Will be filled by caller
|
||||
entryTime: timestamp,
|
||||
});
|
||||
|
||||
// Update WIP snapshot
|
||||
updateWIPSnapshot(assetId);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Track material removal from an asset
|
||||
*/
|
||||
const trackMaterialRemoval = useCallback((assetId: string, materialId: string, materialType: string, toAsset?: string) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
if (!materialRemovalsRef.current[assetId]) {
|
||||
materialRemovalsRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
// Calculate processing time
|
||||
const processingTimes = materialProcessingTimesRef.current[assetId] || [];
|
||||
const entryRecord = processingTimes.find((p) => p.materialId === materialId && !p.exitTime);
|
||||
let processingTime: number | undefined;
|
||||
|
||||
if (entryRecord) {
|
||||
processingTime = timestamp - entryRecord.entryTime;
|
||||
entryRecord.exitTime = timestamp;
|
||||
entryRecord.processingDuration = processingTime;
|
||||
}
|
||||
|
||||
materialRemovalsRef.current[assetId].push({
|
||||
materialId,
|
||||
materialType,
|
||||
timestamp,
|
||||
toAsset,
|
||||
processingTime,
|
||||
});
|
||||
|
||||
// Update material lifecycle
|
||||
const lifecycle = materialLifecycleRef.current[materialId];
|
||||
if (lifecycle && lifecycle.path.length > 0) {
|
||||
const lastPathEntry = lifecycle.path[lifecycle.path.length - 1];
|
||||
if (lastPathEntry.assetId === assetId) {
|
||||
lastPathEntry.exitTime = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
// Update WIP snapshot
|
||||
updateWIPSnapshot(assetId);
|
||||
|
||||
// Update throughput snapshot
|
||||
updateThroughputSnapshot(assetId);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Track asset state change
|
||||
*/
|
||||
const trackStateChange = useCallback((assetId: string, fromState: string, toState: string) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
if (!assetStateChangesRef.current[assetId]) {
|
||||
assetStateChangesRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
// Calculate duration from previous state
|
||||
const previousChanges = assetStateChangesRef.current[assetId];
|
||||
let duration: number | undefined;
|
||||
|
||||
if (previousChanges.length > 0) {
|
||||
const lastChange = previousChanges[previousChanges.length - 1];
|
||||
duration = timestamp - lastChange.timestamp;
|
||||
lastChange.duration = duration;
|
||||
}
|
||||
|
||||
assetStateChangesRef.current[assetId].push({
|
||||
fromState,
|
||||
toState,
|
||||
timestamp,
|
||||
});
|
||||
|
||||
// Also track in state transitions ref for compatibility
|
||||
if (!stateTransitionsRef.current[assetId]) {
|
||||
stateTransitionsRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
stateTransitionsRef.current[assetId].push({
|
||||
fromState,
|
||||
toState,
|
||||
timestamp,
|
||||
duration: duration || 0,
|
||||
});
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Start a new cycle for an asset
|
||||
*/
|
||||
const startAssetCycle = useCallback((assetId: string, cycleType: string, materialsInvolved: string[] = []) => {
|
||||
const timestamp = Date.now();
|
||||
const cycleId = `${assetId}-${timestamp}`;
|
||||
|
||||
if (!assetCyclesRef.current[assetId]) {
|
||||
assetCyclesRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
assetCyclesRef.current[assetId].push({
|
||||
cycleId,
|
||||
startTime: timestamp,
|
||||
cycleType,
|
||||
materialsInvolved,
|
||||
success: false,
|
||||
});
|
||||
|
||||
return cycleId;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Complete a cycle for an asset
|
||||
*/
|
||||
const completeAssetCycle = useCallback((assetId: string, cycleId: string, success: boolean = true) => {
|
||||
const timestamp = Date.now();
|
||||
const cycles = assetCyclesRef.current[assetId] || [];
|
||||
const cycle = cycles.find((c) => c.cycleId === cycleId);
|
||||
|
||||
if (cycle) {
|
||||
cycle.endTime = timestamp;
|
||||
cycle.success = success;
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Track action completion
|
||||
*/
|
||||
const trackActionCompletion = useCallback((assetId: string, actionId: string, actionType: string, startTime: number, success: boolean = true) => {
|
||||
const endTime = Date.now();
|
||||
|
||||
if (!actionCompletionTimesRef.current[assetId]) {
|
||||
actionCompletionTimesRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
actionCompletionTimesRef.current[assetId].push({
|
||||
actionId,
|
||||
actionType,
|
||||
startTime,
|
||||
endTime,
|
||||
duration: endTime - startTime,
|
||||
success,
|
||||
});
|
||||
|
||||
// Limit history to last 100 actions
|
||||
if (actionCompletionTimesRef.current[assetId].length > 100) {
|
||||
actionCompletionTimesRef.current[assetId] = actionCompletionTimesRef.current[assetId].slice(-100);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Update WIP snapshot for an asset
|
||||
*/
|
||||
const updateWIPSnapshot = useCallback(
|
||||
(assetId: string) => {
|
||||
const timestamp = Date.now();
|
||||
const currentMaterials = getMaterialsByModel(assetId);
|
||||
|
||||
if (!wipSnapshotsRef.current[assetId]) {
|
||||
wipSnapshotsRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
wipSnapshotsRef.current[assetId].push({
|
||||
timestamp,
|
||||
wipCount: currentMaterials.length,
|
||||
materialIds: currentMaterials.map((m) => m.materialId),
|
||||
});
|
||||
|
||||
// Keep only last 100 snapshots
|
||||
if (wipSnapshotsRef.current[assetId].length > 100) {
|
||||
wipSnapshotsRef.current[assetId] = wipSnapshotsRef.current[assetId].slice(-100);
|
||||
}
|
||||
},
|
||||
[getMaterialsByModel]
|
||||
);
|
||||
|
||||
/**
|
||||
* Update throughput snapshot for an asset
|
||||
*/
|
||||
const updateThroughputSnapshot = useCallback((assetId: string) => {
|
||||
const timestamp = Date.now();
|
||||
const timeWindow = 60; // 60 seconds
|
||||
|
||||
if (!throughputSnapshotsRef.current[assetId]) {
|
||||
throughputSnapshotsRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
// Count items processed in the last time window
|
||||
const removals = materialRemovalsRef.current[assetId] || [];
|
||||
const recentRemovals = removals.filter((r) => timestamp - r.timestamp <= timeWindow * 1000);
|
||||
const itemsProcessed = recentRemovals.length;
|
||||
const rate = (itemsProcessed / timeWindow) * 3600; // items per hour
|
||||
|
||||
throughputSnapshotsRef.current[assetId].push({
|
||||
timestamp,
|
||||
itemsProcessed,
|
||||
timeWindow,
|
||||
rate,
|
||||
});
|
||||
|
||||
// Keep only last 100 snapshots
|
||||
if (throughputSnapshotsRef.current[assetId].length > 100) {
|
||||
throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Update performance snapshot for an asset
|
||||
*/
|
||||
const updatePerformanceSnapshot = useCallback((assetId: string, utilization: number, efficiency: number, quality: number, oee: number) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
if (!performanceSnapshotsRef.current[assetId]) {
|
||||
performanceSnapshotsRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
performanceSnapshotsRef.current[assetId].push({
|
||||
timestamp,
|
||||
utilization,
|
||||
efficiency,
|
||||
quality,
|
||||
oee,
|
||||
});
|
||||
|
||||
// Keep only last 100 snapshots
|
||||
if (performanceSnapshotsRef.current[assetId].length > 100) {
|
||||
performanceSnapshotsRef.current[assetId] = performanceSnapshotsRef.current[assetId].slice(-100);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Track bottleneck event
|
||||
*/
|
||||
const trackBottleneckEvent = useCallback((assetId: string, queueLength: number, utilizationRate: number, waitingMaterials: string[]) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
if (!bottleneckEventsRef.current[assetId]) {
|
||||
bottleneckEventsRef.current[assetId] = [];
|
||||
}
|
||||
|
||||
// Only track if it's a significant bottleneck
|
||||
if (queueLength > 2 || utilizationRate > 0.85) {
|
||||
bottleneckEventsRef.current[assetId].push({
|
||||
timestamp,
|
||||
queueLength,
|
||||
utilizationRate,
|
||||
waitingMaterials,
|
||||
});
|
||||
|
||||
// Keep only last 50 events
|
||||
if (bottleneckEventsRef.current[assetId].length > 50) {
|
||||
bottleneckEventsRef.current[assetId] = bottleneckEventsRef.current[assetId].slice(-50);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Update previous asset state for delta tracking
|
||||
*/
|
||||
const updatePreviousAssetState = useCallback((assetId: string, state: string, isActive: boolean, materialCount: number) => {
|
||||
previousAssetStatesRef.current[assetId] = {
|
||||
state,
|
||||
isActive,
|
||||
materialCount,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Get enhanced material flow metrics using tracked data
|
||||
*/
|
||||
const getEnhancedMaterialFlowMetrics = useCallback(
|
||||
(assetId: string) => {
|
||||
const additions = materialAdditionsRef.current[assetId] || [];
|
||||
const removals = materialRemovalsRef.current[assetId] || [];
|
||||
const processingTimes = materialProcessingTimesRef.current[assetId] || [];
|
||||
const wipSnapshots = wipSnapshotsRef.current[assetId] || [];
|
||||
|
||||
// Calculate average processing time
|
||||
const completedProcessing = processingTimes.filter((p) => p.processingDuration);
|
||||
const avgProcessingTime = completedProcessing.length > 0 ? completedProcessing.reduce((sum, p) => sum + (p.processingDuration || 0), 0) / completedProcessing.length : 0;
|
||||
|
||||
// Calculate current WIP
|
||||
const currentWIP = wipSnapshots.length > 0 ? wipSnapshots[wipSnapshots.length - 1].wipCount : 0;
|
||||
|
||||
// Calculate throughput rate
|
||||
const now = Date.now();
|
||||
const oneHourAgo = now - 3600000;
|
||||
const recentRemovals = removals.filter((r) => r.timestamp >= oneHourAgo);
|
||||
const throughputRate = recentRemovals.length; // items per hour
|
||||
|
||||
// Calculate cycle efficiency
|
||||
const totalAdded = additions.length;
|
||||
const totalRemoved = removals.length;
|
||||
const cycleEfficiency = totalAdded > 0 ? (totalRemoved / totalAdded) * 100 : 100;
|
||||
|
||||
return {
|
||||
totalAdded,
|
||||
totalRemoved,
|
||||
currentWIP,
|
||||
avgProcessingTime: avgProcessingTime / 1000, // convert to seconds
|
||||
throughputRate,
|
||||
cycleEfficiency,
|
||||
processingTimeVariance: calculateVariance(completedProcessing.map((p) => p.processingDuration || 0)),
|
||||
};
|
||||
},
|
||||
[getMaterialsByModel]
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculate variance helper
|
||||
*/
|
||||
const calculateVariance = (values: number[]) => {
|
||||
if (values.length === 0) return 0;
|
||||
const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
|
||||
const squaredDiffs = values.map((v) => Math.pow(v - mean, 2));
|
||||
return squaredDiffs.reduce((sum, v) => sum + v, 0) / values.length;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ENHANCED CONVEYOR ANALYSIS
|
||||
// ============================================================================
|
||||
@@ -1632,6 +2173,110 @@ function Analyzer() {
|
||||
};
|
||||
}, [isPlaying]);
|
||||
|
||||
// Monitor material changes and track additions/removals
|
||||
useEffect(() => {
|
||||
if (!isPlaying) return;
|
||||
|
||||
// Track material movements by comparing current materials with previous state
|
||||
materials.forEach((material) => {
|
||||
const currentAssetId = material.current?.modelUuid;
|
||||
const previousAssetId = material.previous?.modelUuid;
|
||||
|
||||
if (currentAssetId && previousAssetId && currentAssetId !== previousAssetId) {
|
||||
// Material moved from one asset to another
|
||||
trackMaterialRemoval(previousAssetId, material.materialId, material.materialType, currentAssetId);
|
||||
trackMaterialAddition(currentAssetId, material.materialId, material.materialType, previousAssetId);
|
||||
} else if (currentAssetId && !previousAssetId) {
|
||||
// Material newly added to an asset
|
||||
trackMaterialAddition(currentAssetId, material.materialId, material.materialType);
|
||||
}
|
||||
});
|
||||
|
||||
// Track material lifecycle completion
|
||||
materials.forEach((material) => {
|
||||
if (!material.isActive && material.endTime) {
|
||||
const lifecycle = materialLifecycleRef.current[material.materialId];
|
||||
if (lifecycle && !lifecycle.completedAt) {
|
||||
lifecycle.completedAt = material.endTime;
|
||||
|
||||
// Calculate total processing and wait times
|
||||
let totalProcessing = 0;
|
||||
let totalWait = 0;
|
||||
|
||||
lifecycle.path.forEach((pathEntry) => {
|
||||
if (pathEntry.exitTime) {
|
||||
const duration = pathEntry.exitTime - pathEntry.entryTime;
|
||||
totalProcessing += duration;
|
||||
}
|
||||
});
|
||||
|
||||
lifecycle.totalProcessingTime = totalProcessing;
|
||||
lifecycle.totalWaitTime = totalWait;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [materials, isPlaying, trackMaterialAddition, trackMaterialRemoval]);
|
||||
|
||||
// Monitor asset state changes
|
||||
useEffect(() => {
|
||||
if (!isPlaying) return;
|
||||
|
||||
const allAssets = [
|
||||
...conveyors.map((c) => ({ id: c.modelUuid, state: c.state, isActive: !c.isPaused, type: "conveyor" as const })),
|
||||
...machines.map((m) => ({ id: m.modelUuid, state: m.state, isActive: m.isActive, type: "machine" as const })),
|
||||
...armBots.map((a) => ({ id: a.modelUuid, state: a.state, isActive: a.isActive, type: "roboticArm" as const })),
|
||||
...vehicles.map((v) => ({ id: v.modelUuid, state: v.state, isActive: v.isActive, type: "vehicle" as const })),
|
||||
...humans.map((h) => ({ id: h.modelUuid, state: h.state, isActive: h.isActive, type: "human" as const })),
|
||||
...cranes.map((c) => ({ id: c.modelUuid, state: c.state, isActive: c.isActive, type: "crane" as const })),
|
||||
...storageUnits.map((s) => ({ id: s.modelUuid, state: s.state, isActive: s.isActive, type: "storage" as const })),
|
||||
];
|
||||
|
||||
allAssets.forEach((asset) => {
|
||||
const previousState = previousAssetStatesRef.current[asset.id];
|
||||
const currentMaterialCount = getMaterialsByModel(asset.id).length;
|
||||
|
||||
if (previousState) {
|
||||
// Check for state change
|
||||
if (previousState.state !== asset.state) {
|
||||
trackStateChange(asset.id, previousState.state, asset.state);
|
||||
}
|
||||
|
||||
// Check for material count change (potential bottleneck)
|
||||
if (currentMaterialCount !== previousState.materialCount) {
|
||||
const utilizationRate = asset.isActive ? 0.8 : 0.2; // Simplified calculation
|
||||
const waitingMaterials = getMaterialsByModel(asset.id).map((m) => m.materialId);
|
||||
trackBottleneckEvent(asset.id, currentMaterialCount, utilizationRate, waitingMaterials);
|
||||
}
|
||||
}
|
||||
|
||||
// Update previous state
|
||||
updatePreviousAssetState(asset.id, asset.state, asset.isActive, currentMaterialCount);
|
||||
});
|
||||
}, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, getMaterialsByModel, trackStateChange, trackBottleneckEvent, updatePreviousAssetState]);
|
||||
|
||||
// Periodic WIP and throughput snapshots
|
||||
useEffect(() => {
|
||||
if (!isPlaying) return;
|
||||
|
||||
const snapshotInterval = setInterval(() => {
|
||||
const allAssetIds = [
|
||||
...conveyors.map((c) => c.modelUuid),
|
||||
...machines.map((m) => m.modelUuid),
|
||||
...armBots.map((a) => a.modelUuid),
|
||||
...vehicles.map((v) => v.modelUuid),
|
||||
...humans.map((h) => h.modelUuid),
|
||||
...cranes.map((c) => c.modelUuid),
|
||||
...storageUnits.map((s) => s.modelUuid),
|
||||
];
|
||||
|
||||
allAssetIds.forEach((assetId) => {
|
||||
updateWIPSnapshot(assetId);
|
||||
});
|
||||
}, 1000); // Every 1 seconds
|
||||
|
||||
return () => clearInterval(snapshotInterval);
|
||||
}, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, updateWIPSnapshot]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user