sigma-memory 0.2.1 → 0.2.2
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/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -22
- package/dist/index.js.map +1 -1
- package/dist/notes.d.ts +1 -1
- package/dist/notes.d.ts.map +1 -1
- package/dist/notes.js +24 -24
- package/dist/notes.js.map +1 -1
- package/dist/ontology.d.ts +3 -3
- package/dist/ontology.d.ts.map +1 -1
- package/dist/ontology.js +26 -23
- package/dist/ontology.js.map +1 -1
- package/dist/types.d.ts +7 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +1 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +39 -33
- package/dist/vector-store.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +176 -176
- package/src/notes.ts +161 -161
- package/src/ontology.ts +359 -352
- package/src/types.ts +63 -63
- package/src/vector-store.ts +356 -357
- package/src/vendor.d.ts +21 -21
package/src/ontology.ts
CHANGED
|
@@ -1,354 +1,361 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import type {
|
|
5
|
+
MemoryConfig,
|
|
6
|
+
OntologyDeleteEntry,
|
|
7
|
+
OntologyEntity,
|
|
8
|
+
OntologyEntityEntry,
|
|
9
|
+
OntologyJSONLEntry,
|
|
10
|
+
OntologyRelation,
|
|
11
|
+
OntologyRelationEntry,
|
|
12
|
+
} from "./types.js";
|
|
13
13
|
|
|
14
14
|
export class OntologyManager {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
15
|
+
private config: MemoryConfig;
|
|
16
|
+
private graphPath: string;
|
|
17
|
+
private entities: Map<string, OntologyEntity> = new Map();
|
|
18
|
+
private relations: Map<string, OntologyRelation> = new Map();
|
|
19
|
+
private loaded = false;
|
|
20
|
+
|
|
21
|
+
constructor(config: MemoryConfig) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.graphPath = config.ontologyPath;
|
|
24
|
+
this.ensureDirectories();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private ensureDirectories(): void {
|
|
28
|
+
const dir = dirname(this.graphPath);
|
|
29
|
+
if (!existsSync(dir)) {
|
|
30
|
+
mkdirSync(dir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private generateId(): string {
|
|
35
|
+
return randomBytes(16).toString("hex");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private loadGraph(): void {
|
|
39
|
+
if (this.loaded) return;
|
|
40
|
+
|
|
41
|
+
this.entities.clear();
|
|
42
|
+
this.relations.clear();
|
|
43
|
+
|
|
44
|
+
if (!existsSync(this.graphPath)) {
|
|
45
|
+
this.loaded = true;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const content = readFileSync(this.graphPath, "utf8");
|
|
50
|
+
const lines = content
|
|
51
|
+
.trim()
|
|
52
|
+
.split("\n")
|
|
53
|
+
.filter((line) => line.trim());
|
|
54
|
+
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
try {
|
|
57
|
+
const entry: OntologyJSONLEntry = JSON.parse(line);
|
|
58
|
+
|
|
59
|
+
switch (entry.kind) {
|
|
60
|
+
case "entity":
|
|
61
|
+
this.entities.set(entry.id, {
|
|
62
|
+
id: entry.id,
|
|
63
|
+
type: entry.type,
|
|
64
|
+
name: entry.name,
|
|
65
|
+
properties: entry.properties,
|
|
66
|
+
createdAt: entry.createdAt,
|
|
67
|
+
updatedAt: entry.updatedAt,
|
|
68
|
+
});
|
|
69
|
+
break;
|
|
70
|
+
|
|
71
|
+
case "relation":
|
|
72
|
+
this.relations.set(entry.id, {
|
|
73
|
+
id: entry.id,
|
|
74
|
+
from: entry.from,
|
|
75
|
+
to: entry.to,
|
|
76
|
+
type: entry.type,
|
|
77
|
+
properties: entry.properties,
|
|
78
|
+
createdAt: entry.createdAt,
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case "delete":
|
|
83
|
+
// Delete entity or relation
|
|
84
|
+
this.entities.delete(entry.targetId);
|
|
85
|
+
this.relations.delete(entry.targetId);
|
|
86
|
+
// Also delete all relations linked to this entity
|
|
87
|
+
for (const [relationId, relation] of this.relations) {
|
|
88
|
+
if (relation.from === entry.targetId || relation.to === entry.targetId) {
|
|
89
|
+
this.relations.delete(relationId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Skip malformed JSONL line
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.loaded = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private appendToFile(entry: OntologyJSONLEntry): void {
|
|
103
|
+
this.ensureDirectories();
|
|
104
|
+
const line = JSON.stringify(entry) + "\n";
|
|
105
|
+
appendFileSync(this.graphPath, line, "utf8");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Ajoute une entité
|
|
110
|
+
*/
|
|
111
|
+
addEntity(entity: Omit<OntologyEntity, "id" | "createdAt" | "updatedAt">): string {
|
|
112
|
+
this.loadGraph();
|
|
113
|
+
|
|
114
|
+
const id = this.generateId();
|
|
115
|
+
const now = new Date().toISOString();
|
|
116
|
+
|
|
117
|
+
const newEntity: OntologyEntity = {
|
|
118
|
+
...entity,
|
|
119
|
+
id,
|
|
120
|
+
createdAt: now,
|
|
121
|
+
updatedAt: now,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
this.entities.set(id, newEntity);
|
|
125
|
+
|
|
126
|
+
const entry: OntologyEntityEntry = {
|
|
127
|
+
kind: "entity",
|
|
128
|
+
...newEntity,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
this.appendToFile(entry);
|
|
132
|
+
return id;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Ajoute une relation
|
|
137
|
+
*/
|
|
138
|
+
addRelation(relation: Omit<OntologyRelation, "id" | "createdAt">): string {
|
|
139
|
+
this.loadGraph();
|
|
140
|
+
|
|
141
|
+
// Verify source and destination entities exist
|
|
142
|
+
if (!this.entities.has(relation.from)) {
|
|
143
|
+
throw new Error(`Source entity not found: ${relation.from}`);
|
|
144
|
+
}
|
|
145
|
+
if (!this.entities.has(relation.to)) {
|
|
146
|
+
throw new Error(`Target entity not found: ${relation.to}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const id = this.generateId();
|
|
150
|
+
const now = new Date().toISOString();
|
|
151
|
+
|
|
152
|
+
const newRelation: OntologyRelation = {
|
|
153
|
+
...relation,
|
|
154
|
+
id,
|
|
155
|
+
createdAt: now,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
this.relations.set(id, newRelation);
|
|
159
|
+
|
|
160
|
+
const entry: OntologyRelationEntry = {
|
|
161
|
+
kind: "relation",
|
|
162
|
+
...newRelation,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
this.appendToFile(entry);
|
|
166
|
+
return id;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Recherche par id/type/nom
|
|
171
|
+
*/
|
|
172
|
+
findEntity(query: { id?: string; type?: string; name?: string }): OntologyEntity[] {
|
|
173
|
+
this.loadGraph();
|
|
174
|
+
|
|
175
|
+
// Direct ID lookup - return exact match
|
|
176
|
+
if (query.id) {
|
|
177
|
+
const entity = this.entities.get(query.id);
|
|
178
|
+
return entity ? [entity] : [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const results: OntologyEntity[] = [];
|
|
182
|
+
|
|
183
|
+
for (const entity of this.entities.values()) {
|
|
184
|
+
let matches = true;
|
|
185
|
+
|
|
186
|
+
if (query.type && entity.type !== query.type) {
|
|
187
|
+
matches = false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (query.name && !entity.name.toLowerCase().includes(query.name.toLowerCase())) {
|
|
191
|
+
matches = false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (matches) {
|
|
195
|
+
results.push(entity);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return results;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Toutes les relations d'une entité
|
|
204
|
+
*/
|
|
205
|
+
findRelations(entityId: string): OntologyRelation[] {
|
|
206
|
+
this.loadGraph();
|
|
207
|
+
|
|
208
|
+
const results: OntologyRelation[] = [];
|
|
209
|
+
|
|
210
|
+
for (const relation of this.relations.values()) {
|
|
211
|
+
if (relation.from === entityId || relation.to === entityId) {
|
|
212
|
+
results.push(relation);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return results;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Retourne le graphe complet
|
|
221
|
+
*/
|
|
222
|
+
getGraph(): { entities: OntologyEntity[]; relations: OntologyRelation[] } {
|
|
223
|
+
this.loadGraph();
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
entities: Array.from(this.entities.values()),
|
|
227
|
+
relations: Array.from(this.relations.values()),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Supprime entité + ses relations
|
|
233
|
+
*/
|
|
234
|
+
removeEntity(id: string): void {
|
|
235
|
+
this.loadGraph();
|
|
236
|
+
|
|
237
|
+
if (!this.entities.has(id)) {
|
|
238
|
+
throw new Error(`Entity not found: ${id}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Mark as deleted in file
|
|
242
|
+
const deleteEntry: OntologyDeleteEntry = {
|
|
243
|
+
kind: "delete",
|
|
244
|
+
targetId: id,
|
|
245
|
+
deletedAt: new Date().toISOString(),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
this.appendToFile(deleteEntry);
|
|
249
|
+
|
|
250
|
+
// Remove from memory
|
|
251
|
+
this.entities.delete(id);
|
|
252
|
+
|
|
253
|
+
// Remove all linked relations
|
|
254
|
+
for (const [relationId, relation] of this.relations) {
|
|
255
|
+
if (relation.from === id || relation.to === id) {
|
|
256
|
+
this.relations.delete(relationId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Trouve le chemin entre deux entités (BFS)
|
|
263
|
+
*/
|
|
264
|
+
queryPath(
|
|
265
|
+
fromId: string,
|
|
266
|
+
toId: string,
|
|
267
|
+
maxDepth = 5,
|
|
268
|
+
): Array<{ entity: OntologyEntity; relation?: OntologyRelation }> | null {
|
|
269
|
+
this.loadGraph();
|
|
270
|
+
|
|
271
|
+
if (!this.entities.has(fromId) || !this.entities.has(toId)) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (fromId === toId) {
|
|
276
|
+
return [{ entity: this.entities.get(fromId)! }];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// BFS pour trouver le chemin le plus court
|
|
280
|
+
const queue: Array<{ entityId: string; path: Array<{ entity: OntologyEntity; relation?: OntologyRelation }> }> = [
|
|
281
|
+
{ entityId: fromId, path: [{ entity: this.entities.get(fromId)! }] },
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
const visited = new Set<string>([fromId]);
|
|
285
|
+
|
|
286
|
+
while (queue.length > 0) {
|
|
287
|
+
const { entityId, path } = queue.shift()!;
|
|
288
|
+
|
|
289
|
+
if (path.length > maxDepth) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Trouve toutes les relations sortantes
|
|
294
|
+
for (const relation of this.relations.values()) {
|
|
295
|
+
if (relation.from === entityId) {
|
|
296
|
+
const targetId = relation.to;
|
|
297
|
+
|
|
298
|
+
if (targetId === toId) {
|
|
299
|
+
// Found!
|
|
300
|
+
return [...path, { entity: this.entities.get(targetId)!, relation }];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!visited.has(targetId)) {
|
|
304
|
+
visited.add(targetId);
|
|
305
|
+
queue.push({
|
|
306
|
+
entityId: targetId,
|
|
307
|
+
path: [...path, { entity: this.entities.get(targetId)!, relation }],
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Relations bidirectionnelles (from <-> to)
|
|
313
|
+
if (relation.to === entityId) {
|
|
314
|
+
const targetId = relation.from;
|
|
315
|
+
|
|
316
|
+
if (targetId === toId) {
|
|
317
|
+
// Found!
|
|
318
|
+
return [...path, { entity: this.entities.get(targetId)!, relation }];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!visited.has(targetId)) {
|
|
322
|
+
visited.add(targetId);
|
|
323
|
+
queue.push({
|
|
324
|
+
entityId: targetId,
|
|
325
|
+
path: [...path, { entity: this.entities.get(targetId)!, relation }],
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return null; // No path found
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Exporte tout le graphe en JSON lisible
|
|
337
|
+
*/
|
|
338
|
+
export(): { entities: OntologyEntity[]; relations: OntologyRelation[] } {
|
|
339
|
+
return this.getGraph();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Statistiques : nombre d'entités par type, nombre de relations par type
|
|
344
|
+
*/
|
|
345
|
+
stats(): { entitiesByType: Record<string, number>; relationsByType: Record<string, number> } {
|
|
346
|
+
this.loadGraph();
|
|
347
|
+
|
|
348
|
+
const entitiesByType: Record<string, number> = {};
|
|
349
|
+
const relationsByType: Record<string, number> = {};
|
|
350
|
+
|
|
351
|
+
for (const entity of this.entities.values()) {
|
|
352
|
+
entitiesByType[entity.type] = (entitiesByType[entity.type] || 0) + 1;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for (const relation of this.relations.values()) {
|
|
356
|
+
relationsByType[relation.type] = (relationsByType[relation.type] || 0) + 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { entitiesByType, relationsByType };
|
|
360
|
+
}
|
|
361
|
+
}
|