@flexabrain/mcp-server
Version:
Advanced electrical schematic analysis MCP server with rail engineering expertise
501 lines • 25.7 kB
JavaScript
import { electricalSchematicAnalyzer } from '../services/electrical-analyzer.js';
import * as path from 'path';
import * as fs from 'fs/promises';
/**
* MCP Tool: Batch Analyze Schematics
*
* Processes multiple electrical schematic images in batch mode with
* consolidated reporting, cross-schematic analysis, and system-wide
* insights leveraging rail engineering expertise for comprehensive evaluation.
*/
export async function batchAnalyzeSchematics(args) {
try {
// Validate input directory
const dirStats = await fs.stat(args.input_directory);
if (!dirStats.isDirectory()) {
throw new Error(`Input path is not a directory: ${args.input_directory}`);
}
// Find schematic files
const schematicFiles = await findSchematicFiles(args.input_directory, args.file_pattern);
if (schematicFiles.length === 0) {
throw new Error(`No schematic files found in ${args.input_directory} matching pattern: ${args.file_pattern || '*.{png,jpg,jpeg,tiff,bmp}'}`);
}
// Configure analysis options
const analysisOptions = {
analysis_depth: args.analysis_depth || 'standard',
focus_areas: args.focus_areas || ['safety', 'compliance'],
...(args.rail_system_context && {
rail_system_context: {
system_type: args.rail_system_context.system_type,
voltage_system: args.rail_system_context.voltage_system,
region: args.rail_system_context.region,
applicable_standards: ['EN 50155', 'IEC 61375', 'IEEE 519']
}
})
};
// Process schematics
const batchResults = await processSchematics(schematicFiles, analysisOptions, args);
// Generate comprehensive batch report
let report = '# 📊 Batch Schematic Analysis Report\n\n';
// Executive Summary
report += generateBatchSummary(batchResults, args);
// Processing Overview
report += generateProcessingOverview(batchResults, schematicFiles.length);
// Cross-Schematic Analysis
if (args.cross_reference_analysis) {
report += generateCrossSchematicAnalysis(batchResults);
}
// System-Wide Analysis
report += generateSystemWideAnalysis(batchResults, args);
// Individual Results Summary
if (args.output_format === 'individual' || args.output_format === 'both') {
report += generateIndividualResultsSummary(batchResults);
}
// Consolidated Analysis
if (args.output_format === 'consolidated' || args.output_format === 'both') {
report += generateConsolidatedAnalysis(batchResults);
}
// Recommendations & Action Items
report += generateBatchRecommendations(batchResults);
// Quality & Performance Metrics
report += generateBatchMetrics(batchResults);
return report;
}
catch (error) {
return `❌ **Error in batch schematic analysis**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\n**Troubleshooting**:\n- Verify input directory exists and is readable\n- Check file permissions for schematic images\n- Ensure sufficient system resources for batch processing\n- Verify image formats are supported`;
}
}
/**
* Find schematic files in directory
*/
async function findSchematicFiles(directory, pattern) {
const files = await fs.readdir(directory);
const imageExtensions = ['.png', '.jpg', '.jpeg', '.tiff', '.tif', '.bmp'];
const schematicFiles = files.filter(file => {
const ext = path.extname(file).toLowerCase();
const matchesExtension = imageExtensions.includes(ext);
if (pattern) {
// Simple pattern matching - could be enhanced with glob patterns
const patternRegex = new RegExp(pattern.replace(/\*/g, '.*'), 'i');
return matchesExtension && patternRegex.test(file);
}
return matchesExtension;
});
return schematicFiles.map(file => path.join(directory, file));
}
/**
* Process multiple schematics
*/
async function processSchematics(files, analysisOptions, args) {
const results = [];
const maxConcurrent = args.max_concurrent || 3;
const startTime = Date.now();
if (args.parallel_processing && files.length > 1) {
// Process in parallel batches
for (let i = 0; i < files.length; i += maxConcurrent) {
const batch = files.slice(i, i + maxConcurrent);
const batchPromises = batch.map(async (file) => {
try {
const result = await electricalSchematicAnalyzer.analyzeSchematic(file, analysisOptions);
return {
file_path: file,
file_name: path.basename(file),
success: result.success,
analysis: result.analysis,
errors: result.errors,
processing_time: result.processing_stats?.total_time || 0
};
}
catch (error) {
return {
file_path: file,
file_name: path.basename(file),
success: false,
analysis: null,
errors: [{ message: error instanceof Error ? error.message : String(error) }],
processing_time: 0
};
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
}
else {
// Process sequentially
for (const file of files) {
try {
const result = await electricalSchematicAnalyzer.analyzeSchematic(file, analysisOptions);
results.push({
file_path: file,
file_name: path.basename(file),
success: result.success,
analysis: result.analysis,
errors: result.errors,
processing_time: result.processing_stats?.total_time || 0
});
}
catch (error) {
results.push({
file_path: file,
file_name: path.basename(file),
success: false,
analysis: null,
errors: [{ message: error instanceof Error ? error.message : String(error) }],
processing_time: 0
});
}
}
}
// Add batch processing metadata
const totalTime = Date.now() - startTime;
results.forEach((result) => {
result.batch_metadata = {
total_files: files.length,
processing_mode: args.parallel_processing ? 'parallel' : 'sequential',
total_batch_time: totalTime,
analysis_depth: args.analysis_depth || 'standard'
};
});
return results;
}
/**
* Generate batch summary
*/
function generateBatchSummary(results, args) {
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
const totalComponents = successful.reduce((sum, r) => sum + (r.analysis?.components?.length || 0), 0);
const avgProcessingTime = results.reduce((sum, r) => sum + r.processing_time, 0) / results.length;
let summary = '## 📋 Batch Processing Summary\n\n';
summary += `**Total Files Processed**: ${results.length}\n`;
summary += `**Successful Analyses**: ${successful.length}\n`;
summary += `**Failed Analyses**: ${failed.length}\n`;
summary += `**Success Rate**: ${((successful.length / results.length) * 100).toFixed(1)}%\n`;
summary += `**Total Components Found**: ${totalComponents}\n`;
summary += `**Average Processing Time**: ${Math.round(avgProcessingTime / 1000)} seconds\n`;
summary += `**Processing Mode**: ${args.parallel_processing ? 'Parallel' : 'Sequential'}\n\n`;
// Overall quality assessment
const avgConfidence = successful.reduce((sum, r) => sum + (r.analysis?.overall_confidence || 0), 0) / Math.max(successful.length, 1);
const qualityRating = avgConfidence >= 0.8 ? '🟢 Excellent' :
avgConfidence >= 0.6 ? '🟡 Good' :
avgConfidence >= 0.4 ? '🟠 Fair' : '🔴 Poor';
summary += `**Overall Analysis Quality**: ${qualityRating} (${(avgConfidence * 100).toFixed(1)}%)\n\n`;
if (failed.length > 0) {
summary += '### ⚠️ Processing Issues\n\n';
for (const failure of failed.slice(0, 3)) { // Show first 3 failures
summary += `- **${failure.file_name}**: ${failure.errors?.[0]?.message || 'Unknown error'}\n`;
}
if (failed.length > 3) {
summary += `- *...and ${failed.length - 3} more files with issues*\n`;
}
summary += '\n';
}
return summary;
}
/**
* Generate processing overview
*/
function generateProcessingOverview(results, totalFiles) {
let overview = '## ⚙️ Processing Overview\n\n';
// Performance metrics
const totalProcessingTime = results.reduce((sum, r) => sum + r.processing_time, 0);
const batchTime = results[0]?.batch_metadata?.total_batch_time || totalProcessingTime;
const efficiency = totalProcessingTime > 0 ? (batchTime / totalProcessingTime) : 1;
overview += '### Performance Metrics\n\n';
overview += `- **Total Processing Time**: ${Math.round(totalProcessingTime / 1000)} seconds\n`;
overview += `- **Actual Batch Time**: ${Math.round(batchTime / 1000)} seconds\n`;
overview += `- **Processing Efficiency**: ${(efficiency * 100).toFixed(1)}%\n`;
overview += `- **Throughput**: ${(totalFiles / (batchTime / 1000 / 60)).toFixed(1)} files/minute\n\n`;
// File distribution
overview += '### File Analysis Distribution\n\n';
overview += '| Result | Count | Percentage |\n';
overview += '|--------|-------|------------|\n';
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
const warnings = results.filter(r => r.success && r.errors?.length > 0).length;
overview += `| ✅ Successful | ${successful} | ${((successful / totalFiles) * 100).toFixed(1)}% |\n`;
overview += `| ⚠️ With Warnings | ${warnings} | ${((warnings / totalFiles) * 100).toFixed(1)}% |\n`;
overview += `| ❌ Failed | ${failed} | ${((failed / totalFiles) * 100).toFixed(1)}% |\n`;
overview += '\n';
return overview;
}
/**
* Generate cross-schematic analysis
*/
function generateCrossSchematicAnalysis(results) {
let crossAnalysis = '## 🔗 Cross-Schematic Analysis\n\n';
const successful = results.filter(r => r.success && r.analysis);
if (successful.length < 2) {
crossAnalysis += 'Cross-schematic analysis requires at least 2 successfully processed schematics.\n\n';
return crossAnalysis;
}
// Component correlation analysis
crossAnalysis += '### Component Correlation\n\n';
const allComponents = successful.flatMap(r => r.analysis.components || []);
const componentCounts = allComponents.reduce((acc, comp) => {
acc[comp.type] = (acc[comp.type] || 0) + 1;
return acc;
}, {});
crossAnalysis += '| Component Type | Total Count | Avg per Schematic | Files Present |\n';
crossAnalysis += '|----------------|-------------|-------------------|---------------|\n';
for (const [type, count] of Object.entries(componentCounts)) {
const countNum = count;
const avgPerSchematic = (countNum / successful.length).toFixed(1);
const filesWithType = successful.filter(r => r.analysis.components.some((c) => c.type === type)).length;
crossAnalysis += `| ${formatComponentType(type)} | ${countNum} | ${avgPerSchematic} | ${filesWithType}/${successful.length} |\n`;
}
crossAnalysis += '\n';
// Safety consistency analysis
crossAnalysis += '### Safety Consistency Analysis\n\n';
const safetyLevels = successful.map(r => r.analysis.safety_analysis?.overall_safety_level || 'unknown');
const safetyConsistency = [...new Set(safetyLevels)].length === 1;
if (safetyConsistency) {
crossAnalysis += `✅ **Consistent Safety Level**: All schematics show ${safetyLevels[0]?.toUpperCase()} safety level\n\n`;
}
else {
crossAnalysis += '⚠️ **Inconsistent Safety Levels**: Mixed safety levels detected\n\n';
const safetyDistribution = safetyLevels.reduce((acc, level) => {
acc[level] = (acc[level] || 0) + 1;
return acc;
}, {});
for (const [level, count] of Object.entries(safetyDistribution)) {
crossAnalysis += `- **${level.toUpperCase()}**: ${count} schematic(s)\n`;
}
crossAnalysis += '\n';
}
// Common component patterns
crossAnalysis += '### Common Component Patterns\n\n';
const commonComponents = Object.entries(componentCounts)
.filter(([, count]) => count >= successful.length * 0.5) // Present in 50%+ of schematics
.sort(([, a], [, b]) => b - a)
.slice(0, 5);
if (commonComponents.length > 0) {
crossAnalysis += 'Components found in multiple schematics:\n\n';
for (const [type, count] of commonComponents) {
const countNum = count;
const prevalence = ((countNum / allComponents.length) * 100).toFixed(1);
crossAnalysis += `- **${formatComponentType(type)}**: ${countNum} instances (${prevalence}% of all components)\n`;
}
crossAnalysis += '\n';
}
return crossAnalysis;
}
/**
* Generate system-wide analysis
*/
function generateSystemWideAnalysis(results, args) {
let systemAnalysis = '## 🏗️ System-Wide Analysis\n\n';
const successful = results.filter(r => r.success && r.analysis);
if (successful.length === 0) {
systemAnalysis += 'No successful analyses available for system-wide assessment.\n\n';
return systemAnalysis;
}
// System architecture overview
systemAnalysis += '### System Architecture Overview\n\n';
const schematicTypes = successful.map(r => r.analysis.schematic_type);
const typeDistribution = schematicTypes.reduce((acc, type) => {
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {});
systemAnalysis += '**Schematic Type Distribution**:\n';
for (const [type, count] of Object.entries(typeDistribution)) {
systemAnalysis += `- **${formatComponentType(type)}**: ${count} schematic(s)\n`;
}
systemAnalysis += '\n';
// System complexity assessment
const totalComponents = successful.reduce((sum, r) => sum + (r.analysis.components?.length || 0), 0);
const avgComplexity = totalComponents / successful.length;
const complexityRating = avgComplexity > 50 ? '🔴 Very Complex' :
avgComplexity > 25 ? '🟠 Complex' :
avgComplexity > 10 ? '🟡 Moderate' : '🟢 Simple';
systemAnalysis += `**System Complexity**: ${complexityRating} (${avgComplexity.toFixed(1)} components per schematic)\n\n`;
// Integrated safety assessment
systemAnalysis += '### Integrated Safety Assessment\n\n';
const criticalSafetyCount = successful.filter(r => r.analysis.safety_analysis?.overall_safety_level === 'critical').length;
const highSafetyCount = successful.filter(r => r.analysis.safety_analysis?.overall_safety_level === 'high').length;
if (criticalSafetyCount > 0) {
systemAnalysis += `🔴 **Critical Safety Alert**: ${criticalSafetyCount} schematic(s) show critical safety levels\n`;
}
if (highSafetyCount > 0) {
systemAnalysis += `🟠 **High Safety Risk**: ${highSafetyCount} schematic(s) show high safety risks\n`;
}
if (criticalSafetyCount === 0 && highSafetyCount === 0) {
systemAnalysis += `✅ **Safety Status**: No critical or high-risk safety issues identified\n`;
}
systemAnalysis += '\n';
// Rail system integration
if (args.rail_system_context) {
systemAnalysis += '### Rail System Integration\n\n';
systemAnalysis += `**System Type**: ${args.rail_system_context.system_type}\n`;
systemAnalysis += `**Voltage System**: ${args.rail_system_context.voltage_system}\n`;
systemAnalysis += `**Region**: ${args.rail_system_context.region}\n\n`;
// Check for rail-specific components
const railComponents = successful.flatMap(r => r.analysis.components || [])
.filter((c) => ['converter', 'transformer', 'circuit_breaker'].includes(c.type));
systemAnalysis += `**Rail-Specific Components**: ${railComponents.length} identified across all schematics\n\n`;
}
return systemAnalysis;
}
/**
* Generate individual results summary
*/
function generateIndividualResultsSummary(results) {
let individual = '## 📄 Individual Analysis Results\n\n';
for (const result of results) {
individual += `### ${result.file_name}\n\n`;
if (result.success && result.analysis) {
const analysis = result.analysis;
individual += `- **Status**: ✅ Success\n`;
individual += `- **Components Found**: ${analysis.components?.length || 0}\n`;
individual += `- **Confidence**: ${((analysis.overall_confidence || 0) * 100).toFixed(1)}%\n`;
individual += `- **Safety Level**: ${analysis.safety_analysis?.overall_safety_level?.toUpperCase() || 'Unknown'}\n`;
individual += `- **Processing Time**: ${Math.round(result.processing_time / 1000)}s\n\n`;
}
else {
individual += `- **Status**: ❌ Failed\n`;
individual += `- **Error**: ${result.errors?.[0]?.message || 'Unknown error'}\n\n`;
}
}
return individual;
}
/**
* Generate consolidated analysis
*/
function generateConsolidatedAnalysis(results) {
let consolidated = '## 📊 Consolidated Analysis\n\n';
const successful = results.filter(r => r.success && r.analysis);
if (successful.length === 0) {
consolidated += 'No successful analyses available for consolidation.\n\n';
return consolidated;
}
// Consolidated component inventory
consolidated += '### Consolidated Component Inventory\n\n';
const allComponents = successful.flatMap(r => r.analysis.components || []);
const componentStats = allComponents.reduce((acc, comp) => {
const type = comp.type;
if (!acc[type]) {
acc[type] = { count: 0, avgConfidence: 0, safetyLevels: [] };
}
acc[type].count += 1;
acc[type].avgConfidence += comp.confidence;
acc[type].safetyLevels.push(comp.safety_level);
return acc;
}, {});
consolidated += '| Component Type | Total Count | Avg Confidence | Predominant Safety Level |\n';
consolidated += '|----------------|-------------|----------------|-------------------------|\n';
for (const [type, stats] of Object.entries(componentStats)) {
const statsTyped = stats;
const avgConf = (statsTyped.avgConfidence / statsTyped.count * 100).toFixed(1);
const safetyMode = getMostCommon(statsTyped.safetyLevels);
const safetyModeStr = typeof safetyMode === 'string' ? safetyMode.toUpperCase() : 'Unknown';
consolidated += `| ${formatComponentType(type)} | ${statsTyped.count} | ${avgConf}% | ${safetyModeStr} |\n`;
}
consolidated += '\n';
// Consolidated safety summary
consolidated += '### Consolidated Safety Summary\n\n';
const allSafetyIssues = successful.flatMap(r => [
...(r.analysis.safety_analysis?.safety_recommendations || []),
...(r.analysis.expert_recommendations?.recommendations?.filter((rec) => rec.type === 'SAFETY') || [])
]);
const criticalIssues = allSafetyIssues.filter((issue) => issue.priority === 'CRITICAL').length;
const highIssues = allSafetyIssues.filter((issue) => issue.priority === 'HIGH').length;
consolidated += `- **Critical Safety Issues**: ${criticalIssues}\n`;
consolidated += `- **High Priority Issues**: ${highIssues}\n`;
consolidated += `- **Total Safety Recommendations**: ${allSafetyIssues.length}\n\n`;
return consolidated;
}
/**
* Generate batch recommendations
*/
function generateBatchRecommendations(results) {
let recommendations = '## 💡 Batch Analysis Recommendations\n\n';
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
// Processing improvements
recommendations += '### Processing Improvements\n\n';
if (failed.length > 0) {
recommendations += `1. **Address Failed Analyses**: ${failed.length} files failed processing\n`;
recommendations += ' - Check image quality and format\n';
recommendations += ' - Verify file permissions and accessibility\n';
recommendations += ' - Consider image preprocessing for problem files\n\n';
}
const avgProcessingTime = results.reduce((sum, r) => sum + r.processing_time, 0) / results.length;
if (avgProcessingTime > 30000) { // 30 seconds
recommendations += '2. **Optimize Processing Performance**:\n';
recommendations += ' - Consider reducing analysis depth for bulk processing\n';
recommendations += ' - Implement more aggressive parallel processing\n';
recommendations += ' - Use image preprocessing to standardize inputs\n\n';
}
// Quality improvements
const avgConfidence = successful.reduce((sum, r) => sum + (r.analysis?.overall_confidence || 0), 0) / Math.max(successful.length, 1);
if (avgConfidence < 0.7) {
recommendations += '3. **Improve Analysis Quality**:\n';
recommendations += ' - Enhance image preprocessing pipeline\n';
recommendations += ' - Consider manual verification for low-confidence results\n';
recommendations += ' - Update component recognition patterns\n\n';
}
// System-wide recommendations
recommendations += '### System-Wide Recommendations\n\n';
const hasHighRiskSafety = successful.some(r => ['critical', 'high'].includes(r.analysis?.safety_analysis?.overall_safety_level));
if (hasHighRiskSafety) {
recommendations += '1. **Safety Priority Actions**:\n';
recommendations += ' - Conduct immediate safety review of high-risk schematics\n';
recommendations += ' - Implement safety mitigation measures\n';
recommendations += ' - Schedule regular safety assessments\n\n';
}
// Standardization recommendations
const schematicTypes = [...new Set(successful.map(r => r.analysis?.schematic_type))];
if (schematicTypes.length > successful.length * 0.5) {
recommendations += '2. **Standardization Opportunities**:\n';
recommendations += ' - Consider standardizing schematic formats\n';
recommendations += ' - Implement consistent component labeling\n';
recommendations += ' - Develop drawing standards for better analysis\n\n';
}
return recommendations;
}
/**
* Generate batch metrics
*/
function generateBatchMetrics(results) {
let metrics = '## 📊 Quality & Performance Metrics\n\n';
const successful = results.filter(r => r.success);
const totalProcessingTime = results.reduce((sum, r) => sum + r.processing_time, 0);
metrics += '### Performance Metrics\n\n';
metrics += `- **Total Files**: ${results.length}\n`;
metrics += `- **Success Rate**: ${((successful.length / results.length) * 100).toFixed(1)}%\n`;
metrics += `- **Total Processing Time**: ${Math.round(totalProcessingTime / 1000)} seconds\n`;
metrics += `- **Average per File**: ${Math.round(totalProcessingTime / results.length / 1000)} seconds\n`;
metrics += `- **Throughput**: ${(results.length / (totalProcessingTime / 1000 / 60)).toFixed(1)} files/minute\n\n`;
if (successful.length > 0) {
metrics += '### Quality Metrics\n\n';
const confidences = successful.map(r => r.analysis?.overall_confidence || 0);
const avgConfidence = confidences.reduce((sum, c) => sum + c, 0) / confidences.length;
const minConfidence = Math.min(...confidences);
const maxConfidence = Math.max(...confidences);
metrics += `- **Average Confidence**: ${(avgConfidence * 100).toFixed(1)}%\n`;
metrics += `- **Confidence Range**: ${(minConfidence * 100).toFixed(1)}% - ${(maxConfidence * 100).toFixed(1)}%\n`;
const totalComponents = successful.reduce((sum, r) => sum + (r.analysis?.components?.length || 0), 0);
metrics += `- **Total Components Found**: ${totalComponents}\n`;
metrics += `- **Average Components per Schematic**: ${(totalComponents / successful.length).toFixed(1)}\n\n`;
}
metrics += '---\n';
metrics += `*Batch analysis completed on ${new Date().toISOString().split('T')[0]} using FlexaBrain MCP Server*\n`;
return metrics;
}
// Helper functions
function formatComponentType(type) {
return type.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
}
function getMostCommon(arr) {
if (arr.length === 0)
return undefined;
const counts = arr.reduce((acc, item) => {
acc[String(item)] = (acc[String(item)] || 0) + 1;
return acc;
}, {});
const maxCount = Math.max(...Object.values(counts));
const mostCommon = Object.keys(counts).find(key => counts[key] === maxCount);
return mostCommon;
}
//# sourceMappingURL=batch-analyze-schematics.js.map