feat: add simulation analyzer module with web worker for asset tracking and system analysis

This commit is contained in:
2025-12-29 11:55:23 +05:30
parent 9ae5d657b7
commit 7e33432c4f
3 changed files with 228 additions and 119 deletions

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
});
}
});
}