feat: add simulation analyzer module with web worker for asset tracking and system analysis
This commit is contained in:
@@ -52,6 +52,11 @@ function Analyzer() {
|
||||
}
|
||||
}, [isPlaying, setAnalysis, setAnalyzing]);
|
||||
|
||||
// Helper to sanitize payload (strip functions)
|
||||
const sanitizePayload = (payload: AnalyzerStatePayload) => {
|
||||
return JSON.parse(JSON.stringify(payload));
|
||||
};
|
||||
|
||||
// Send Updates to Worker
|
||||
useEffect(() => {
|
||||
if (!isPlaying) return;
|
||||
@@ -78,9 +83,10 @@ function Analyzer() {
|
||||
};
|
||||
|
||||
setAnalyzing(true);
|
||||
// Sanitize payload to remove functions (e.g., callbacks in humanEventManagerData) to avoid DataCloneError
|
||||
workerRef.current?.postMessage({
|
||||
type: "UPDATE",
|
||||
payload,
|
||||
payload: sanitizePayload(payload),
|
||||
});
|
||||
}, [conveyors, vehicles, machines, armBots, humans, cranes, storageUnits, materials, materialHistory, speed, isPlaying, isPaused, getSimulationTime, humanEventManagerRef, setAnalyzing]);
|
||||
|
||||
@@ -106,7 +112,7 @@ function Analyzer() {
|
||||
isPaused,
|
||||
};
|
||||
setAnalyzing(true);
|
||||
workerRef.current?.postMessage({ type: "UPDATE", payload });
|
||||
workerRef.current?.postMessage({ type: "UPDATE", payload: sanitizePayload(payload) });
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
|
||||
@@ -178,121 +178,214 @@ export const analyzeSystem = (allAssets: any[], materials: any[], startTime: str
|
||||
const advancedAnalytics = { financials, supplyChain, sustainability, reliability };
|
||||
|
||||
// Predictive Insights
|
||||
const maintenanceAlerts: any[] = [];
|
||||
const optimizationOpportunities: any[] = [];
|
||||
const trendAnalysis: any[] = [];
|
||||
const anomalyDetection: any[] = [];
|
||||
const resourceAllocation: any[] = [];
|
||||
const scenarioModeling: any[] = [];
|
||||
|
||||
// 1. Maintenance Alerts
|
||||
allAssets.forEach((asset) => {
|
||||
const errorRate = asset.quality?.errorRate || 0;
|
||||
const utilization = asset.timeMetrics?.utilizationRate || 0;
|
||||
|
||||
if (errorRate > 5) {
|
||||
maintenanceAlerts.push({
|
||||
const maintenanceAlerts = allAssets
|
||||
.filter((asset) => {
|
||||
const errorCount = asset.quality?.errorCount || 0;
|
||||
const uptime = asset.timeMetrics?.uptime || 100;
|
||||
return uptime < 95 || errorCount > 3;
|
||||
})
|
||||
.map((asset) => {
|
||||
const errorCount = asset.quality?.errorCount || 0;
|
||||
const reliability = asset.timeMetrics?.reliability || 100; // Assuming this exists or using 100
|
||||
return {
|
||||
assetId: asset.assetId,
|
||||
severity: "high",
|
||||
message: `High error rate (${errorRate.toFixed(1)}%) detected. Immediate maintenance recommended.`,
|
||||
predictedFailureProbability: Math.min(0.9, errorRate / 20),
|
||||
});
|
||||
}
|
||||
if (utilization > 0.95) {
|
||||
maintenanceAlerts.push({
|
||||
assetId: asset.assetId,
|
||||
severity: "medium",
|
||||
message: `Asset operating at near maximum capacity (${(utilization * 100).toFixed(1)}%). Risk of wear and tear.`,
|
||||
predictedFailureProbability: 0.4,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Optimization Opportunities
|
||||
bottlenecks.forEach((b) => {
|
||||
if (b.severity === "high" || b.severity === "critical") {
|
||||
optimizationOpportunities.push({
|
||||
type: "bottleneck_mitigation",
|
||||
target: b.assetName,
|
||||
potentialGain: "15-20% throughput increase",
|
||||
action: "Add parallel processing unit or increase speed",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const underutilizedTypes = assetTypePerformance.filter((t) => t.averageUtilization < 30);
|
||||
underutilizedTypes.forEach((t) => {
|
||||
optimizationOpportunities.push({
|
||||
type: "resource_consolidation",
|
||||
target: t.assetType,
|
||||
potentialGain: "Reduced operational costs",
|
||||
action: `Consolidate ${t.assetType} resources or reassign tasks due to low utilization (${t.averageUtilization.toFixed(1)}%)`,
|
||||
assetName: asset.assetName,
|
||||
assetType: asset.assetType,
|
||||
alertType: errorCount > 5 ? "critical" : errorCount > 3 ? "predictive" : "preventive",
|
||||
estimatedTimeToFailure: Math.max(0, 100 - reliability) * 1000,
|
||||
confidence: Math.min(90 + errorCount * 2, 100),
|
||||
recommendation: errorCount > 5 ? "Immediate maintenance required" : "Schedule preventive maintenance",
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// 3. Trend Analysis
|
||||
const recentFlow = flowRates.slice(-10);
|
||||
if (recentFlow.length >= 5) {
|
||||
const firstHalf = recentFlow.slice(0, Math.floor(recentFlow.length / 2));
|
||||
const secondHalf = recentFlow.slice(Math.floor(recentFlow.length / 2));
|
||||
const avgFirst = firstHalf.reduce((a, b) => a + b, 0) / firstHalf.length;
|
||||
const avgSecond = secondHalf.reduce((a, b) => a + b, 0) / secondHalf.length;
|
||||
|
||||
let direction = "stable";
|
||||
if (avgSecond > avgFirst * 1.1) direction = "increasing";
|
||||
else if (avgSecond < avgFirst * 0.9) direction = "decreasing";
|
||||
|
||||
trendAnalysis.push({
|
||||
metric: "System Throughput",
|
||||
direction,
|
||||
strength: Math.abs((avgSecond - avgFirst) / avgFirst),
|
||||
forecast: direction === "increasing" ? "Expect higher output" : "Potential slowdown ahead",
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Anomaly Detection
|
||||
if (varianceCoefficient > 0.4) {
|
||||
anomalyDetection.push({
|
||||
type: "flow_instability",
|
||||
severity: "medium",
|
||||
description: "High variance in system throughput detected. Process flow is unstable.",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
if (assetsInError > totalAssets * 0.1) {
|
||||
anomalyDetection.push({
|
||||
type: "system_health_risk",
|
||||
severity: "high",
|
||||
description: `Unusual number of assets in error state (${assetsInError}).`,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Resource Allocation
|
||||
const sortedTypes = [...assetTypePerformance].sort((a, b) => b.averageUtilization - a.averageUtilization);
|
||||
if (sortedTypes.length > 1) {
|
||||
const highest = sortedTypes[0];
|
||||
const lowest = sortedTypes[sortedTypes.length - 1];
|
||||
if (highest.averageUtilization > 80 && lowest.averageUtilization < 40) {
|
||||
resourceAllocation.push({
|
||||
from: lowest.assetType,
|
||||
to: highest.assetType,
|
||||
recommendation: `Shift focus/budget from ${lowest.assetType} to ${highest.assetType} to balance load.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Scenario Modeling (Simple heuristics)
|
||||
scenarioModeling.push({
|
||||
scenario: "Increase Bottleneck Speed 20%",
|
||||
impact: {
|
||||
throughput: totalThroughput * 1.15, // Estimate
|
||||
cost: totalCost * 1.05,
|
||||
const optimizationOpportunities = [
|
||||
...bottlenecks
|
||||
.filter((b: any) => b.severity === "critical" || b.severity === "high")
|
||||
.map((b: any) => ({
|
||||
area: "throughput",
|
||||
description: `High utilization bottleneck at ${b.assetName} (${b.utilizationRate.toFixed(1)}% utilization)`,
|
||||
potentialImprovement: Math.min(100 - b.utilizationRate, 20),
|
||||
priority: b.severity === "critical" ? "high" : "medium",
|
||||
})),
|
||||
{
|
||||
area: "efficiency",
|
||||
description: `System OEE at ${weightedOEE.toFixed(1)}% - target improvements available`,
|
||||
potentialImprovement: Math.max(0, 85 - weightedOEE),
|
||||
priority: weightedOEE < 80 ? "high" : "medium",
|
||||
},
|
||||
recommendation: "Highly Recommended",
|
||||
{
|
||||
area: "cost",
|
||||
description: `Total system cost: $${totalCost.toFixed(2)}`,
|
||||
potentialImprovement: Math.min(20, totalValueAdded > 0 ? (totalCost / totalValueAdded) * 100 : 0),
|
||||
priority: totalValueAdded < totalCost ? "high" : "medium",
|
||||
},
|
||||
];
|
||||
|
||||
const trendAnalysis = assetTypePerformance.map((type) => {
|
||||
// We need historical data for this type
|
||||
// In worker, we have historicalData object
|
||||
const historicalTypeData = allAssets
|
||||
.filter((a) => a.assetType === type.assetType)
|
||||
.flatMap((a) => historicalData[a.assetId] || [])
|
||||
.filter((d) => d.performance !== undefined);
|
||||
|
||||
const recentPerformance = historicalTypeData.slice(-10).map((d: any) => d.performance);
|
||||
const olderPerformance = historicalTypeData.slice(-20, -10).map((d: any) => d.performance);
|
||||
|
||||
const recentAvg = recentPerformance.length > 0 ? recentPerformance.reduce((sum: number, p: number) => sum + p, 0) / recentPerformance.length : 0;
|
||||
const olderAvg = olderPerformance.length > 0 ? olderPerformance.reduce((sum: number, p: number) => sum + p, 0) / olderPerformance.length : 0;
|
||||
|
||||
let trend = "stable";
|
||||
let changeRate = 0;
|
||||
|
||||
if (olderAvg > 0 && recentAvg > 0) {
|
||||
changeRate = ((recentAvg - olderAvg) / olderAvg) * 100;
|
||||
if (Math.abs(changeRate) > 5) {
|
||||
trend = changeRate > 0 ? "increasing" : "decreasing";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
metric: `${type.assetType} Performance`,
|
||||
trend,
|
||||
changeRate,
|
||||
forecast: [recentAvg * 1.02, recentAvg * 1.01, recentAvg],
|
||||
};
|
||||
});
|
||||
|
||||
const predictiveInsights = { maintenanceAlerts, optimizationOpportunities, trendAnalysis, anomalyDetection, resourceAllocation, scenarioModeling };
|
||||
const anomalyDetection = allAssets
|
||||
.filter((a) => (a.quality?.errorRate || 0) > 5 || (a.efficiency?.overallEffectiveness || 0) < 40)
|
||||
.slice(0, 5)
|
||||
.map((a) => ({
|
||||
id: `anomaly-${a.assetId}`,
|
||||
assetId: a.assetId,
|
||||
assetName: a.assetName,
|
||||
severity: (a.quality?.errorRate || 0) > 10 ? "critical" : "high",
|
||||
description: `Abnormal behavior detected: ${(a.quality?.errorRate || 0) > 5 ? "High error rate" : "Low efficiency"}`,
|
||||
detectedAt: new Date().toISOString(),
|
||||
probability: Math.min(99, 70 + (a.quality?.errorRate || 0) * 2),
|
||||
}));
|
||||
|
||||
// Resource Allocation
|
||||
const resourceAllocation: any[] = [];
|
||||
const humanUtilization =
|
||||
allAssets.filter((a) => a.assetType === "human").reduce((sum, a) => sum + (a.timeMetrics?.utilizationRate || 0), 0) / Math.max(1, allAssets.filter((a) => a.assetType === "human").length);
|
||||
const machineUtilization =
|
||||
allAssets.filter((a) => a.assetType === "machine").reduce((sum, a) => sum + (a.timeMetrics?.utilizationRate || 0), 0) / Math.max(1, allAssets.filter((a) => a.assetType === "machine").length);
|
||||
|
||||
// Previous logic for resource allocation
|
||||
if (humanUtilization > 0.85) {
|
||||
const current = allAssets.filter((a) => a.assetType === "human").length;
|
||||
resourceAllocation.push({
|
||||
resourceType: "human",
|
||||
currentAllocation: current,
|
||||
recommendedAllocation: current + Math.ceil(current * 0.2),
|
||||
impact: "High human worker utilization. Adding personnel could improve throughput by 15%.",
|
||||
});
|
||||
} else if (humanUtilization < 0.3 && allAssets.filter((a) => a.assetType === "human").length > 0) {
|
||||
const current = allAssets.filter((a) => a.assetType === "human").length;
|
||||
resourceAllocation.push({
|
||||
resourceType: "human",
|
||||
currentAllocation: current,
|
||||
recommendedAllocation: Math.max(1, current - 1),
|
||||
impact: "Low human utilization. Reducing personnel could save costs with minimal throughput impact.",
|
||||
});
|
||||
}
|
||||
|
||||
if (machineUtilization > 0.9) {
|
||||
const current = allAssets.filter((a) => a.assetType === "machine").length;
|
||||
resourceAllocation.push({
|
||||
resourceType: "machine",
|
||||
currentAllocation: current,
|
||||
recommendedAllocation: current + 1,
|
||||
impact: "Critical bottleneck at machining stations. Adding capacity recommended.",
|
||||
});
|
||||
}
|
||||
|
||||
// Scenario Modeling
|
||||
const scenarioModeling: any[] = []; // Initialize array
|
||||
// Confidence Calculation Helpers
|
||||
const analysisDurationMinutes = (Date.now() - new Date(startTime).getTime()) / 60000;
|
||||
const dataQuantityScore = Math.min(15, (completedMaterials / 50) * 15);
|
||||
const stabilityScore = Math.max(0, (1 - varianceCoefficient) * 20);
|
||||
const durationScore = Math.min(15, analysisDurationMinutes * 1.5);
|
||||
|
||||
const baseSystemConfidence = 45 + dataQuantityScore + stabilityScore + durationScore;
|
||||
|
||||
// 1. Throughput Optimization (Bottleneck focus)
|
||||
if (bottlenecks.length > 0) {
|
||||
const topBottleneck = bottlenecks[0];
|
||||
const projectedGain = Math.min(15, (topBottleneck.utilizationRate / 100) * 12);
|
||||
let throughputConfidence = baseSystemConfidence + (topBottleneck.severity === "critical" ? 10 : 5);
|
||||
if (varianceCoefficient > 0.5) throughputConfidence -= 10;
|
||||
|
||||
scenarioModeling.push({
|
||||
scenarioName: "Throughput Maximization",
|
||||
changeDescription: `Increase capacity of bottleneck '${topBottleneck.assetName}' by 15%`,
|
||||
expectedOutcome: `Projected system throughput increase of ${projectedGain.toFixed(1)}%`,
|
||||
confidence: Math.min(98, Math.round(throughputConfidence)),
|
||||
});
|
||||
} else {
|
||||
scenarioModeling.push({
|
||||
scenarioName: "Capacity Expansion",
|
||||
changeDescription: "Increase base conveyor speeds by 10%",
|
||||
expectedOutcome: "Projected throughput increase of 8-10%",
|
||||
confidence: Math.min(95, Math.round(baseSystemConfidence + 5)),
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Energy Efficiency (Idle time focus)
|
||||
const idleRatio = totalAssets > 0 ? totalIdleTime / (totalSystemTime || 1) : 0;
|
||||
if (idleRatio > 0.3) {
|
||||
const potentialSavings = idleRatio * 0.4 * 100;
|
||||
const energyConfidence = baseSystemConfidence + 10;
|
||||
scenarioModeling.push({
|
||||
scenarioName: "Energy Conservation",
|
||||
changeDescription: "Implement aggressive sleep guidelines for idle assets",
|
||||
expectedOutcome: `Reduce energy costs by ${potentialSavings.toFixed(1)}% (${((potentialSavings / 100) * totalEnergyConsumed).toFixed(1)} kWh)`,
|
||||
confidence: Math.min(99, Math.round(energyConfidence)),
|
||||
});
|
||||
} else {
|
||||
scenarioModeling.push({
|
||||
scenarioName: "Green Energy Transition",
|
||||
changeDescription: "Switch to high-efficiency motors",
|
||||
expectedOutcome: `Reduce carbon footprint by 5% (${(totalEnergyConsumed * 0.05 * ENERGY_CONSTANTS.CO2_PER_KWH).toFixed(1)} kg CO2)`,
|
||||
confidence: Math.min(95, Math.round(baseSystemConfidence + 5)),
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Quality/Cost (OEE focus)
|
||||
let qualityConfidence = baseSystemConfidence;
|
||||
const totalErrors = assetsInError + Object.values(errorCounts).reduce((a, b) => a + b, 0);
|
||||
if (totalErrors < 5) qualityConfidence -= 5;
|
||||
else qualityConfidence += 5;
|
||||
|
||||
if (weightedOEE < 85) {
|
||||
const qualityGap = 100 - weightedOEE;
|
||||
const gain = qualityGap * 0.3;
|
||||
scenarioModeling.push({
|
||||
scenarioName: "Quality Optimization",
|
||||
changeDescription: "Implement predictive maintenance cycle reduction",
|
||||
expectedOutcome: `Improve OEE by ${gain.toFixed(1)}% through reduced downtime`,
|
||||
confidence: Math.min(90, Math.round(qualityConfidence)),
|
||||
});
|
||||
} else {
|
||||
scenarioModeling.push({
|
||||
scenarioName: "Premium Production",
|
||||
changeDescription: "Tighten tolerance margins by 50%",
|
||||
expectedOutcome: "Product quality score +15%, slight throughput reduction (-2%)",
|
||||
confidence: Math.min(92, Math.round(qualityConfidence + 5)),
|
||||
});
|
||||
}
|
||||
|
||||
const predictiveInsights = {
|
||||
maintenanceAlerts: maintenanceAlerts.slice(0, 5),
|
||||
optimizationOpportunities,
|
||||
trendAnalysis: trendAnalysis.filter((t: any) => t.trend !== "stable" || Math.abs(t.changeRate) > 1),
|
||||
anomalyDetection,
|
||||
resourceAllocation,
|
||||
scenarioModeling,
|
||||
};
|
||||
|
||||
return {
|
||||
assets: allAssets,
|
||||
|
||||
@@ -102,18 +102,28 @@ const runTrackingLogic = (data: AnalyzerStatePayload) => {
|
||||
// Humans (from Event Manager Data)
|
||||
if (data.humanEventManagerData) {
|
||||
data.humanEventManagerData.forEach((h: any) => {
|
||||
const id = h.modelUuid; // Assuming structure
|
||||
const id = h.humanId;
|
||||
if (id) {
|
||||
// Update total actions
|
||||
const total = Object.values(h.actionCounts || {}).reduce((a: any, b: any) => a + b, 0) as number;
|
||||
completedActions[id] = total;
|
||||
let total = 0;
|
||||
const typeCounts: Record<string, number> = {};
|
||||
|
||||
// Update breakdown
|
||||
if (h.actionCounts) {
|
||||
Object.entries(h.actionCounts).forEach(([action, count]) => {
|
||||
completedActions[`${id}_${action.toLowerCase()}`] = count as number;
|
||||
if (Array.isArray(h.actionQueue)) {
|
||||
h.actionQueue.forEach((action: any) => {
|
||||
const count = action.count || 0;
|
||||
total += count;
|
||||
|
||||
if (action.actionType) {
|
||||
const type = action.actionType.toLowerCase();
|
||||
typeCounts[type] = (typeCounts[type] || 0) + count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
completedActions[id] = total;
|
||||
|
||||
Object.entries(typeCounts).forEach(([type, count]) => {
|
||||
completedActions[`${id}_${type}`] = count;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user