dpth 0.1.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/dist/embed.js ADDED
@@ -0,0 +1,270 @@
1
+ /**
2
+ * dpth.io Embedding System
3
+ *
4
+ * Everything in dpth.io can be embedded for semantic search.
5
+ * Find similar entities, metrics, and patterns without explicit joins.
6
+ *
7
+ * "What metrics behave like churn?"
8
+ * "Find entities similar to our best customers"
9
+ * "What patterns look like this anomaly?"
10
+ */
11
+ // ─── Embedding Store ─────────────────────────────────
12
+ const embeddings = new Map();
13
+ // ─── Text Generation for Embedding ───────────────────
14
+ /**
15
+ * Generate text representation of an entity for embedding
16
+ */
17
+ export function entityToText(entity) {
18
+ const parts = [
19
+ `${entity.type}: ${entity.name}`,
20
+ ...entity.aliases.map(a => `also known as ${a}`),
21
+ ];
22
+ // Add key attributes
23
+ for (const [key, value] of Object.entries(entity.attributes)) {
24
+ if (typeof value.current === 'string' || typeof value.current === 'number') {
25
+ parts.push(`${key}: ${value.current}`);
26
+ }
27
+ }
28
+ // Add source information
29
+ const sources = entity.sources.map(s => s.sourceId).join(', ');
30
+ parts.push(`found in: ${sources}`);
31
+ return parts.join('. ');
32
+ }
33
+ /**
34
+ * Generate text representation of a metric for embedding
35
+ */
36
+ export function metricToText(metric) {
37
+ const parts = [
38
+ `metric: ${metric.name}`,
39
+ metric.unit ? `measured in ${metric.unit}` : '',
40
+ `aggregated by ${metric.aggregation}`,
41
+ ];
42
+ // Add summary statistics
43
+ if (metric.points.length > 0) {
44
+ const values = metric.points.map(p => p.value);
45
+ const min = Math.min(...values);
46
+ const max = Math.max(...values);
47
+ const avg = values.reduce((a, b) => a + b, 0) / values.length;
48
+ parts.push(`ranges from ${min.toFixed(2)} to ${max.toFixed(2)}`);
49
+ parts.push(`average ${avg.toFixed(2)}`);
50
+ parts.push(`${metric.points.length} data points`);
51
+ }
52
+ return parts.filter(p => p).join('. ');
53
+ }
54
+ /**
55
+ * Generate text representation of a pattern for embedding
56
+ */
57
+ export function patternToText(pattern) {
58
+ return `${pattern.type} pattern: ${pattern.summary}. ${pattern.explanation || ''}`;
59
+ }
60
+ // ─── Embedding Generation ────────────────────────────
61
+ /**
62
+ * Simple bag-of-words embedding (placeholder for real embeddings)
63
+ *
64
+ * In production, this would call an embedding API (OpenAI, Cohere, etc.)
65
+ * or use a local model. For now, we use TF-IDF-like vectors.
66
+ */
67
+ function generateEmbedding(text) {
68
+ // Tokenize and normalize
69
+ const tokens = text.toLowerCase()
70
+ .replace(/[^a-z0-9\s]/g, ' ')
71
+ .split(/\s+/)
72
+ .filter(t => t.length > 2);
73
+ // Create a simple hash-based embedding (384 dimensions like many real models)
74
+ const dimensions = 384;
75
+ const vector = new Array(dimensions).fill(0);
76
+ for (const token of tokens) {
77
+ // Hash token to dimension indices
78
+ const hash1 = simpleHash(token) % dimensions;
79
+ const hash2 = simpleHash(token + '_2') % dimensions;
80
+ const hash3 = simpleHash(token + '_3') % dimensions;
81
+ // Add contribution (simulating learned embeddings)
82
+ vector[hash1] += 1;
83
+ vector[hash2] += 0.5;
84
+ vector[hash3] += 0.25;
85
+ }
86
+ // Normalize to unit vector
87
+ const magnitude = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
88
+ if (magnitude > 0) {
89
+ for (let i = 0; i < dimensions; i++) {
90
+ vector[i] /= magnitude;
91
+ }
92
+ }
93
+ return vector;
94
+ }
95
+ /**
96
+ * Simple string hash function
97
+ */
98
+ function simpleHash(str) {
99
+ let hash = 0;
100
+ for (let i = 0; i < str.length; i++) {
101
+ const char = str.charCodeAt(i);
102
+ hash = ((hash << 5) - hash) + char;
103
+ hash = hash & hash; // Convert to 32-bit integer
104
+ }
105
+ return Math.abs(hash);
106
+ }
107
+ // ─── Embedding CRUD ──────────────────────────────────
108
+ /**
109
+ * Embed an entity
110
+ */
111
+ export function embedEntity(entity) {
112
+ const text = entityToText(entity);
113
+ const vector = generateEmbedding(text);
114
+ const embedding = {
115
+ id: entity.id,
116
+ type: 'entity',
117
+ vector,
118
+ text,
119
+ updatedAt: new Date(),
120
+ };
121
+ embeddings.set(`entity:${entity.id}`, embedding);
122
+ return embedding;
123
+ }
124
+ /**
125
+ * Embed a metric
126
+ */
127
+ export function embedMetric(metric) {
128
+ const text = metricToText(metric);
129
+ const vector = generateEmbedding(text);
130
+ const embedding = {
131
+ id: metric.id,
132
+ type: 'metric',
133
+ vector,
134
+ text,
135
+ updatedAt: new Date(),
136
+ };
137
+ embeddings.set(`metric:${metric.id}`, embedding);
138
+ return embedding;
139
+ }
140
+ /**
141
+ * Embed a pattern
142
+ */
143
+ export function embedPattern(pattern) {
144
+ const text = patternToText(pattern);
145
+ const vector = generateEmbedding(text);
146
+ const embedding = {
147
+ id: pattern.id,
148
+ type: 'pattern',
149
+ vector,
150
+ text,
151
+ updatedAt: new Date(),
152
+ };
153
+ embeddings.set(`pattern:${pattern.id}`, embedding);
154
+ return embedding;
155
+ }
156
+ /**
157
+ * Embed arbitrary text (for queries)
158
+ */
159
+ export function embedText(text) {
160
+ return generateEmbedding(text);
161
+ }
162
+ // ─── Similarity Search ───────────────────────────────
163
+ /**
164
+ * Calculate cosine similarity between two vectors
165
+ */
166
+ export function cosineSimilarity(a, b) {
167
+ if (a.length !== b.length)
168
+ return 0;
169
+ let dotProduct = 0;
170
+ let normA = 0;
171
+ let normB = 0;
172
+ for (let i = 0; i < a.length; i++) {
173
+ dotProduct += a[i] * b[i];
174
+ normA += a[i] * a[i];
175
+ normB += b[i] * b[i];
176
+ }
177
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
178
+ if (denominator === 0)
179
+ return 0;
180
+ return dotProduct / denominator;
181
+ }
182
+ /**
183
+ * Find similar items by ID
184
+ */
185
+ export function findSimilar(query) {
186
+ const key = `${query.type}:${query.id}`;
187
+ const queryEmbedding = embeddings.get(key);
188
+ if (!queryEmbedding)
189
+ return [];
190
+ return findSimilarByVector(queryEmbedding.vector, query.type, query.minScore, query.limit, query.id // Exclude self
191
+ );
192
+ }
193
+ /**
194
+ * Find similar items by text query
195
+ */
196
+ export function searchByText(text, type, minScore = 0.3, limit = 10) {
197
+ const queryVector = embedText(text);
198
+ return findSimilarByVector(queryVector, type, minScore, limit);
199
+ }
200
+ /**
201
+ * Find similar items by vector
202
+ */
203
+ function findSimilarByVector(queryVector, type, minScore = 0.3, limit = 10, excludeId) {
204
+ const results = [];
205
+ for (const [key, embedding] of embeddings) {
206
+ // Filter by type if specified
207
+ if (type && embedding.type !== type)
208
+ continue;
209
+ // Exclude self
210
+ if (excludeId && embedding.id === excludeId)
211
+ continue;
212
+ const score = cosineSimilarity(queryVector, embedding.vector);
213
+ if (score >= minScore) {
214
+ results.push({
215
+ id: embedding.id,
216
+ type: embedding.type,
217
+ score,
218
+ text: embedding.text,
219
+ });
220
+ }
221
+ }
222
+ // Sort by score descending
223
+ results.sort((a, b) => b.score - a.score);
224
+ return results.slice(0, limit);
225
+ }
226
+ /**
227
+ * Get embedding by key
228
+ */
229
+ export function getEmbedding(type, id) {
230
+ return embeddings.get(`${type}:${id}`);
231
+ }
232
+ /**
233
+ * Get embedding stats
234
+ */
235
+ export function getEmbeddingStats() {
236
+ const byType = { entity: 0, metric: 0, pattern: 0 };
237
+ for (const embedding of embeddings.values()) {
238
+ byType[embedding.type]++;
239
+ }
240
+ return {
241
+ total: embeddings.size,
242
+ byType,
243
+ };
244
+ }
245
+ /**
246
+ * Clear all embeddings (for testing)
247
+ */
248
+ export function clearEmbeddings() {
249
+ embeddings.clear();
250
+ }
251
+ // ─── Batch Operations ────────────────────────────────
252
+ /**
253
+ * Re-embed all entities (useful after model upgrade)
254
+ */
255
+ export async function reembedAllEntities(entities, onProgress) {
256
+ for (let i = 0; i < entities.length; i++) {
257
+ embedEntity(entities[i]);
258
+ onProgress?.(i + 1, entities.length);
259
+ }
260
+ }
261
+ /**
262
+ * Re-embed all metrics
263
+ */
264
+ export async function reembedAllMetrics(metrics, onProgress) {
265
+ for (let i = 0; i < metrics.length; i++) {
266
+ embedMetric(metrics[i]);
267
+ onProgress?.(i + 1, metrics.length);
268
+ }
269
+ }
270
+ //# sourceMappingURL=embed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed.js","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * dpth.io Entity System
3
+ *
4
+ * Unified identity across all data sources. The same person in Salesforce,
5
+ * Slack, GitHub, and your HR system is ONE entity in dpth.io.
6
+ *
7
+ * Key concepts:
8
+ * - Entity resolution: Fuzzy matching to merge identities
9
+ * - Source refs: Track where this entity appears
10
+ * - Temporal attributes: Every attribute has history
11
+ */
12
+ import { Entity, EntityId, EntityType, SourceId } from './types.js';
13
+ /**
14
+ * Create a new entity
15
+ */
16
+ export declare function createEntity(type: EntityType, name: string, sourceId: SourceId, externalId: string, attributes?: Record<string, unknown>): Entity;
17
+ /**
18
+ * Find entity by ID
19
+ */
20
+ export declare function getEntity(id: EntityId): Entity | undefined;
21
+ /**
22
+ * Find entity by source reference
23
+ */
24
+ export declare function findEntityBySource(sourceId: SourceId, externalId: string): Entity | undefined;
25
+ /**
26
+ * Update an entity's attribute (temporal update)
27
+ */
28
+ export declare function updateEntityAttribute(entityId: EntityId, key: string, value: unknown, sourceId: SourceId): Entity | undefined;
29
+ interface MatchCandidate {
30
+ entity: Entity;
31
+ score: number;
32
+ matchedOn: string[];
33
+ }
34
+ /**
35
+ * Find potential matches for a new record
36
+ */
37
+ export declare function findMatches(type: EntityType, name: string, email?: string, aliases?: string[]): MatchCandidate[];
38
+ /**
39
+ * Resolve or create: Find a matching entity or create a new one
40
+ */
41
+ export declare function resolveOrCreate(type: EntityType, name: string, sourceId: SourceId, externalId: string, options?: {
42
+ email?: string;
43
+ aliases?: string[];
44
+ attributes?: Record<string, unknown>;
45
+ minConfidence?: number;
46
+ }): {
47
+ entity: Entity;
48
+ isNew: boolean;
49
+ confidence: number;
50
+ };
51
+ /**
52
+ * Manually merge two entities
53
+ */
54
+ export declare function mergeEntities(keepId: EntityId, mergeId: EntityId): Entity | undefined;
55
+ /**
56
+ * Get all entities of a type
57
+ */
58
+ export declare function getEntitiesByType(type: EntityType): Entity[];
59
+ /**
60
+ * Get all entities from a source
61
+ */
62
+ export declare function getEntitiesBySource(sourceId: SourceId): Entity[];
63
+ /**
64
+ * Get entity attribute value at a specific point in time
65
+ */
66
+ export declare function getAttributeAt<T>(entity: Entity, key: string, at: Date): T | undefined;
67
+ /**
68
+ * Get entity count by type (for stats)
69
+ */
70
+ export declare function getEntityStats(): Record<EntityType, number>;
71
+ /**
72
+ * Clear all entities (for testing)
73
+ */
74
+ export declare function clearEntities(): void;
75
+ export {};
76
+ //# sourceMappingURL=entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../src/entity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,UAAU,EAEV,QAAQ,EAET,MAAM,YAAY,CAAC;AAoBpB;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,CAwCR;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAE1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,QAAQ,GACjB,MAAM,GAAG,SAAS,CAoCpB;AAID,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,cAAc,EAAE,CA8ClB;AAkCD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IACR,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAmDxD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CA2CrF;AAID;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,CAE5D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAIhE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,CAAC,GAAG,SAAS,CActF;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAM3D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAGpC"}
package/dist/entity.js ADDED
@@ -0,0 +1,327 @@
1
+ /**
2
+ * dpth.io Entity System
3
+ *
4
+ * Unified identity across all data sources. The same person in Salesforce,
5
+ * Slack, GitHub, and your HR system is ONE entity in dpth.io.
6
+ *
7
+ * Key concepts:
8
+ * - Entity resolution: Fuzzy matching to merge identities
9
+ * - Source refs: Track where this entity appears
10
+ * - Temporal attributes: Every attribute has history
11
+ */
12
+ import crypto from 'crypto';
13
+ // ─── Entity Store ────────────────────────────────────
14
+ /** In-memory entity store (will be persisted later) */
15
+ const entities = new Map();
16
+ const sourceIndex = new Map(); // "sourceId:externalId" → entityId
17
+ /** Generate a unique entity ID */
18
+ function generateEntityId() {
19
+ return `ent_${crypto.randomBytes(12).toString('hex')}`;
20
+ }
21
+ /** Create index key for source lookup */
22
+ function sourceKey(sourceId, externalId) {
23
+ return `${sourceId}:${externalId}`;
24
+ }
25
+ // ─── Entity CRUD ─────────────────────────────────────
26
+ /**
27
+ * Create a new entity
28
+ */
29
+ export function createEntity(type, name, sourceId, externalId, attributes) {
30
+ const id = generateEntityId();
31
+ const now = new Date();
32
+ const entity = {
33
+ id,
34
+ type,
35
+ name,
36
+ aliases: [],
37
+ sources: [{
38
+ sourceId,
39
+ externalId,
40
+ confidence: 1.0,
41
+ lastSeen: now,
42
+ }],
43
+ attributes: {},
44
+ createdAt: now,
45
+ updatedAt: now,
46
+ };
47
+ // Convert attributes to temporal values
48
+ if (attributes) {
49
+ for (const [key, value] of Object.entries(attributes)) {
50
+ entity.attributes[key] = {
51
+ current: value,
52
+ history: [{
53
+ value,
54
+ validFrom: now,
55
+ validTo: null,
56
+ source: sourceId,
57
+ }],
58
+ };
59
+ }
60
+ }
61
+ // Store and index
62
+ entities.set(id, entity);
63
+ sourceIndex.set(sourceKey(sourceId, externalId), id);
64
+ return entity;
65
+ }
66
+ /**
67
+ * Find entity by ID
68
+ */
69
+ export function getEntity(id) {
70
+ return entities.get(id);
71
+ }
72
+ /**
73
+ * Find entity by source reference
74
+ */
75
+ export function findEntityBySource(sourceId, externalId) {
76
+ const entityId = sourceIndex.get(sourceKey(sourceId, externalId));
77
+ return entityId ? entities.get(entityId) : undefined;
78
+ }
79
+ /**
80
+ * Update an entity's attribute (temporal update)
81
+ */
82
+ export function updateEntityAttribute(entityId, key, value, sourceId) {
83
+ const entity = entities.get(entityId);
84
+ if (!entity)
85
+ return undefined;
86
+ const now = new Date();
87
+ const existing = entity.attributes[key];
88
+ if (existing) {
89
+ // Close the current history entry
90
+ const currentHistory = existing.history.find(h => h.validTo === null);
91
+ if (currentHistory) {
92
+ currentHistory.validTo = now;
93
+ }
94
+ // Add new entry
95
+ existing.history.push({
96
+ value,
97
+ validFrom: now,
98
+ validTo: null,
99
+ source: sourceId,
100
+ });
101
+ existing.current = value;
102
+ }
103
+ else {
104
+ // New attribute
105
+ entity.attributes[key] = {
106
+ current: value,
107
+ history: [{
108
+ value,
109
+ validFrom: now,
110
+ validTo: null,
111
+ source: sourceId,
112
+ }],
113
+ };
114
+ }
115
+ entity.updatedAt = now;
116
+ return entity;
117
+ }
118
+ /**
119
+ * Find potential matches for a new record
120
+ */
121
+ export function findMatches(type, name, email, aliases) {
122
+ const candidates = [];
123
+ const searchTerms = [name.toLowerCase(), ...(aliases || []).map(a => a.toLowerCase())];
124
+ if (email)
125
+ searchTerms.push(email.toLowerCase());
126
+ for (const entity of entities.values()) {
127
+ if (entity.type !== type)
128
+ continue;
129
+ const matchedOn = [];
130
+ let score = 0;
131
+ // Check name
132
+ const entityName = entity.name.toLowerCase();
133
+ if (entityName === name.toLowerCase()) {
134
+ score += 0.8; // Exact name match is strong signal
135
+ matchedOn.push('exact_name');
136
+ }
137
+ else if (fuzzyMatch(entityName, name.toLowerCase()) > 0.85) {
138
+ score += 0.5;
139
+ matchedOn.push('fuzzy_name');
140
+ }
141
+ else if (fuzzyMatch(entityName, name.toLowerCase()) > 0.7) {
142
+ score += 0.3;
143
+ matchedOn.push('partial_name');
144
+ }
145
+ // Check email attribute
146
+ const entityEmail = entity.attributes['email']?.current;
147
+ if (email && entityEmail && entityEmail.toLowerCase() === email.toLowerCase()) {
148
+ score += 0.9; // Email is very strong signal
149
+ matchedOn.push('email');
150
+ }
151
+ // Check aliases
152
+ for (const alias of entity.aliases) {
153
+ if (searchTerms.includes(alias.toLowerCase())) {
154
+ score += 0.3;
155
+ matchedOn.push('alias');
156
+ break;
157
+ }
158
+ }
159
+ if (score > 0.3) {
160
+ candidates.push({ entity, score: Math.min(1, score), matchedOn });
161
+ }
162
+ }
163
+ return candidates.sort((a, b) => b.score - a.score);
164
+ }
165
+ /**
166
+ * Simple fuzzy string matching (Levenshtein-based similarity)
167
+ */
168
+ function fuzzyMatch(a, b) {
169
+ if (a === b)
170
+ return 1;
171
+ if (a.length === 0 || b.length === 0)
172
+ return 0;
173
+ const matrix = [];
174
+ for (let i = 0; i <= a.length; i++) {
175
+ matrix[i] = [i];
176
+ }
177
+ for (let j = 0; j <= b.length; j++) {
178
+ matrix[0][j] = j;
179
+ }
180
+ for (let i = 1; i <= a.length; i++) {
181
+ for (let j = 1; j <= b.length; j++) {
182
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
183
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
184
+ }
185
+ }
186
+ const distance = matrix[a.length][b.length];
187
+ const maxLen = Math.max(a.length, b.length);
188
+ return 1 - distance / maxLen;
189
+ }
190
+ /**
191
+ * Resolve or create: Find a matching entity or create a new one
192
+ */
193
+ export function resolveOrCreate(type, name, sourceId, externalId, options) {
194
+ // First, check if we already have this exact source reference
195
+ const existing = findEntityBySource(sourceId, externalId);
196
+ if (existing) {
197
+ // Update last seen
198
+ const sourceRef = existing.sources.find(s => s.sourceId === sourceId && s.externalId === externalId);
199
+ if (sourceRef)
200
+ sourceRef.lastSeen = new Date();
201
+ return { entity: existing, isNew: false, confidence: 1.0 };
202
+ }
203
+ // Try to find matches
204
+ const minConfidence = options?.minConfidence ?? 0.7;
205
+ const matches = findMatches(type, name, options?.email, options?.aliases);
206
+ if (matches.length > 0 && matches[0].score >= minConfidence) {
207
+ // Merge into existing entity
208
+ const match = matches[0];
209
+ const entity = match.entity;
210
+ // Add source reference
211
+ entity.sources.push({
212
+ sourceId,
213
+ externalId,
214
+ confidence: match.score,
215
+ lastSeen: new Date(),
216
+ });
217
+ sourceIndex.set(sourceKey(sourceId, externalId), entity.id);
218
+ // Add aliases
219
+ if (!entity.aliases.includes(name) && entity.name !== name) {
220
+ entity.aliases.push(name);
221
+ }
222
+ // Merge attributes
223
+ if (options?.attributes) {
224
+ for (const [key, value] of Object.entries(options.attributes)) {
225
+ updateEntityAttribute(entity.id, key, value, sourceId);
226
+ }
227
+ }
228
+ entity.updatedAt = new Date();
229
+ return { entity, isNew: false, confidence: match.score };
230
+ }
231
+ // Create new entity
232
+ const entity = createEntity(type, name, sourceId, externalId, options?.attributes);
233
+ if (options?.aliases) {
234
+ entity.aliases.push(...options.aliases);
235
+ }
236
+ return { entity, isNew: true, confidence: 1.0 };
237
+ }
238
+ /**
239
+ * Manually merge two entities
240
+ */
241
+ export function mergeEntities(keepId, mergeId) {
242
+ const keep = entities.get(keepId);
243
+ const merge = entities.get(mergeId);
244
+ if (!keep || !merge)
245
+ return undefined;
246
+ // Merge sources
247
+ for (const source of merge.sources) {
248
+ if (!keep.sources.find(s => s.sourceId === source.sourceId && s.externalId === source.externalId)) {
249
+ keep.sources.push(source);
250
+ }
251
+ // Update source index
252
+ sourceIndex.set(sourceKey(source.sourceId, source.externalId), keepId);
253
+ }
254
+ // Merge aliases
255
+ for (const alias of merge.aliases) {
256
+ if (!keep.aliases.includes(alias)) {
257
+ keep.aliases.push(alias);
258
+ }
259
+ }
260
+ if (!keep.aliases.includes(merge.name) && keep.name !== merge.name) {
261
+ keep.aliases.push(merge.name);
262
+ }
263
+ // Merge attributes (keep latest)
264
+ for (const [key, value] of Object.entries(merge.attributes)) {
265
+ if (!keep.attributes[key]) {
266
+ keep.attributes[key] = value;
267
+ }
268
+ else {
269
+ // Merge history
270
+ keep.attributes[key].history.push(...value.history);
271
+ // Sort by validFrom
272
+ keep.attributes[key].history.sort((a, b) => new Date(a.validFrom).getTime() - new Date(b.validFrom).getTime());
273
+ }
274
+ }
275
+ // Delete merged entity
276
+ entities.delete(mergeId);
277
+ keep.updatedAt = new Date();
278
+ return keep;
279
+ }
280
+ // ─── Query Helpers ───────────────────────────────────
281
+ /**
282
+ * Get all entities of a type
283
+ */
284
+ export function getEntitiesByType(type) {
285
+ return Array.from(entities.values()).filter(e => e.type === type);
286
+ }
287
+ /**
288
+ * Get all entities from a source
289
+ */
290
+ export function getEntitiesBySource(sourceId) {
291
+ return Array.from(entities.values()).filter(e => e.sources.some(s => s.sourceId === sourceId));
292
+ }
293
+ /**
294
+ * Get entity attribute value at a specific point in time
295
+ */
296
+ export function getAttributeAt(entity, key, at) {
297
+ const attr = entity.attributes[key];
298
+ if (!attr)
299
+ return undefined;
300
+ const timestamp = at.getTime();
301
+ for (const entry of attr.history) {
302
+ const from = new Date(entry.validFrom).getTime();
303
+ const to = entry.validTo ? new Date(entry.validTo).getTime() : Date.now();
304
+ if (timestamp >= from && timestamp <= to) {
305
+ return entry.value;
306
+ }
307
+ }
308
+ return undefined;
309
+ }
310
+ /**
311
+ * Get entity count by type (for stats)
312
+ */
313
+ export function getEntityStats() {
314
+ const stats = {};
315
+ for (const entity of entities.values()) {
316
+ stats[entity.type] = (stats[entity.type] || 0) + 1;
317
+ }
318
+ return stats;
319
+ }
320
+ /**
321
+ * Clear all entities (for testing)
322
+ */
323
+ export function clearEntities() {
324
+ entities.clear();
325
+ sourceIndex.clear();
326
+ }
327
+ //# sourceMappingURL=entity.js.map