UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

320 lines 12.8 kB
/** * Pattern Recognition Engine * @description Detects patterns in AI-generated payloads to identify entity types and transformation opportunities * * Purpose: Analyze payload structure and content to suggest appropriate transformations * and identify the most likely intended entity types and operations. * * Key Features: * - Entity type detection based on field patterns * - Operation inference from payload structure * - Transformation opportunity identification * - Confidence scoring for suggestions * * @author Optimizely MCP Server * @version 1.0.0 */ import { getLogger } from '../logging/Logger.js'; /** * Pattern Recognition Engine for intelligent payload analysis */ export class PatternRecognitionEngine { logger = getLogger(); // Entity signature patterns based on common field combinations entitySignatures = { ruleset: { requiredFields: ['enabled', 'rules', 'default_variation_key'], optionalFields: ['rollout_id', 'archived'], confidence: 0.9 }, flag: { requiredFields: ['key', 'name'], optionalFields: ['description', 'archived', 'environments'], confidence: 0.8 }, experiment: { requiredFields: ['name', 'variations'], optionalFields: ['metrics', 'audience_conditions', 'traffic_allocation'], confidence: 0.85 }, event: { requiredFields: ['key', 'name'], optionalFields: ['description', 'event_type', 'category'], confidence: 0.8 }, audience: { requiredFields: ['name', 'conditions'], optionalFields: ['description', 'archived'], confidence: 0.85 }, page: { requiredFields: ['name', 'conditions'], optionalFields: ['activation_code', 'page_type'], confidence: 0.8 }, variation: { requiredFields: ['key', 'name'], optionalFields: ['actions', 'weight', 'feature_enabled'], confidence: 0.75 } }; // Common AI naming patterns that suggest specific transformations aiNamingPatterns = [ { pattern: /ruleset_data|ruleset_config|rules_data/i, suggests: 'ruleset_transformation', confidence: 0.9 }, { pattern: /ab_test|a_b_test|split_test/i, suggests: 'experiment_structure', confidence: 0.85 }, { pattern: /traffic_split|traffic_allocation|percentage/i, suggests: 'traffic_allocation_structure', confidence: 0.8 }, { pattern: /conditions|targeting|audience/i, suggests: 'audience_conditions_structure', confidence: 0.75 } ]; constructor() { this.logger.debug('PatternRecognitionEngine initialized'); } /** * Analyze payload and detect patterns */ async analyzePayload(payload) { this.logger.debug(`Starting pattern analysis for payload with ${Object.keys(payload || {}).length} root fields`); const complexity = this.calculateComplexity(payload); const suspectedEntityTypes = this.detectEntityTypes(payload); const structurePatterns = this.detectStructurePatterns(payload); const transformationOpportunities = this.identifyTransformationOpportunities(payload, structurePatterns); const analysis = { suspectedEntityTypes, structurePatterns, complexity, transformationOpportunities }; this.logger.debug(`Pattern analysis completed: ${suspectedEntityTypes.length} entity matches, ${structurePatterns.length} structure patterns`); return analysis; } /** * Detect likely entity types based on field patterns */ detectEntityTypes(payload) { const patterns = []; const payloadFields = this.extractAllFieldNames(payload); for (const [entityType, signature] of Object.entries(this.entitySignatures)) { const matchedRequired = signature.requiredFields.filter(field => payloadFields.some(payloadField => this.fieldMatches(payloadField, field))); const matchedOptional = signature.optionalFields.filter(field => payloadFields.some(payloadField => this.fieldMatches(payloadField, field))); const requiredScore = matchedRequired.length / signature.requiredFields.length; const optionalScore = matchedOptional.length / signature.optionalFields.length; // Need at least 50% of required fields for consideration if (requiredScore >= 0.5) { const confidence = (requiredScore * 0.7 + optionalScore * 0.3) * signature.confidence; patterns.push({ entityType, confidence, matchedFields: [...matchedRequired, ...matchedOptional], reasoning: `Matched ${matchedRequired.length}/${signature.requiredFields.length} required fields and ${matchedOptional.length}/${signature.optionalFields.length} optional fields` }); } } // Sort by confidence descending return patterns.sort((a, b) => b.confidence - a.confidence); } /** * Detect structure patterns that suggest transformations */ detectStructurePatterns(payload) { const patterns = []; // Check for nested ruleset data pattern if (this.hasNestedRulesetPattern(payload)) { patterns.push({ pattern: 'nested_ruleset_data', confidence: 0.9, transformationHint: 'Extract nested ruleset data to root level for PATCH operation', affectedFields: this.findNestedRulesetFields(payload) }); } // Check for array-based rules that should be object if (this.hasArrayRulesPattern(payload)) { patterns.push({ pattern: 'array_rules_structure', confidence: 0.85, transformationHint: 'Convert rules array to proper ruleset structure', affectedFields: ['rules'] }); } // Check for AI naming conventions const aiPatterns = this.detectAINamingPatterns(payload); patterns.push(...aiPatterns); return patterns; } /** * Check if payload has nested ruleset data that needs extraction */ hasNestedRulesetPattern(payload) { if (!payload || typeof payload !== 'object') return false; // Look for ruleset_data field containing nested structure if (payload.ruleset_data && typeof payload.ruleset_data === 'object') { const nestedFields = Object.keys(payload.ruleset_data); return nestedFields.some(field => ['rules', 'enabled', 'default_variation_key', 'rollout_id'].includes(field)); } return false; } /** * Find fields that contain nested ruleset data */ findNestedRulesetFields(payload) { const fields = []; if (payload.ruleset_data) { fields.push('ruleset_data'); // Also check for common nested fields const nested = payload.ruleset_data; if (nested.rules) fields.push('ruleset_data.rules'); if (nested.enabled !== undefined) fields.push('ruleset_data.enabled'); if (nested.default_variation_key) fields.push('ruleset_data.default_variation_key'); } return fields; } /** * Check for array-based rules structure */ hasArrayRulesPattern(payload) { return Array.isArray(payload.rules) && payload.rules.length > 0; } /** * Detect AI naming patterns that suggest transformations */ detectAINamingPatterns(payload) { const patterns = []; const fieldNames = this.extractAllFieldNames(payload); for (const aiPattern of this.aiNamingPatterns) { const matchingFields = fieldNames.filter(field => aiPattern.pattern.test(field)); if (matchingFields.length > 0) { patterns.push({ pattern: aiPattern.suggests, confidence: aiPattern.confidence, transformationHint: this.getTransformationHint(aiPattern.suggests), affectedFields: matchingFields }); } } return patterns; } /** * Get transformation hint for a pattern type */ getTransformationHint(patternType) { const hints = { 'ruleset_transformation': 'Transform nested ruleset structure to flat PATCH format', 'experiment_structure': 'Convert A/B test structure to proper experiment format', 'traffic_allocation_structure': 'Transform traffic allocation to proper percentage format', 'audience_conditions_structure': 'Convert audience conditions to proper condition array format' }; return hints[patternType] || 'Apply standard transformation patterns'; } /** * Calculate payload complexity metrics */ calculateComplexity(payload) { let depth = 0; let fieldCount = 0; let nestedObjectCount = 0; let arrayCount = 0; const analyze = (obj, currentDepth) => { if (currentDepth > depth) depth = currentDepth; if (obj && typeof obj === 'object') { if (Array.isArray(obj)) { arrayCount++; obj.forEach(item => analyze(item, currentDepth + 1)); } else { const fields = Object.keys(obj); fieldCount += fields.length; fields.forEach(key => { const value = obj[key]; if (value && typeof value === 'object') { nestedObjectCount++; analyze(value, currentDepth + 1); } }); } } }; analyze(payload, 0); return { depth, fieldCount, nestedObjectCount, arrayCount }; } /** * Identify transformation opportunities based on patterns */ identifyTransformationOpportunities(payload, structurePatterns) { const opportunities = []; // Based on structure patterns structurePatterns.forEach(pattern => { opportunities.push(pattern.transformationHint); }); // Additional heuristics if (this.hasNestedRulesetPattern(payload)) { opportunities.push('Flatten ruleset_data to root level for API compatibility'); } if (payload.rules && Array.isArray(payload.rules)) { opportunities.push('Wrap rules array in proper ruleset structure'); } return [...new Set(opportunities)]; // Remove duplicates } /** * Extract all field names from payload (including nested) */ extractAllFieldNames(payload) { const fields = []; const extract = (obj, prefix = '') => { if (obj && typeof obj === 'object' && !Array.isArray(obj)) { Object.keys(obj).forEach(key => { const fullKey = prefix ? `${prefix}.${key}` : key; fields.push(fullKey); if (obj[key] && typeof obj[key] === 'object') { extract(obj[key], fullKey); } }); } }; extract(payload); return fields; } /** * Check if payload field matches template field (with fuzzy matching) */ fieldMatches(payloadField, templateField) { // Exact match if (payloadField === templateField) return true; // Case insensitive match if (payloadField.toLowerCase() === templateField.toLowerCase()) return true; // Snake_case vs camelCase const normalizedPayload = payloadField.toLowerCase().replace(/_/g, ''); const normalizedTemplate = templateField.toLowerCase().replace(/_/g, ''); if (normalizedPayload === normalizedTemplate) return true; // Partial matches for compound fields if (payloadField.includes(templateField) || templateField.includes(payloadField)) return true; return false; } } /** * Singleton instance for global use */ export const patternRecognitionEngine = new PatternRecognitionEngine(); //# sourceMappingURL=PatternRecognitionEngine.js.map