@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
448 lines • 17.3 kB
JavaScript
/**
* Smart Field Mapper
*
* An intelligent field mapping system that learns from patterns and
* automatically generates mappings for new fields. This prevents the
* need for constant manual updates when new field patterns emerge.
*/
import { FIELDS } from '../generated/fields.generated.js';
import { FieldMapper } from './FieldMapper.js';
export class SmartFieldMapper extends FieldMapper {
learnedPatterns = new Map();
fieldRelationships = [];
unmappedFieldLog = new Map();
constructor() {
super();
this.initializeSmartPatterns();
this.analyzeEntityRelationships();
}
/**
* Initialize smart patterns based on field analysis
*/
initializeSmartPatterns() {
// Analyze all fields to find patterns
const fieldPatterns = this.analyzeFieldPatterns();
// Common suffixes and their meanings
const suffixMappings = [
{ suffix: '_id', type: 'reference', confidence: 0.95 },
{ suffix: '_key', type: 'reference', confidence: 0.95 },
{ suffix: '_name', type: 'display', confidence: 0.9 },
{ suffix: '_url', type: 'link', confidence: 0.9 },
{ suffix: '_time', type: 'timestamp', confidence: 0.9 },
{ suffix: '_date', type: 'timestamp', confidence: 0.9 },
{ suffix: '_at', type: 'timestamp', confidence: 0.88 },
{ suffix: '_enabled', type: 'boolean', confidence: 0.9 },
{ suffix: '_count', type: 'numeric', confidence: 0.9 },
{ suffix: '_percentage', type: 'numeric', confidence: 0.9 },
{ suffix: '_threshold', type: 'numeric', confidence: 0.9 },
{ suffix: '_list', type: 'array', confidence: 0.85 },
{ suffix: '_ids', type: 'array', confidence: 0.9 },
{ suffix: '_priorities', type: 'array', confidence: 0.9 }
];
// Common prefixes and their meanings
const prefixMappings = [
{ prefix: 'is_', type: 'boolean', confidence: 0.95 },
{ prefix: 'has_', type: 'boolean', confidence: 0.95 },
{ prefix: 'can_', type: 'boolean', confidence: 0.95 },
{ prefix: 'should_', type: 'boolean', confidence: 0.9 },
{ prefix: 'parent_', type: 'reference', confidence: 0.95 },
{ prefix: 'child_', type: 'reference', confidence: 0.9 },
{ prefix: 'related_', type: 'reference', confidence: 0.9 },
{ prefix: 'default_', type: 'default_value', confidence: 0.9 },
{ prefix: 'min_', type: 'constraint', confidence: 0.9 },
{ prefix: 'max_', type: 'constraint', confidence: 0.9 },
{ prefix: 'total_', type: 'aggregate', confidence: 0.9 },
{ prefix: 'num_', type: 'count', confidence: 0.9 }
];
// Entity name patterns (for references)
const entityNames = [
'project', 'experiment', 'flag', 'feature', 'variation',
'event', 'audience', 'campaign', 'page', 'rule',
'ruleset', 'environment', 'webhook', 'group', 'layer',
'metric', 'attribute', 'extension', 'collaborator'
];
// Generate smart patterns for entity references
for (const entity of entityNames) {
// Direct entity_id pattern
this.learnedPatterns.set(`${entity}_id_pattern`, {
pattern: new RegExp(`^(${entity}[_\\s-]?(?:id|identifier))$`, 'i'),
targetField: `${entity}_id`,
confidence: 0.95,
successCount: 0,
source: 'inferred'
});
// Parent entity pattern
this.learnedPatterns.set(`parent_${entity}_pattern`, {
pattern: new RegExp(`^(?:parent|associated|linked)[_\\s-]?${entity}(?:[_\\s-]?(?:id|key))?$`, 'i'),
targetField: `${entity}_id`,
confidence: 0.92,
successCount: 0,
source: 'inferred'
});
}
}
/**
* Analyze entity relationships from schema
*/
analyzeEntityRelationships() {
// Map common relationship patterns
this.fieldRelationships = [
{
fromEntity: 'variation',
toEntity: 'flag',
fieldPattern: 'flag_key',
examples: ['flag_key', 'parent_flag', 'feature_flag_key']
},
{
fromEntity: 'variation',
toEntity: 'experiment',
fieldPattern: 'experiment_id',
examples: ['experiment_id', 'parent_experiment', 'exp_id']
},
{
fromEntity: 'experiment',
toEntity: 'campaign',
fieldPattern: 'campaign_id',
examples: ['campaign_id', 'parent_campaign']
},
{
fromEntity: 'experiment',
toEntity: 'feature',
fieldPattern: 'feature_id',
examples: ['feature_id', 'feature_key', 'attached_feature']
},
{
fromEntity: 'event',
toEntity: 'page',
fieldPattern: 'page_id',
examples: ['page_id', 'parent_page', 'associated_page']
},
{
fromEntity: 'rule',
toEntity: 'layer',
fieldPattern: 'layer_id',
examples: ['layer_id', 'layer_experiment_id']
}
];
}
/**
* Analyze field patterns from schema
*/
analyzeFieldPatterns() {
const patterns = new Map();
for (const entityName of Object.keys(FIELDS)) {
const entity = FIELDS[entityName];
const fields = new Set();
if (entity.required)
entity.required.forEach(f => fields.add(f));
if (entity.optional)
entity.optional.forEach(f => fields.add(f));
patterns.set(entityName, fields);
}
return patterns;
}
/**
* Override mapFields to add smart detection
*/
async mapFields(payload, options) {
// First try standard mapping
const standardMappings = await super.mapFields(payload, options);
// For unmapped fields, try smart detection
const payloadFields = Object.keys(payload);
const mappedFields = new Set(standardMappings.map(m => m.aiFieldName));
const unmappedFields = payloadFields.filter(f => !mappedFields.has(f));
const smartMappings = [];
for (const field of unmappedFields) {
const smartMapping = this.detectSmartMapping(field, options.entityType);
if (smartMapping) {
smartMappings.push(smartMapping);
// Log for learning
this.logSuccessfulMapping(field, smartMapping.templateFieldName);
}
else {
// Track unmapped fields
this.trackUnmappedField(field);
}
}
return [...standardMappings, ...smartMappings];
}
/**
* Detect smart mapping for a field
*/
detectSmartMapping(field, entityType) {
// 1. Check learned patterns
for (const [key, pattern] of this.learnedPatterns) {
if (pattern.pattern.test(field)) {
return {
aiFieldName: field,
templateFieldName: pattern.targetField,
confidence: pattern.confidence,
transformationType: 'custom'
};
}
}
// 2. Check for entity references
const entityRefMatch = this.detectEntityReference(field);
if (entityRefMatch) {
return entityRefMatch;
}
// 3. Check for suffix patterns
const suffixMatch = this.detectSuffixPattern(field);
if (suffixMatch) {
return suffixMatch;
}
// 4. Check for prefix patterns
const prefixMatch = this.detectPrefixPattern(field);
if (prefixMatch) {
return prefixMatch;
}
// 5. Use edit distance for close matches
const closeMatch = this.findClosestMatch(field, entityType);
if (closeMatch && closeMatch.confidence > 0.7) {
return closeMatch;
}
return null;
}
/**
* Detect entity reference patterns
*/
detectEntityReference(field) {
const fieldLower = field.toLowerCase();
for (const relationship of this.fieldRelationships) {
// Check if field contains the entity name
if (fieldLower.includes(relationship.toEntity)) {
// Check for ID/key suffix
if (fieldLower.includes('_id') || fieldLower.includes('id')) {
return {
aiFieldName: field,
templateFieldName: `${relationship.toEntity}_id`,
confidence: 0.9,
transformationType: 'custom'
};
}
if (fieldLower.includes('_key') || fieldLower.includes('key')) {
return {
aiFieldName: field,
templateFieldName: `${relationship.toEntity}_key`,
confidence: 0.9,
transformationType: 'custom'
};
}
}
}
return null;
}
/**
* Detect suffix patterns
*/
detectSuffixPattern(field) {
const suffixPatterns = [
{ suffix: '_id', confidence: 0.85 },
{ suffix: '_key', confidence: 0.85 },
{ suffix: '_time', confidence: 0.8 },
{ suffix: '_date', confidence: 0.8 },
{ suffix: '_url', confidence: 0.8 },
{ suffix: '_enabled', confidence: 0.85 },
{ suffix: '_count', confidence: 0.8 }
];
for (const pattern of suffixPatterns) {
if (field.endsWith(pattern.suffix)) {
// Try to find the base field
const baseField = field.substring(0, field.length - pattern.suffix.length);
// Check if this could be a known field
const targetField = this.inferTargetField(baseField, pattern.suffix);
if (targetField) {
return {
aiFieldName: field,
templateFieldName: targetField,
confidence: pattern.confidence,
transformationType: 'custom'
};
}
}
}
return null;
}
/**
* Detect prefix patterns
*/
detectPrefixPattern(field) {
const prefixPatterns = [
{ prefix: 'is_', type: 'boolean', confidence: 0.9 },
{ prefix: 'has_', type: 'boolean', confidence: 0.9 },
{ prefix: 'parent_', type: 'reference', confidence: 0.85 },
{ prefix: 'default_', type: 'default', confidence: 0.85 }
];
for (const pattern of prefixPatterns) {
if (field.startsWith(pattern.prefix)) {
const baseField = field.substring(pattern.prefix.length);
// For boolean fields, often the target is the same
if (pattern.type === 'boolean') {
return {
aiFieldName: field,
templateFieldName: field,
confidence: pattern.confidence,
transformationType: 'direct'
};
}
// For references, try to find the entity
if (pattern.type === 'reference') {
const targetField = this.inferReferenceField(baseField);
if (targetField) {
return {
aiFieldName: field,
templateFieldName: targetField,
confidence: pattern.confidence,
transformationType: 'custom'
};
}
}
}
}
return null;
}
/**
* Find closest match using edit distance
*/
findClosestMatch(field, entityType) {
const entity = FIELDS[entityType];
if (!entity)
return null;
const allFields = new Set();
if (entity.required)
entity.required.forEach(f => allFields.add(f));
if (entity.optional)
entity.optional.forEach(f => allFields.add(f));
let bestMatch = null;
for (const targetField of allFields) {
const distance = this.editDistance(field.toLowerCase(), targetField.toLowerCase());
const similarity = 1 - (distance / Math.max(field.length, targetField.length));
if (similarity > 0.7) {
if (!bestMatch || distance < bestMatch.distance) {
bestMatch = { field: targetField, distance };
}
}
}
if (bestMatch) {
const similarity = 1 - (bestMatch.distance / Math.max(field.length, bestMatch.field.length));
return {
aiFieldName: field,
templateFieldName: bestMatch.field,
confidence: similarity,
transformationType: 'custom'
};
}
return null;
}
/**
* Calculate edit distance between two strings
*/
editDistance(a, b) {
const matrix = [];
for (let i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
}
else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
}
}
}
return matrix[b.length][a.length];
}
/**
* Infer target field based on patterns
*/
inferTargetField(baseField, suffix) {
// Common inferences
if (suffix === '_id') {
// Check if base is an entity name
const entities = ['project', 'experiment', 'flag', 'feature', 'campaign', 'page'];
if (entities.includes(baseField)) {
return `${baseField}_id`;
}
}
// Direct match for some common patterns
const directMatches = {
'created_time': 'created',
'updated_time': 'last_modified',
'modified_time': 'last_modified',
'start_date': 'start_time',
'end_date': 'end_time'
};
return directMatches[baseField + suffix] || null;
}
/**
* Infer reference field from base
*/
inferReferenceField(baseField) {
// If it ends with an entity name, add _id
const entities = ['project', 'experiment', 'flag', 'feature', 'campaign'];
for (const entity of entities) {
if (baseField === entity || baseField.endsWith(`_${entity}`)) {
return `${entity}_id`;
}
}
return null;
}
/**
* Log successful mapping for learning
*/
logSuccessfulMapping(fromField, toField) {
this.logger.debug({
action: 'smart_mapping_success',
from: fromField,
to: toField
}, 'SmartFieldMapper: Successful smart mapping');
}
/**
* Track unmapped fields for analysis
*/
trackUnmappedField(field) {
const count = this.unmappedFieldLog.get(field) || 0;
this.unmappedFieldLog.set(field, count + 1);
// Alert if field appears frequently
if (count + 1 === 5) {
this.logger.warn({
field,
occurrences: count + 1
}, 'SmartFieldMapper: Frequently unmapped field detected');
}
}
/**
* Get unmapped field report
*/
getUnmappedFieldReport() {
return Array.from(this.unmappedFieldLog.entries())
.map(([field, count]) => ({ field, count }))
.sort((a, b) => b.count - a.count);
}
/**
* Generate mapping recommendations
*/
generateRecommendations() {
const recommendations = [];
for (const [field, count] of this.unmappedFieldLog) {
if (count >= 3) {
// Try to infer a mapping
const smartMapping = this.detectSmartMapping(field, 'generic');
if (smartMapping) {
recommendations.push({
field,
suggestedMapping: smartMapping.templateFieldName,
confidence: smartMapping.confidence
});
}
}
}
return recommendations;
}
}
//# sourceMappingURL=SmartFieldMapper.js.map