@soulcraft/brainy 4.8.5 → 4.9.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 +54 -0
- package/dist/brainy.js +0 -13
- package/dist/import/ImportCoordinator.d.ts +21 -0
- package/dist/import/ImportCoordinator.js +156 -11
- package/dist/importers/SmartExcelImporter.js +71 -2
- package/dist/vfs/PathResolver.js +0 -13
- package/dist/vfs/VirtualFileSystem.js +5 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [4.9.0](https://github.com/soulcraftlabs/brainy/compare/v4.8.6...v4.9.0) (2025-10-28)
|
|
6
|
+
|
|
7
|
+
**UNIVERSAL RELATIONSHIP EXTRACTION - Knowledge Graph Builder**
|
|
8
|
+
|
|
9
|
+
This release transforms Brainy imports from entity extractors into true knowledge graph builders with full provenance tracking and semantic relationship enhancement.
|
|
10
|
+
|
|
11
|
+
### ✨ Features
|
|
12
|
+
|
|
13
|
+
* **import**: Universal relationship extraction with provenance tracking
|
|
14
|
+
- **Document Entity Creation**: Every import now creates a `document` entity representing the source file
|
|
15
|
+
- **Provenance Relationships**: Full data lineage with `document → entity` relationships for every imported entity
|
|
16
|
+
- **Relationship Type Metadata**: All relationships tagged as `vfs`, `semantic`, or `provenance` for filtering
|
|
17
|
+
- **Enhanced Column Detection**: 7 relationship types (vs 1 previously) - Location, Owner, Creator, Uses, Member, Friend, Related
|
|
18
|
+
- **Type-Based Inference**: Smart relationship classification based on entity types and context analysis
|
|
19
|
+
- **Impact**: Workshop import now creates ~3,900 relationships (vs 581), with 5-20+ connections per entity
|
|
20
|
+
|
|
21
|
+
* **import**: New configuration option `createProvenanceLinks` (defaults to `true`)
|
|
22
|
+
- Enables/disables provenance relationship creation
|
|
23
|
+
- Backward compatible - all features opt-in
|
|
24
|
+
|
|
25
|
+
### 📊 Impact
|
|
26
|
+
|
|
27
|
+
**Before v4.9.0:**
|
|
28
|
+
```
|
|
29
|
+
Import: glossary.xlsx (1,149 rows)
|
|
30
|
+
Result: 1,149 entities, 581 relationships (VFS only)
|
|
31
|
+
Graph: Isolated nodes, 0 semantic connections
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**After v4.9.0:**
|
|
35
|
+
```
|
|
36
|
+
Import: glossary.xlsx (1,149 rows)
|
|
37
|
+
Result: 1,150 entities (+ document), ~3,900 relationships
|
|
38
|
+
- 1,149 provenance (document → entity)
|
|
39
|
+
- ~1,500 semantic (entity ↔ entity, diverse types)
|
|
40
|
+
- 581 VFS (directory structure, marked separately)
|
|
41
|
+
Graph: Rich network, 5-20+ connections per entity
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 🔧 Technical Details
|
|
45
|
+
|
|
46
|
+
* **Files Modified**: 3 files, 257 insertions(+), 11 deletions(-)
|
|
47
|
+
- `ImportCoordinator.ts`: +175 lines (document entity, provenance, inference)
|
|
48
|
+
- `SmartExcelImporter.ts`: +65 lines (enhanced column patterns)
|
|
49
|
+
- `VirtualFileSystem.ts`: +2 lines (relationship type metadata)
|
|
50
|
+
|
|
51
|
+
* **Universal Support**: Works across ALL 7 import formats (Excel, PDF, CSV, JSON, Markdown, YAML, DOCX)
|
|
52
|
+
* **Backward Compatible**: 100% - all features opt-in, existing imports unchanged
|
|
53
|
+
|
|
54
|
+
### [4.8.6](https://github.com/soulcraftlabs/brainy/compare/v4.8.5...v4.8.6) (2025-10-28)
|
|
55
|
+
|
|
56
|
+
- fix: per-sheet column detection in Excel importer (401443a)
|
|
57
|
+
|
|
58
|
+
|
|
5
59
|
### [4.7.4](https://github.com/soulcraftlabs/brainy/compare/v4.7.3...v4.7.4) (2025-10-27)
|
|
6
60
|
|
|
7
61
|
**CRITICAL SYSTEMIC VFS BUG FIX - Workshop Team Unblocked!**
|
package/dist/brainy.js
CHANGED
|
@@ -464,13 +464,6 @@ export class Brainy {
|
|
|
464
464
|
async convertNounToEntity(noun) {
|
|
465
465
|
// v4.8.0: Storage adapters ALREADY extract standard fields to top-level!
|
|
466
466
|
// Just read from top-level fields of HNSWNounWithMetadata
|
|
467
|
-
console.log(`[DEBUG convertNounToEntity] Converting noun ${noun.id}:`, {
|
|
468
|
-
nounMetadataKeys: noun.metadata ? Object.keys(noun.metadata) : [],
|
|
469
|
-
nounType: noun.type,
|
|
470
|
-
hasName: !!noun.metadata?.name,
|
|
471
|
-
hasPath: !!noun.metadata?.path,
|
|
472
|
-
hasVfsType: !!noun.metadata?.vfsType
|
|
473
|
-
});
|
|
474
467
|
// v4.8.0: Clean structure with standard fields at top-level
|
|
475
468
|
const entity = {
|
|
476
469
|
id: noun.id,
|
|
@@ -487,12 +480,6 @@ export class Brainy {
|
|
|
487
480
|
// ONLY custom user fields in metadata (v4.8.0: already separated by storage adapter)
|
|
488
481
|
metadata: noun.metadata
|
|
489
482
|
};
|
|
490
|
-
console.log(`[DEBUG convertNounToEntity] Converted entity metadata:`, {
|
|
491
|
-
entityMetadataKeys: entity.metadata ? Object.keys(entity.metadata) : [],
|
|
492
|
-
metadata_name: entity.metadata?.name,
|
|
493
|
-
metadata_path: entity.metadata?.path,
|
|
494
|
-
metadata_vfsType: entity.metadata?.vfsType
|
|
495
|
-
});
|
|
496
483
|
return entity;
|
|
497
484
|
}
|
|
498
485
|
/**
|
|
@@ -44,6 +44,8 @@ export interface ValidImportOptions {
|
|
|
44
44
|
createEntities?: boolean;
|
|
45
45
|
/** Create relationships in knowledge graph */
|
|
46
46
|
createRelationships?: boolean;
|
|
47
|
+
/** Create provenance relationships (document → entity) [v4.9.0] */
|
|
48
|
+
createProvenanceLinks?: boolean;
|
|
47
49
|
/** Preserve source file in VFS */
|
|
48
50
|
preserveSource?: boolean;
|
|
49
51
|
/** Enable neural entity extraction */
|
|
@@ -267,6 +269,7 @@ export declare class ImportCoordinator {
|
|
|
267
269
|
private extract;
|
|
268
270
|
/**
|
|
269
271
|
* Create entities and relationships in knowledge graph
|
|
272
|
+
* v4.9.0: Added sourceInfo parameter for document entity creation
|
|
270
273
|
*/
|
|
271
274
|
private createGraphEntities;
|
|
272
275
|
/**
|
|
@@ -315,4 +318,22 @@ export declare class ImportCoordinator {
|
|
|
315
318
|
* @returns Current optimal flush interval
|
|
316
319
|
*/
|
|
317
320
|
private getProgressiveFlushInterval;
|
|
321
|
+
/**
|
|
322
|
+
* Infer relationship type based on entity types and context
|
|
323
|
+
* v4.9.0: Semantic relationship enhancement
|
|
324
|
+
*
|
|
325
|
+
* @param sourceType - Type of source entity
|
|
326
|
+
* @param targetType - Type of target entity
|
|
327
|
+
* @param context - Optional context string for additional hints
|
|
328
|
+
* @returns Inferred verb type
|
|
329
|
+
*/
|
|
330
|
+
private inferRelationshipType;
|
|
331
|
+
/**
|
|
332
|
+
* Count entities by type for document metadata
|
|
333
|
+
* v4.9.0: Used for document entity statistics
|
|
334
|
+
*
|
|
335
|
+
* @param rows - Extracted rows from import
|
|
336
|
+
* @returns Record of entity type counts
|
|
337
|
+
*/
|
|
338
|
+
private countByType;
|
|
318
339
|
}
|
|
@@ -20,7 +20,7 @@ import { SmartMarkdownImporter } from '../importers/SmartMarkdownImporter.js';
|
|
|
20
20
|
import { SmartYAMLImporter } from '../importers/SmartYAMLImporter.js';
|
|
21
21
|
import { SmartDOCXImporter } from '../importers/SmartDOCXImporter.js';
|
|
22
22
|
import { VFSStructureGenerator } from '../importers/VFSStructureGenerator.js';
|
|
23
|
-
import { NounType } from '../types/graphTypes.js';
|
|
23
|
+
import { NounType, VerbType } from '../types/graphTypes.js';
|
|
24
24
|
import { v4 as uuidv4 } from '../universal/uuid.js';
|
|
25
25
|
import * as fs from 'fs';
|
|
26
26
|
import * as path from 'path';
|
|
@@ -134,7 +134,10 @@ export class ImportCoordinator {
|
|
|
134
134
|
message: 'Creating knowledge graph...'
|
|
135
135
|
});
|
|
136
136
|
// Create entities and relationships in graph
|
|
137
|
-
const graphResult = await this.createGraphEntities(normalizedResult, vfsResult, opts
|
|
137
|
+
const graphResult = await this.createGraphEntities(normalizedResult, vfsResult, opts, {
|
|
138
|
+
sourceFilename: normalizedSource.filename || `import.${detection.format}`,
|
|
139
|
+
format: detection.format
|
|
140
|
+
});
|
|
138
141
|
// Report complete
|
|
139
142
|
options.onProgress?.({
|
|
140
143
|
stage: 'complete',
|
|
@@ -409,8 +412,9 @@ export class ImportCoordinator {
|
|
|
409
412
|
}
|
|
410
413
|
/**
|
|
411
414
|
* Create entities and relationships in knowledge graph
|
|
415
|
+
* v4.9.0: Added sourceInfo parameter for document entity creation
|
|
412
416
|
*/
|
|
413
|
-
async createGraphEntities(extractionResult, vfsResult, options) {
|
|
417
|
+
async createGraphEntities(extractionResult, vfsResult, options, sourceInfo) {
|
|
414
418
|
const entities = [];
|
|
415
419
|
const relationships = [];
|
|
416
420
|
let mergedCount = 0;
|
|
@@ -419,7 +423,14 @@ export class ImportCoordinator {
|
|
|
419
423
|
// Previously: if (!options.createEntities) treated undefined as false
|
|
420
424
|
// Now: Only skip when explicitly set to false
|
|
421
425
|
if (options.createEntities === false) {
|
|
422
|
-
return {
|
|
426
|
+
return {
|
|
427
|
+
entities,
|
|
428
|
+
relationships,
|
|
429
|
+
merged: 0,
|
|
430
|
+
newEntities: 0,
|
|
431
|
+
documentEntity: undefined,
|
|
432
|
+
provenanceCount: 0
|
|
433
|
+
};
|
|
423
434
|
}
|
|
424
435
|
// Extract rows/sections/entities from result (unified across formats)
|
|
425
436
|
const rows = extractionResult.rows || extractionResult.sections || extractionResult.entities || [];
|
|
@@ -444,6 +455,29 @@ export class ImportCoordinator {
|
|
|
444
455
|
` Tip: For large imports, deduplicate manually after import or use smaller batches\n` +
|
|
445
456
|
` Override: Set deduplicationThreshold to force enable (not recommended for >500 entities)`);
|
|
446
457
|
}
|
|
458
|
+
// ============================================
|
|
459
|
+
// v4.9.0: Create document entity for import source
|
|
460
|
+
// ============================================
|
|
461
|
+
let documentEntityId = null;
|
|
462
|
+
let provenanceCount = 0;
|
|
463
|
+
if (sourceInfo && options.createProvenanceLinks !== false) {
|
|
464
|
+
console.log(`📄 Creating document entity for import source: ${sourceInfo.sourceFilename}`);
|
|
465
|
+
documentEntityId = await this.brain.add({
|
|
466
|
+
data: sourceInfo.sourceFilename,
|
|
467
|
+
type: NounType.Document,
|
|
468
|
+
metadata: {
|
|
469
|
+
name: sourceInfo.sourceFilename,
|
|
470
|
+
sourceFile: sourceInfo.sourceFilename,
|
|
471
|
+
format: sourceInfo.format,
|
|
472
|
+
importedAt: Date.now(),
|
|
473
|
+
importSource: true,
|
|
474
|
+
vfsPath: vfsResult.rootPath,
|
|
475
|
+
totalRows: rows.length,
|
|
476
|
+
byType: this.countByType(rows)
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
console.log(`✅ Document entity created: ${documentEntityId}`);
|
|
480
|
+
}
|
|
447
481
|
// Create entities in graph
|
|
448
482
|
for (const row of rows) {
|
|
449
483
|
const entity = row.entity || row;
|
|
@@ -506,6 +540,25 @@ export class ImportCoordinator {
|
|
|
506
540
|
type: entity.type,
|
|
507
541
|
vfsPath: vfsFile?.path
|
|
508
542
|
});
|
|
543
|
+
// ============================================
|
|
544
|
+
// v4.9.0: Create provenance relationship (document → entity)
|
|
545
|
+
// ============================================
|
|
546
|
+
if (documentEntityId && options.createProvenanceLinks !== false) {
|
|
547
|
+
await this.brain.relate({
|
|
548
|
+
from: documentEntityId,
|
|
549
|
+
to: entityId,
|
|
550
|
+
type: VerbType.Contains,
|
|
551
|
+
metadata: {
|
|
552
|
+
relationshipType: 'provenance',
|
|
553
|
+
evidence: `Extracted from ${sourceInfo?.sourceFilename}`,
|
|
554
|
+
sheet: row.sheet,
|
|
555
|
+
rowNumber: row.rowNumber,
|
|
556
|
+
extractedAt: Date.now(),
|
|
557
|
+
format: sourceInfo?.format
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
provenanceCount++;
|
|
561
|
+
}
|
|
509
562
|
// Collect relationships for batch creation
|
|
510
563
|
if (options.createRelationships && row.relationships) {
|
|
511
564
|
for (const rel of row.relationships) {
|
|
@@ -624,14 +677,30 @@ export class ImportCoordinator {
|
|
|
624
677
|
});
|
|
625
678
|
}
|
|
626
679
|
// Batch create all relationships using brain.relateMany() for performance
|
|
680
|
+
// v4.9.0: Enhanced with type-based inference and semantic metadata
|
|
627
681
|
if (options.createRelationships && relationships.length > 0) {
|
|
628
682
|
try {
|
|
629
|
-
const relationshipParams = relationships.map(rel =>
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
683
|
+
const relationshipParams = relationships.map(rel => {
|
|
684
|
+
// Get entity types for inference
|
|
685
|
+
const sourceEntity = entities.find(e => e.id === rel.from);
|
|
686
|
+
const targetEntity = entities.find(e => e.id === rel.to);
|
|
687
|
+
// Infer better relationship type if generic and we have entity types
|
|
688
|
+
let verbType = rel.type;
|
|
689
|
+
if (verbType === VerbType.RelatedTo && sourceEntity && targetEntity) {
|
|
690
|
+
verbType = this.inferRelationshipType(sourceEntity.type, targetEntity.type, rel.metadata?.evidence);
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
from: rel.from,
|
|
694
|
+
to: rel.to,
|
|
695
|
+
type: verbType, // Enhanced type
|
|
696
|
+
metadata: {
|
|
697
|
+
...(rel.metadata || {}),
|
|
698
|
+
relationshipType: 'semantic', // v4.9.0: Distinguish from VFS/provenance
|
|
699
|
+
inferredType: verbType !== rel.type, // Track if type was enhanced
|
|
700
|
+
originalType: rel.type
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
});
|
|
635
704
|
const relationshipIds = await this.brain.relateMany({
|
|
636
705
|
items: relationshipParams,
|
|
637
706
|
parallel: true,
|
|
@@ -666,7 +735,9 @@ export class ImportCoordinator {
|
|
|
666
735
|
entities,
|
|
667
736
|
relationships,
|
|
668
737
|
merged: mergedCount,
|
|
669
|
-
newEntities: newCount
|
|
738
|
+
newEntities: newCount,
|
|
739
|
+
documentEntity: documentEntityId || undefined,
|
|
740
|
+
provenanceCount
|
|
670
741
|
};
|
|
671
742
|
}
|
|
672
743
|
/**
|
|
@@ -908,5 +979,79 @@ ${optionDetails}
|
|
|
908
979
|
return 5000; // Performance-focused interval for large imports
|
|
909
980
|
}
|
|
910
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Infer relationship type based on entity types and context
|
|
984
|
+
* v4.9.0: Semantic relationship enhancement
|
|
985
|
+
*
|
|
986
|
+
* @param sourceType - Type of source entity
|
|
987
|
+
* @param targetType - Type of target entity
|
|
988
|
+
* @param context - Optional context string for additional hints
|
|
989
|
+
* @returns Inferred verb type
|
|
990
|
+
*/
|
|
991
|
+
inferRelationshipType(sourceType, targetType, context) {
|
|
992
|
+
// Context-based inference (highest priority)
|
|
993
|
+
if (context) {
|
|
994
|
+
const lowerContext = context.toLowerCase();
|
|
995
|
+
if (lowerContext.includes('live') || lowerContext.includes('reside') || lowerContext.includes('dwell')) {
|
|
996
|
+
return VerbType.LocatedAt;
|
|
997
|
+
}
|
|
998
|
+
if (lowerContext.includes('create') || lowerContext.includes('invent') || lowerContext.includes('make')) {
|
|
999
|
+
return VerbType.Creates;
|
|
1000
|
+
}
|
|
1001
|
+
if (lowerContext.includes('own') || lowerContext.includes('possess') || lowerContext.includes('belong')) {
|
|
1002
|
+
return VerbType.PartOf;
|
|
1003
|
+
}
|
|
1004
|
+
if (lowerContext.includes('work') || lowerContext.includes('collaborate') || lowerContext.includes('team')) {
|
|
1005
|
+
return VerbType.WorksWith;
|
|
1006
|
+
}
|
|
1007
|
+
if (lowerContext.includes('use') || lowerContext.includes('wield') || lowerContext.includes('employ')) {
|
|
1008
|
+
return VerbType.Uses;
|
|
1009
|
+
}
|
|
1010
|
+
if (lowerContext.includes('know') || lowerContext.includes('friend') || lowerContext.includes('ally')) {
|
|
1011
|
+
return VerbType.FriendOf;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
// Type-based inference (fallback)
|
|
1015
|
+
// Sort types for consistent lookup
|
|
1016
|
+
const sortedTypes = [sourceType, targetType].sort();
|
|
1017
|
+
const typeKey = `${sortedTypes[0]}+${sortedTypes[1]}`;
|
|
1018
|
+
const typeMapping = {
|
|
1019
|
+
// Person relationships
|
|
1020
|
+
[`${NounType.Person}+${NounType.Location}`]: VerbType.LocatedAt,
|
|
1021
|
+
[`${NounType.Person}+${NounType.Thing}`]: VerbType.Uses,
|
|
1022
|
+
[`${NounType.Person}+${NounType.Person}`]: VerbType.FriendOf,
|
|
1023
|
+
[`${NounType.Person}+${NounType.Concept}`]: VerbType.RelatedTo,
|
|
1024
|
+
[`${NounType.Person}+${NounType.Event}`]: VerbType.RelatedTo,
|
|
1025
|
+
// Location relationships
|
|
1026
|
+
[`${NounType.Location}+${NounType.Thing}`]: VerbType.Contains,
|
|
1027
|
+
[`${NounType.Location}+${NounType.Concept}`]: VerbType.RelatedTo,
|
|
1028
|
+
[`${NounType.Location}+${NounType.Event}`]: VerbType.LocatedAt,
|
|
1029
|
+
// Thing relationships
|
|
1030
|
+
[`${NounType.Thing}+${NounType.Concept}`]: VerbType.RelatedTo,
|
|
1031
|
+
[`${NounType.Thing}+${NounType.Event}`]: VerbType.RelatedTo,
|
|
1032
|
+
// Concept relationships
|
|
1033
|
+
[`${NounType.Concept}+${NounType.Concept}`]: VerbType.RelatedTo,
|
|
1034
|
+
[`${NounType.Concept}+${NounType.Event}`]: VerbType.RelatedTo,
|
|
1035
|
+
// Event relationships
|
|
1036
|
+
[`${NounType.Event}+${NounType.Event}`]: VerbType.Precedes
|
|
1037
|
+
};
|
|
1038
|
+
return typeMapping[typeKey] || VerbType.RelatedTo;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Count entities by type for document metadata
|
|
1042
|
+
* v4.9.0: Used for document entity statistics
|
|
1043
|
+
*
|
|
1044
|
+
* @param rows - Extracted rows from import
|
|
1045
|
+
* @returns Record of entity type counts
|
|
1046
|
+
*/
|
|
1047
|
+
countByType(rows) {
|
|
1048
|
+
const counts = {};
|
|
1049
|
+
for (const row of rows) {
|
|
1050
|
+
const entity = row.entity || row;
|
|
1051
|
+
const type = entity.type || NounType.Thing;
|
|
1052
|
+
counts[type] = (counts[type] || 0) + 1;
|
|
1053
|
+
}
|
|
1054
|
+
return counts;
|
|
1055
|
+
}
|
|
911
1056
|
}
|
|
912
1057
|
//# sourceMappingURL=ImportCoordinator.js.map
|
|
@@ -102,8 +102,24 @@ export class SmartExcelImporter {
|
|
|
102
102
|
if (rows.length === 0) {
|
|
103
103
|
return this.emptyResult(startTime);
|
|
104
104
|
}
|
|
105
|
-
// Detect
|
|
106
|
-
|
|
105
|
+
// CRITICAL FIX (v4.8.6): Detect columns per-sheet, not globally
|
|
106
|
+
// Different sheets may have different column structures (Term vs Name, etc.)
|
|
107
|
+
// Group rows by sheet and detect columns for each sheet separately
|
|
108
|
+
const rowsBySheet = new Map();
|
|
109
|
+
for (const row of rows) {
|
|
110
|
+
const sheet = row._sheet || 'default';
|
|
111
|
+
if (!rowsBySheet.has(sheet)) {
|
|
112
|
+
rowsBySheet.set(sheet, []);
|
|
113
|
+
}
|
|
114
|
+
rowsBySheet.get(sheet).push(row);
|
|
115
|
+
}
|
|
116
|
+
// Detect columns for each sheet
|
|
117
|
+
const columnsBySheet = new Map();
|
|
118
|
+
for (const [sheet, sheetRows] of rowsBySheet) {
|
|
119
|
+
if (sheetRows.length > 0) {
|
|
120
|
+
columnsBySheet.set(sheet, this.detectColumns(sheetRows[0], opts));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
107
123
|
// Process each row with BATCHED PARALLEL PROCESSING (v3.38.0)
|
|
108
124
|
const extractedRows = [];
|
|
109
125
|
const entityMap = new Map();
|
|
@@ -121,6 +137,9 @@ export class SmartExcelImporter {
|
|
|
121
137
|
// Process chunk in parallel for massive speedup
|
|
122
138
|
const chunkResults = await Promise.all(chunk.map(async (row, chunkIndex) => {
|
|
123
139
|
const i = chunkStart + chunkIndex;
|
|
140
|
+
// CRITICAL FIX (v4.8.6): Use sheet-specific column mapping
|
|
141
|
+
const sheet = row._sheet || 'default';
|
|
142
|
+
const columns = columnsBySheet.get(sheet) || this.detectColumns(row, opts);
|
|
124
143
|
// Extract data from row
|
|
125
144
|
const term = this.getColumnValue(row, columns.term) || `Entity_${i}`;
|
|
126
145
|
const definition = this.getColumnValue(row, columns.definition) || '';
|
|
@@ -186,6 +205,9 @@ export class SmartExcelImporter {
|
|
|
186
205
|
evidence: `Extracted from: "${definition.substring(0, 100)}..."`
|
|
187
206
|
});
|
|
188
207
|
}
|
|
208
|
+
// ============================================
|
|
209
|
+
// v4.9.0: Enhanced column-based relationship detection
|
|
210
|
+
// ============================================
|
|
189
211
|
// Parse explicit "Related Terms" column
|
|
190
212
|
if (relatedTerms) {
|
|
191
213
|
const terms = relatedTerms.split(/[,;]/).map(t => t.trim()).filter(Boolean);
|
|
@@ -204,6 +226,53 @@ export class SmartExcelImporter {
|
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
228
|
}
|
|
229
|
+
// v4.9.0: Check for additional relationship-indicating columns
|
|
230
|
+
// Expanded patterns for various relationship types
|
|
231
|
+
const relationshipColumnPatterns = [
|
|
232
|
+
{ pattern: /^(location|home|lives in|resides|dwelling|place)$/i, defaultType: VerbType.LocatedAt },
|
|
233
|
+
{ pattern: /^(owner|owned by|belongs to|possessed by|wielder)$/i, defaultType: VerbType.PartOf },
|
|
234
|
+
{ pattern: /^(created by|made by|invented by|authored by|creator|author)$/i, defaultType: VerbType.CreatedBy },
|
|
235
|
+
{ pattern: /^(uses|utilizes|requires|needs|employs|tool|weapon|item)$/i, defaultType: VerbType.Uses },
|
|
236
|
+
{ pattern: /^(member of|part of|within|inside|group|organization)$/i, defaultType: VerbType.PartOf },
|
|
237
|
+
{ pattern: /^(knows|friend|associate|colleague|ally|companion)$/i, defaultType: VerbType.FriendOf },
|
|
238
|
+
{ pattern: /^(connection|link|reference|see also|related to)$/i, defaultType: VerbType.RelatedTo }
|
|
239
|
+
];
|
|
240
|
+
// Check all columns in the row
|
|
241
|
+
for (const [columnName, columnValue] of Object.entries(row)) {
|
|
242
|
+
// Skip standard columns
|
|
243
|
+
if (!columnValue || columnName === columns.term || columnName === columns.definition || columnName === columns.type) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// Find matching relationship pattern
|
|
247
|
+
const matchedPattern = relationshipColumnPatterns.find(rcp => rcp.pattern.test(columnName));
|
|
248
|
+
if (matchedPattern && typeof columnValue === 'string') {
|
|
249
|
+
// Parse comma/semicolon-separated values
|
|
250
|
+
const references = columnValue.split(/[,;]+/).map(s => s.trim()).filter(Boolean);
|
|
251
|
+
for (const ref of references) {
|
|
252
|
+
if (ref.toLowerCase() !== term.toLowerCase()) {
|
|
253
|
+
// Use SmartRelationshipExtractor for better type inference, fallback to pattern default
|
|
254
|
+
let verbType;
|
|
255
|
+
try {
|
|
256
|
+
verbType = await this.inferRelationship(term, ref, `${term}'s ${columnName}: ${ref}. ${definition}`, mainEntityType);
|
|
257
|
+
// If inference returns generic type, use column pattern hint
|
|
258
|
+
if (verbType === VerbType.RelatedTo) {
|
|
259
|
+
verbType = matchedPattern.defaultType;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
verbType = matchedPattern.defaultType;
|
|
264
|
+
}
|
|
265
|
+
relationships.push({
|
|
266
|
+
from: entityId,
|
|
267
|
+
to: ref,
|
|
268
|
+
type: verbType,
|
|
269
|
+
confidence: 0.9, // High confidence for explicit columns
|
|
270
|
+
evidence: `Column "${columnName}": ${ref}`
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
207
276
|
}
|
|
208
277
|
return {
|
|
209
278
|
term,
|
package/dist/vfs/PathResolver.js
CHANGED
|
@@ -162,29 +162,16 @@ export class PathResolver {
|
|
|
162
162
|
from: dirId,
|
|
163
163
|
type: VerbType.Contains
|
|
164
164
|
});
|
|
165
|
-
console.log(`[DEBUG PathResolver] getChildren(${dirId}): Found ${relations.length} Contains relations`);
|
|
166
165
|
const validChildren = [];
|
|
167
166
|
const childNames = new Set();
|
|
168
167
|
// Fetch all child entities via relationships
|
|
169
168
|
for (const relation of relations) {
|
|
170
169
|
const entity = await this.brain.get(relation.to);
|
|
171
|
-
console.log(`[DEBUG PathResolver] Retrieved entity ${relation.to}:`, {
|
|
172
|
-
exists: !!entity,
|
|
173
|
-
type: entity?.type,
|
|
174
|
-
hasMetadata: !!entity?.metadata,
|
|
175
|
-
metadataKeys: entity?.metadata ? Object.keys(entity.metadata) : [],
|
|
176
|
-
metadata_vfsType: entity?.metadata?.vfsType,
|
|
177
|
-
metadata_name: entity?.metadata?.name
|
|
178
|
-
});
|
|
179
170
|
if (entity && entity.metadata?.vfsType && entity.metadata?.name) {
|
|
180
171
|
validChildren.push(entity);
|
|
181
172
|
childNames.add(entity.metadata.name);
|
|
182
173
|
}
|
|
183
|
-
else {
|
|
184
|
-
console.log(`[DEBUG PathResolver] ❌ FILTERED OUT entity ${relation.to} - missing vfsType or name`);
|
|
185
|
-
}
|
|
186
174
|
}
|
|
187
|
-
console.log(`[DEBUG PathResolver] Returning ${validChildren.length} valid children (filtered from ${relations.length})`);
|
|
188
175
|
// Update cache
|
|
189
176
|
this.parentCache.set(dirId, childNames);
|
|
190
177
|
return validChildren;
|
|
@@ -616,7 +616,10 @@ export class VirtualFileSystem {
|
|
|
616
616
|
from: parentId,
|
|
617
617
|
to: entity,
|
|
618
618
|
type: VerbType.Contains,
|
|
619
|
-
metadata: {
|
|
619
|
+
metadata: {
|
|
620
|
+
isVFS: true, // v4.5.1: Mark as VFS relationship
|
|
621
|
+
relationshipType: 'vfs' // v4.9.0: Standardized relationship type metadata
|
|
622
|
+
}
|
|
620
623
|
});
|
|
621
624
|
}
|
|
622
625
|
// Update path resolver cache
|
|
@@ -683,19 +686,6 @@ export class VirtualFileSystem {
|
|
|
683
686
|
}
|
|
684
687
|
// Get children
|
|
685
688
|
let children = await this.pathResolver.getChildren(entityId);
|
|
686
|
-
console.log(`[DEBUG VFS] readdir(${path}): Found ${children.length} children`);
|
|
687
|
-
// Debug first child
|
|
688
|
-
if (children.length > 0) {
|
|
689
|
-
const firstChild = children[0];
|
|
690
|
-
console.log(`[DEBUG VFS] First child structure:`, {
|
|
691
|
-
id: firstChild.id,
|
|
692
|
-
type: firstChild.type,
|
|
693
|
-
metadataKeys: Object.keys(firstChild.metadata || {}),
|
|
694
|
-
metadata_name: firstChild.metadata?.name,
|
|
695
|
-
metadata_path: firstChild.metadata?.path,
|
|
696
|
-
metadata_vfsType: firstChild.metadata?.vfsType
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
689
|
// Apply filters
|
|
700
690
|
if (options?.filter) {
|
|
701
691
|
children = this.filterDirectoryEntries(children, options.filter);
|
|
@@ -715,17 +705,12 @@ export class VirtualFileSystem {
|
|
|
715
705
|
await this.updateAccessTime(entityId);
|
|
716
706
|
// Return appropriate format
|
|
717
707
|
if (options?.withFileTypes) {
|
|
718
|
-
|
|
708
|
+
return children.map(child => ({
|
|
719
709
|
name: child.metadata.name,
|
|
720
710
|
path: child.metadata.path,
|
|
721
711
|
type: child.metadata.vfsType,
|
|
722
712
|
entityId: child.id
|
|
723
713
|
}));
|
|
724
|
-
console.log(`[DEBUG VFS] Returning ${result.length} VFSDirent items`);
|
|
725
|
-
if (result.length > 0) {
|
|
726
|
-
console.log(`[DEBUG VFS] First VFSDirent:`, result[0]);
|
|
727
|
-
}
|
|
728
|
-
return result;
|
|
729
714
|
}
|
|
730
715
|
return children.map(child => child.metadata.name);
|
|
731
716
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|