@flexabrain/mcp-server
Version:
Advanced electrical schematic analysis MCP server with rail engineering expertise
362 lines • 16.1 kB
JavaScript
/**
* FlexaBrain MCP Server - Electrical Component Classification Engine
*
* Advanced component classification system leveraging rail electrical engineering expertise.
* Identifies and classifies electrical components from OCR results using pattern matching,
* context analysis, and domain knowledge.
*/
import { ComponentType, ComponentCategory, SafetyLevel } from '../types/electrical.js';
import { RAIL_COMPONENT_PATTERNS, RAIL_COMPONENT_SPECIFICATIONS } from '../data/rail-components.js';
export class ElectricalComponentClassifier {
MINIMUM_CONFIDENCE = 0.3;
CONTEXT_BOOST_FACTOR = 0.15;
PATTERN_CONFIDENCE_WEIGHT = 0.7;
OCR_CONFIDENCE_WEIGHT = 0.3;
/**
* Classify electrical components from OCR results
*/
async classifyComponents(ocrResult, context) {
console.error(`FlexaBrain Classifier: Processing ${ocrResult.words.length} OCR words`);
const components = [];
const potentialMatches = [];
// First pass: Find potential component matches
for (const word of ocrResult.words) {
const matches = this.findComponentMatches(word, context);
potentialMatches.push(...matches);
}
// Second pass: Apply context analysis and filtering
const filteredMatches = this.applyContextAnalysis(potentialMatches, ocrResult, context);
// Third pass: Create final component objects
for (const match of filteredMatches) {
if (match.confidence >= this.MINIMUM_CONFIDENCE) {
const component = this.createElectricalComponent(match, context);
components.push(component);
}
}
console.error(`FlexaBrain Classifier: Identified ${components.length} electrical components`);
return this.deduplicateComponents(components);
}
/**
* Find potential component matches for a single OCR word
*/
findComponentMatches(word, context) {
const matches = [];
const cleanText = word.text.trim().toUpperCase();
if (cleanText.length < 2)
return matches; // Skip very short text
// Test against all component patterns
for (const [componentType, pattern] of Object.entries(RAIL_COMPONENT_PATTERNS)) {
const typeKey = componentType;
for (const regex of pattern.patterns) {
if (regex.test(cleanText)) {
const baseConfidence = this.calculatePatternConfidence(cleanText, regex, pattern);
const contextScore = this.calculateContextScore(cleanText, pattern, context);
const match = {
component_id: cleanText,
type: typeKey,
pattern_match: regex,
confidence: this.combineConfidences(baseConfidence, word.confidence, contextScore),
context_score: contextScore,
location: word.bbox,
ocr_word: word
};
matches.push(match);
}
}
}
return matches;
}
/**
* Calculate confidence score for pattern matching
*/
calculatePatternConfidence(text, pattern, componentPattern) {
// Base confidence from pattern boost
let confidence = componentPattern.confidence_boost;
// Boost for exact prefix matches
const hasExactPrefix = componentPattern.prefixes.some(prefix => text.startsWith(prefix.toUpperCase()));
if (hasExactPrefix) {
confidence = Math.min(confidence + 0.1, 1.0);
}
// Special handling for signal references (8-digit numbers)
if (pattern.source === '^\\d{8}$' && text.length === 8) {
confidence = 0.98; // Very high confidence for 8-digit signal references
}
// Penalize very short or very long identifiers
if (text.length < 3)
confidence *= 0.8;
if (text.length > 12)
confidence *= 0.9;
return Math.max(0.1, Math.min(confidence, 1.0));
}
/**
* Calculate context-based confidence boost
*/
calculateContextScore(componentId, pattern, context) {
let contextScore = 0;
if (!context)
return contextScore;
// Schematic type context
if (context.schematic_type) {
const schematicBoosts = {
'traction_power': ['CONVERTER', 'TRANSFORMER', 'CIRCUIT_BREAKER'],
'signaling': ['SIGNAL_REFERENCE', 'SIGNAL_MODULE', 'RELAY'],
'control': ['CONTACTOR', 'RELAY', 'SWITCH'],
'auxiliary': ['TRANSFORMER', 'CIRCUIT_BREAKER', 'FUSE']
};
const relevantTypes = schematicBoosts[context.schematic_type] || [];
if (relevantTypes.some(type => pattern.description.toUpperCase().includes(type))) {
contextScore += 0.1;
}
}
// Rail system type context
if (context.rail_system_type) {
const railSystemBoosts = {
'METRO': ['converter', 'circuit_breaker', 'contactor'],
'HIGH_SPEED': ['transformer', 'converter', 'signal'],
'FREIGHT': ['transformer', 'circuit_breaker', 'junction']
};
const relevantComponents = railSystemBoosts[context.rail_system_type] || [];
if (relevantComponents.some(comp => pattern.description.toLowerCase().includes(comp))) {
contextScore += 0.08;
}
}
// Voltage level context
if (context.voltage_level) {
if (context.voltage_level > 1000 && pattern.description.includes('transformer')) {
contextScore += 0.05; // High voltage systems likely have transformers
}
if (context.voltage_level < 1000 && pattern.description.includes('contactor')) {
contextScore += 0.05; // Low voltage systems likely have contactors
}
}
// Surrounding text context
if (context.surrounding_text) {
const contextClues = pattern.context_clues || [];
const surroundingText = context.surrounding_text.join(' ').toLowerCase();
const matchingClues = contextClues.filter(clue => surroundingText.includes(clue.toLowerCase()));
contextScore += matchingClues.length * 0.02;
}
return Math.min(contextScore, 0.3); // Cap context boost at 30%
}
/**
* Combine different confidence scores
*/
combineConfidences(patternConfidence, ocrConfidence, contextScore) {
const baseConfidence = (patternConfidence * this.PATTERN_CONFIDENCE_WEIGHT) +
(ocrConfidence * this.OCR_CONFIDENCE_WEIGHT);
return Math.min(baseConfidence + contextScore, 1.0);
}
/**
* Apply context analysis to filter and improve matches
*/
applyContextAnalysis(matches, ocrResult, context) {
// Group matches by component ID to handle duplicates
const matchGroups = new Map();
for (const match of matches) {
const key = match.component_id;
if (!matchGroups.has(key)) {
matchGroups.set(key, []);
}
matchGroups.get(key).push(match);
}
const filteredMatches = [];
// For each group, select the best match
for (const [componentId, groupMatches] of matchGroups) {
if (groupMatches.length === 0)
continue;
// Sort by confidence (highest first)
groupMatches.sort((a, b) => b.confidence - a.confidence);
const bestMatch = groupMatches[0];
// Apply additional filtering
if (bestMatch && this.validateComponentMatch(bestMatch, ocrResult, context)) {
filteredMatches.push(bestMatch);
}
}
return filteredMatches;
}
/**
* Validate that a component match makes sense in context
*/
validateComponentMatch(match, ocrResult, context) {
// Basic validation: minimum OCR confidence
if (match.ocr_word.confidence < 0.5) {
return false;
}
// Validate component ID format
if (!this.isValidComponentId(match.component_id, match.type)) {
return false;
}
// Context-specific validation
if (context?.schematic_type) {
return this.isComponentAppropriateForSchematic(match.type, context.schematic_type);
}
return true;
}
/**
* Check if component ID format is valid for the component type
*/
isValidComponentId(componentId, type) {
const id = componentId.toUpperCase();
// Rail-specific validation rules
switch (type) {
case ComponentType.SIGNAL_REFERENCE:
return /^\d{6,8}$/.test(id); // Signal references must be 6-8 digits
case ComponentType.CONVERTER:
return /^(A|CONV|INV|REC)\d+[A-Z]?$/.test(id);
case ComponentType.CIRCUIT_BREAKER:
return /^(CB|Q|BR|MCB)\d+[A-Z]?$/.test(id);
case ComponentType.TRANSFORMER:
return /^(TR?|TRANS|TX)\d+[A-Z]?$/.test(id);
default:
return id.length >= 2 && id.length <= 12;
}
}
/**
* Check if component type is appropriate for schematic type
*/
isComponentAppropriateForSchematic(componentType, schematicType) {
const appropriateComponents = {
'traction_power': [
ComponentType.CONVERTER, ComponentType.TRANSFORMER, ComponentType.CIRCUIT_BREAKER,
ComponentType.CONTACTOR, ComponentType.FUSE, ComponentType.AMMETER, ComponentType.VOLTMETER
],
'signaling': [
ComponentType.SIGNAL_REFERENCE, ComponentType.SIGNAL_MODULE, ComponentType.RELAY,
ComponentType.COMMUNICATION_MODULE, ComponentType.TERMINAL_BLOCK
],
'control': [
ComponentType.CONTACTOR, ComponentType.RELAY, ComponentType.SWITCH, ComponentType.PUSHBUTTON,
ComponentType.INDICATOR, ComponentType.TERMINAL_BLOCK
],
'auxiliary': [
ComponentType.TRANSFORMER, ComponentType.CIRCUIT_BREAKER, ComponentType.FUSE,
ComponentType.CONTACTOR, ComponentType.JUNCTION_BOX
]
};
const allowedTypes = appropriateComponents[schematicType];
return !allowedTypes || allowedTypes.includes(componentType);
}
/**
* Create ElectricalComponent object from match
*/
createElectricalComponent(match, context) {
const specification = RAIL_COMPONENT_SPECIFICATIONS[match.type];
const category = this.determineComponentCategory(match.type);
const safetyLevel = this.determineSafetyLevel(match.type, specification);
const component = {
id: match.component_id,
type: match.type,
category,
location: match.location,
confidence: match.confidence,
classification: {
type: match.type,
category,
confidence: match.confidence,
specifications: specification,
safety_level: safetyLevel
},
specifications: specification,
safety_level: safetyLevel,
compliance_status: 'REQUIRES_REVIEW'
};
// Add context-specific properties
if (context?.rail_system_type) {
component.rail_system_type = context.rail_system_type;
}
return component;
}
/**
* Determine component category based on type
*/
determineComponentCategory(type) {
const categoryMap = {
[ComponentType.CONVERTER]: ComponentCategory.TRACTION_POWER,
[ComponentType.TRANSFORMER]: ComponentCategory.TRACTION_POWER,
[ComponentType.INVERTER]: ComponentCategory.TRACTION_POWER,
[ComponentType.RECTIFIER]: ComponentCategory.TRACTION_POWER,
[ComponentType.CIRCUIT_BREAKER]: ComponentCategory.PROTECTION,
[ComponentType.FUSE]: ComponentCategory.PROTECTION,
[ComponentType.SURGE_PROTECTOR]: ComponentCategory.PROTECTION,
[ComponentType.RELAY]: ComponentCategory.CONTROL,
[ComponentType.CONTACTOR]: ComponentCategory.CONTROL,
[ComponentType.SWITCH]: ComponentCategory.CONTROL,
[ComponentType.PUSHBUTTON]: ComponentCategory.CONTROL,
[ComponentType.INDICATOR]: ComponentCategory.CONTROL,
[ComponentType.SIGNAL_MODULE]: ComponentCategory.SIGNALING,
[ComponentType.SIGNAL_REFERENCE]: ComponentCategory.SIGNALING,
[ComponentType.COMMUNICATION_MODULE]: ComponentCategory.COMMUNICATION,
[ComponentType.AMMETER]: ComponentCategory.MEASUREMENT,
[ComponentType.VOLTMETER]: ComponentCategory.MEASUREMENT,
[ComponentType.FREQUENCY_METER]: ComponentCategory.MEASUREMENT,
[ComponentType.POWER_METER]: ComponentCategory.MEASUREMENT,
[ComponentType.JUNCTION_BOX]: ComponentCategory.AUXILIARY_POWER,
[ComponentType.TERMINAL_BLOCK]: ComponentCategory.AUXILIARY_POWER,
[ComponentType.CONNECTOR]: ComponentCategory.AUXILIARY_POWER,
[ComponentType.CABLE]: ComponentCategory.AUXILIARY_POWER,
[ComponentType.UNKNOWN]: ComponentCategory.UNCLASSIFIED,
[ComponentType.OTHER]: ComponentCategory.UNCLASSIFIED
};
return categoryMap[type] || ComponentCategory.UNCLASSIFIED;
}
/**
* Determine safety level based on component type and specifications
*/
determineSafetyLevel(type, spec) {
// High-risk components
if ([ComponentType.CONVERTER, ComponentType.TRANSFORMER].includes(type)) {
return SafetyLevel.CRITICAL;
}
if (type === ComponentType.CIRCUIT_BREAKER) {
return SafetyLevel.HIGH;
}
// Check voltage rating
if (spec.voltage_rating && spec.voltage_rating.max > 1000) {
return SafetyLevel.HIGH;
}
if (spec.voltage_rating && spec.voltage_rating.max > 230) {
return SafetyLevel.MEDIUM;
}
return SafetyLevel.LOW;
}
/**
* Remove duplicate components (same ID, overlapping locations)
*/
deduplicateComponents(components) {
const unique = new Map();
for (const component of components) {
const existing = unique.get(component.id);
if (!existing || component.confidence > existing.confidence) {
unique.set(component.id, component);
}
}
return Array.from(unique.values()).sort((a, b) => b.confidence - a.confidence);
}
/**
* Get classification statistics
*/
getClassificationStats(components) {
const stats = {
total: components.length,
by_type: {},
by_category: {},
by_safety_level: {},
average_confidence: 0
};
let totalConfidence = 0;
for (const component of components) {
// Count by type
stats.by_type[component.type] = (stats.by_type[component.type] || 0) + 1;
// Count by category
stats.by_category[component.category] = (stats.by_category[component.category] || 0) + 1;
// Count by safety level
stats.by_safety_level[component.safety_level] = (stats.by_safety_level[component.safety_level] || 0) + 1;
totalConfidence += component.confidence;
}
stats.average_confidence = components.length > 0 ? totalConfidence / components.length : 0;
return stats;
}
}
// Export singleton instance
export const electricalComponentClassifier = new ElectricalComponentClassifier();
//# sourceMappingURL=component-classifier.js.map