UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

485 lines 19.2 kB
/** * InsightsEngine - Analyzes query results to detect patterns and provide recommendations */ import { INSIGHT_PATTERNS } from './constants.js'; export class InsightsEngine { customPatterns = new Map(); /** * Analyze results and generate insights */ analyzeResults(results, context) { const insights = []; // Add performance insights insights.push(...this.analyzePerformance(context)); // Add data pattern insights insights.push(...this.analyzeDataPatterns(results, context)); // Add entity-specific insights insights.push(...this.analyzeEntitySpecificPatterns(results, context)); // Add recommendation insights insights.push(...this.generateRecommendations(results, context)); // Sort by severity and dedup return this.deduplicateAndSort(insights); } /** * Register a custom insight pattern */ registerPattern(name, pattern) { this.customPatterns.set(name, pattern); } analyzePerformance(context) { const insights = []; // Slow query warning if (context.executionTime > 5000) { insights.push({ type: 'anomaly', title: 'Slow query detected', description: `Query took ${(context.executionTime / 1000).toFixed(2)} seconds to execute`, severity: 'warning', data: { executionTime: context.executionTime, suggestion: 'Consider adding more specific filters or optimizing the query' } }); } // Large result set if (context.resultCount > 1000) { insights.push({ type: 'pattern', title: 'Large result set', description: `Query returned ${context.resultCount} results`, severity: 'info', data: { resultCount: context.resultCount, hasMore: context.hasMoreResults } }); } // No results if (context.resultCount === 0) { insights.push({ type: 'pattern', title: 'No results found', description: 'The query returned no matching results', severity: 'info', data: { suggestions: this.getNoResultsSuggestions(context.queryIntent) } }); } return insights; } analyzeDataPatterns(results, context) { const insights = []; if (results.length === 0) return insights; // Analyze based on entity type switch (context.queryIntent.primaryEntity) { case 'flags': insights.push(...this.analyzeFlagPatterns(results)); break; case 'experiments': insights.push(...this.analyzeExperimentPatterns(results)); break; case 'audiences': insights.push(...this.analyzeAudiencePatterns(results)); break; case 'variations': insights.push(...this.analyzeVariationPatterns(results)); break; } // Analyze common patterns insights.push(...this.analyzeCommonPatterns(results)); return insights; } analyzeFlagPatterns(results) { const insights = []; // High variation count const highVariationFlags = results.filter(r => (r.variation_count || r.variations?.length || 0) > 5); if (highVariationFlags.length > 0) { insights.push({ type: 'pattern', title: 'Flags with high variation count', description: `Found ${highVariationFlags.length} flags with more than 5 variations`, severity: 'info', data: { flags: highVariationFlags.map(f => ({ key: f.key, name: f.name, variationCount: f.variation_count || f.variations?.length })), recommendation: 'Consider simplifying flags with many variations' } }); } // Unused flags (no rules or all disabled) const unusedFlags = results.filter(r => !r.rules || r.rules.length === 0 || (r.rules_count === 0 && r.enabled === false)); if (unusedFlags.length > 0) { insights.push({ type: 'pattern', title: 'Potentially unused flags', description: `Found ${unusedFlags.length} flags with no active rules`, severity: 'warning', data: { flags: unusedFlags.map(f => ({ key: f.key, name: f.name })), recommendation: 'Review these flags for potential cleanup' } }); } // Complex variable definitions const complexVariableFlags = results.filter(r => { const varCount = r.variable_count || (r.variable_definitions && Object.keys(r.variable_definitions).length) || 0; return varCount > 10; }); if (complexVariableFlags.length > 0) { insights.push({ type: 'pattern', title: 'Flags with complex variable structures', description: `Found ${complexVariableFlags.length} flags with more than 10 variables`, severity: 'info', data: { flags: complexVariableFlags.map(f => ({ key: f.key, name: f.name, variableCount: f.variable_count })) } }); } return insights; } analyzeExperimentPatterns(results) { const insights = []; // Status distribution const statusCounts = this.countByField(results, 'status'); if (Object.keys(statusCounts).length > 0) { insights.push({ type: 'pattern', title: 'Experiment status distribution', description: 'Overview of experiment statuses', severity: 'info', data: { distribution: statusCounts, total: results.length } }); } // Experiments with many audiences const complexTargeting = results.filter(r => (r.audience_count || r.audience_ids?.length || 0) > 3); if (complexTargeting.length > 0) { insights.push({ type: 'pattern', title: 'Experiments with complex targeting', description: `Found ${complexTargeting.length} experiments targeting more than 3 audiences`, severity: 'info', data: { experiments: complexTargeting.map(e => ({ name: e.name, audienceCount: e.audience_count || e.audience_ids?.length })), recommendation: 'Complex targeting may reduce reach' } }); } // Long-running experiments const longRunning = results.filter(r => { if (!r.created_time || r.status !== 'running') return false; const daysRunning = this.daysBetween(new Date(r.created_time), new Date()); return daysRunning > 90; }); if (longRunning.length > 0) { insights.push({ type: 'anomaly', title: 'Long-running experiments', description: `Found ${longRunning.length} experiments running for more than 90 days`, severity: 'warning', data: { experiments: longRunning.map(e => ({ name: e.name, daysRunning: this.daysBetween(new Date(e.created_time), new Date()) })), recommendation: 'Consider reviewing long-running experiments for conclusive results' } }); } return insights; } analyzeAudiencePatterns(results) { const insights = []; // Unused audiences const unusedAudiences = results.filter(r => r.experiment_count === 0 || (!r.experiments || r.experiments.length === 0)); if (unusedAudiences.length > 0) { insights.push({ type: 'pattern', title: 'Unused audiences', description: `Found ${unusedAudiences.length} audiences not used in any experiments`, severity: 'warning', data: { audiences: unusedAudiences.map(a => ({ id: a.id, name: a.name })), recommendation: 'Consider archiving unused audiences' } }); } // Complex condition audiences const complexAudiences = results.filter(r => { const conditionCount = r.condition_count || (r.conditions && this.countConditions(r.conditions)) || 0; return conditionCount > 5; }); if (complexAudiences.length > 0) { insights.push({ type: 'pattern', title: 'Audiences with complex conditions', description: `Found ${complexAudiences.length} audiences with more than 5 conditions`, severity: 'info', data: { audiences: complexAudiences.map(a => ({ name: a.name, conditionCount: a.condition_count })), recommendation: 'Complex conditions may impact performance' } }); } return insights; } analyzeVariationPatterns(results) { const insights = []; // Disabled variations const disabledVariations = results.filter(r => r.enabled === false); if (disabledVariations.length > 0) { insights.push({ type: 'pattern', title: 'Disabled variations', description: `Found ${disabledVariations.length} disabled variations`, severity: 'info', data: { variations: disabledVariations.map(v => ({ key: v.key, flagKey: v.flag_key })) } }); } // Variations with custom code const customCodeVariations = results.filter(r => r.has_custom_code || (r.actions && r.actions.some((a) => a.type === 'custom_code'))); if (customCodeVariations.length > 0) { insights.push({ type: 'pattern', title: 'Variations with custom code', description: `Found ${customCodeVariations.length} variations using custom code`, severity: 'info', data: { count: customCodeVariations.length, recommendation: 'Ensure custom code is tested and maintained' } }); } return insights; } analyzeCommonPatterns(results) { const insights = []; // Archived items const archivedItems = results.filter(r => r.archived === true); if (archivedItems.length > 0) { const percentArchived = (archivedItems.length / results.length) * 100; if (percentArchived > 30) { insights.push({ type: 'pattern', title: 'High percentage of archived items', description: `${percentArchived.toFixed(1)}% of results are archived`, severity: 'info', data: { archivedCount: archivedItems.length, totalCount: results.length, recommendation: 'Consider filtering out archived items or cleaning up old data' } }); } } // Recently modified items const recentlyModified = results.filter(r => { const updateField = r.updated_time || r.last_modified || r.updated_at; if (!updateField) return false; const daysSince = this.daysBetween(new Date(updateField), new Date()); return daysSince <= 7; }); if (recentlyModified.length > 0) { insights.push({ type: 'pattern', title: 'Recent activity', description: `${recentlyModified.length} items modified in the last 7 days`, severity: 'info', data: { recentCount: recentlyModified.length, percentage: ((recentlyModified.length / results.length) * 100).toFixed(1) } }); } return insights; } analyzeEntitySpecificPatterns(results, context) { const insights = []; // Check standard patterns for (const [patternName, pattern] of Object.entries(INSIGHT_PATTERNS)) { const matching = results.filter(r => pattern.condition(r)); if (matching.length > 0) { const description = pattern.template .replace('{count}', matching.length.toString()) .replace('{allocation}', matching[0]?.traffic_allocation || ''); insights.push({ type: pattern.type, title: pattern.title, description, severity: pattern.type === 'anomaly' ? 'warning' : 'info', data: { matchingItems: matching.length, examples: matching.slice(0, 3).map(item => ({ id: item.id, name: item.name || item.key })) } }); } } // Check custom patterns for (const [name, pattern] of this.customPatterns) { const matching = results.filter(r => pattern.condition(r)); if (matching.length > 0) { insights.push(pattern.generateInsight(matching, context)); } } return insights; } generateRecommendations(results, context) { const recommendations = []; // Optimization recommendations based on query if (context.queryIntent.groupBy && context.queryIntent.groupBy.length > 2) { recommendations.push({ type: 'recommendation', title: 'Query optimization suggestion', description: 'Consider reducing the number of grouping fields for better performance', severity: 'info', data: { currentGrouping: context.queryIntent.groupBy, suggestion: 'Use fewer grouping fields or create a summary view' } }); } // Data quality recommendations const nullCounts = this.countNullFields(results); const highNullFields = Object.entries(nullCounts) .filter(([field, count]) => count / results.length > 0.5) .map(([field]) => field); if (highNullFields.length > 0) { recommendations.push({ type: 'recommendation', title: 'Data quality improvement', description: 'Several fields have high null rates', severity: 'info', data: { fields: highNullFields, recommendation: 'Consider making these fields required or providing defaults' } }); } return recommendations; } getNoResultsSuggestions(intent) { const suggestions = []; if (intent.filters && intent.filters.length > 2) { suggestions.push('Try removing some filters to broaden the search'); } if (intent.timeRange) { suggestions.push('Try expanding the time range'); } if (intent.filters?.some(f => f.operator === 'eq')) { suggestions.push('Try using "contains" instead of exact matches'); } suggestions.push('Check if the data has been synced recently'); return suggestions; } countByField(results, field) { const counts = {}; for (const result of results) { const value = result[field]; if (value !== undefined && value !== null) { const key = String(value); counts[key] = (counts[key] || 0) + 1; } } return counts; } countConditions(conditions) { if (!conditions) return 0; if (typeof conditions === 'string') { try { conditions = JSON.parse(conditions); } catch { return 1; } } if (Array.isArray(conditions)) { return conditions.length; } if (conditions.and) { return conditions.and.length; } if (conditions.or) { return conditions.or.length; } return 1; } countNullFields(results) { const nullCounts = {}; if (results.length === 0) return nullCounts; // Get all fields from first result const fields = Object.keys(results[0]); for (const field of fields) { nullCounts[field] = results.filter(r => r[field] === null || r[field] === undefined).length; } return nullCounts; } daysBetween(date1, date2) { const diffTime = Math.abs(date2.getTime() - date1.getTime()); return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); } deduplicateAndSort(insights) { // Remove duplicates based on title and type const seen = new Set(); const unique = insights.filter(insight => { const key = `${insight.type}:${insight.title}`; if (seen.has(key)) return false; seen.add(key); return true; }); // Sort by severity then type const severityOrder = { critical: 0, warning: 1, info: 2 }; const typeOrder = { anomaly: 0, recommendation: 1, pattern: 2, trend: 3 }; return unique.sort((a, b) => { const severityDiff = (severityOrder[a.severity || 'info'] || 2) - (severityOrder[b.severity || 'info'] || 2); if (severityDiff !== 0) return severityDiff; return (typeOrder[a.type] || 3) - (typeOrder[b.type] || 3); }); } } //# sourceMappingURL=InsightsEngine.js.map