@unrdf/react 26.4.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/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@unrdf/react",
3
+ "version": "26.4.2",
4
+ "description": "UNRDF React - AI Semantic Analysis Tools for RDF Knowledge Graphs (Optional Extension)",
5
+ "type": "module",
6
+ "main": "src/index.mjs",
7
+ "exports": {
8
+ ".": "./src/index.mjs",
9
+ "./ai-semantic": "./src/ai-semantic/index.mjs",
10
+ "./semantic-analyzer": "./src/ai-semantic/semantic-analyzer.mjs",
11
+ "./embeddings-manager": "./src/ai-semantic/embeddings-manager.mjs",
12
+ "./nlp-query-builder": "./src/ai-semantic/nlp-query-builder.mjs",
13
+ "./anomaly-detector": "./src/ai-semantic/anomaly-detector.mjs"
14
+ },
15
+ "sideEffects": false,
16
+ "files": [
17
+ "src/",
18
+ "dist/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "test": "vitest run --coverage",
24
+ "test:fast": "vitest run --coverage",
25
+ "test:watch": "vitest --coverage",
26
+ "build": "node build.config.mjs",
27
+ "lint": "eslint src/ --max-warnings=0",
28
+ "lint:fix": "eslint src/ --fix",
29
+ "format": "prettier --write src/",
30
+ "format:check": "prettier --check src/",
31
+ "clean": "rm -rf dist/ .nyc_output/ coverage/",
32
+ "dev": "echo 'Development mode for @unrdf/react'"
33
+ },
34
+ "keywords": [
35
+ "rdf",
36
+ "react",
37
+ "ai",
38
+ "semantic",
39
+ "nlp",
40
+ "embeddings",
41
+ "anomaly-detection",
42
+ "knowledge-graph"
43
+ ],
44
+ "dependencies": {
45
+ "@opentelemetry/api": "^1.9.0",
46
+ "@unrdf/core": "workspace:*",
47
+ "@unrdf/oxigraph": "workspace:*",
48
+ "lru-cache": "^11.1.0",
49
+ "zod": "^4.1.13"
50
+ },
51
+ "peerDependencies": {
52
+ "react": "^18.0.0 || ^19.0.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "react": {
56
+ "optional": true
57
+ }
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^24.10.1",
61
+ "vitest": "^4.0.15"
62
+ },
63
+ "engines": {
64
+ "node": ">=18.0.0"
65
+ },
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "https://github.com/unrdf/unrdf.git",
69
+ "directory": "packages/react"
70
+ },
71
+ "bugs": {
72
+ "url": "https://github.com/unrdf/unrdf/issues"
73
+ },
74
+ "homepage": "https://github.com/unrdf/unrdf#readme",
75
+ "license": "MIT"
76
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * AI Semantic Analysis Module
3
+ *
4
+ * Provides AI-powered semantic analysis tools for RDF knowledge graphs.
5
+ *
6
+ * @module ai-semantic
7
+ */
8
+
9
+ export {
10
+ SemanticAnalyzer,
11
+ createSemanticAnalyzer,
12
+ defaultSemanticAnalyzer,
13
+ } from './semantic-analyzer.mjs';
14
+
15
+ export {
16
+ EmbeddingsManager,
17
+ createEmbeddingsManager,
18
+ defaultEmbeddingsManager,
19
+ } from './embeddings-manager.mjs';
20
+
21
+ export {
22
+ NLPQueryBuilder,
23
+ createNLPQueryBuilder,
24
+ defaultNLPQueryBuilder,
25
+ } from './nlp-query-builder.mjs';
26
+
27
+ export {
28
+ AnomalyDetector,
29
+ createAnomalyDetector,
30
+ defaultAnomalyDetector,
31
+ } from './anomaly-detector.mjs';
@@ -0,0 +1,781 @@
1
+ /**
2
+ * @file AI Semantic Analyzer for RDF Graph Analysis
3
+ * @module ai-semantic/semantic-analyzer
4
+ *
5
+ * @description
6
+ * Implements AI-powered semantic analysis for RDF knowledge graphs.
7
+ * Analyzes semantic relationships, extracts key concepts, computes similarity,
8
+ * and suggests ontology improvements based on data patterns.
9
+ *
10
+ * Integrates with UNRDF's Knowledge Hook system and provides OTEL observability.
11
+ */
12
+
13
+ import { trace, SpanStatusCode } from '@opentelemetry/api';
14
+ import LRUCache from 'lru-cache';
15
+ import { z } from 'zod';
16
+
17
+ const tracer = trace.getTracer('unrdf-ai-semantic');
18
+
19
+ /**
20
+ * Semantic analysis result schema
21
+ */
22
+ const SemanticAnalysisResultSchema = z.object({
23
+ concepts: z.array(
24
+ z.object({
25
+ uri: z.string(),
26
+ label: z.string().optional(),
27
+ frequency: z.number(),
28
+ centrality: z.number(),
29
+ type: z.string().optional(),
30
+ })
31
+ ),
32
+ relationships: z.array(
33
+ z.object({
34
+ subject: z.string(),
35
+ predicate: z.string(),
36
+ object: z.string(),
37
+ strength: z.number(),
38
+ })
39
+ ),
40
+ patterns: z.array(
41
+ z.object({
42
+ pattern: z.string(),
43
+ count: z.number(),
44
+ confidence: z.number(),
45
+ })
46
+ ),
47
+ suggestions: z.array(
48
+ z.object({
49
+ type: z.enum([
50
+ 'missing_inverse',
51
+ 'missing_subclass',
52
+ 'inconsistent_domain',
53
+ 'redundant_property',
54
+ ]),
55
+ description: z.string(),
56
+ priority: z.enum(['high', 'medium', 'low']),
57
+ })
58
+ ),
59
+ statistics: z.object({
60
+ totalTriples: z.number(),
61
+ uniqueSubjects: z.number(),
62
+ uniquePredicates: z.number(),
63
+ uniqueObjects: z.number(),
64
+ avgDegree: z.number(),
65
+ density: z.number(),
66
+ }),
67
+ duration: z.number(),
68
+ });
69
+
70
+ /**
71
+ * Semantic similarity result schema
72
+ */
73
+ const SimilarityResultSchema = z.object({
74
+ similarity: z.number().min(0).max(1),
75
+ method: z.string(),
76
+ commonProperties: z.array(z.string()),
77
+ commonNeighbors: z.array(z.string()),
78
+ });
79
+
80
+ /**
81
+ * Semantic Analyzer Configuration
82
+ */
83
+ const SemanticAnalyzerConfigSchema = z.object({
84
+ cacheSize: z.number().default(1000),
85
+ enableCache: z.boolean().default(true),
86
+ maxConcepts: z.number().default(100),
87
+ minConceptFrequency: z.number().default(2),
88
+ similarityThreshold: z.number().min(0).max(1).default(0.7),
89
+ });
90
+
91
+ /**
92
+ * AI Semantic Analyzer for RDF graphs
93
+ */
94
+ export class SemanticAnalyzer {
95
+ /**
96
+ * Create a new semantic analyzer
97
+ * @param {Object} [config] - Analyzer configuration
98
+ */
99
+ constructor(config = {}) {
100
+ this.config = SemanticAnalyzerConfigSchema.parse(config);
101
+
102
+ // LRU cache for analysis results
103
+ this.cache = this.config.enableCache ? new LRUCache({ max: this.config.cacheSize }) : null;
104
+
105
+ // Statistics
106
+ this.stats = {
107
+ analyses: 0,
108
+ cacheHits: 0,
109
+ cacheMisses: 0,
110
+ avgDuration: 0,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Analyze an RDF graph to understand semantic relationships
116
+ * @param {Store} store - RDF store to analyze
117
+ * @param {Object} [options] - Analysis options
118
+ * @param {boolean} [options.useCache=true] - Use cached results
119
+ * @param {number} [options.maxConcepts] - Maximum concepts to extract
120
+ * @returns {Promise<Object>} Analysis results
121
+ */
122
+ async analyze(store, options = {}) {
123
+ return tracer.startActiveSpan('semantic.analyze', async span => {
124
+ const startTime = Date.now();
125
+
126
+ try {
127
+ span.setAttributes({
128
+ 'semantic.store_size': store.size,
129
+ 'semantic.cache_enabled': this.config.enableCache,
130
+ });
131
+
132
+ // Check cache
133
+ const cacheKey = this._getCacheKey(store);
134
+ if (options.useCache !== false && this.cache) {
135
+ const cached = this.cache.get(cacheKey);
136
+ if (cached) {
137
+ this.stats.cacheHits++;
138
+ span.setAttribute('semantic.cache_hit', true);
139
+ span.setStatus({ code: SpanStatusCode.OK });
140
+ return cached;
141
+ }
142
+ this.stats.cacheMisses++;
143
+ }
144
+
145
+ // Extract concepts and entities
146
+ const concepts = await this._extractConcepts(store, options.maxConcepts);
147
+ span.setAttribute('semantic.concepts_count', concepts.length);
148
+
149
+ // Analyze relationships
150
+ const relationships = await this._analyzeRelationships(store);
151
+ span.setAttribute('semantic.relationships_count', relationships.length);
152
+
153
+ // Detect patterns
154
+ const patterns = await this._detectPatterns(store);
155
+ span.setAttribute('semantic.patterns_count', patterns.length);
156
+
157
+ // Generate suggestions
158
+ const suggestions = await this._generateSuggestions(store, concepts, relationships);
159
+ span.setAttribute('semantic.suggestions_count', suggestions.length);
160
+
161
+ // Calculate statistics
162
+ const statistics = await this._calculateStatistics(store);
163
+
164
+ const duration = Date.now() - startTime;
165
+ const result = SemanticAnalysisResultSchema.parse({
166
+ concepts,
167
+ relationships,
168
+ patterns,
169
+ suggestions,
170
+ statistics,
171
+ duration,
172
+ });
173
+
174
+ // Update cache
175
+ if (this.cache) {
176
+ this.cache.set(cacheKey, result);
177
+ }
178
+
179
+ // Update stats
180
+ this.stats.analyses++;
181
+ this.stats.avgDuration =
182
+ (this.stats.avgDuration * (this.stats.analyses - 1) + duration) / this.stats.analyses;
183
+
184
+ span.setAttributes({
185
+ 'semantic.duration_ms': duration,
186
+ 'semantic.analysis_complete': true,
187
+ });
188
+ span.setStatus({ code: SpanStatusCode.OK });
189
+
190
+ return result;
191
+ } catch (error) {
192
+ span.recordException(error);
193
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
194
+ throw error;
195
+ }
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Extract key concepts and entities from the graph
201
+ * @param {Store} store - RDF store
202
+ * @param {number} [maxConcepts] - Maximum concepts to return
203
+ * @returns {Promise<Array>} Key concepts
204
+ * @private
205
+ */
206
+ async _extractConcepts(store, maxConcepts) {
207
+ return tracer.startActiveSpan('semantic.extract_concepts', async span => {
208
+ try {
209
+ const conceptMap = new Map();
210
+
211
+ // Count subject occurrences
212
+ for (const quad of store) {
213
+ const subject = quad.subject.value;
214
+ if (!conceptMap.has(subject)) {
215
+ conceptMap.set(subject, {
216
+ uri: subject,
217
+ frequency: 0,
218
+ types: new Set(),
219
+ labels: new Set(),
220
+ });
221
+ }
222
+ const concept = conceptMap.get(subject);
223
+ concept.frequency++;
224
+
225
+ // Track types
226
+ if (quad.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') {
227
+ concept.types.add(quad.object.value);
228
+ }
229
+
230
+ // Track labels
231
+ if (quad.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#label') {
232
+ concept.labels.add(quad.object.value);
233
+ }
234
+ }
235
+
236
+ // Calculate centrality (simplified PageRank-like metric)
237
+ const centrality = await this._calculateCentrality(store, conceptMap);
238
+
239
+ // Convert to array and filter
240
+ let concepts = Array.from(conceptMap.entries())
241
+ .map(([uri, data]) => ({
242
+ uri,
243
+ label: data.labels.size > 0 ? Array.from(data.labels)[0] : undefined,
244
+ frequency: data.frequency,
245
+ centrality: centrality.get(uri) || 0,
246
+ type: data.types.size > 0 ? Array.from(data.types)[0] : undefined,
247
+ }))
248
+ .filter(c => c.frequency >= this.config.minConceptFrequency)
249
+ .sort((a, b) => b.centrality - a.centrality);
250
+
251
+ // Limit results
252
+ const limit = maxConcepts || this.config.maxConcepts;
253
+ concepts = concepts.slice(0, limit);
254
+
255
+ span.setAttribute('semantic.concepts_extracted', concepts.length);
256
+ span.setStatus({ code: SpanStatusCode.OK });
257
+ return concepts;
258
+ } catch (error) {
259
+ span.recordException(error);
260
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
261
+ throw error;
262
+ }
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Calculate centrality scores for concepts (simplified PageRank)
268
+ * @param {Store} store - RDF store
269
+ * @param {Map} conceptMap - Map of concepts
270
+ * @returns {Promise<Map>} Centrality scores
271
+ * @private
272
+ */
273
+ async _calculateCentrality(store, conceptMap) {
274
+ const centrality = new Map();
275
+ const damping = 0.85;
276
+ const iterations = 10;
277
+
278
+ // Initialize
279
+ for (const uri of conceptMap.keys()) {
280
+ centrality.set(uri, 1.0);
281
+ }
282
+
283
+ // Build adjacency list
284
+ const outLinks = new Map();
285
+ const inLinks = new Map();
286
+
287
+ for (const quad of store) {
288
+ const subj = quad.subject.value;
289
+ const obj = quad.object.value;
290
+
291
+ if (conceptMap.has(subj) && conceptMap.has(obj)) {
292
+ if (!outLinks.has(subj)) outLinks.set(subj, []);
293
+ if (!inLinks.has(obj)) inLinks.set(obj, []);
294
+ outLinks.get(subj).push(obj);
295
+ inLinks.get(obj).push(subj);
296
+ }
297
+ }
298
+
299
+ // PageRank iterations
300
+ for (let i = 0; i < iterations; i++) {
301
+ const newCentrality = new Map();
302
+
303
+ for (const uri of conceptMap.keys()) {
304
+ let rank = 1 - damping;
305
+ const incoming = inLinks.get(uri) || [];
306
+
307
+ for (const source of incoming) {
308
+ const outDegree = (outLinks.get(source) || []).length;
309
+ if (outDegree > 0) {
310
+ rank += damping * (centrality.get(source) / outDegree);
311
+ }
312
+ }
313
+
314
+ newCentrality.set(uri, rank);
315
+ }
316
+
317
+ centrality.clear();
318
+ for (const [uri, rank] of newCentrality) {
319
+ centrality.set(uri, rank);
320
+ }
321
+ }
322
+
323
+ return centrality;
324
+ }
325
+
326
+ /**
327
+ * Analyze relationships between concepts
328
+ * @param {Store} store - RDF store
329
+ * @returns {Promise<Array>} Relationships with strength scores
330
+ * @private
331
+ */
332
+ async _analyzeRelationships(store) {
333
+ return tracer.startActiveSpan('semantic.analyze_relationships', async span => {
334
+ try {
335
+ const relationshipMap = new Map();
336
+
337
+ for (const quad of store) {
338
+ const key = `${quad.subject.value}|${quad.predicate.value}|${quad.object.value}`;
339
+ if (!relationshipMap.has(key)) {
340
+ relationshipMap.set(key, {
341
+ subject: quad.subject.value,
342
+ predicate: quad.predicate.value,
343
+ object: quad.object.value,
344
+ count: 0,
345
+ });
346
+ }
347
+ relationshipMap.get(key).count++;
348
+ }
349
+
350
+ // Calculate relationship strength (normalized by max count)
351
+ const maxCount = Math.max(...Array.from(relationshipMap.values()).map(r => r.count), 1);
352
+
353
+ const relationships = Array.from(relationshipMap.values())
354
+ .map(r => ({
355
+ subject: r.subject,
356
+ predicate: r.predicate,
357
+ object: r.object,
358
+ strength: r.count / maxCount,
359
+ }))
360
+ .sort((a, b) => b.strength - a.strength)
361
+ .slice(0, 100); // Top 100 relationships
362
+
363
+ span.setAttribute('semantic.relationships_analyzed', relationships.length);
364
+ span.setStatus({ code: SpanStatusCode.OK });
365
+ return relationships;
366
+ } catch (error) {
367
+ span.recordException(error);
368
+ span.setStatus({
369
+ code: SpanStatusCode.ERROR,
370
+ message: error.message,
371
+ });
372
+ throw error;
373
+ }
374
+ });
375
+ }
376
+
377
+ /**
378
+ * Detect common patterns in the graph
379
+ * @param {Store} store - RDF store
380
+ * @returns {Promise<Array>} Detected patterns
381
+ * @private
382
+ */
383
+ async _detectPatterns(store) {
384
+ return tracer.startActiveSpan('semantic.detect_patterns', async span => {
385
+ try {
386
+ const patterns = [];
387
+ const predicateCount = new Map();
388
+
389
+ // Count predicate usage
390
+ for (const quad of store) {
391
+ const pred = quad.predicate.value;
392
+ predicateCount.set(pred, (predicateCount.get(pred) || 0) + 1);
393
+ }
394
+
395
+ // Identify common predicates
396
+ const totalTriples = store.size;
397
+ for (const [predicate, count] of predicateCount.entries()) {
398
+ const confidence = count / totalTriples;
399
+ if (count >= 3) {
400
+ patterns.push({
401
+ pattern: `Common predicate: ${predicate}`,
402
+ count,
403
+ confidence,
404
+ });
405
+ }
406
+ }
407
+
408
+ // Detect type patterns
409
+ const typePatterns = await this._detectTypePatterns(store);
410
+ patterns.push(...typePatterns);
411
+
412
+ // Sort by confidence
413
+ patterns.sort((a, b) => b.confidence - a.confidence);
414
+
415
+ span.setAttribute('semantic.patterns_detected', patterns.length);
416
+ span.setStatus({ code: SpanStatusCode.OK });
417
+ return patterns.slice(0, 20); // Top 20 patterns
418
+ } catch (error) {
419
+ span.recordException(error);
420
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
421
+ throw error;
422
+ }
423
+ });
424
+ }
425
+
426
+ /**
427
+ * Detect type-based patterns
428
+ * @param {Store} store - RDF store
429
+ * @returns {Promise<Array>} Type patterns
430
+ * @private
431
+ */
432
+ async _detectTypePatterns(store) {
433
+ const typeCount = new Map();
434
+ const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
435
+
436
+ for (const quad of store) {
437
+ if (quad.predicate.value === RDF_TYPE) {
438
+ const type = quad.object.value;
439
+ typeCount.set(type, (typeCount.get(type) || 0) + 1);
440
+ }
441
+ }
442
+
443
+ const totalEntities = typeCount.size;
444
+ return Array.from(typeCount.entries())
445
+ .filter(([_, count]) => count >= 2)
446
+ .map(([type, count]) => ({
447
+ pattern: `Entity type: ${type}`,
448
+ count,
449
+ confidence: count / Math.max(totalEntities, 1),
450
+ }));
451
+ }
452
+
453
+ /**
454
+ * Generate ontology improvement suggestions
455
+ * @param {Store} store - RDF store
456
+ * @param {Array} concepts - Extracted concepts
457
+ * @param {Array} relationships - Analyzed relationships
458
+ * @returns {Promise<Array>} Suggestions
459
+ * @private
460
+ */
461
+ async _generateSuggestions(store, concepts, relationships) {
462
+ return tracer.startActiveSpan('semantic.generate_suggestions', async span => {
463
+ try {
464
+ const suggestions = [];
465
+
466
+ // Check for missing inverse properties
467
+ const inverseCheck = await this._checkMissingInverses(store, relationships);
468
+ suggestions.push(...inverseCheck);
469
+
470
+ // Check for inconsistent domains/ranges
471
+ const domainCheck = await this._checkInconsistentDomains(store);
472
+ suggestions.push(...domainCheck);
473
+
474
+ // Check for potential subclass relationships
475
+ const subclassCheck = await this._checkPotentialSubclasses(store, concepts);
476
+ suggestions.push(...subclassCheck);
477
+
478
+ span.setAttribute('semantic.suggestions_generated', suggestions.length);
479
+ span.setStatus({ code: SpanStatusCode.OK });
480
+ return suggestions;
481
+ } catch (error) {
482
+ span.recordException(error);
483
+ span.setStatus({
484
+ code: SpanStatusCode.ERROR,
485
+ message: error.message,
486
+ });
487
+ throw error;
488
+ }
489
+ });
490
+ }
491
+
492
+ /**
493
+ * Check for missing inverse properties
494
+ * @param {Store} store - RDF store
495
+ * @param {Array} relationships - Relationships
496
+ * @returns {Promise<Array>} Suggestions
497
+ * @private
498
+ */
499
+ async _checkMissingInverses(store, relationships) {
500
+ const suggestions = [];
501
+ const predicates = new Set(relationships.map(r => r.predicate));
502
+
503
+ for (const pred of predicates) {
504
+ // Simple heuristic: check if there's a reverse relationship pattern
505
+ const forward = relationships.filter(r => r.predicate === pred);
506
+ const reverse = relationships.filter(r =>
507
+ forward.some(f => f.subject === r.object && f.object === r.subject)
508
+ );
509
+
510
+ if (forward.length > 5 && reverse.length === 0) {
511
+ suggestions.push({
512
+ type: 'missing_inverse',
513
+ description: `Consider adding inverse property for ${pred}`,
514
+ priority: 'medium',
515
+ });
516
+ }
517
+ }
518
+
519
+ return suggestions;
520
+ }
521
+
522
+ /**
523
+ * Check for inconsistent domain/range definitions
524
+ * @param {Store} store - RDF store
525
+ * @returns {Promise<Array>} Suggestions
526
+ * @private
527
+ */
528
+ async _checkInconsistentDomains(store) {
529
+ const suggestions = [];
530
+ const RDFS_DOMAIN = 'http://www.w3.org/2000/01/rdf-schema#domain';
531
+ const _RDFS_RANGE = 'http://www.w3.org/2000/01/rdf-schema#range';
532
+
533
+ // This is a simplified check - a full implementation would be more comprehensive
534
+ const domainMap = new Map();
535
+
536
+ for (const quad of store) {
537
+ if (quad.predicate.value === RDFS_DOMAIN) {
538
+ domainMap.set(quad.subject.value, quad.object.value);
539
+ }
540
+ }
541
+
542
+ // Check for predicates used with entities outside their declared domain
543
+ for (const quad of store) {
544
+ const expectedDomain = domainMap.get(quad.predicate.value);
545
+ if (expectedDomain) {
546
+ // Would need to check actual types - simplified here
547
+ // In practice, query for rdf:type of subjects
548
+ }
549
+ }
550
+
551
+ return suggestions;
552
+ }
553
+
554
+ /**
555
+ * Check for potential subclass relationships
556
+ * @param {Store} store - RDF store
557
+ * @param {Array} concepts - Concepts
558
+ * @returns {Promise<Array>} Suggestions
559
+ * @private
560
+ */
561
+ async _checkPotentialSubclasses(store, _concepts) {
562
+ const suggestions = [];
563
+ const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
564
+
565
+ // Find entities with multiple types that might indicate subclass relationships
566
+ const entityTypes = new Map();
567
+
568
+ for (const quad of store) {
569
+ if (quad.predicate.value === RDF_TYPE) {
570
+ if (!entityTypes.has(quad.subject.value)) {
571
+ entityTypes.set(quad.subject.value, new Set());
572
+ }
573
+ entityTypes.get(quad.subject.value).add(quad.object.value);
574
+ }
575
+ }
576
+
577
+ // Entities with multiple types might suggest missing subclass axioms
578
+ for (const [entity, types] of entityTypes.entries()) {
579
+ if (types.size > 1) {
580
+ const typeArray = Array.from(types);
581
+ suggestions.push({
582
+ type: 'missing_subclass',
583
+ description: `Entity ${entity} has multiple types ${typeArray.join(', ')} - consider adding subclass relationships`,
584
+ priority: 'low',
585
+ });
586
+ }
587
+ }
588
+
589
+ return suggestions.slice(0, 5); // Limit suggestions
590
+ }
591
+
592
+ /**
593
+ * Calculate graph statistics
594
+ * @param {Store} store - RDF store
595
+ * @returns {Promise<Object>} Statistics
596
+ * @private
597
+ */
598
+ async _calculateStatistics(store) {
599
+ const subjects = new Set();
600
+ const predicates = new Set();
601
+ const objects = new Set();
602
+
603
+ for (const quad of store) {
604
+ subjects.add(quad.subject.value);
605
+ predicates.add(quad.predicate.value);
606
+ objects.add(quad.object.value);
607
+ }
608
+
609
+ const uniqueSubjects = subjects.size;
610
+ const uniquePredicates = predicates.size;
611
+ const uniqueObjects = objects.size;
612
+ const totalTriples = store.size;
613
+
614
+ // Calculate average degree (average number of edges per node)
615
+ const nodes = new Set([...subjects, ...objects]);
616
+ const avgDegree = totalTriples / Math.max(nodes.size, 1);
617
+
618
+ // Calculate density (actual edges / possible edges)
619
+ const maxPossibleEdges = nodes.size * (nodes.size - 1);
620
+ const density = maxPossibleEdges > 0 ? totalTriples / maxPossibleEdges : 0;
621
+
622
+ return {
623
+ totalTriples,
624
+ uniqueSubjects,
625
+ uniquePredicates,
626
+ uniqueObjects,
627
+ avgDegree,
628
+ density,
629
+ };
630
+ }
631
+
632
+ /**
633
+ * Compute semantic similarity between two concepts
634
+ * @param {Store} store - RDF store
635
+ * @param {string} concept1 - First concept URI
636
+ * @param {string} concept2 - Second concept URI
637
+ * @param {Object} [options] - Similarity options
638
+ * @returns {Promise<Object>} Similarity result
639
+ */
640
+ async computeSimilarity(store, concept1, concept2, _options = {}) {
641
+ return tracer.startActiveSpan('semantic.compute_similarity', async span => {
642
+ try {
643
+ span.setAttributes({
644
+ 'semantic.concept1': concept1,
645
+ 'semantic.concept2': concept2,
646
+ });
647
+
648
+ // Get properties for both concepts
649
+ const props1 = this._getConceptProperties(store, concept1);
650
+ const props2 = this._getConceptProperties(store, concept2);
651
+
652
+ // Get neighbors
653
+ const neighbors1 = this._getConceptNeighbors(store, concept1);
654
+ const neighbors2 = this._getConceptNeighbors(store, concept2);
655
+
656
+ // Jaccard similarity on properties
657
+ const commonProps = props1.filter(p => props2.includes(p));
658
+ const propSimilarity =
659
+ commonProps.length / Math.max(new Set([...props1, ...props2]).size, 1);
660
+
661
+ // Jaccard similarity on neighbors
662
+ const commonNeighbors = neighbors1.filter(n => neighbors2.includes(n));
663
+ const neighborSimilarity =
664
+ commonNeighbors.length / Math.max(new Set([...neighbors1, ...neighbors2]).size, 1);
665
+
666
+ // Combined similarity (weighted average)
667
+ const similarity = propSimilarity * 0.6 + neighborSimilarity * 0.4;
668
+
669
+ const result = SimilarityResultSchema.parse({
670
+ similarity,
671
+ method: 'jaccard',
672
+ commonProperties: commonProps,
673
+ commonNeighbors: commonNeighbors,
674
+ });
675
+
676
+ span.setAttributes({
677
+ 'semantic.similarity_score': similarity,
678
+ 'semantic.common_properties': commonProps.length,
679
+ 'semantic.common_neighbors': commonNeighbors.length,
680
+ });
681
+ span.setStatus({ code: SpanStatusCode.OK });
682
+
683
+ return result;
684
+ } catch (error) {
685
+ span.recordException(error);
686
+ span.setStatus({
687
+ code: SpanStatusCode.ERROR,
688
+ message: error.message,
689
+ });
690
+ throw error;
691
+ }
692
+ });
693
+ }
694
+
695
+ /**
696
+ * Get properties of a concept
697
+ * @param {Store} store - RDF store
698
+ * @param {string} conceptUri - Concept URI
699
+ * @returns {Array<string>} Property URIs
700
+ * @private
701
+ */
702
+ _getConceptProperties(store, conceptUri) {
703
+ const properties = [];
704
+ for (const quad of store) {
705
+ if (quad.subject.value === conceptUri) {
706
+ properties.push(quad.predicate.value);
707
+ }
708
+ }
709
+ return properties;
710
+ }
711
+
712
+ /**
713
+ * Get neighbors of a concept
714
+ * @param {Store} store - RDF store
715
+ * @param {string} conceptUri - Concept URI
716
+ * @returns {Array<string>} Neighbor URIs
717
+ * @private
718
+ */
719
+ _getConceptNeighbors(store, conceptUri) {
720
+ const neighbors = [];
721
+ for (const quad of store) {
722
+ if (quad.subject.value === conceptUri) {
723
+ neighbors.push(quad.object.value);
724
+ }
725
+ if (quad.object.value === conceptUri) {
726
+ neighbors.push(quad.subject.value);
727
+ }
728
+ }
729
+ return neighbors;
730
+ }
731
+
732
+ /**
733
+ * Generate cache key for a store
734
+ * @param {Store} store - RDF store
735
+ * @returns {string} Cache key
736
+ * @private
737
+ */
738
+ _getCacheKey(store) {
739
+ // Simple hash based on size and a sample of quads
740
+ const sample = Array.from(store)
741
+ .slice(0, 10)
742
+ .map(q => `${q.subject.value}|${q.predicate.value}|${q.object.value}`)
743
+ .join('::');
744
+ return `${store.size}:${sample}`;
745
+ }
746
+
747
+ /**
748
+ * Clear the analysis cache
749
+ */
750
+ clearCache() {
751
+ if (this.cache) {
752
+ this.cache.clear();
753
+ }
754
+ }
755
+
756
+ /**
757
+ * Get analyzer statistics
758
+ * @returns {Object} Statistics
759
+ */
760
+ getStats() {
761
+ return {
762
+ ...this.stats,
763
+ cacheSize: this.cache ? this.cache.size : 0,
764
+ cacheHitRate: this.stats.analyses > 0 ? this.stats.cacheHits / this.stats.analyses : 0,
765
+ };
766
+ }
767
+ }
768
+
769
+ /**
770
+ * Create a semantic analyzer instance
771
+ * @param {Object} [config] - Configuration
772
+ * @returns {SemanticAnalyzer} Semantic analyzer
773
+ */
774
+ export function createSemanticAnalyzer(config = {}) {
775
+ return new SemanticAnalyzer(config);
776
+ }
777
+
778
+ /**
779
+ * Default semantic analyzer instance
780
+ */
781
+ export const defaultSemanticAnalyzer = createSemanticAnalyzer();
package/src/hooks.mjs ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @fileoverview All hooks export (for tree-shaking)
3
+ * @module react-hooks/hooks
4
+ */
5
+
6
+ // Core
7
+ export { useKnowledgeEngine } from './core/useKnowledgeEngine.mjs';
8
+ export { useStore } from './core/useStore.mjs';
9
+ export { useTriples } from './core/useTriples.mjs';
10
+ export { useGraphs } from './core/useGraphs.mjs';
11
+ export { useTerms } from './core/useTerms.mjs';
12
+
13
+ // Query
14
+ export { useSPARQLQuery } from './query/useSPARQLQuery.mjs';
15
+ export { useQueryAsync } from './query/useQueryAsync.mjs';
16
+ export { useShapeValidation } from './query/useShapeValidation.mjs';
17
+ export { useReasoning } from './query/useReasoning.mjs';
18
+ export { useDeltaQuery } from './query/useDeltaQuery.mjs';
19
+
20
+ // Knowledge Hooks
21
+ export { useKnowledgeHook } from './knowledge-hooks/useKnowledgeHook.mjs';
22
+ export { useHookManager } from './knowledge-hooks/useHookManager.mjs';
23
+ export { useHookRegistry } from './knowledge-hooks/useHookRegistry.mjs';
24
+ export { useHookExecution } from './knowledge-hooks/useHookExecution.mjs';
25
+
26
+ // Storage
27
+ // useIndexedDBStore removed - use useOfflineStore from './composition/use-offline-store.mjs' instead
28
+ export { useQuadStore } from './storage/useQuadStore.mjs';
29
+ export { useTransaction } from './storage/useTransaction.mjs';
30
+ export { useAuditTrail } from './storage/useAuditTrail.mjs';
31
+
32
+ // Cache
33
+ export { useQueryCache } from './cache/useQueryCache.mjs';
34
+ export { useMemoizedQuery } from './cache/useMemoizedQuery.mjs';
35
+ export { useCacheStats } from './cache/useCacheStats.mjs';
36
+
37
+ // Effects
38
+ export { useKnowledgeEffect } from './effects/useKnowledgeEffect.mjs';
39
+ export { useDeltaTracking } from './effects/useDeltaTracking.mjs';
40
+ export { useGraphListener } from './effects/useGraphListener.mjs';
41
+
42
+ // Utils
43
+ export { useNamespaces } from './utils/useNamespaces.mjs';
44
+ export { useValidation } from './utils/useValidation.mjs';
45
+ export { useDebug } from './utils/useDebug.mjs';
46
+ export { usePerformanceTracking } from './utils/usePerformanceTracking.mjs';
47
+
48
+ // Batch
49
+ export { useBatchOperations } from './batch/useBatchOperations.mjs';
50
+ export { useOptimizedBatch } from './batch/useOptimizedBatch.mjs';
package/src/index.mjs ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @unrdf/react
3
+ *
4
+ * AI Semantic Analysis Tools for RDF Knowledge Graphs
5
+ *
6
+ * Provides AI-powered analysis capabilities for:
7
+ * - Semantic analysis and concept extraction (SemanticAnalyzer)
8
+ * - Graph embeddings with TransE/ComplEx/RotatE (EmbeddingsManager)
9
+ * - Natural language to SPARQL query building (NLPQueryBuilder)
10
+ * - Anomaly detection for data quality (AnomalyDetector)
11
+ *
12
+ * @module @unrdf/react
13
+ */
14
+
15
+ // AI Semantic Analysis
16
+ export {
17
+ SemanticAnalyzer,
18
+ createSemanticAnalyzer,
19
+ defaultSemanticAnalyzer,
20
+ } from './ai-semantic/semantic-analyzer.mjs';
21
+
22
+ export {
23
+ EmbeddingsManager,
24
+ createEmbeddingsManager,
25
+ defaultEmbeddingsManager,
26
+ } from './ai-semantic/embeddings-manager.mjs';
27
+
28
+ export {
29
+ NLPQueryBuilder,
30
+ createNLPQueryBuilder,
31
+ defaultNLPQueryBuilder,
32
+ } from './ai-semantic/nlp-query-builder.mjs';
33
+
34
+ export {
35
+ AnomalyDetector,
36
+ createAnomalyDetector,
37
+ defaultAnomalyDetector,
38
+ } from './ai-semantic/anomaly-detector.mjs';