@soulcraft/brainy 4.1.3 → 4.2.0
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 +100 -7
- package/dist/brainy.d.ts +74 -16
- package/dist/brainy.js +74 -16
- package/dist/import/FormatDetector.d.ts +6 -1
- package/dist/import/FormatDetector.js +40 -1
- package/dist/import/ImportCoordinator.d.ts +155 -5
- package/dist/import/ImportCoordinator.js +346 -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/package.json +4 -1
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextSignal - Relationship-based entity type classification
|
|
3
|
+
*
|
|
4
|
+
* PRODUCTION-READY: Infers types from relationship patterns in context
|
|
5
|
+
*
|
|
6
|
+
* Weight: 5% (lowest, but critical for ambiguous cases)
|
|
7
|
+
* Speed: Very fast (~1ms) - pure pattern matching on context
|
|
8
|
+
*
|
|
9
|
+
* Key insight: Context reveals type even when entity itself is ambiguous
|
|
10
|
+
* Examples:
|
|
11
|
+
* - "CEO of X" → X is Organization
|
|
12
|
+
* - "lives in Y" → Y is Location
|
|
13
|
+
* - "uses Z" → Z is Technology
|
|
14
|
+
* - "attended W" → W is Event
|
|
15
|
+
*
|
|
16
|
+
* This signal fills the gap when:
|
|
17
|
+
* - Entity is too ambiguous for other signals
|
|
18
|
+
* - Multiple signals conflict
|
|
19
|
+
* - Need relationship-aware classification
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* ContextSignal - Infer entity types from relationship context
|
|
23
|
+
*
|
|
24
|
+
* Production features:
|
|
25
|
+
* - 50+ relationship patterns organized by type
|
|
26
|
+
* - Attribute patterns (e.g., "fast X" → X is Object/Technology)
|
|
27
|
+
* - Multi-pattern matching with confidence boosting
|
|
28
|
+
* - LRU cache for hot entities
|
|
29
|
+
* - Graceful degradation on errors
|
|
30
|
+
*/
|
|
31
|
+
export class ContextSignal {
|
|
32
|
+
constructor(brain, options) {
|
|
33
|
+
// Pre-compiled relationship patterns
|
|
34
|
+
this.relationshipPatterns = [];
|
|
35
|
+
// LRU cache for hot entities
|
|
36
|
+
this.cache = new Map();
|
|
37
|
+
this.cacheOrder = [];
|
|
38
|
+
// Statistics
|
|
39
|
+
this.stats = {
|
|
40
|
+
calls: 0,
|
|
41
|
+
cacheHits: 0,
|
|
42
|
+
relationshipMatches: 0,
|
|
43
|
+
attributeMatches: 0,
|
|
44
|
+
combinedMatches: 0
|
|
45
|
+
};
|
|
46
|
+
this.brain = brain;
|
|
47
|
+
this.options = {
|
|
48
|
+
minConfidence: options?.minConfidence ?? 0.50,
|
|
49
|
+
timeout: options?.timeout ?? 50,
|
|
50
|
+
cacheSize: options?.cacheSize ?? 500
|
|
51
|
+
};
|
|
52
|
+
this.initializePatterns();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Initialize relationship patterns
|
|
56
|
+
*
|
|
57
|
+
* Patterns organized by target type:
|
|
58
|
+
* - Person: roles, actions, possessives
|
|
59
|
+
* - Organization: membership, leadership, employment
|
|
60
|
+
* - Location: spatial relationships, residence
|
|
61
|
+
* - Technology: usage, implementation, integration
|
|
62
|
+
* - Event: attendance, occurrence, scheduling
|
|
63
|
+
* - Concept: understanding, application, theory
|
|
64
|
+
* - Object: ownership, interaction, description
|
|
65
|
+
*/
|
|
66
|
+
initializePatterns() {
|
|
67
|
+
// Person patterns - who someone is or what they do
|
|
68
|
+
this.addPatterns([
|
|
69
|
+
{
|
|
70
|
+
pattern: /\b(?:CEO|CTO|CFO|director|manager|founder|owner|president)\s+(?:of|at)\s+$/i,
|
|
71
|
+
targetType: 'organization',
|
|
72
|
+
confidence: 0.75,
|
|
73
|
+
evidence: 'Leadership role indicates organization'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
pattern: /\b(?:employee|member|staff|worker|engineer|developer|team member)\s+(?:of|at)\s+$/i,
|
|
77
|
+
targetType: 'organization',
|
|
78
|
+
confidence: 0.70,
|
|
79
|
+
evidence: 'Employment relationship indicates organization'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
pattern: /\b(?:lives|resides|located|based)\s+(?:in|at)\s+$/i,
|
|
83
|
+
targetType: 'location',
|
|
84
|
+
confidence: 0.80,
|
|
85
|
+
evidence: 'Residence indicates location'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
pattern: /\b(?:born|raised|grew up)\s+(?:in|at)\s+$/i,
|
|
89
|
+
targetType: 'location',
|
|
90
|
+
confidence: 0.75,
|
|
91
|
+
evidence: 'Origin indicates location'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
pattern: /\b(?:uses|utilizes|works with|familiar with)\s+$/i,
|
|
95
|
+
targetType: 'thing',
|
|
96
|
+
confidence: 0.65,
|
|
97
|
+
evidence: 'Tool usage indicates thing/tool'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
pattern: /\b(?:attended|participated in|spoke at|presented at)\s+$/i,
|
|
101
|
+
targetType: 'event',
|
|
102
|
+
confidence: 0.75,
|
|
103
|
+
evidence: 'Attendance indicates event'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
pattern: /\b(?:believes in|understands|studies|researches)\s+$/i,
|
|
107
|
+
targetType: 'concept',
|
|
108
|
+
confidence: 0.60,
|
|
109
|
+
evidence: 'Intellectual engagement indicates concept'
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
pattern: /\b(?:owns|possesses|has|carries)\s+(?:a|an|the)?\s*$/i,
|
|
113
|
+
targetType: 'thing',
|
|
114
|
+
confidence: 0.70,
|
|
115
|
+
evidence: 'Possession indicates physical thing'
|
|
116
|
+
}
|
|
117
|
+
]);
|
|
118
|
+
// Organization patterns - organizational relationships
|
|
119
|
+
this.addPatterns([
|
|
120
|
+
{
|
|
121
|
+
pattern: /\b(?:subsidiary|division|branch|department)\s+of\s+$/i,
|
|
122
|
+
targetType: 'organization',
|
|
123
|
+
confidence: 0.80,
|
|
124
|
+
evidence: 'Organizational hierarchy indicates parent organization'
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
pattern: /\b(?:partner|collaborator|vendor|supplier)\s+(?:of|to)\s+$/i,
|
|
128
|
+
targetType: 'organization',
|
|
129
|
+
confidence: 0.70,
|
|
130
|
+
evidence: 'Business relationship indicates organization'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
pattern: /\b(?:headquarters|office|facility)\s+(?:in|at)\s+$/i,
|
|
134
|
+
targetType: 'location',
|
|
135
|
+
confidence: 0.75,
|
|
136
|
+
evidence: 'Physical presence indicates location'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
pattern: /\b(?:acquired|purchased|bought|merged with)\s+$/i,
|
|
140
|
+
targetType: 'organization',
|
|
141
|
+
confidence: 0.75,
|
|
142
|
+
evidence: 'Acquisition indicates organization'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
pattern: /\b(?:implements|uses|adopts|integrates)\s+$/i,
|
|
146
|
+
targetType: 'thing',
|
|
147
|
+
confidence: 0.65,
|
|
148
|
+
evidence: 'Tool adoption indicates thing/service'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
pattern: /\b(?:organized|hosted|sponsored)\s+$/i,
|
|
152
|
+
targetType: 'event',
|
|
153
|
+
confidence: 0.70,
|
|
154
|
+
evidence: 'Event organization indicates event'
|
|
155
|
+
}
|
|
156
|
+
]);
|
|
157
|
+
// Location patterns - spatial relationships
|
|
158
|
+
this.addPatterns([
|
|
159
|
+
{
|
|
160
|
+
pattern: /\b(?:capital|largest city|major city)\s+of\s+$/i,
|
|
161
|
+
targetType: 'location',
|
|
162
|
+
confidence: 0.85,
|
|
163
|
+
evidence: 'Geographic relationship indicates location'
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
pattern: /\b(?:near|adjacent to|next to|close to)\s+$/i,
|
|
167
|
+
targetType: 'location',
|
|
168
|
+
confidence: 0.70,
|
|
169
|
+
evidence: 'Spatial proximity indicates location'
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
pattern: /\b(?:north|south|east|west)\s+of\s+$/i,
|
|
173
|
+
targetType: 'location',
|
|
174
|
+
confidence: 0.75,
|
|
175
|
+
evidence: 'Directional relationship indicates location'
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
pattern: /\b(?:located in|situated in|found in)\s+$/i,
|
|
179
|
+
targetType: 'location',
|
|
180
|
+
confidence: 0.80,
|
|
181
|
+
evidence: 'Location reference indicates place'
|
|
182
|
+
}
|
|
183
|
+
]);
|
|
184
|
+
// Technology patterns - technical relationships
|
|
185
|
+
this.addPatterns([
|
|
186
|
+
{
|
|
187
|
+
pattern: /\b(?:built with|powered by|runs on)\s+$/i,
|
|
188
|
+
targetType: 'thing',
|
|
189
|
+
confidence: 0.75,
|
|
190
|
+
evidence: 'Technical foundation indicates thing/tool'
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
pattern: /\b(?:integrated with|connects to|compatible with)\s+$/i,
|
|
194
|
+
targetType: 'interface',
|
|
195
|
+
confidence: 0.70,
|
|
196
|
+
evidence: 'Integration indicates interface/API'
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
pattern: /\b(?:deployed on|hosted on|running on)\s+$/i,
|
|
200
|
+
targetType: 'service',
|
|
201
|
+
confidence: 0.75,
|
|
202
|
+
evidence: 'Deployment indicates service/platform'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
pattern: /\b(?:developed by|created by|maintained by)\s+$/i,
|
|
206
|
+
targetType: 'organization',
|
|
207
|
+
confidence: 0.70,
|
|
208
|
+
evidence: 'Development indicates organization/person'
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
pattern: /\b(?:API for|SDK for|library for)\s+$/i,
|
|
212
|
+
targetType: 'interface',
|
|
213
|
+
confidence: 0.75,
|
|
214
|
+
evidence: 'Technical tooling indicates interface/API'
|
|
215
|
+
}
|
|
216
|
+
]);
|
|
217
|
+
// Event patterns - temporal relationships
|
|
218
|
+
this.addPatterns([
|
|
219
|
+
{
|
|
220
|
+
pattern: /\b(?:before|after|during|since)\s+$/i,
|
|
221
|
+
targetType: 'event',
|
|
222
|
+
confidence: 0.60,
|
|
223
|
+
evidence: 'Temporal relationship indicates event'
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
pattern: /\b(?:scheduled for|planned for|occurring on)\s+$/i,
|
|
227
|
+
targetType: 'event',
|
|
228
|
+
confidence: 0.70,
|
|
229
|
+
evidence: 'Scheduling indicates event'
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
pattern: /\b(?:keynote at|session at|talk at|presentation at)\s+$/i,
|
|
233
|
+
targetType: 'event',
|
|
234
|
+
confidence: 0.80,
|
|
235
|
+
evidence: 'Speaking engagement indicates event'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
pattern: /\b(?:registration for|tickets to|attending)\s+$/i,
|
|
239
|
+
targetType: 'event',
|
|
240
|
+
confidence: 0.75,
|
|
241
|
+
evidence: 'Participation indicates event'
|
|
242
|
+
}
|
|
243
|
+
]);
|
|
244
|
+
// Concept patterns - intellectual relationships
|
|
245
|
+
this.addPatterns([
|
|
246
|
+
{
|
|
247
|
+
pattern: /\b(?:theory of|principle of|concept of|idea of)\s+$/i,
|
|
248
|
+
targetType: 'concept',
|
|
249
|
+
confidence: 0.75,
|
|
250
|
+
evidence: 'Theoretical framework indicates concept'
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
pattern: /\b(?:based on|derived from|inspired by)\s+$/i,
|
|
254
|
+
targetType: 'concept',
|
|
255
|
+
confidence: 0.60,
|
|
256
|
+
evidence: 'Intellectual lineage indicates concept'
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
pattern: /\b(?:example of|instance of|case of)\s+$/i,
|
|
260
|
+
targetType: 'concept',
|
|
261
|
+
confidence: 0.65,
|
|
262
|
+
evidence: 'Exemplification indicates concept'
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
pattern: /\b(?:methodology|approach|strategy)\s+(?:for|of)\s+$/i,
|
|
266
|
+
targetType: 'process',
|
|
267
|
+
confidence: 0.70,
|
|
268
|
+
evidence: 'Method reference indicates process'
|
|
269
|
+
}
|
|
270
|
+
]);
|
|
271
|
+
// Object patterns - physical relationships
|
|
272
|
+
this.addPatterns([
|
|
273
|
+
{
|
|
274
|
+
pattern: /\b(?:made of|composed of|constructed from)\s+$/i,
|
|
275
|
+
targetType: 'thing',
|
|
276
|
+
confidence: 0.65,
|
|
277
|
+
evidence: 'Material composition indicates thing'
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
pattern: /\b(?:part of|component of|piece of)\s+$/i,
|
|
281
|
+
targetType: 'thing',
|
|
282
|
+
confidence: 0.70,
|
|
283
|
+
evidence: 'Physical composition indicates thing'
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
pattern: /\b(?:weighs|measures|dimensions)\s+$/i,
|
|
287
|
+
targetType: 'thing',
|
|
288
|
+
confidence: 0.75,
|
|
289
|
+
evidence: 'Physical measurement indicates thing'
|
|
290
|
+
}
|
|
291
|
+
]);
|
|
292
|
+
// Attribute patterns - descriptive relationships
|
|
293
|
+
this.addPatterns([
|
|
294
|
+
{
|
|
295
|
+
pattern: /\b(?:fast|slow|quick|rapid|speedy)\s+$/i,
|
|
296
|
+
targetType: 'thing',
|
|
297
|
+
confidence: 0.55,
|
|
298
|
+
evidence: 'Speed attribute indicates thing/tool'
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
pattern: /\b(?:large|small|tiny|huge|massive)\s+$/i,
|
|
302
|
+
targetType: 'thing',
|
|
303
|
+
confidence: 0.55,
|
|
304
|
+
evidence: 'Size attribute indicates physical thing'
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
pattern: /\b(?:expensive|cheap|affordable|costly)\s+$/i,
|
|
308
|
+
targetType: 'thing',
|
|
309
|
+
confidence: 0.60,
|
|
310
|
+
evidence: 'Price attribute indicates purchasable thing'
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
pattern: /\b(?:annual|monthly|weekly|daily)\s+$/i,
|
|
314
|
+
targetType: 'event',
|
|
315
|
+
confidence: 0.65,
|
|
316
|
+
evidence: 'Frequency indicates recurring event'
|
|
317
|
+
}
|
|
318
|
+
]);
|
|
319
|
+
// Document patterns - information relationships
|
|
320
|
+
this.addPatterns([
|
|
321
|
+
{
|
|
322
|
+
pattern: /\b(?:chapter (?:in|of)|section (?:in|of)|page in)\s+$/i,
|
|
323
|
+
targetType: 'document',
|
|
324
|
+
confidence: 0.75,
|
|
325
|
+
evidence: 'Document structure indicates document'
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
pattern: /\b(?:author of|wrote|published)\s+$/i,
|
|
329
|
+
targetType: 'document',
|
|
330
|
+
confidence: 0.70,
|
|
331
|
+
evidence: 'Authorship indicates document'
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
pattern: /\b(?:reference to|citation of|mentioned in)\s+$/i,
|
|
335
|
+
targetType: 'document',
|
|
336
|
+
confidence: 0.65,
|
|
337
|
+
evidence: 'Citation indicates document'
|
|
338
|
+
}
|
|
339
|
+
]);
|
|
340
|
+
// Project patterns - work relationships
|
|
341
|
+
this.addPatterns([
|
|
342
|
+
{
|
|
343
|
+
pattern: /\b(?:milestone in|phase of|stage of)\s+$/i,
|
|
344
|
+
targetType: 'project',
|
|
345
|
+
confidence: 0.70,
|
|
346
|
+
evidence: 'Project structure indicates project'
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
pattern: /\b(?:deliverable for|outcome of|goal of)\s+$/i,
|
|
350
|
+
targetType: 'project',
|
|
351
|
+
confidence: 0.65,
|
|
352
|
+
evidence: 'Project objective indicates project'
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
pattern: /\b(?:working on|contributing to|involved in)\s+$/i,
|
|
356
|
+
targetType: 'project',
|
|
357
|
+
confidence: 0.60,
|
|
358
|
+
evidence: 'Work engagement indicates project'
|
|
359
|
+
}
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Add relationship patterns in bulk
|
|
364
|
+
*/
|
|
365
|
+
addPatterns(patterns) {
|
|
366
|
+
this.relationshipPatterns.push(...patterns);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Classify entity type using context-based signals
|
|
370
|
+
*
|
|
371
|
+
* Main entry point - checks relationship patterns in definition/context
|
|
372
|
+
*
|
|
373
|
+
* @param candidate Entity text to classify
|
|
374
|
+
* @param context Context with definition and related entities
|
|
375
|
+
* @returns TypeSignal with classification result
|
|
376
|
+
*/
|
|
377
|
+
async classify(candidate, context) {
|
|
378
|
+
this.stats.calls++;
|
|
379
|
+
// Context signal requires context to work
|
|
380
|
+
if (!context?.definition && !context?.allTerms) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
// Check cache first
|
|
384
|
+
const cacheKey = this.getCacheKey(candidate, context);
|
|
385
|
+
const cached = this.getFromCache(cacheKey);
|
|
386
|
+
if (cached !== undefined) {
|
|
387
|
+
this.stats.cacheHits++;
|
|
388
|
+
return cached;
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
// Build search text from context
|
|
392
|
+
const searchText = this.buildSearchText(candidate, context);
|
|
393
|
+
// Check relationship patterns
|
|
394
|
+
const relationshipMatch = this.matchRelationshipPatterns(candidate, searchText);
|
|
395
|
+
// Check attribute patterns
|
|
396
|
+
const attributeMatch = this.matchAttributePatterns(candidate, searchText);
|
|
397
|
+
// Combine results
|
|
398
|
+
const result = this.combineResults([relationshipMatch, attributeMatch]);
|
|
399
|
+
// Cache result (including nulls to avoid recomputation)
|
|
400
|
+
if (!result || result.confidence >= this.options.minConfidence) {
|
|
401
|
+
this.addToCache(cacheKey, result);
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
// Graceful degradation - return null instead of throwing
|
|
407
|
+
console.warn(`ContextSignal error for "${candidate}":`, error);
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Build search text from candidate and context
|
|
413
|
+
*
|
|
414
|
+
* Extracts text around candidate to find relationship patterns
|
|
415
|
+
*/
|
|
416
|
+
buildSearchText(candidate, context) {
|
|
417
|
+
const parts = [];
|
|
418
|
+
// Add definition if available
|
|
419
|
+
if (context.definition) {
|
|
420
|
+
parts.push(context.definition);
|
|
421
|
+
}
|
|
422
|
+
// Add related terms if available
|
|
423
|
+
if (context.allTerms && context.allTerms.length > 0) {
|
|
424
|
+
parts.push(...context.allTerms);
|
|
425
|
+
}
|
|
426
|
+
return parts.join(' ').toLowerCase();
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Match relationship patterns in context
|
|
430
|
+
*
|
|
431
|
+
* Looks for patterns like "CEO of X" where X is the candidate
|
|
432
|
+
*/
|
|
433
|
+
matchRelationshipPatterns(candidate, searchText) {
|
|
434
|
+
const candidateLower = candidate.toLowerCase();
|
|
435
|
+
const matches = [];
|
|
436
|
+
// Find all occurrences of candidate in search text
|
|
437
|
+
// Only use word boundaries if candidate is all word characters
|
|
438
|
+
const useWordBoundaries = /^[a-z0-9_]+$/i.test(candidateLower);
|
|
439
|
+
const pattern = useWordBoundaries
|
|
440
|
+
? `\\b${this.escapeRegex(candidateLower)}\\b`
|
|
441
|
+
: this.escapeRegex(candidateLower);
|
|
442
|
+
const regex = new RegExp(pattern, 'gi');
|
|
443
|
+
let match;
|
|
444
|
+
while ((match = regex.exec(searchText)) !== null) {
|
|
445
|
+
const beforeText = searchText.substring(Math.max(0, match.index - 100), match.index);
|
|
446
|
+
// Check each pattern against the text before the candidate
|
|
447
|
+
for (const pattern of this.relationshipPatterns) {
|
|
448
|
+
// Skip attribute patterns in relationship matching
|
|
449
|
+
if (pattern.evidence.includes('attribute'))
|
|
450
|
+
continue;
|
|
451
|
+
const patternMatch = pattern.pattern.test(beforeText);
|
|
452
|
+
if (patternMatch) {
|
|
453
|
+
matches.push({ pattern, matchText: beforeText });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (matches.length === 0)
|
|
458
|
+
return null;
|
|
459
|
+
// Find best match
|
|
460
|
+
let bestMatch = matches[0];
|
|
461
|
+
for (const match of matches) {
|
|
462
|
+
if (match.pattern.confidence > bestMatch.pattern.confidence) {
|
|
463
|
+
bestMatch = match;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
this.stats.relationshipMatches++;
|
|
467
|
+
return {
|
|
468
|
+
source: 'context-relationship',
|
|
469
|
+
type: bestMatch.pattern.targetType,
|
|
470
|
+
confidence: bestMatch.pattern.confidence,
|
|
471
|
+
evidence: bestMatch.pattern.evidence,
|
|
472
|
+
metadata: {
|
|
473
|
+
relationshipPattern: bestMatch.pattern.pattern.source,
|
|
474
|
+
contextMatch: bestMatch.matchText.substring(Math.max(0, bestMatch.matchText.length - 50))
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Match attribute patterns in context
|
|
480
|
+
*
|
|
481
|
+
* Looks for descriptive patterns like "fast X" where X is the candidate
|
|
482
|
+
*/
|
|
483
|
+
matchAttributePatterns(candidate, searchText) {
|
|
484
|
+
const candidateLower = candidate.toLowerCase();
|
|
485
|
+
const matches = [];
|
|
486
|
+
// Find all occurrences of candidate in search text
|
|
487
|
+
// Only use word boundaries if candidate is all word characters
|
|
488
|
+
const useWordBoundaries = /^[a-z0-9_]+$/i.test(candidateLower);
|
|
489
|
+
const pattern = useWordBoundaries
|
|
490
|
+
? `\\b${this.escapeRegex(candidateLower)}\\b`
|
|
491
|
+
: this.escapeRegex(candidateLower);
|
|
492
|
+
const regex = new RegExp(pattern, 'gi');
|
|
493
|
+
let match;
|
|
494
|
+
while ((match = regex.exec(searchText)) !== null) {
|
|
495
|
+
const beforeText = searchText.substring(Math.max(0, match.index - 50), match.index);
|
|
496
|
+
// Check each attribute pattern against the text before the candidate
|
|
497
|
+
for (const pattern of this.relationshipPatterns) {
|
|
498
|
+
// Only check attribute patterns
|
|
499
|
+
if (!pattern.evidence.includes('attribute'))
|
|
500
|
+
continue;
|
|
501
|
+
const patternMatch = pattern.pattern.test(beforeText);
|
|
502
|
+
if (patternMatch) {
|
|
503
|
+
matches.push({ pattern, matchText: beforeText });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (matches.length === 0)
|
|
508
|
+
return null;
|
|
509
|
+
// Find best match
|
|
510
|
+
let bestMatch = matches[0];
|
|
511
|
+
for (const match of matches) {
|
|
512
|
+
if (match.pattern.confidence > bestMatch.pattern.confidence) {
|
|
513
|
+
bestMatch = match;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
this.stats.attributeMatches++;
|
|
517
|
+
return {
|
|
518
|
+
source: 'context-attribute',
|
|
519
|
+
type: bestMatch.pattern.targetType,
|
|
520
|
+
confidence: bestMatch.pattern.confidence,
|
|
521
|
+
evidence: bestMatch.pattern.evidence,
|
|
522
|
+
metadata: {
|
|
523
|
+
relationshipPattern: bestMatch.pattern.pattern.source,
|
|
524
|
+
contextMatch: bestMatch.matchText
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Combine results from relationship and attribute matching
|
|
530
|
+
*
|
|
531
|
+
* Prefers relationship matches over attribute matches
|
|
532
|
+
*/
|
|
533
|
+
combineResults(matches) {
|
|
534
|
+
// Filter out null matches
|
|
535
|
+
const validMatches = matches.filter((m) => m !== null);
|
|
536
|
+
if (validMatches.length === 0)
|
|
537
|
+
return null;
|
|
538
|
+
// Prefer relationship matches over attribute matches
|
|
539
|
+
const relationshipMatch = validMatches.find(m => m.source === 'context-relationship');
|
|
540
|
+
if (relationshipMatch && relationshipMatch.confidence >= this.options.minConfidence) {
|
|
541
|
+
return relationshipMatch;
|
|
542
|
+
}
|
|
543
|
+
// Fall back to attribute match
|
|
544
|
+
const attributeMatch = validMatches.find(m => m.source === 'context-attribute');
|
|
545
|
+
if (attributeMatch && attributeMatch.confidence >= this.options.minConfidence) {
|
|
546
|
+
return attributeMatch;
|
|
547
|
+
}
|
|
548
|
+
// If multiple matches of same type, use highest confidence
|
|
549
|
+
validMatches.sort((a, b) => b.confidence - a.confidence);
|
|
550
|
+
const best = validMatches[0];
|
|
551
|
+
if (best.confidence >= this.options.minConfidence) {
|
|
552
|
+
return best;
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Get statistics about signal performance
|
|
558
|
+
*/
|
|
559
|
+
getStats() {
|
|
560
|
+
return {
|
|
561
|
+
...this.stats,
|
|
562
|
+
cacheSize: this.cache.size,
|
|
563
|
+
cacheHitRate: this.stats.calls > 0 ? this.stats.cacheHits / this.stats.calls : 0,
|
|
564
|
+
relationshipMatchRate: this.stats.calls > 0 ? this.stats.relationshipMatches / this.stats.calls : 0,
|
|
565
|
+
attributeMatchRate: this.stats.calls > 0 ? this.stats.attributeMatches / this.stats.calls : 0
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Reset statistics (useful for testing)
|
|
570
|
+
*/
|
|
571
|
+
resetStats() {
|
|
572
|
+
this.stats = {
|
|
573
|
+
calls: 0,
|
|
574
|
+
cacheHits: 0,
|
|
575
|
+
relationshipMatches: 0,
|
|
576
|
+
attributeMatches: 0,
|
|
577
|
+
combinedMatches: 0
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Clear cache
|
|
582
|
+
*/
|
|
583
|
+
clearCache() {
|
|
584
|
+
this.cache.clear();
|
|
585
|
+
this.cacheOrder = [];
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get pattern count (for testing)
|
|
589
|
+
*/
|
|
590
|
+
getPatternCount() {
|
|
591
|
+
return this.relationshipPatterns.length;
|
|
592
|
+
}
|
|
593
|
+
// ========== Private Helper Methods ==========
|
|
594
|
+
/**
|
|
595
|
+
* Generate cache key from candidate and context
|
|
596
|
+
*/
|
|
597
|
+
getCacheKey(candidate, context) {
|
|
598
|
+
const normalized = candidate.toLowerCase().trim();
|
|
599
|
+
if (!context?.definition)
|
|
600
|
+
return normalized;
|
|
601
|
+
return `${normalized}:${context.definition.substring(0, 50)}`;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Get from LRU cache (returns undefined if not found, null if cached as null)
|
|
605
|
+
*/
|
|
606
|
+
getFromCache(key) {
|
|
607
|
+
// Check if key exists in cache (including null values)
|
|
608
|
+
if (!this.cache.has(key))
|
|
609
|
+
return undefined;
|
|
610
|
+
const cached = this.cache.get(key);
|
|
611
|
+
// Move to end (most recently used)
|
|
612
|
+
this.cacheOrder = this.cacheOrder.filter(k => k !== key);
|
|
613
|
+
this.cacheOrder.push(key);
|
|
614
|
+
return cached ?? null;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Add to LRU cache with eviction
|
|
618
|
+
*/
|
|
619
|
+
addToCache(key, value) {
|
|
620
|
+
// Add to cache
|
|
621
|
+
this.cache.set(key, value);
|
|
622
|
+
this.cacheOrder.push(key);
|
|
623
|
+
// Evict oldest if over limit
|
|
624
|
+
if (this.cache.size > this.options.cacheSize) {
|
|
625
|
+
const oldest = this.cacheOrder.shift();
|
|
626
|
+
if (oldest) {
|
|
627
|
+
this.cache.delete(oldest);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Escape special regex characters
|
|
633
|
+
*/
|
|
634
|
+
escapeRegex(str) {
|
|
635
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Create a new ContextSignal instance
|
|
640
|
+
*
|
|
641
|
+
* Convenience factory function
|
|
642
|
+
*/
|
|
643
|
+
export function createContextSignal(brain, options) {
|
|
644
|
+
return new ContextSignal(brain, options);
|
|
645
|
+
}
|
|
646
|
+
//# sourceMappingURL=ContextSignal.js.map
|