@claude-vector/core
Version:
Core vector search engine for code intelligence
932 lines (782 loc) • 28.5 kB
JavaScript
/**
* Advanced Relevance Scorer - 高度な関連性スコアリングシステム
*
* 機能:
* - 多次元評価(セマンティック、時間的、複雑度、使用頻度、フェーズ適合)
* - 開発フェーズ別重み付け
* - 個人使用パターン学習
* - 動的スコア調整
*/
import { EventEmitter } from 'events';
export class AdvancedRelevanceScorer extends EventEmitter {
constructor(options = {}) {
super();
this.options = {
learningEnabled: options.learningEnabled ?? true,
useTemporalDecay: options.useTemporalDecay ?? true,
usagePatternWeight: options.usagePatternWeight || 0.2,
complexityWeight: options.complexityWeight || 0.15,
semanticWeight: options.semanticWeight || 0.4,
temporalWeight: options.temporalWeight || 0.1,
contextWeight: options.contextWeight || 0.15,
adaptiveWeighting: options.adaptiveWeighting ?? true,
...options
};
// スコアリング次元の定義
this.scoringDimensions = {
semantic: {
weight: this.options.semanticWeight,
description: 'Semantic similarity based on content and meaning',
calculator: this.calculateSemanticScore.bind(this)
},
temporal: {
weight: this.options.temporalWeight,
description: 'Time-based relevance considering recency and update frequency',
calculator: this.calculateTemporalScore.bind(this)
},
complexity: {
weight: this.options.complexityWeight,
description: 'Complexity matching between query context and code',
calculator: this.calculateComplexityScore.bind(this)
},
usage: {
weight: this.options.usagePatternWeight,
description: 'Usage frequency and importance in codebase',
calculator: this.calculateUsageScore.bind(this)
},
context: {
weight: this.options.contextWeight,
description: 'Contextual relevance for development phase and task',
calculator: this.calculateContextScore.bind(this)
}
};
// 開発フェーズ別重み調整
this.phaseWeightAdjustments = {
exploration: {
semantic: 0.3,
temporal: 0.1,
complexity: 0.1,
usage: 0.2,
context: 0.3
},
implementation: {
semantic: 0.4,
temporal: 0.15,
complexity: 0.2,
usage: 0.15,
context: 0.1
},
debugging: {
semantic: 0.5,
temporal: 0.1,
complexity: 0.3,
usage: 0.05,
context: 0.05
},
refactoring: {
semantic: 0.3,
temporal: 0.2,
complexity: 0.3,
usage: 0.1,
context: 0.1
},
testing: {
semantic: 0.4,
temporal: 0.1,
complexity: 0.2,
usage: 0.2,
context: 0.1
},
documentation: {
semantic: 0.3,
temporal: 0.1,
complexity: 0.1,
usage: 0.2,
context: 0.3
}
};
// 学習データ
this.userFeedback = new Map(); // チャンクID -> フィードバックデータ
this.queryPatterns = new Map(); // クエリパターン -> パフォーマンスデータ
this.contextualPreferences = new Map(); // コンテキスト -> 優先パターン
this.performanceHistory = [];
// キャッシュ
this.scoreCache = new Map();
this.complexityCache = new Map();
this.temporalCache = new Map();
}
/**
* 関連性スコアの計算
*/
async calculateRelevanceScore(query, chunk, searchContext = {}) {
const cacheKey = this.generateCacheKey(query, chunk, searchContext);
// キャッシュチェック
if (this.scoreCache.has(cacheKey)) {
return this.scoreCache.get(cacheKey);
}
const startTime = Date.now();
// 各次元のスコア計算
const dimensionScores = {};
const weights = this.getAdjustedWeights(searchContext);
for (const [dimension, config] of Object.entries(this.scoringDimensions)) {
try {
dimensionScores[dimension] = await config.calculator(query, chunk, searchContext);
} catch (error) {
console.warn(`Error calculating ${dimension} score:`, error.message);
dimensionScores[dimension] = 0;
}
}
// 重み付き総合スコア計算
let totalScore = 0;
let totalWeight = 0;
for (const [dimension, score] of Object.entries(dimensionScores)) {
const weight = weights[dimension] || this.scoringDimensions[dimension].weight;
totalScore += score * weight;
totalWeight += weight;
}
const finalScore = totalWeight > 0 ? totalScore / totalWeight : 0;
// 学習ベースの調整
const adjustedScore = await this.applyLearningAdjustments(
finalScore,
dimensionScores,
query,
chunk,
searchContext
);
// 結果の構築
const result = {
finalScore: Math.max(0, Math.min(1, adjustedScore)),
dimensionScores,
weights,
metadata: {
calculationTime: Date.now() - startTime,
cacheUsed: false,
learningApplied: this.options.learningEnabled,
context: searchContext.phase || 'general'
}
};
// キャッシュに保存
this.scoreCache.set(cacheKey, result);
// パフォーマンス履歴の記録
this.recordPerformance(query, chunk, result, searchContext);
return result;
}
/**
* セマンティックスコアの計算
*/
async calculateSemanticScore(query, chunk, searchContext) {
// 基本的なコサイン類似度
const baseSimilarity = chunk.score || 0;
// メタデータベースの意味的関連性
const metadataRelevance = this.calculateMetadataRelevance(query, chunk.metadata);
// キーワードマッチング強化
const keywordBoost = this.calculateKeywordBoost(query, chunk);
// セマンティックタイプマッチング
const typeRelevance = this.calculateTypeRelevance(query, chunk.metadata);
// パターンマッチング
const patternRelevance = this.calculatePatternRelevance(query, chunk.metadata);
// 組み合わせスコア
let semanticScore = baseSimilarity * 0.5 +
metadataRelevance * 0.2 +
keywordBoost * 0.15 +
typeRelevance * 0.1 +
patternRelevance * 0.05;
// クエリ長による調整
const queryLength = query.split(' ').length;
if (queryLength > 5) {
semanticScore *= 1.1; // 長いクエリには重みを上げる
}
return Math.max(0, Math.min(1, semanticScore));
}
/**
* メタデータ関連性の計算
*/
calculateMetadataRelevance(query, metadata) {
let relevance = 0;
const queryLower = query.toLowerCase();
// 名前との関連性
if (metadata.semantic?.name && queryLower.includes(metadata.semantic.name.toLowerCase())) {
relevance += 0.3;
}
// セマンティックタイプとの関連性
if (metadata.semantic?.type && queryLower.includes(metadata.semantic.type)) {
relevance += 0.2;
}
// 目的との関連性
if (metadata.semantic?.purpose && queryLower.includes(metadata.semantic.purpose)) {
relevance += 0.25;
}
// 依存関係との関連性
if (metadata.relationships?.dependencies) {
const matchingDeps = metadata.relationships.dependencies.filter(dep =>
queryLower.includes(dep.toLowerCase())
);
relevance += matchingDeps.length * 0.05;
}
// タグとの関連性
if (metadata.searchOptimization?.keywords) {
const matchingKeywords = metadata.searchOptimization.keywords.filter(keyword =>
queryLower.includes(keyword.toLowerCase())
);
relevance += matchingKeywords.length * 0.03;
}
return Math.min(relevance, 1);
}
/**
* キーワードブーストの計算
*/
calculateKeywordBoost(query, chunk) {
const content = chunk.content.toLowerCase();
const queryWords = query.toLowerCase().split(/\s+/).filter(word => word.length > 2);
let boost = 0;
const totalWords = queryWords.length;
for (const word of queryWords) {
// 完全一致
if (content.includes(word)) {
boost += 0.1;
}
// 部分一致(語幹)
const stemmed = this.simpleStem(word);
if (stemmed !== word && content.includes(stemmed)) {
boost += 0.05;
}
}
return totalWords > 0 ? boost / totalWords : 0;
}
/**
* 簡単な語幹抽出
*/
simpleStem(word) {
// 英語の基本的な語尾変化を処理
return word
.replace(/ing$/, '')
.replace(/ed$/, '')
.replace(/s$/, '')
.replace(/ly$/, '');
}
/**
* タイプ関連性の計算
*/
calculateTypeRelevance(query, metadata) {
const queryLower = query.toLowerCase();
const semanticType = metadata.semantic?.type || metadata.semanticType;
// クエリにタイプ関連のキーワードが含まれている場合
const typeKeywords = {
'function': ['function', 'func', 'method', 'procedure'],
'class': ['class', 'object', 'constructor', 'instance'],
'variable': ['variable', 'var', 'const', 'let', 'value'],
'export': ['export', 'module', 'public', 'api'],
'import': ['import', 'require', 'include', 'dependency']
};
const keywords = typeKeywords[semanticType] || [];
const matchingKeywords = keywords.filter(keyword => queryLower.includes(keyword));
return matchingKeywords.length > 0 ? 0.3 : 0;
}
/**
* パターン関連性の計算
*/
calculatePatternRelevance(query, metadata) {
if (!metadata.semantic?.patterns) return 0;
const queryLower = query.toLowerCase();
let relevance = 0;
const patternKeywords = {
'async_pattern': ['async', 'await', 'promise', 'asynchronous'],
'callback_pattern': ['callback', 'cb', 'handler', 'listener'],
'constructor_pattern': ['constructor', 'new', 'instance', 'init'],
'module_export_pattern': ['export', 'module', 'public']
};
for (const pattern of metadata.semantic.patterns) {
const keywords = patternKeywords[pattern] || [];
const matches = keywords.filter(keyword => queryLower.includes(keyword));
if (matches.length > 0) {
relevance += 0.1;
}
}
return Math.min(relevance, 0.5);
}
/**
* 時間的関連性スコアの計算
*/
async calculateTemporalScore(query, chunk, searchContext) {
const metadata = chunk.metadata;
let temporalScore = 0.5; // ベーススコア
// 最終更新時刻による調整
if (metadata.development?.lastModified) {
const lastModified = new Date(metadata.development.lastModified);
const now = new Date();
const daysDiff = (now - lastModified) / (1000 * 60 * 60 * 24);
// 時間的減衰関数(exponential decay)
if (this.options.useTemporalDecay) {
const decayFactor = Math.exp(-daysDiff / 30); // 30日で半減
temporalScore *= (0.5 + 0.5 * decayFactor);
}
}
// 変更頻度による調整
if (metadata.development?.changeFrequency) {
// 頻繁に変更されるコードは関連性が高い可能性
temporalScore += metadata.development.changeFrequency * 0.2;
}
// バグ履歴による調整
if (metadata.development?.bugHistory) {
const recentBugs = metadata.development.bugHistory.filter(bug => {
const bugDate = new Date(bug.date);
const daysDiff = (new Date() - bugDate) / (1000 * 60 * 60 * 24);
return daysDiff < 30 && !bug.resolved;
});
if (recentBugs.length > 0 && searchContext.phase === 'debugging') {
temporalScore += 0.3; // デバッグフェーズでは最近のバグは重要
}
}
return Math.max(0, Math.min(1, temporalScore));
}
/**
* 複雑度スコアの計算
*/
async calculateComplexityScore(query, chunk, searchContext) {
const metadata = chunk.metadata;
let complexityScore = 0.5;
if (!metadata.complexity) return complexityScore;
const cognitive = metadata.complexity.cognitive || 0;
const cyclomatic = metadata.complexity.cyclomatic || 0;
// 開発フェーズに基づく複雑度適合性
switch (searchContext.phase) {
case 'exploration':
// 探索時は中程度の複雑度が好ましい
if (cognitive >= 3 && cognitive <= 7) {
complexityScore += 0.3;
}
break;
case 'implementation':
// 実装時は具体的で適度な複雑度
if (cognitive >= 2 && cognitive <= 10) {
complexityScore += 0.2;
}
break;
case 'debugging':
// デバッグ時は高複雑度のコードが重要
if (cognitive > 5) {
complexityScore += 0.4;
}
break;
case 'refactoring':
// リファクタリング時は高複雑度が対象
if (cognitive > 8 || cyclomatic > 10) {
complexityScore += 0.5;
}
break;
default:
// 一般的には中程度の複雑度
if (cognitive >= 3 && cognitive <= 8) {
complexityScore += 0.2;
}
}
// 保守性指数による調整
if (metadata.complexity.maintainabilityIndex) {
const maintainability = metadata.complexity.maintainabilityIndex / 100;
if (searchContext.phase === 'refactoring') {
// リファクタリング時は低保守性が重要
complexityScore += (1 - maintainability) * 0.3;
} else {
// その他は高保守性が好ましい
complexityScore += maintainability * 0.2;
}
}
return Math.max(0, Math.min(1, complexityScore));
}
/**
* 使用パターンスコアの計算
*/
async calculateUsageScore(query, chunk, searchContext) {
const metadata = chunk.metadata;
let usageScore = 0.5;
// AI重要度による調整
if (metadata.aiContext?.importance) {
usageScore += metadata.aiContext.importance * 0.3;
}
// 検索可能性による調整
if (metadata.aiContext?.searchability) {
usageScore += metadata.aiContext.searchability * 0.2;
}
// 依存関係の数による調整(重要度の指標)
if (metadata.relationships?.dependencies) {
const depCount = metadata.relationships.dependencies.length;
usageScore += Math.min(depCount / 20, 0.2);
}
// エクスポートされているかどうか
if (metadata.semantic?.type === 'export' || metadata.relationships?.exports?.length > 0) {
usageScore += 0.15;
}
// テストカバレッジによる調整
if (metadata.quality?.testCoverage) {
usageScore += metadata.quality.testCoverage * 0.1;
}
return Math.max(0, Math.min(1, usageScore));
}
/**
* コンテキストスコアの計算
*/
async calculateContextScore(query, chunk, searchContext) {
const metadata = chunk.metadata;
let contextScore = 0.5;
// コンテキスト関連性(メタデータから)
if (metadata.aiContext?.contextRelevance) {
contextScore += metadata.aiContext.contextRelevance * 0.3;
}
// 開発フェーズ適合性
if (metadata.development?.phase === searchContext.phase) {
contextScore += 0.2;
}
// ファイルタイプとクエリの適合性
const fileType = this.inferFileType(metadata);
const queryContext = this.inferQueryContext(query);
if (this.isContextualMatch(fileType, queryContext)) {
contextScore += 0.15;
}
// プロジェクト内での位置による調整
if (metadata.file) {
const fileDepth = metadata.file.split('/').length;
// 深い階層のファイルは特定の目的を持つ可能性が高い
if (fileDepth > 3 && searchContext.phase === 'implementation') {
contextScore += 0.1;
}
// ルート近くのファイルは設定や概要の可能性
if (fileDepth <= 2 && searchContext.phase === 'exploration') {
contextScore += 0.1;
}
}
return Math.max(0, Math.min(1, contextScore));
}
/**
* ファイルタイプの推論
*/
inferFileType(metadata) {
const fileName = metadata.file?.toLowerCase() || '';
if (fileName.includes('test') || fileName.includes('spec')) return 'test';
if (fileName.includes('config') || fileName.includes('setting')) return 'config';
if (fileName.includes('component')) return 'component';
if (fileName.includes('service') || fileName.includes('api')) return 'service';
if (fileName.includes('util') || fileName.includes('helper')) return 'utility';
if (fileName.includes('doc') || fileName.endsWith('.md')) return 'documentation';
return 'code';
}
/**
* クエリコンテキストの推論
*/
inferQueryContext(query) {
const queryLower = query.toLowerCase();
if (queryLower.includes('test') || queryLower.includes('spec')) return 'test';
if (queryLower.includes('error') || queryLower.includes('bug')) return 'debug';
if (queryLower.includes('config') || queryLower.includes('setting')) return 'config';
if (queryLower.includes('component')) return 'component';
if (queryLower.includes('api') || queryLower.includes('service')) return 'service';
if (queryLower.includes('util') || queryLower.includes('helper')) return 'utility';
return 'general';
}
/**
* コンテキスト適合性の判定
*/
isContextualMatch(fileType, queryContext) {
const contextMap = {
'test': ['test', 'debug'],
'config': ['config'],
'component': ['component', 'general'],
'service': ['service', 'general'],
'utility': ['utility', 'general'],
'documentation': ['general']
};
return contextMap[fileType]?.includes(queryContext) || false;
}
/**
* 調整された重みの取得
*/
getAdjustedWeights(searchContext) {
const phase = searchContext.phase || 'implementation';
const phaseWeights = this.phaseWeightAdjustments[phase];
if (!phaseWeights || !this.options.adaptiveWeighting) {
return Object.fromEntries(
Object.entries(this.scoringDimensions).map(([dim, config]) => [dim, config.weight])
);
}
return phaseWeights;
}
/**
* 学習ベースの調整適用
*/
async applyLearningAdjustments(baseScore, dimensionScores, query, chunk, searchContext) {
if (!this.options.learningEnabled) {
return baseScore;
}
let adjustedScore = baseScore;
// ユーザーフィードバックによる調整
const chunkId = chunk.metadata.id || this.generateChunkId(chunk);
const feedback = this.userFeedback.get(chunkId);
if (feedback) {
const feedbackAdjustment = this.calculateFeedbackAdjustment(feedback, searchContext);
adjustedScore += feedbackAdjustment;
}
// クエリパターンによる調整
const patternAdjustment = this.calculatePatternAdjustment(query, dimensionScores, searchContext);
adjustedScore += patternAdjustment;
// コンテキスト学習による調整
const contextAdjustment = this.calculateContextLearningAdjustment(searchContext, chunk);
adjustedScore += contextAdjustment;
return adjustedScore;
}
/**
* フィードバック調整の計算
*/
calculateFeedbackAdjustment(feedback, searchContext) {
let adjustment = 0;
// 正のフィードバック
if (feedback.positive > 0) {
adjustment += Math.min(feedback.positive * 0.1, 0.2);
}
// 負のフィードバック
if (feedback.negative > 0) {
adjustment -= Math.min(feedback.negative * 0.1, 0.2);
}
// コンテキスト特有のフィードバック
const contextFeedback = feedback.contexts?.[searchContext.phase];
if (contextFeedback) {
adjustment += contextFeedback.score * 0.05;
}
return adjustment;
}
/**
* パターン調整の計算
*/
calculatePatternAdjustment(query, dimensionScores, searchContext) {
const queryPattern = this.extractQueryPattern(query);
const patternData = this.queryPatterns.get(queryPattern);
if (!patternData) return 0;
// パターンの成功率に基づく調整
let adjustment = 0;
if (patternData.successRate > 0.7) {
adjustment += 0.1;
} else if (patternData.successRate < 0.3) {
adjustment -= 0.1;
}
// 次元スコアのパターンマッチング
const scoreSimilarity = this.calculateScoreSimilarity(dimensionScores, patternData.averageScores);
adjustment += scoreSimilarity * 0.05;
return adjustment;
}
/**
* コンテキスト学習調整の計算
*/
calculateContextLearningAdjustment(searchContext, chunk) {
const contextKey = `${searchContext.phase}_${chunk.metadata.semantic?.type}`;
const preferences = this.contextualPreferences.get(contextKey);
if (!preferences) return 0;
// 学習された優先度に基づく調整
return preferences.averageScore * 0.1 - 0.05; // -0.05から+0.05の範囲
}
/**
* ユーザーフィードバックの記録
*/
recordUserFeedback(chunkId, feedbackType, searchContext) {
if (!this.userFeedback.has(chunkId)) {
this.userFeedback.set(chunkId, {
positive: 0,
negative: 0,
neutral: 0,
contexts: {}
});
}
const feedback = this.userFeedback.get(chunkId);
// 全体フィードバック
feedback[feedbackType]++;
// コンテキスト特有のフィードバック
const contextKey = searchContext.phase || 'general';
if (!feedback.contexts[contextKey]) {
feedback.contexts[contextKey] = { count: 0, score: 0 };
}
const contextFeedback = feedback.contexts[contextKey];
contextFeedback.count++;
// スコア調整
switch (feedbackType) {
case 'positive':
contextFeedback.score = Math.min(contextFeedback.score + 0.1, 1);
break;
case 'negative':
contextFeedback.score = Math.max(contextFeedback.score - 0.1, -1);
break;
case 'neutral':
// 中性フィードバックは徐々に0に近づける
contextFeedback.score *= 0.9;
break;
}
this.emit('feedback-recorded', { chunkId, feedbackType, searchContext });
}
/**
* パフォーマンス記録
*/
recordPerformance(query, chunk, scoringResult, searchContext) {
this.performanceHistory.push({
query,
chunkId: chunk.metadata.id || this.generateChunkId(chunk),
finalScore: scoringResult.finalScore,
dimensionScores: scoringResult.dimensionScores,
searchContext,
timestamp: Date.now()
});
// 履歴サイズの制限
if (this.performanceHistory.length > 1000) {
this.performanceHistory.shift();
}
// クエリパターンの更新
this.updateQueryPattern(query, scoringResult, searchContext);
}
/**
* クエリパターンの更新
*/
updateQueryPattern(query, scoringResult, searchContext) {
const pattern = this.extractQueryPattern(query);
if (!this.queryPatterns.has(pattern)) {
this.queryPatterns.set(pattern, {
count: 0,
successRate: 0.5,
averageScores: {},
contexts: new Set()
});
}
const patternData = this.queryPatterns.get(pattern);
patternData.count++;
patternData.contexts.add(searchContext.phase || 'general');
// 平均スコアの更新
for (const [dimension, score] of Object.entries(scoringResult.dimensionScores)) {
if (!patternData.averageScores[dimension]) {
patternData.averageScores[dimension] = score;
} else {
// 移動平均
patternData.averageScores[dimension] =
(patternData.averageScores[dimension] * 0.9 + score * 0.1);
}
}
}
/**
* クエリパターンの抽出
*/
extractQueryPattern(query) {
// 簡略化されたパターン抽出
const words = query.toLowerCase().split(/\s+/).filter(word => word.length > 2);
// キーワードのカテゴリ化
const categories = {
'error': ['error', 'bug', 'issue', 'problem', 'exception'],
'function': ['function', 'method', 'func', 'procedure'],
'class': ['class', 'object', 'constructor'],
'implement': ['implement', 'create', 'build', 'develop'],
'test': ['test', 'spec', 'unit', 'integration']
};
const detectedCategories = [];
for (const [category, keywords] of Object.entries(categories)) {
if (keywords.some(keyword => words.includes(keyword))) {
detectedCategories.push(category);
}
}
return detectedCategories.length > 0 ? detectedCategories.join('_') : 'general';
}
/**
* スコア類似度の計算
*/
calculateScoreSimilarity(scores1, scores2) {
const dimensions = Object.keys(scores1);
let similarity = 0;
let count = 0;
for (const dim of dimensions) {
if (scores2[dim] !== undefined) {
const diff = Math.abs(scores1[dim] - scores2[dim]);
similarity += 1 - diff;
count++;
}
}
return count > 0 ? similarity / count : 0;
}
/**
* キャッシュキーの生成
*/
generateCacheKey(query, chunk, searchContext) {
const chunkId = chunk.metadata.id || this.generateChunkId(chunk);
const contextKey = JSON.stringify({
phase: searchContext.phase,
options: searchContext.options
});
return `${query}_${chunkId}_${this.simpleHash(contextKey)}`;
}
/**
* チャンクIDの生成
*/
generateChunkId(chunk) {
const metadata = chunk.metadata;
const identifier = `${metadata.file}_${metadata.startLine}_${metadata.endLine}`;
return this.simpleHash(identifier);
}
/**
* 簡単なハッシュ関数
*/
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
/**
* キャッシュのクリア
*/
clearCache() {
this.scoreCache.clear();
this.complexityCache.clear();
this.temporalCache.clear();
this.emit('cache-cleared');
}
/**
* 学習データのエクスポート
*/
exportLearningData() {
return {
userFeedback: Object.fromEntries(this.userFeedback),
queryPatterns: Object.fromEntries(this.queryPatterns),
contextualPreferences: Object.fromEntries(this.contextualPreferences),
performanceHistory: this.performanceHistory.slice(-100), // 最新100件
exportedAt: Date.now()
};
}
/**
* 学習データのインポート
*/
importLearningData(data) {
if (data.userFeedback) {
this.userFeedback = new Map(Object.entries(data.userFeedback));
}
if (data.queryPatterns) {
this.queryPatterns = new Map(Object.entries(data.queryPatterns));
}
if (data.contextualPreferences) {
this.contextualPreferences = new Map(Object.entries(data.contextualPreferences));
}
if (data.performanceHistory) {
this.performanceHistory = data.performanceHistory;
}
this.emit('learning-data-imported', data);
}
/**
* 統計情報の取得
*/
getStats() {
const recentPerformance = this.performanceHistory.slice(-50);
const avgScore = recentPerformance.reduce((sum, p) => sum + p.finalScore, 0) / recentPerformance.length;
return {
totalFeedback: this.userFeedback.size,
queryPatterns: this.queryPatterns.size,
performanceHistory: this.performanceHistory.length,
averageScore: avgScore || 0,
cacheSize: this.scoreCache.size,
learningEnabled: this.options.learningEnabled,
lastUpdate: this.performanceHistory.length > 0 ?
this.performanceHistory[this.performanceHistory.length - 1].timestamp : null
};
}
}