UNPKG

@flexabrain/mcp-server

Version:

Advanced electrical schematic analysis MCP server with rail engineering expertise

501 lines 25.7 kB
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