@soulcraft/brainy 4.1.4 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/import/FormatDetector.d.ts +6 -1
- package/dist/import/FormatDetector.js +40 -1
- package/dist/import/ImportCoordinator.d.ts +102 -4
- package/dist/import/ImportCoordinator.js +248 -6
- package/dist/import/InstancePool.d.ts +136 -0
- package/dist/import/InstancePool.js +231 -0
- package/dist/importers/SmartCSVImporter.d.ts +2 -1
- package/dist/importers/SmartCSVImporter.js +11 -22
- package/dist/importers/SmartDOCXImporter.d.ts +125 -0
- package/dist/importers/SmartDOCXImporter.js +227 -0
- package/dist/importers/SmartExcelImporter.d.ts +12 -1
- package/dist/importers/SmartExcelImporter.js +40 -25
- package/dist/importers/SmartJSONImporter.d.ts +1 -0
- package/dist/importers/SmartJSONImporter.js +25 -6
- package/dist/importers/SmartMarkdownImporter.d.ts +2 -1
- package/dist/importers/SmartMarkdownImporter.js +11 -16
- package/dist/importers/SmartPDFImporter.d.ts +2 -1
- package/dist/importers/SmartPDFImporter.js +11 -22
- package/dist/importers/SmartYAMLImporter.d.ts +121 -0
- package/dist/importers/SmartYAMLImporter.js +275 -0
- package/dist/importers/VFSStructureGenerator.js +12 -0
- package/dist/neural/SmartExtractor.d.ts +279 -0
- package/dist/neural/SmartExtractor.js +592 -0
- package/dist/neural/SmartRelationshipExtractor.d.ts +217 -0
- package/dist/neural/SmartRelationshipExtractor.js +396 -0
- package/dist/neural/embeddedTypeEmbeddings.d.ts +1 -1
- package/dist/neural/embeddedTypeEmbeddings.js +2 -2
- package/dist/neural/entityExtractor.d.ts +3 -0
- package/dist/neural/entityExtractor.js +34 -36
- package/dist/neural/presets.d.ts +189 -0
- package/dist/neural/presets.js +365 -0
- package/dist/neural/signals/ContextSignal.d.ts +166 -0
- package/dist/neural/signals/ContextSignal.js +646 -0
- package/dist/neural/signals/EmbeddingSignal.d.ts +175 -0
- package/dist/neural/signals/EmbeddingSignal.js +435 -0
- package/dist/neural/signals/ExactMatchSignal.d.ts +220 -0
- package/dist/neural/signals/ExactMatchSignal.js +542 -0
- package/dist/neural/signals/PatternSignal.d.ts +159 -0
- package/dist/neural/signals/PatternSignal.js +478 -0
- package/dist/neural/signals/VerbContextSignal.d.ts +102 -0
- package/dist/neural/signals/VerbContextSignal.js +390 -0
- package/dist/neural/signals/VerbEmbeddingSignal.d.ts +131 -0
- package/dist/neural/signals/VerbEmbeddingSignal.js +304 -0
- package/dist/neural/signals/VerbExactMatchSignal.d.ts +115 -0
- package/dist/neural/signals/VerbExactMatchSignal.js +335 -0
- package/dist/neural/signals/VerbPatternSignal.d.ts +104 -0
- package/dist/neural/signals/VerbPatternSignal.js +457 -0
- package/dist/types/graphTypes.d.ts +2 -0
- package/dist/utils/metadataIndex.d.ts +22 -0
- package/dist/utils/metadataIndex.js +76 -0
- package/package.json +4 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingSignal - Neural entity type classification using embeddings
|
|
3
|
+
*
|
|
4
|
+
* PRODUCTION-READY: Merges neural + graph + temporal signals into one
|
|
5
|
+
* 3x faster than separate signals (single embedding lookup)
|
|
6
|
+
*
|
|
7
|
+
* Weight: 35% (20% neural + 10% graph + 5% temporal boost)
|
|
8
|
+
* Speed: Fast (~10ms) - single embedding lookup with parallel checking
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Single embedding computation (efficient)
|
|
12
|
+
* - Parallel checking against 3 sources
|
|
13
|
+
* - Confidence boosting when multiple sources agree
|
|
14
|
+
* - LRU cache for hot entities
|
|
15
|
+
* - Uses pre-computed type embeddings (zero initialization cost)
|
|
16
|
+
*/
|
|
17
|
+
import type { Brainy } from '../../brainy.js';
|
|
18
|
+
import type { NounType } from '../../types/graphTypes.js';
|
|
19
|
+
import type { Vector } from '../../coreTypes.js';
|
|
20
|
+
/**
|
|
21
|
+
* Signal result with classification details
|
|
22
|
+
*/
|
|
23
|
+
export interface TypeSignal {
|
|
24
|
+
source: 'embedding-type' | 'embedding-graph' | 'embedding-history' | 'embedding-combined';
|
|
25
|
+
type: NounType;
|
|
26
|
+
confidence: number;
|
|
27
|
+
evidence: string;
|
|
28
|
+
metadata?: {
|
|
29
|
+
typeScore?: number;
|
|
30
|
+
graphScore?: number;
|
|
31
|
+
historyScore?: number;
|
|
32
|
+
agreementBoost?: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Options for embedding signal
|
|
37
|
+
*/
|
|
38
|
+
export interface EmbeddingSignalOptions {
|
|
39
|
+
minConfidence?: number;
|
|
40
|
+
checkGraph?: boolean;
|
|
41
|
+
checkHistory?: boolean;
|
|
42
|
+
timeout?: number;
|
|
43
|
+
cacheSize?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* EmbeddingSignal - Neural type classification with parallel source checking
|
|
47
|
+
*
|
|
48
|
+
* Production features:
|
|
49
|
+
* - Pre-computed type embeddings (instant initialization)
|
|
50
|
+
* - Parallel source checking (type + graph + history)
|
|
51
|
+
* - LRU cache for performance
|
|
52
|
+
* - Confidence boosting when sources agree
|
|
53
|
+
* - Graceful degradation on errors
|
|
54
|
+
*/
|
|
55
|
+
export declare class EmbeddingSignal {
|
|
56
|
+
private brain;
|
|
57
|
+
private options;
|
|
58
|
+
private typeEmbeddings;
|
|
59
|
+
private initialized;
|
|
60
|
+
private cache;
|
|
61
|
+
private cacheOrder;
|
|
62
|
+
private historicalEntities;
|
|
63
|
+
private readonly MAX_HISTORY;
|
|
64
|
+
private stats;
|
|
65
|
+
constructor(brain: Brainy, options?: EmbeddingSignalOptions);
|
|
66
|
+
/**
|
|
67
|
+
* Initialize type embeddings (lazy, happens once)
|
|
68
|
+
*
|
|
69
|
+
* PRODUCTION OPTIMIZATION: Uses pre-computed embeddings
|
|
70
|
+
* Zero runtime cost - embeddings loaded instantly
|
|
71
|
+
*/
|
|
72
|
+
private init;
|
|
73
|
+
/**
|
|
74
|
+
* Classify entity type using embedding-based signals
|
|
75
|
+
*
|
|
76
|
+
* Main entry point - embeds candidate once, checks all sources in parallel
|
|
77
|
+
*
|
|
78
|
+
* @param candidate Entity text to classify
|
|
79
|
+
* @param context Optional context for better matching
|
|
80
|
+
* @returns TypeSignal with classification result
|
|
81
|
+
*/
|
|
82
|
+
classify(candidate: string, context?: {
|
|
83
|
+
definition?: string;
|
|
84
|
+
allTerms?: string[];
|
|
85
|
+
metadata?: any;
|
|
86
|
+
}): Promise<TypeSignal | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Match against NounType embeddings (31 types)
|
|
89
|
+
*
|
|
90
|
+
* Returns best matching type with confidence
|
|
91
|
+
*/
|
|
92
|
+
private matchTypeEmbeddings;
|
|
93
|
+
/**
|
|
94
|
+
* Match against existing graph entities
|
|
95
|
+
*
|
|
96
|
+
* Finds similar entities already in the graph
|
|
97
|
+
* Boosts confidence for entities similar to existing ones
|
|
98
|
+
*/
|
|
99
|
+
private matchGraphEntities;
|
|
100
|
+
/**
|
|
101
|
+
* Match against historical import data
|
|
102
|
+
*
|
|
103
|
+
* Temporal boosting: entities imported recently are more relevant
|
|
104
|
+
* Helps with batch imports of similar entities
|
|
105
|
+
*/
|
|
106
|
+
private matchHistoricalData;
|
|
107
|
+
/**
|
|
108
|
+
* Combine results from all sources with confidence boosting
|
|
109
|
+
*
|
|
110
|
+
* Key insight: When multiple sources agree, boost confidence
|
|
111
|
+
* This is the "ensemble" effect that makes this signal powerful
|
|
112
|
+
*/
|
|
113
|
+
private combineResults;
|
|
114
|
+
/**
|
|
115
|
+
* Add entity to historical data (for temporal boosting)
|
|
116
|
+
*
|
|
117
|
+
* Call this after successful imports to improve future matching
|
|
118
|
+
*/
|
|
119
|
+
addToHistory(text: string, type: NounType, vector: Vector): void;
|
|
120
|
+
/**
|
|
121
|
+
* Clear historical data (useful between import sessions)
|
|
122
|
+
*/
|
|
123
|
+
clearHistory(): void;
|
|
124
|
+
/**
|
|
125
|
+
* Get statistics about signal performance
|
|
126
|
+
*/
|
|
127
|
+
getStats(): {
|
|
128
|
+
cacheSize: number;
|
|
129
|
+
historySize: number;
|
|
130
|
+
cacheHitRate: number;
|
|
131
|
+
typeMatchRate: number;
|
|
132
|
+
graphMatchRate: number;
|
|
133
|
+
historyMatchRate: number;
|
|
134
|
+
calls: number;
|
|
135
|
+
cacheHits: number;
|
|
136
|
+
typeMatches: number;
|
|
137
|
+
graphMatches: number;
|
|
138
|
+
historyMatches: number;
|
|
139
|
+
combinedBoosts: number;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Reset statistics (useful for testing)
|
|
143
|
+
*/
|
|
144
|
+
resetStats(): void;
|
|
145
|
+
/**
|
|
146
|
+
* Clear cache
|
|
147
|
+
*/
|
|
148
|
+
clearCache(): void;
|
|
149
|
+
/**
|
|
150
|
+
* Generate cache key from candidate and context
|
|
151
|
+
*/
|
|
152
|
+
private getCacheKey;
|
|
153
|
+
/**
|
|
154
|
+
* Get from LRU cache
|
|
155
|
+
*/
|
|
156
|
+
private getFromCache;
|
|
157
|
+
/**
|
|
158
|
+
* Add to LRU cache with eviction
|
|
159
|
+
*/
|
|
160
|
+
private addToCache;
|
|
161
|
+
/**
|
|
162
|
+
* Embed text with timeout protection
|
|
163
|
+
*/
|
|
164
|
+
private embedWithTimeout;
|
|
165
|
+
/**
|
|
166
|
+
* Calculate cosine similarity between two vectors
|
|
167
|
+
*/
|
|
168
|
+
private cosineSimilarity;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create a new EmbeddingSignal instance
|
|
172
|
+
*
|
|
173
|
+
* Convenience factory function
|
|
174
|
+
*/
|
|
175
|
+
export declare function createEmbeddingSignal(brain: Brainy, options?: EmbeddingSignalOptions): EmbeddingSignal;
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingSignal - Neural entity type classification using embeddings
|
|
3
|
+
*
|
|
4
|
+
* PRODUCTION-READY: Merges neural + graph + temporal signals into one
|
|
5
|
+
* 3x faster than separate signals (single embedding lookup)
|
|
6
|
+
*
|
|
7
|
+
* Weight: 35% (20% neural + 10% graph + 5% temporal boost)
|
|
8
|
+
* Speed: Fast (~10ms) - single embedding lookup with parallel checking
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Single embedding computation (efficient)
|
|
12
|
+
* - Parallel checking against 3 sources
|
|
13
|
+
* - Confidence boosting when multiple sources agree
|
|
14
|
+
* - LRU cache for hot entities
|
|
15
|
+
* - Uses pre-computed type embeddings (zero initialization cost)
|
|
16
|
+
*/
|
|
17
|
+
import { getNounTypeEmbeddings } from '../embeddedTypeEmbeddings.js';
|
|
18
|
+
/**
|
|
19
|
+
* EmbeddingSignal - Neural type classification with parallel source checking
|
|
20
|
+
*
|
|
21
|
+
* Production features:
|
|
22
|
+
* - Pre-computed type embeddings (instant initialization)
|
|
23
|
+
* - Parallel source checking (type + graph + history)
|
|
24
|
+
* - LRU cache for performance
|
|
25
|
+
* - Confidence boosting when sources agree
|
|
26
|
+
* - Graceful degradation on errors
|
|
27
|
+
*/
|
|
28
|
+
export class EmbeddingSignal {
|
|
29
|
+
constructor(brain, options) {
|
|
30
|
+
// Pre-computed type embeddings (loaded once)
|
|
31
|
+
this.typeEmbeddings = new Map();
|
|
32
|
+
this.initialized = false;
|
|
33
|
+
// LRU cache for hot entities (includes null results to avoid recomputation)
|
|
34
|
+
this.cache = new Map();
|
|
35
|
+
this.cacheOrder = [];
|
|
36
|
+
// Historical data for temporal boosting
|
|
37
|
+
this.historicalEntities = [];
|
|
38
|
+
this.MAX_HISTORY = 1000; // Keep last 1000 imports
|
|
39
|
+
// Statistics
|
|
40
|
+
this.stats = {
|
|
41
|
+
calls: 0,
|
|
42
|
+
cacheHits: 0,
|
|
43
|
+
typeMatches: 0,
|
|
44
|
+
graphMatches: 0,
|
|
45
|
+
historyMatches: 0,
|
|
46
|
+
combinedBoosts: 0
|
|
47
|
+
};
|
|
48
|
+
this.brain = brain;
|
|
49
|
+
this.options = {
|
|
50
|
+
minConfidence: options?.minConfidence ?? 0.60,
|
|
51
|
+
checkGraph: options?.checkGraph ?? true,
|
|
52
|
+
checkHistory: options?.checkHistory ?? true,
|
|
53
|
+
timeout: options?.timeout ?? 100,
|
|
54
|
+
cacheSize: options?.cacheSize ?? 1000
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Initialize type embeddings (lazy, happens once)
|
|
59
|
+
*
|
|
60
|
+
* PRODUCTION OPTIMIZATION: Uses pre-computed embeddings
|
|
61
|
+
* Zero runtime cost - embeddings loaded instantly
|
|
62
|
+
*/
|
|
63
|
+
async init() {
|
|
64
|
+
if (this.initialized)
|
|
65
|
+
return;
|
|
66
|
+
// Load pre-computed type embeddings (instant, no computation)
|
|
67
|
+
const embeddings = getNounTypeEmbeddings();
|
|
68
|
+
for (const [type, vector] of embeddings.entries()) {
|
|
69
|
+
this.typeEmbeddings.set(type, vector);
|
|
70
|
+
}
|
|
71
|
+
this.initialized = true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Classify entity type using embedding-based signals
|
|
75
|
+
*
|
|
76
|
+
* Main entry point - embeds candidate once, checks all sources in parallel
|
|
77
|
+
*
|
|
78
|
+
* @param candidate Entity text to classify
|
|
79
|
+
* @param context Optional context for better matching
|
|
80
|
+
* @returns TypeSignal with classification result
|
|
81
|
+
*/
|
|
82
|
+
async classify(candidate, context) {
|
|
83
|
+
this.stats.calls++;
|
|
84
|
+
// Ensure initialized
|
|
85
|
+
await this.init();
|
|
86
|
+
// Check cache first
|
|
87
|
+
const cacheKey = this.getCacheKey(candidate, context);
|
|
88
|
+
const cached = this.getFromCache(cacheKey);
|
|
89
|
+
if (cached) {
|
|
90
|
+
this.stats.cacheHits++;
|
|
91
|
+
return cached;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
// Embed candidate once (efficiency!)
|
|
95
|
+
const vector = await this.embedWithTimeout(candidate);
|
|
96
|
+
// Check all three sources in parallel
|
|
97
|
+
const [typeMatch, graphMatch, historyMatch] = await Promise.all([
|
|
98
|
+
this.matchTypeEmbeddings(vector, candidate),
|
|
99
|
+
this.options.checkGraph ? this.matchGraphEntities(vector, candidate) : null,
|
|
100
|
+
this.options.checkHistory ? this.matchHistoricalData(vector, candidate) : null
|
|
101
|
+
]);
|
|
102
|
+
// Combine results with confidence boosting
|
|
103
|
+
const result = this.combineResults([typeMatch, graphMatch, historyMatch]);
|
|
104
|
+
// Cache result (including nulls to avoid recomputation)
|
|
105
|
+
if (!result || result.confidence >= this.options.minConfidence) {
|
|
106
|
+
this.addToCache(cacheKey, result);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
// Graceful degradation - return null instead of throwing
|
|
112
|
+
console.warn(`EmbeddingSignal error for "${candidate}":`, error);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Match against NounType embeddings (31 types)
|
|
118
|
+
*
|
|
119
|
+
* Returns best matching type with confidence
|
|
120
|
+
*/
|
|
121
|
+
async matchTypeEmbeddings(vector, candidate) {
|
|
122
|
+
let bestType = null;
|
|
123
|
+
let bestScore = 0;
|
|
124
|
+
// Check similarity against all type embeddings
|
|
125
|
+
for (const [type, typeVector] of this.typeEmbeddings.entries()) {
|
|
126
|
+
const similarity = this.cosineSimilarity(vector, typeVector);
|
|
127
|
+
if (similarity > bestScore) {
|
|
128
|
+
bestScore = similarity;
|
|
129
|
+
bestType = type;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Use lower threshold for type matching (0.40) to catch more matches
|
|
133
|
+
// Production systems can adjust minConfidence on the signal itself
|
|
134
|
+
if (bestType && bestScore >= 0.40) {
|
|
135
|
+
this.stats.typeMatches++;
|
|
136
|
+
return {
|
|
137
|
+
type: bestType,
|
|
138
|
+
confidence: bestScore,
|
|
139
|
+
source: 'embedding-type',
|
|
140
|
+
metadata: { typeScore: bestScore }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Match against existing graph entities
|
|
147
|
+
*
|
|
148
|
+
* Finds similar entities already in the graph
|
|
149
|
+
* Boosts confidence for entities similar to existing ones
|
|
150
|
+
*/
|
|
151
|
+
async matchGraphEntities(vector, candidate) {
|
|
152
|
+
try {
|
|
153
|
+
// Query HNSW index for similar entities
|
|
154
|
+
const similar = await this.brain.similar({
|
|
155
|
+
to: vector,
|
|
156
|
+
limit: 5,
|
|
157
|
+
threshold: 0.70 // Higher threshold for graph matching
|
|
158
|
+
});
|
|
159
|
+
if (similar.length === 0)
|
|
160
|
+
return null;
|
|
161
|
+
// Use the most similar entity's type
|
|
162
|
+
const best = similar[0];
|
|
163
|
+
const entity = await this.brain.get(best.id);
|
|
164
|
+
if (entity && entity.type) {
|
|
165
|
+
this.stats.graphMatches++;
|
|
166
|
+
return {
|
|
167
|
+
type: entity.type,
|
|
168
|
+
confidence: best.score * 0.95, // Slight discount for graph match
|
|
169
|
+
source: 'embedding-graph',
|
|
170
|
+
metadata: {
|
|
171
|
+
graphScore: best.score,
|
|
172
|
+
matchedEntity: best.id,
|
|
173
|
+
totalMatches: similar.length
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
// Graceful degradation if HNSW not available
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Match against historical import data
|
|
186
|
+
*
|
|
187
|
+
* Temporal boosting: entities imported recently are more relevant
|
|
188
|
+
* Helps with batch imports of similar entities
|
|
189
|
+
*/
|
|
190
|
+
async matchHistoricalData(vector, candidate) {
|
|
191
|
+
if (this.historicalEntities.length === 0)
|
|
192
|
+
return null;
|
|
193
|
+
let bestMatch = null;
|
|
194
|
+
let bestScore = 0;
|
|
195
|
+
// Check against recent history
|
|
196
|
+
const recentThreshold = Date.now() - 3600000; // Last hour
|
|
197
|
+
for (const historical of this.historicalEntities) {
|
|
198
|
+
const similarity = this.cosineSimilarity(vector, historical.vector);
|
|
199
|
+
// Boost recent entities
|
|
200
|
+
const recencyBoost = historical.timestamp > recentThreshold ? 1.05 : 1.0;
|
|
201
|
+
const usageBoost = 1 + (Math.log(historical.usageCount + 1) * 0.02);
|
|
202
|
+
const adjustedScore = similarity * recencyBoost * usageBoost;
|
|
203
|
+
if (adjustedScore > bestScore && similarity >= 0.75) {
|
|
204
|
+
bestScore = adjustedScore;
|
|
205
|
+
bestMatch = historical;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (bestMatch) {
|
|
209
|
+
this.stats.historyMatches++;
|
|
210
|
+
return {
|
|
211
|
+
type: bestMatch.type,
|
|
212
|
+
confidence: Math.min(bestScore, 0.95), // Cap at 0.95
|
|
213
|
+
source: 'embedding-history',
|
|
214
|
+
metadata: {
|
|
215
|
+
historyScore: bestScore,
|
|
216
|
+
matchedText: bestMatch.text,
|
|
217
|
+
recency: bestMatch.timestamp,
|
|
218
|
+
usageCount: bestMatch.usageCount
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Combine results from all sources with confidence boosting
|
|
226
|
+
*
|
|
227
|
+
* Key insight: When multiple sources agree, boost confidence
|
|
228
|
+
* This is the "ensemble" effect that makes this signal powerful
|
|
229
|
+
*/
|
|
230
|
+
combineResults(matches) {
|
|
231
|
+
// Filter out null matches
|
|
232
|
+
const validMatches = matches.filter((m) => m !== null);
|
|
233
|
+
if (validMatches.length === 0)
|
|
234
|
+
return null;
|
|
235
|
+
// Count votes by type
|
|
236
|
+
const typeVotes = new Map();
|
|
237
|
+
for (const match of validMatches) {
|
|
238
|
+
const existing = typeVotes.get(match.type) || [];
|
|
239
|
+
typeVotes.set(match.type, [...existing, match]);
|
|
240
|
+
}
|
|
241
|
+
// Find type with most votes and highest combined confidence
|
|
242
|
+
let bestType = null;
|
|
243
|
+
let bestCombinedScore = 0;
|
|
244
|
+
let bestMatches = [];
|
|
245
|
+
for (const [type, matches] of typeVotes.entries()) {
|
|
246
|
+
// Calculate combined score with agreement boosting
|
|
247
|
+
const avgConfidence = matches.reduce((sum, m) => sum + m.confidence, 0) / matches.length;
|
|
248
|
+
const agreementBoost = matches.length > 1 ? 0.05 * (matches.length - 1) : 0;
|
|
249
|
+
const combinedScore = avgConfidence + agreementBoost;
|
|
250
|
+
if (combinedScore > bestCombinedScore) {
|
|
251
|
+
bestCombinedScore = combinedScore;
|
|
252
|
+
bestType = type;
|
|
253
|
+
bestMatches = matches;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (!bestType || bestCombinedScore < this.options.minConfidence) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
// Track combined boosts
|
|
260
|
+
if (bestMatches.length > 1) {
|
|
261
|
+
this.stats.combinedBoosts++;
|
|
262
|
+
}
|
|
263
|
+
// Build evidence string
|
|
264
|
+
const sources = bestMatches.map(m => m.source.replace('embedding-', '')).join('+');
|
|
265
|
+
const evidence = `Matched via ${sources} (${bestMatches.length} source${bestMatches.length > 1 ? 's' : ''} agree)`;
|
|
266
|
+
// Combine metadata
|
|
267
|
+
const metadata = {
|
|
268
|
+
agreementBoost: bestMatches.length > 1 ? 0.05 * (bestMatches.length - 1) : 0
|
|
269
|
+
};
|
|
270
|
+
for (const match of bestMatches) {
|
|
271
|
+
if (match.source === 'embedding-type')
|
|
272
|
+
metadata.typeScore = match.metadata?.typeScore;
|
|
273
|
+
if (match.source === 'embedding-graph')
|
|
274
|
+
metadata.graphScore = match.metadata?.graphScore;
|
|
275
|
+
if (match.source === 'embedding-history')
|
|
276
|
+
metadata.historyScore = match.metadata?.historyScore;
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
source: bestMatches.length > 1 ? 'embedding-combined' : bestMatches[0].source,
|
|
280
|
+
type: bestType,
|
|
281
|
+
confidence: Math.min(bestCombinedScore, 1.0), // Cap at 1.0
|
|
282
|
+
evidence,
|
|
283
|
+
metadata
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Add entity to historical data (for temporal boosting)
|
|
288
|
+
*
|
|
289
|
+
* Call this after successful imports to improve future matching
|
|
290
|
+
*/
|
|
291
|
+
addToHistory(text, type, vector) {
|
|
292
|
+
// Check if already exists
|
|
293
|
+
const existing = this.historicalEntities.find(h => h.text.toLowerCase() === text.toLowerCase());
|
|
294
|
+
if (existing) {
|
|
295
|
+
existing.usageCount++;
|
|
296
|
+
existing.timestamp = Date.now();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Add new historical entity
|
|
300
|
+
this.historicalEntities.push({
|
|
301
|
+
text,
|
|
302
|
+
type,
|
|
303
|
+
vector,
|
|
304
|
+
timestamp: Date.now(),
|
|
305
|
+
usageCount: 1
|
|
306
|
+
});
|
|
307
|
+
// Trim to max size (keep most recent and most used)
|
|
308
|
+
if (this.historicalEntities.length > this.MAX_HISTORY) {
|
|
309
|
+
// Sort by recency and usage
|
|
310
|
+
this.historicalEntities.sort((a, b) => {
|
|
311
|
+
const aScore = a.timestamp + (a.usageCount * 60000); // 1 minute per usage
|
|
312
|
+
const bScore = b.timestamp + (b.usageCount * 60000);
|
|
313
|
+
return bScore - aScore;
|
|
314
|
+
});
|
|
315
|
+
// Keep top MAX_HISTORY
|
|
316
|
+
this.historicalEntities = this.historicalEntities.slice(0, this.MAX_HISTORY);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Clear historical data (useful between import sessions)
|
|
321
|
+
*/
|
|
322
|
+
clearHistory() {
|
|
323
|
+
this.historicalEntities = [];
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get statistics about signal performance
|
|
327
|
+
*/
|
|
328
|
+
getStats() {
|
|
329
|
+
return {
|
|
330
|
+
...this.stats,
|
|
331
|
+
cacheSize: this.cache.size,
|
|
332
|
+
historySize: this.historicalEntities.length,
|
|
333
|
+
cacheHitRate: this.stats.calls > 0 ? this.stats.cacheHits / this.stats.calls : 0,
|
|
334
|
+
typeMatchRate: this.stats.calls > 0 ? this.stats.typeMatches / this.stats.calls : 0,
|
|
335
|
+
graphMatchRate: this.stats.calls > 0 ? this.stats.graphMatches / this.stats.calls : 0,
|
|
336
|
+
historyMatchRate: this.stats.calls > 0 ? this.stats.historyMatches / this.stats.calls : 0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Reset statistics (useful for testing)
|
|
341
|
+
*/
|
|
342
|
+
resetStats() {
|
|
343
|
+
this.stats = {
|
|
344
|
+
calls: 0,
|
|
345
|
+
cacheHits: 0,
|
|
346
|
+
typeMatches: 0,
|
|
347
|
+
graphMatches: 0,
|
|
348
|
+
historyMatches: 0,
|
|
349
|
+
combinedBoosts: 0
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Clear cache
|
|
354
|
+
*/
|
|
355
|
+
clearCache() {
|
|
356
|
+
this.cache.clear();
|
|
357
|
+
this.cacheOrder = [];
|
|
358
|
+
}
|
|
359
|
+
// ========== Private Helper Methods ==========
|
|
360
|
+
/**
|
|
361
|
+
* Generate cache key from candidate and context
|
|
362
|
+
*/
|
|
363
|
+
getCacheKey(candidate, context) {
|
|
364
|
+
const normalized = candidate.toLowerCase().trim();
|
|
365
|
+
if (!context?.definition)
|
|
366
|
+
return normalized;
|
|
367
|
+
return `${normalized}:${context.definition.substring(0, 50)}`;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get from LRU cache
|
|
371
|
+
*/
|
|
372
|
+
getFromCache(key) {
|
|
373
|
+
// Check if key exists in cache (including null values)
|
|
374
|
+
if (!this.cache.has(key))
|
|
375
|
+
return null;
|
|
376
|
+
const cached = this.cache.get(key);
|
|
377
|
+
// Move to end (most recently used)
|
|
378
|
+
this.cacheOrder = this.cacheOrder.filter(k => k !== key);
|
|
379
|
+
this.cacheOrder.push(key);
|
|
380
|
+
return cached ?? null;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Add to LRU cache with eviction
|
|
384
|
+
*/
|
|
385
|
+
addToCache(key, value) {
|
|
386
|
+
// Add to cache
|
|
387
|
+
this.cache.set(key, value);
|
|
388
|
+
this.cacheOrder.push(key);
|
|
389
|
+
// Evict oldest if over limit
|
|
390
|
+
if (this.cache.size > this.options.cacheSize) {
|
|
391
|
+
const oldest = this.cacheOrder.shift();
|
|
392
|
+
if (oldest) {
|
|
393
|
+
this.cache.delete(oldest);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Embed text with timeout protection
|
|
399
|
+
*/
|
|
400
|
+
async embedWithTimeout(text) {
|
|
401
|
+
return Promise.race([
|
|
402
|
+
this.brain.embed(text),
|
|
403
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Embedding timeout')), this.options.timeout))
|
|
404
|
+
]);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Calculate cosine similarity between two vectors
|
|
408
|
+
*/
|
|
409
|
+
cosineSimilarity(a, b) {
|
|
410
|
+
if (a.length !== b.length) {
|
|
411
|
+
throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
|
|
412
|
+
}
|
|
413
|
+
let dotProduct = 0;
|
|
414
|
+
let normA = 0;
|
|
415
|
+
let normB = 0;
|
|
416
|
+
for (let i = 0; i < a.length; i++) {
|
|
417
|
+
dotProduct += a[i] * b[i];
|
|
418
|
+
normA += a[i] * a[i];
|
|
419
|
+
normB += b[i] * b[i];
|
|
420
|
+
}
|
|
421
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
422
|
+
if (denominator === 0)
|
|
423
|
+
return 0;
|
|
424
|
+
return dotProduct / denominator;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Create a new EmbeddingSignal instance
|
|
429
|
+
*
|
|
430
|
+
* Convenience factory function
|
|
431
|
+
*/
|
|
432
|
+
export function createEmbeddingSignal(brain, options) {
|
|
433
|
+
return new EmbeddingSignal(brain, options);
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=EmbeddingSignal.js.map
|