@codai/memorai-mcp
Version:
MemorAI CBD-based MCP Server - High-Performance Vector Memory System
801 lines (796 loc) • 32.8 kB
JavaScript
/**
* Phase 3: AI-Powered Memory Intelligence - Predictive Memory Engine
*
* Revolutionary predictive capabilities that set new industry standards:
* - Predicts what memories users will need before they search
* - Optimizes memory creation timing
* - Forecasts relationship formation and importance evolution
* - Provides proactive memory management suggestions
*/
export class PredictiveMemoryEngine {
openaiClient;
memories;
predictionHistory;
userPatterns;
predictionCache;
cacheTimeout = 5 * 60 * 1000; // 5 minutes
constructor(openaiClient, memories) {
this.openaiClient = openaiClient;
this.memories = memories || new Map();
this.predictionHistory = new Map();
this.userPatterns = new Map();
this.predictionCache = new Map();
}
/**
* Predict what memories user will need before they search
* Revolutionary proactive memory suggestions
*/
async predictNeededMemories(agentId, context) {
const cacheKey = `needed_memories_${agentId}_${JSON.stringify(context)}`;
const cached = this.getCachedPrediction(cacheKey);
if (cached)
return cached;
try {
// Get agent's memories and activity patterns
const agentMemories = Array.from(this.memories.values())
.filter(m => m.metadata.agentId === agentId);
if (agentMemories.length === 0) {
return [];
}
// Analyze user patterns
const userPattern = await this.analyzeUserPattern(agentId, context);
// Score memories based on predicted need
const scoredMemories = await this.scoreMemoriesByPredictedNeed(agentMemories, context, userPattern);
// Generate predictions using AI if available
const aiPredictions = this.openaiClient
? await this.generateAIPredictions(agentMemories, context, userPattern)
: [];
// Combine and rank predictions
const predictions = await this.combineAndRankPredictions(scoredMemories, aiPredictions, context);
this.cachePrediction(cacheKey, predictions);
return predictions;
}
catch (error) {
console.error('Error predicting needed memories:', error);
return [];
}
}
/**
* Predict optimal memory creation timing
* Smart timing recommendations based on context and patterns
*/
async predictMemoryCreationTiming(agentId, contentType, context) {
try {
const userPattern = await this.analyzeUserPattern(agentId, context);
// Analyze timing factors
const factors = [];
// Current workload analysis
const workloadFactor = this.analyzeWorkloadTiming(context);
factors.push(workloadFactor);
// Time of day optimization
const timeFactor = this.analyzeTimeOfDayOptimal(userPattern, new Date());
factors.push(timeFactor);
// Memory type considerations
const typeFactor = this.analyzeMemoryTypeTiming(contentType, userPattern);
factors.push(typeFactor);
// Calculate optimal timing
const optimalTiming = this.calculateOptimalTiming(factors, context);
return optimalTiming;
}
catch (error) {
console.error('Error predicting optimal timing:', error);
return {
recommendedTime: new Date().toISOString(),
reasoning: 'Using current time due to prediction error',
confidence: 0.3,
factors: []
};
}
}
/**
* Predict memory relationship formation
* Forecast future connections between memories
*/
async predictFutureRelationships(memoryId, timeHorizon = 30 // days
) {
try {
const sourceMemory = this.memories.get(memoryId);
if (!sourceMemory) {
return [];
}
const agentMemories = Array.from(this.memories.values())
.filter(m => m.metadata.agentId === sourceMemory.metadata.agentId);
const predictions = [];
for (const targetMemory of agentMemories) {
if (targetMemory.id === memoryId)
continue;
// Skip if relationship already exists
if (this.hasExistingRelationship(sourceMemory, targetMemory)) {
continue;
}
const prediction = await this.predictRelationshipFormation(sourceMemory, targetMemory, timeHorizon);
if (prediction && prediction.formationProbability > 0.3) {
predictions.push(prediction);
}
}
// Sort by formation probability
return predictions.sort((a, b) => b.formationProbability - a.formationProbability);
}
catch (error) {
console.error('Error predicting relationships:', error);
return [];
}
}
/**
* Predict memory importance evolution
* Forecast how memory importance will change over time
*/
async predictImportanceChanges(memoryId, timeHorizon = 90 // days
) {
try {
const memory = this.memories.get(memoryId);
if (!memory) {
throw new Error(`Memory ${memoryId} not found`);
}
const currentImportance = memory.metadata.importance || 0.5;
// Generate time points for prediction
const timePoints = this.generateTimePoints(timeHorizon);
// Analyze importance drivers
const drivers = await this.analyzeImportanceDrivers(memory);
// Predict importance at each time point
const predictedImportance = await this.forecastImportanceTrajectory(memory, drivers, timePoints);
// Determine trend direction
const trendDirection = this.determineTrendDirection(predictedImportance);
// Calculate confidence intervals
const confidenceInterval = this.calculateConfidenceIntervals(predictedImportance, drivers);
return {
memoryId,
currentImportance,
predictedImportance,
timePoints: timePoints.map(t => t.toISOString()),
trendDirection,
confidenceInterval,
factors: drivers
};
}
catch (error) {
console.error('Error predicting importance changes:', error);
throw error;
}
}
// Private helper methods
getCachedPrediction(key) {
const cached = this.predictionCache.get(key);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data;
}
return null;
}
cachePrediction(key, data) {
this.predictionCache.set(key, {
data,
timestamp: Date.now()
});
}
async analyzeUserPattern(agentId, context) {
// Check if we have cached pattern
let pattern = this.userPatterns.get(agentId);
if (!pattern) {
// Analyze from memory access patterns
pattern = await this.extractUserPatternFromMemories(agentId);
this.userPatterns.set(agentId, pattern);
}
// Update with current context
if (context) {
pattern = this.updatePatternWithContext(pattern, context);
}
return pattern;
}
async extractUserPatternFromMemories(agentId) {
const memories = Array.from(this.memories.values())
.filter(m => m.metadata.agentId === agentId);
// Analyze creation times for peak hours
const creationHours = memories
.map(m => new Date(m.metadata.timestamp).getHours())
.reduce((acc, hour) => {
acc[hour] = (acc[hour] || 0) + 1;
return acc;
}, {});
const peakHours = Object.entries(creationHours)
.sort(([, a], [, b]) => b - a)
.slice(0, 3)
.map(([hour]) => `${hour}:00`);
// Analyze preferred memory types
const memoryTypes = memories
.map(m => m.metadata.entityType || 'general')
.reduce((acc, type) => {
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {});
const preferredMemoryTypes = Object.entries(memoryTypes)
.sort(([, a], [, b]) => b - a)
.slice(0, 3)
.map(([type]) => type);
return {
peakHours,
preferredMemoryTypes,
searchPatterns: [], // Would need search history
collaborationStyle: 'individual', // Default
learningStyle: 'textual' // Default
};
}
updatePatternWithContext(pattern, context) {
// Update pattern based on current context
return {
...pattern,
// Could enhance pattern with real-time context data
};
}
async scoreMemoriesByPredictedNeed(memories, context, userPattern) {
const predictions = [];
for (const memory of memories) {
const score = await this.calculateNeedScore(memory, context, userPattern);
if (score.predictedRelevance > 0.3) {
predictions.push({
memoryId: memory.id,
title: this.extractTitle(memory.content),
content: memory.content.substring(0, 200) + '...',
predictedRelevance: score.predictedRelevance,
reasoning: score.reasoning,
suggestedActions: score.suggestedActions,
confidence: score.confidence,
timeToNeed: score.timeToNeed
});
}
}
return predictions;
}
async calculateNeedScore(memory, context, userPattern) {
let score = 0.0;
const reasons = [];
const actions = [];
// Time-based scoring
const timeScore = this.calculateTimeBasedScore(memory, context);
score += timeScore * 0.3;
if (timeScore > 0.5) {
reasons.push('Recent access pattern suggests relevance');
}
// Context-based scoring
const contextScore = this.calculateContextScore(memory, context);
score += contextScore * 0.4;
if (contextScore > 0.5) {
reasons.push('Matches current task context');
actions.push('Review for current task');
}
// Pattern-based scoring
const patternScore = this.calculatePatternScore(memory, userPattern);
score += patternScore * 0.3;
if (patternScore > 0.5) {
reasons.push('Aligns with user behavior patterns');
}
// Calculate time to need
const timeToNeed = this.estimateTimeToNeed(score, context);
return {
predictedRelevance: Math.min(score, 1.0),
reasoning: reasons.join('; ') || 'Low relevance score',
suggestedActions: actions.length > 0 ? actions : ['Monitor for future relevance'],
confidence: score * 0.8, // Slightly lower confidence than score
timeToNeed
};
}
calculateTimeBasedScore(memory, context) {
const now = new Date();
const memoryTime = new Date(memory.metadata.timestamp);
const daysSince = (now.getTime() - memoryTime.getTime()) / (1000 * 60 * 60 * 24);
// Recent memories get higher scores
if (daysSince < 1)
return 0.9;
if (daysSince < 7)
return 0.7;
if (daysSince < 30)
return 0.5;
return 0.2;
}
calculateContextScore(memory, context) {
let score = 0.0;
// Check if memory relates to current task
if (context.currentTask) {
const taskWords = context.currentTask.toLowerCase().split(' ');
const memoryWords = memory.content.toLowerCase().split(' ');
const overlap = taskWords.filter(word => memoryWords.includes(word)).length;
score += (overlap / taskWords.length) * 0.5;
}
// Check recent activity relevance
if (context.recentActivity) {
const recentMemoryIds = context.recentActivity
.flatMap(a => a.memoryIds)
.filter(id => id === memory.id);
if (recentMemoryIds.length > 0) {
score += 0.4;
}
}
// Check working memory
if (context.workingMemory && context.workingMemory.includes(memory.id)) {
score += 0.3;
}
return Math.min(score, 1.0);
}
calculatePatternScore(memory, userPattern) {
let score = 0.0;
// Check if memory type is preferred
const memoryType = memory.metadata.entityType || 'general';
if (userPattern.preferredMemoryTypes.includes(memoryType)) {
score += 0.4;
}
// Check time pattern match
const memoryHour = new Date(memory.metadata.timestamp).getHours();
const currentHour = new Date().getHours();
if (userPattern.peakHours.includes(`${currentHour}:00`)) {
score += 0.3;
}
return Math.min(score, 1.0);
}
estimateTimeToNeed(score, context) {
// Higher scores mean sooner need
if (score > 0.8)
return 5; // 5 minutes
if (score > 0.6)
return 30; // 30 minutes
if (score > 0.4)
return 120; // 2 hours
return 480; // 8 hours
}
async generateAIPredictions(memories, context, userPattern) {
if (!this.openaiClient) {
return [];
}
try {
const prompt = this.buildPredictionPrompt(memories, context, userPattern);
const response = await this.openaiClient.chat.completions.create({
model: 'gpt-4',
messages: [{
role: 'user',
content: prompt
}],
temperature: 0.3,
max_tokens: 2000
});
const aiResponse = response.choices[0]?.message?.content;
if (!aiResponse)
return [];
return this.parseAIPredictions(aiResponse, memories);
}
catch (error) {
console.error('Error generating AI predictions:', error);
return [];
}
}
buildPredictionPrompt(memories, context, userPattern) {
const memoryPreview = memories.slice(0, 10).map(m => `${m.id}: ${m.content.substring(0, 100)}...`).join('\n');
return `
As an AI memory prediction system, analyze the user's current context and predict which memories they will likely need soon.
Current Context:
- Task: ${context.currentTask || 'Unknown'}
- Time: ${context.timeOfDay}
- Recent Activity: ${context.recentActivity?.length || 0} recent actions
User Pattern:
- Peak Hours: ${userPattern.peakHours.join(', ')}
- Preferred Types: ${userPattern.preferredMemoryTypes.join(', ')}
- Learning Style: ${userPattern.learningStyle}
Available Memories (sample):
${memoryPreview}
Predict the top 5 memories the user will likely need in the next few hours. For each, provide:
1. Memory ID
2. Relevance score (0.0-1.0)
3. Reasoning
4. Suggested actions
5. Confidence level
Format as JSON array with these fields.
`;
}
parseAIPredictions(aiResponse, memories) {
try {
const parsed = JSON.parse(aiResponse);
if (!Array.isArray(parsed))
return [];
return parsed.map(p => ({
memoryId: p.memoryId || '',
title: this.extractTitle(memories.find(m => m.id === p.memoryId)?.content || ''),
content: memories.find(m => m.id === p.memoryId)?.content?.substring(0, 200) + '...' || '',
predictedRelevance: p.relevance || 0.5,
reasoning: p.reasoning || 'AI prediction',
suggestedActions: Array.isArray(p.actions) ? p.actions : ['Review'],
confidence: p.confidence || 0.5,
timeToNeed: 60 // Default 1 hour
})).filter(p => p.memoryId);
}
catch (error) {
console.error('Error parsing AI predictions:', error);
return [];
}
}
async combineAndRankPredictions(scoredMemories, aiPredictions, context) {
// Combine predictions, giving weight to both sources
const combined = new Map();
// Add scored memories
scoredMemories.forEach(prediction => {
combined.set(prediction.memoryId, prediction);
});
// Enhance with AI predictions
aiPredictions.forEach(aiPrediction => {
const existing = combined.get(aiPrediction.memoryId);
if (existing) {
// Combine scores
existing.predictedRelevance = (existing.predictedRelevance + aiPrediction.predictedRelevance) / 2;
existing.confidence = Math.max(existing.confidence, aiPrediction.confidence);
existing.reasoning += '; ' + aiPrediction.reasoning;
}
else {
combined.set(aiPrediction.memoryId, aiPrediction);
}
});
// Sort by relevance and return top predictions
return Array.from(combined.values())
.sort((a, b) => b.predictedRelevance - a.predictedRelevance)
.slice(0, 10);
}
analyzeWorkloadTiming(context) {
const workload = context?.environmentalFactors?.workload || 'moderate';
let impact = 0;
let description = '';
switch (workload) {
case 'light':
impact = 0.3;
description = 'Light workload - good time for memory creation';
break;
case 'moderate':
impact = 0.0;
description = 'Moderate workload - standard timing';
break;
case 'heavy':
impact = -0.4;
description = 'Heavy workload - consider delaying non-critical memories';
break;
case 'critical':
impact = -0.8;
description = 'Critical workload - delay unless urgent';
break;
}
return {
factor: 'Current Workload',
impact,
description
};
}
analyzeTimeOfDayOptimal(pattern, currentTime) {
const currentHour = `${currentTime.getHours()}:00`;
const isPeakHour = pattern.peakHours.includes(currentHour);
return {
factor: 'Time of Day',
impact: isPeakHour ? 0.4 : -0.2,
description: isPeakHour
? 'Peak productivity hour - optimal for memory creation'
: 'Off-peak hour - consider waiting for better timing'
};
}
analyzeMemoryTypeTiming(contentType, pattern) {
const isPreferredType = pattern.preferredMemoryTypes.includes(contentType);
return {
factor: 'Memory Type Preference',
impact: isPreferredType ? 0.2 : -0.1,
description: isPreferredType
? 'Preferred memory type - good timing match'
: 'Non-preferred type - consider alternative timing'
};
}
calculateOptimalTiming(factors, context) {
const totalImpact = factors.reduce((sum, factor) => sum + factor.impact, 0);
const avgImpact = totalImpact / factors.length;
// Calculate delay based on impact
let delayMinutes = 0;
if (avgImpact < -0.5)
delayMinutes = 120; // 2 hours
else if (avgImpact < -0.2)
delayMinutes = 30; // 30 minutes
else if (avgImpact > 0.3)
delayMinutes = -5; // Immediate (negative delay)
const recommendedTime = new Date(Date.now() + delayMinutes * 60 * 1000);
const confidence = Math.min(0.9, 0.5 + Math.abs(avgImpact));
return {
recommendedTime: recommendedTime.toISOString(),
reasoning: this.generateTimingReasoning(factors, avgImpact),
confidence,
factors
};
}
generateTimingReasoning(factors, avgImpact) {
if (avgImpact > 0.3) {
return 'Excellent timing - multiple factors favor immediate memory creation';
}
else if (avgImpact > 0) {
return 'Good timing - conditions are favorable for memory creation';
}
else if (avgImpact > -0.3) {
return 'Acceptable timing - some minor factors suggest slight delay';
}
else {
return 'Consider delaying - current conditions not optimal for memory creation';
}
}
hasExistingRelationship(memory1, memory2) {
return memory1.relationships.some((rel) => rel.targetMemoryId === memory2.id || rel.sourceMemoryId === memory2.id);
}
async predictRelationshipFormation(sourceMemory, targetMemory, timeHorizon) {
try {
// Calculate content similarity
const contentSimilarity = await this.calculateContentSimilarity(sourceMemory.content, targetMemory.content);
// Calculate temporal proximity
const temporalProximity = this.calculateTemporalProximity(sourceMemory, targetMemory);
// Calculate project/session alignment
const contextAlignment = this.calculateContextAlignment(sourceMemory, targetMemory);
// Determine relationship type and strength
const relationshipType = this.predictRelationshipType(sourceMemory, targetMemory);
const strength = (contentSimilarity + temporalProximity + contextAlignment) / 3;
// Calculate formation probability
const formationProbability = this.calculateFormationProbability(strength, contentSimilarity, temporalProximity, contextAlignment);
if (formationProbability < 0.3) {
return null; // Too low probability
}
// Estimate time to formation
const timeToFormation = this.estimateFormationTime(formationProbability, timeHorizon);
return {
sourceMemoryId: sourceMemory.id,
targetMemoryId: targetMemory.id,
relationshipType,
strength,
formationProbability,
timeToFormation,
reasoning: this.generateRelationshipReasoning(contentSimilarity, temporalProximity, contextAlignment, relationshipType)
};
}
catch (error) {
console.error('Error predicting relationship formation:', error);
return null;
}
}
async calculateContentSimilarity(content1, content2) {
// Simple similarity calculation - could be enhanced with embeddings
const words1 = new Set(content1.toLowerCase().split(/\s+/));
const words2 = new Set(content2.toLowerCase().split(/\s+/));
const intersection = new Set([...words1].filter(word => words2.has(word)));
const union = new Set([...words1, ...words2]);
return intersection.size / union.size;
}
calculateTemporalProximity(memory1, memory2) {
const time1 = new Date(memory1.metadata.timestamp).getTime();
const time2 = new Date(memory2.metadata.timestamp).getTime();
const timeDiff = Math.abs(time1 - time2);
const daysDiff = timeDiff / (1000 * 60 * 60 * 24);
// Closer in time = higher proximity
if (daysDiff < 1)
return 0.9;
if (daysDiff < 7)
return 0.7;
if (daysDiff < 30)
return 0.5;
return 0.2;
}
calculateContextAlignment(memory1, memory2) {
let alignment = 0;
// Same project
if (memory1.metadata.project === memory2.metadata.project) {
alignment += 0.4;
}
// Same session
if (memory1.metadata.session === memory2.metadata.session) {
alignment += 0.3;
}
// Same entity type
if (memory1.metadata.entityType === memory2.metadata.entityType) {
alignment += 0.2;
}
// Shared tags
const tags1 = memory1.metadata.tags || [];
const tags2 = memory2.metadata.tags || [];
const sharedTags = tags1.filter(tag => tags2.includes(tag));
if (sharedTags.length > 0) {
alignment += Math.min(0.3, sharedTags.length * 0.1);
}
return Math.min(alignment, 1.0);
}
predictRelationshipType(sourceMemory, targetMemory) {
// Simple heuristics - could be enhanced with AI
const time1 = new Date(sourceMemory.metadata.timestamp).getTime();
const time2 = new Date(targetMemory.metadata.timestamp).getTime();
if (time2 > time1) {
return 'follows';
}
else if (sourceMemory.content.includes('related') || targetMemory.content.includes('related')) {
return 'related';
}
else if (sourceMemory.content.includes('update') || targetMemory.content.includes('update')) {
return 'updates';
}
else {
return 'similar';
}
}
calculateFormationProbability(strength, contentSimilarity, temporalProximity, contextAlignment) {
// Weighted combination
return (strength * 0.4 +
contentSimilarity * 0.3 +
temporalProximity * 0.2 +
contextAlignment * 0.1);
}
estimateFormationTime(probability, maxDays) {
// Higher probability = sooner formation
if (probability > 0.8)
return 1;
if (probability > 0.6)
return 3;
if (probability > 0.4)
return 7;
return Math.min(maxDays, 14);
}
generateRelationshipReasoning(contentSimilarity, temporalProximity, contextAlignment, relationshipType) {
const reasons = [];
if (contentSimilarity > 0.6) {
reasons.push('high content similarity');
}
if (temporalProximity > 0.6) {
reasons.push('temporal proximity');
}
if (contextAlignment > 0.6) {
reasons.push('shared context');
}
const baseReason = `Predicted ${relationshipType} relationship`;
return reasons.length > 0
? `${baseReason} based on ${reasons.join(', ')}`
: `${baseReason} with moderate confidence`;
}
async analyzeImportanceDrivers(memory) {
const drivers = [];
// Age factor
const daysSinceCreation = (Date.now() - new Date(memory.metadata.timestamp).getTime()) / (1000 * 60 * 60 * 24);
drivers.push({
factor: 'Age',
impact: daysSinceCreation > 30 ? -0.1 : 0.1,
description: daysSinceCreation > 30 ? 'Older memories tend to lose importance' : 'Recent memory maintains relevance',
confidence: 0.8
});
// Relationship count
const relationshipCount = memory.relationships.length;
drivers.push({
factor: 'Connectivity',
impact: relationshipCount * 0.05,
description: `${relationshipCount} relationships ${relationshipCount > 3 ? 'increase' : 'maintain'} importance`,
confidence: 0.7
});
// Entity type
const entityType = memory.metadata.entityType;
const typeImportance = this.getEntityTypeImportance(entityType);
drivers.push({
factor: 'Content Type',
impact: typeImportance,
description: `${entityType || 'general'} content type ${typeImportance > 0 ? 'enhances' : 'reduces'} long-term importance`,
confidence: 0.6
});
return drivers;
}
getEntityTypeImportance(entityType) {
switch (entityType) {
case 'decision': return 0.2;
case 'plan': return 0.15;
case 'task': return 0.1;
case 'meeting': return 0.05;
case 'note': return -0.05;
default: return 0;
}
}
generateTimePoints(days) {
const points = [];
const now = new Date();
// Generate points at intervals
for (let i = 0; i <= days; i += Math.max(1, Math.floor(days / 10))) {
points.push(new Date(now.getTime() + i * 24 * 60 * 60 * 1000));
}
return points;
}
async forecastImportanceTrajectory(memory, drivers, timePoints) {
const currentImportance = memory.metadata.importance || 0.5;
const trajectory = [];
for (let i = 0; i < timePoints.length; i++) {
const dayOffset = i * (90 / timePoints.length); // Spread over time horizon
let importance = currentImportance;
// Apply driver impacts over time
drivers.forEach(driver => {
const timeDecay = Math.exp(-dayOffset / 30); // Exponential decay
importance += driver.impact * timeDecay * driver.confidence;
});
// Add some random variation to simulate uncertainty
const variation = (Math.random() - 0.5) * 0.1;
importance += variation;
// Clamp to valid range
importance = Math.max(0, Math.min(1, importance));
trajectory.push(importance);
}
return trajectory;
}
determineTrendDirection(predictions) {
if (predictions.length < 2)
return 'stable';
const first = predictions[0];
const last = predictions[predictions.length - 1];
if (first === undefined || last === undefined)
return 'stable';
const change = last - first;
// Calculate variance to detect volatility
const mean = predictions.reduce((sum, val) => sum + val, 0) / predictions.length;
const variance = predictions.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / predictions.length;
if (variance > 0.05)
return 'volatile';
if (change > 0.1)
return 'increasing';
if (change < -0.1)
return 'decreasing';
return 'stable';
}
calculateConfidenceIntervals(predictions, drivers) {
const avgConfidence = drivers.reduce((sum, d) => sum + d.confidence, 0) / drivers.length;
const margin = (1 - avgConfidence) * 0.2; // Confidence affects margin size
return predictions.map(prediction => ({
min: Math.max(0, prediction - margin),
max: Math.min(1, prediction + margin)
}));
}
extractTitle(content) {
// Extract first line or first 50 characters as title
const firstLine = content.split('\n')[0];
if (!firstLine)
return 'Untitled Memory';
return firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine;
}
/**
* Record prediction accuracy for continuous learning
*/
async recordPredictionOutcome(predictionId, actualOutcome, accuracy, feedback) {
const history = this.predictionHistory.get(predictionId);
if (history) {
history.actualOutcome = actualOutcome;
history.accuracy = accuracy;
history.feedback = feedback;
}
}
/**
* Get prediction accuracy statistics
*/
getPredictionStats() {
const histories = Array.from(this.predictionHistory.values());
const withAccuracy = histories.filter(h => h.accuracy !== undefined);
if (withAccuracy.length === 0) {
return {
totalPredictions: histories.length,
averageAccuracy: 0,
accuracyByType: {}
};
}
const averageAccuracy = withAccuracy.reduce((sum, h) => sum + (h.accuracy || 0), 0) / withAccuracy.length;
const accuracyByType = {};
const typeGroups = withAccuracy.reduce((groups, h) => {
const type = h.type;
if (!groups[type])
groups[type] = [];
groups[type].push(h.accuracy || 0);
return groups;
}, {});
Object.entries(typeGroups).forEach(([type, accuracies]) => {
accuracyByType[type] = accuracies.reduce((sum, acc) => sum + acc, 0) / accuracies.length;
});
return {
totalPredictions: histories.length,
averageAccuracy,
accuracyByType
};
}
}
//# sourceMappingURL=predictive-engine.js.map