@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
320 lines • 12.8 kB
JavaScript
/**
* 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