open-brainy/src/neural/neuralImport.ts
David Snelling 970e08c466 feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write
Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt,
confidence, weight, service, data, createdBy, _rev) now have exactly one
home — top level — enforced by three layers driven from a single source of
truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS /
RESERVED_RELATION_FIELDS, exported):

1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams
   metadata (and the transact() ops that extend them) reject a literal
   reserved key as a TypeScript error while keeping generic T ergonomics
   (typed bags, untyped brains, index-signature shapes, and a documented
   exemption for T-declared reserved keys). Pinned by @ts-expect-error
   type tests run under vitest typecheck mode on every unit run.

2. Write time — the 7.x update() remap is ported to 8.0 and extended to
   every write path: add/update/relate/updateRelation, their transact()
   mirrors, and db.with() overlays. User-settable fields lift to their
   dedicated param (top-level wins when both are supplied — closes the 7.x
   trap where update({metadata:{confidence}}) silently no-oped), and
   system-managed fields drop with a one-shot warning naming the right
   path. A remapped subtype satisfies subtype-pairing enforcement exactly
   like a top-level one.

3. Read time — every storage combine goes through one canonical hydration
   helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over
   splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level
   and entity/relation.metadata carry ONLY custom fields on live reads,
   batch reads, paginated listings, getRelations by source/target, streamed
   verbs, and historical asOf() materialization alike.

Read-path echoes found and fixed (previously the full stored record —
including the verb type key — leaked inside metadata): noun pagination,
verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback),
getVerbsBySourceBatch (which also dropped subtype/data), and the
filesystem verb stream. getRelations() results now surface
confidence/updatedAt top-level via verbsToRelations, updateRelation() no
longer erases service/createdBy, relate() persists its top-level
confidence/service params, and the dead convertHNSWVerbToGraphVerb echo
path is deleted. Import paths (CLI extract, deduplicator, coordinators,
neural import) write confidence through the dedicated param instead of the
bag. UpdateRelationParams is now exported from the package root.

Documented for consumers in docs/concepts/consistency-model.md ("Reserved
fields") and RELEASES.md. Regression tests ported from the 7.x fix and
extended to the full 8.0 contract (17 runtime tests + 41 type-level
assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:13:09 -07:00

848 lines
No EOL
28 KiB
TypeScript

/**
* Neural Import - Atomic Age AI-Powered Data Understanding System
*
* 🧠 Leveraging the brain-in-jar to understand and automatically structure data
* ⚛️ Complete with confidence scoring and relationship weight calculation
*/
import { Brainy } from '../brainy.js'
import { NounType, VerbType } from '../types/graphTypes.js'
import * as fs from '../universal/fs.js'
import * as path from '../universal/path.js'
// @ts-ignore
import chalk from 'chalk'
// @ts-ignore
import ora from 'ora'
// @ts-ignore
import boxen from 'boxen'
// @ts-ignore
import Table from 'cli-table3'
// @ts-ignore
import prompts from 'prompts'
// Neural Import Types
export interface NeuralAnalysisResult {
detectedEntities: DetectedEntity[]
detectedRelationships: DetectedRelationship[]
confidence: number
insights: NeuralInsight[]
preview: ProcessedData[]
}
export interface DetectedEntity {
originalData: any
nounType: string
confidence: number
suggestedId: string
reasoning: string
alternativeTypes: Array<{ type: string, confidence: number }>
}
export interface DetectedRelationship {
sourceId: string
targetId: string
verbType: string
confidence: number
weight: number
reasoning: string
context: string
metadata?: Record<string, any>
}
export interface NeuralInsight {
type: 'hierarchy' | 'cluster' | 'pattern' | 'anomaly' | 'opportunity'
description: string
confidence: number
affectedEntities: string[]
recommendation?: string
}
export interface ProcessedData {
id: string
nounType: string
data: any
relationships: Array<{
target: string
verbType: string
weight: number
confidence: number
}>
}
export interface NeuralImportOptions {
confidenceThreshold: number
autoApply: boolean
enableWeights: boolean
previewOnly: boolean
validateOnly: boolean
categoryFilter?: string[]
skipDuplicates: boolean
/**
* Default subtype tag for entities + relationships when the neural extractor
* doesn't set one. Precedence: extractor → this default → Brainy default
* `'extracted'`. Use this to tag a whole neural pass (e.g.
* `'extracted-from-uploads'`) so consumers can query its output later.
* Added 7.30.1.
*/
defaultSubtype?: string
}
/**
* Neural Import Engine - The Brain Behind the Analysis
*/
export class NeuralImport {
private brainy: Brainy
private colors = {
primary: chalk.hex('#3A5F4A'),
success: chalk.hex('#2D4A3A'),
warning: chalk.hex('#D67441'),
error: chalk.hex('#B85C35'),
info: chalk.hex('#4A6B5A'),
dim: chalk.hex('#8A9B8A'),
highlight: chalk.hex('#E88B5A'),
accent: chalk.hex('#F5E6D3'),
brain: chalk.hex('#E88B5A')
}
private emojis = {
brain: '🧠',
atom: '⚛️',
lab: '🔬',
data: '🎛️',
magic: '⚡',
check: '✅',
warning: '⚠️',
sparkle: '✨',
rocket: '🚀',
gear: '⚙️'
}
constructor(brainy: Brainy) {
this.brainy = brainy
}
/**
* Main Neural Import Function - The Master Controller
*/
async neuralImport(filePath: string, options: Partial<NeuralImportOptions> = {}): Promise<NeuralAnalysisResult> {
const opts: NeuralImportOptions = {
confidenceThreshold: 0.7,
autoApply: false,
enableWeights: true,
previewOnly: false,
validateOnly: false,
skipDuplicates: true,
...options
}
console.log(boxen(
`${this.emojis.brain} ${this.colors.brain('NEURAL IMPORT INITIATED')} ${this.emojis.atom}\n\n` +
`${this.colors.accent('◆')} ${this.colors.dim('Activating atomic age AI analysis')}\n` +
`${this.colors.accent('◆')} ${this.colors.dim('File:')} ${this.colors.highlight(filePath)}\n` +
`${this.colors.accent('◆')} ${this.colors.dim('Confidence Threshold:')} ${this.colors.highlight(opts.confidenceThreshold.toString())}`,
{ padding: 1, borderStyle: 'round', borderColor: '#E88B5A' }
))
const spinner = ora(`${this.emojis.brain} Initializing neural analysis...`).start()
try {
// Phase 1: Data Parsing
spinner.text = `${this.emojis.lab} Parsing data structure...`
const rawData = await this.parseFile(filePath)
// Phase 2: Neural Entity Detection
spinner.text = `${this.emojis.atom} Analyzing ${Object.keys(NounType).length} entity types...`
const detectedEntities = await this.detectEntitiesWithNeuralAnalysis(rawData, opts)
// Phase 3: Neural Relationship Detection
spinner.text = `${this.emojis.data} Testing ${Object.keys(VerbType).length} relationship patterns...`
const detectedRelationships = await this.detectRelationshipsWithNeuralAnalysis(detectedEntities, rawData, opts)
// Phase 4: Neural Insights Generation
spinner.text = `${this.emojis.magic} Computing neural insights...`
const insights = await this.generateNeuralInsights(detectedEntities, detectedRelationships)
// Phase 5: Confidence Scoring
const overallConfidence = this.calculateOverallConfidence(detectedEntities, detectedRelationships)
spinner.stop()
const result: NeuralAnalysisResult = {
detectedEntities,
detectedRelationships,
confidence: overallConfidence,
insights,
preview: await this.generatePreview(detectedEntities, detectedRelationships)
}
// Display results
await this.displayNeuralAnalysisResults(result, opts)
// Handle execution based on options
if (opts.previewOnly || opts.validateOnly) {
return result
}
if (!opts.autoApply) {
const shouldExecute = await this.confirmNeuralImport(result)
if (!shouldExecute) {
console.log(this.colors.dim('Neural import cancelled'))
return result
}
}
// Execute the import
await this.executeNeuralImport(result, opts)
return result
} catch (error) {
spinner.fail('Neural analysis failed')
throw error
}
}
/**
* Parse file based on extension
*/
private async parseFile(filePath: string): Promise<any[]> {
const ext = path.extname(filePath).toLowerCase()
const content = await fs.readFile(filePath, 'utf8')
switch (ext) {
case '.json':
const jsonData = JSON.parse(content)
return Array.isArray(jsonData) ? jsonData : [jsonData]
case '.csv':
return this.parseCSV(content)
case '.yaml':
case '.yml':
// For now, basic YAML support - in full implementation would use yaml parser
return JSON.parse(content) // Placeholder
default:
throw new Error(`Unsupported file format: ${ext}`)
}
}
/**
* Basic CSV parser
*/
private parseCSV(content: string): any[] {
const lines = content.split('\n').filter(line => line.trim())
if (lines.length < 2) return []
const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, ''))
const data: any[] = []
for (let i = 1; i < lines.length; i++) {
const values = lines[i].split(',').map(v => v.trim().replace(/"/g, ''))
const row: any = {}
headers.forEach((header, index) => {
row[header] = values[index] || ''
})
data.push(row)
}
return data
}
/**
* Neural Entity Detection - The Core AI Engine
*/
private async detectEntitiesWithNeuralAnalysis(rawData: any[], options: NeuralImportOptions): Promise<DetectedEntity[]> {
const entities: DetectedEntity[] = []
const nounTypes = Object.values(NounType)
for (const [index, dataItem] of rawData.entries()) {
const mainText = this.extractMainText(dataItem)
const detections: Array<{ type: string, confidence: number, reasoning: string }> = []
// Test against all noun types using semantic similarity
for (const nounType of nounTypes) {
const confidence = await this.calculateEntityTypeConfidence(mainText, dataItem, nounType)
if (confidence >= options.confidenceThreshold - 0.2) { // Allow slightly lower for alternatives
const reasoning = await this.generateEntityReasoning(mainText, dataItem, nounType)
detections.push({ type: nounType, confidence, reasoning })
}
}
if (detections.length > 0) {
// Sort by confidence
detections.sort((a, b) => b.confidence - a.confidence)
const primaryType = detections[0]
const alternatives = detections.slice(1, 3) // Top 2 alternatives
entities.push({
originalData: dataItem,
nounType: primaryType.type,
confidence: primaryType.confidence,
suggestedId: this.generateSmartId(dataItem, primaryType.type, index),
reasoning: primaryType.reasoning,
alternativeTypes: alternatives
})
}
}
return entities
}
/**
* Calculate entity type confidence using AI
*/
private async calculateEntityTypeConfidence(text: string, data: any, nounType: string): Promise<number> {
// Base semantic similarity using search instead of similarity method
const searchResults = await this.brainy.find(text + ' ' + nounType)
const textSimilarity = searchResults.length > 0 ? searchResults[0].score : 0.5
// Field-based confidence boost
const fieldBoost = this.calculateFieldBasedConfidence(data, nounType)
// Pattern-based confidence boost
const patternBoost = this.calculatePatternBasedConfidence(text, data, nounType)
// Combine confidences with weights
const combined = (textSimilarity * 0.5) + (fieldBoost * 0.3) + (patternBoost * 0.2)
return Math.min(combined, 1.0)
}
/**
* Field-based confidence calculation
*/
private calculateFieldBasedConfidence(data: any, nounType: string): number {
const fields = Object.keys(data)
let boost = 0
// Field patterns that boost confidence for specific noun types
const fieldPatterns: Record<string, string[]> = {
[NounType.Person]: ['name', 'email', 'phone', 'age', 'firstname', 'lastname', 'employee'],
[NounType.Organization]: ['company', 'organization', 'corp', 'inc', 'ltd', 'department', 'team'],
[NounType.Project]: ['project', 'task', 'deadline', 'status', 'milestone', 'deliverable'],
[NounType.Location]: ['address', 'city', 'country', 'state', 'zip', 'location', 'coordinates'],
[NounType.Product]: ['product', 'price', 'sku', 'inventory', 'category', 'brand'],
[NounType.Event]: ['date', 'time', 'venue', 'event', 'meeting', 'conference', 'schedule']
}
const relevantPatterns = fieldPatterns[nounType] || []
for (const field of fields) {
for (const pattern of relevantPatterns) {
if (field.toLowerCase().includes(pattern)) {
boost += 0.1
}
}
}
return Math.min(boost, 0.5)
}
/**
* Pattern-based confidence calculation
*/
private calculatePatternBasedConfidence(text: string, data: any, nounType: string): number {
let boost = 0
// Content patterns that indicate entity types
const patterns: Record<string, RegExp[]> = {
[NounType.Person]: [
/@.*\.com/i, // Email pattern
/\b[A-Z][a-z]+ [A-Z][a-z]+\b/, // Name pattern
/Mr\.|Mrs\.|Dr\.|Prof\./i // Title pattern
],
[NounType.Organization]: [
/\bInc\.|Corp\.|LLC\.|Ltd\./i, // Corporate suffixes
/Company|Corporation|Enterprise/i
],
[NounType.Location]: [
/\b\d{5}(-\d{4})?\b/, // ZIP code
/Street|Ave|Road|Blvd/i
]
}
const relevantPatterns = patterns[nounType] || []
for (const pattern of relevantPatterns) {
if (pattern.test(text)) {
boost += 0.15
}
}
return Math.min(boost, 0.3)
}
/**
* Generate reasoning for entity type selection
*/
private async generateEntityReasoning(text: string, data: any, nounType: string): Promise<string> {
const reasons: string[] = []
// Semantic similarity reason using search
const searchResults = await this.brainy.find(text + ' ' + nounType)
const similarity = searchResults.length > 0 ? searchResults[0].score : 0.5
if (similarity > 0.7) {
reasons.push(`High semantic similarity (${(similarity * 100).toFixed(1)}%)`)
}
// Field-based reasons
const relevantFields = this.getRelevantFields(data, nounType)
if (relevantFields.length > 0) {
reasons.push(`Contains ${nounType}-specific fields: ${relevantFields.join(', ')}`)
}
// Pattern-based reasons
const matchedPatterns = this.getMatchedPatterns(text, data, nounType)
if (matchedPatterns.length > 0) {
reasons.push(`Matches ${nounType} patterns: ${matchedPatterns.join(', ')}`)
}
return reasons.length > 0 ? reasons.join('; ') : 'General semantic match'
}
/**
* Neural Relationship Detection
*/
private async detectRelationshipsWithNeuralAnalysis(
entities: DetectedEntity[],
rawData: any[],
options: NeuralImportOptions
): Promise<DetectedRelationship[]> {
const relationships: DetectedRelationship[] = []
const verbTypes = Object.values(VerbType)
// For each pair of entities, test relationship possibilities
for (let i = 0; i < entities.length; i++) {
for (let j = i + 1; j < entities.length; j++) {
const sourceEntity = entities[i]
const targetEntity = entities[j]
// Extract context for relationship detection
const context = this.extractRelationshipContext(sourceEntity.originalData, targetEntity.originalData, rawData)
// Test all verb types
for (const verbType of verbTypes) {
const confidence = await this.calculateRelationshipConfidence(
sourceEntity, targetEntity, verbType, context
)
if (confidence >= options.confidenceThreshold - 0.1) { // Slightly lower threshold for relationships
const weight = options.enableWeights ?
this.calculateRelationshipWeight(sourceEntity, targetEntity, verbType, context) :
0.5
const reasoning = await this.generateRelationshipReasoning(sourceEntity, targetEntity, verbType, context)
relationships.push({
sourceId: sourceEntity.suggestedId,
targetId: targetEntity.suggestedId,
verbType,
confidence,
weight,
reasoning,
context,
metadata: this.extractRelationshipMetadata(sourceEntity.originalData, targetEntity.originalData, verbType)
})
}
}
}
}
// Sort by confidence and remove duplicates/conflicts
return this.pruneRelationships(relationships)
}
/**
* Calculate relationship confidence
*/
private async calculateRelationshipConfidence(
source: DetectedEntity,
target: DetectedEntity,
verbType: string,
context: string
): Promise<number> {
// Semantic similarity between entities and verb type using search
const relationshipText = `${this.extractMainText(source.originalData)} ${verbType} ${this.extractMainText(target.originalData)}`
const directResults = await this.brainy.find(relationshipText)
const directScore = directResults.length > 0 ? directResults[0].score : 0.4
const contextResults = await this.brainy.find(context + ' ' + verbType)
const contextSimilarity = contextResults.length > 0 ? contextResults[0].score : 0.5
// Entity type compatibility
const typeCompatibility = this.calculateTypeCompatibility(source.nounType, target.nounType, verbType)
// Combine with weights
return (directScore * 0.4) + (contextSimilarity * 0.4) + (typeCompatibility * 0.2)
}
/**
* Calculate relationship weight/strength
*/
private calculateRelationshipWeight(
source: DetectedEntity,
target: DetectedEntity,
verbType: string,
context: string
): number {
let weight = 0.5 // Base weight
// Context richness (more descriptive = stronger)
const contextWords = context.split(' ').length
weight += Math.min(contextWords / 20, 0.2)
// Entity importance (higher confidence entities = stronger relationships)
const avgEntityConfidence = (source.confidence + target.confidence) / 2
weight += avgEntityConfidence * 0.2
// Verb type specificity (more specific verbs = stronger)
const verbSpecificity = this.getVerbSpecificity(verbType)
weight += verbSpecificity * 0.1
return Math.min(weight, 1.0)
}
/**
* Generate Neural Insights - The Intelligence Layer
*/
private async generateNeuralInsights(entities: DetectedEntity[], relationships: DetectedRelationship[]): Promise<NeuralInsight[]> {
const insights: NeuralInsight[] = []
// Detect hierarchies
const hierarchies = this.detectHierarchies(relationships)
hierarchies.forEach(hierarchy => {
insights.push({
type: 'hierarchy',
description: `Detected ${hierarchy.type} hierarchy with ${hierarchy.levels} levels`,
confidence: hierarchy.confidence,
affectedEntities: hierarchy.entities,
recommendation: `Consider visualizing the ${hierarchy.type} structure`
})
})
// Detect clusters
const clusters = this.detectClusters(entities, relationships)
clusters.forEach(cluster => {
insights.push({
type: 'cluster',
description: `Found cluster of ${cluster.size} ${cluster.primaryType} entities`,
confidence: cluster.confidence,
affectedEntities: cluster.entities,
recommendation: `These ${cluster.primaryType}s might form a natural grouping`
})
})
// Detect patterns
const patterns = this.detectPatterns(relationships)
patterns.forEach(pattern => {
insights.push({
type: 'pattern',
description: `Common relationship pattern: ${pattern.description}`,
confidence: pattern.confidence,
affectedEntities: pattern.entities,
recommendation: pattern.recommendation
})
})
return insights
}
/**
* Display Neural Analysis Results
*/
private async displayNeuralAnalysisResults(result: NeuralAnalysisResult, options: NeuralImportOptions): Promise<void> {
// Entity summary
const entityTable = new Table({
head: [this.colors.brain('Entity Type'), this.colors.brain('Count'), this.colors.brain('Avg Confidence')],
colWidths: [20, 10, 15]
})
const entitySummary = this.summarizeEntities(result.detectedEntities)
Object.entries(entitySummary).forEach(([type, stats]) => {
entityTable.push([
this.colors.highlight(type),
this.colors.primary(stats.count.toString()),
this.colors.success(`${(stats.avgConfidence * 100).toFixed(1)}%`)
])
})
// Relationship summary
const relationshipTable = new Table({
head: [this.colors.brain('Relationship Type'), this.colors.brain('Count'), this.colors.brain('Avg Weight'), this.colors.brain('Avg Confidence')],
colWidths: [20, 10, 12, 15]
})
const relationshipSummary = this.summarizeRelationships(result.detectedRelationships)
Object.entries(relationshipSummary).forEach(([type, stats]) => {
relationshipTable.push([
this.colors.highlight(type),
this.colors.primary(stats.count.toString()),
this.colors.warning(`${stats.avgWeight.toFixed(2)}`),
this.colors.success(`${(stats.avgConfidence * 100).toFixed(1)}%`)
])
})
console.log(boxen(
`${this.emojis.atom} ${this.colors.brain('NEURAL CLASSIFICATION RESULTS')}\n\n` +
entityTable.toString(),
{ padding: 1, borderStyle: 'round', borderColor: '#D67441' }
))
console.log(boxen(
`${this.emojis.data} ${this.colors.brain('NEURAL RELATIONSHIP MAPPING')}\n\n` +
relationshipTable.toString(),
{ padding: 1, borderStyle: 'round', borderColor: '#D67441' }
))
// Display insights
if (result.insights.length > 0) {
const insightsText = result.insights.map(insight =>
`${this.colors.accent('◆')} ${insight.description} (${(insight.confidence * 100).toFixed(1)}% confidence)`
).join('\n')
console.log(boxen(
`${this.emojis.magic} ${this.colors.brain('NEURAL INSIGHTS')}\n\n` +
insightsText,
{ padding: 1, borderStyle: 'round', borderColor: '#E88B5A' }
))
}
}
/**
* Helper methods for the neural system
*/
private extractMainText(data: any): string {
// Extract the most relevant text from a data object
const textFields = ['name', 'title', 'description', 'content', 'text', 'label']
for (const field of textFields) {
if (data[field] && typeof data[field] === 'string') {
return data[field]
}
}
// Fallback: concatenate all string values
return Object.values(data)
.filter(v => typeof v === 'string')
.join(' ')
.substring(0, 200) // Limit length
}
private generateSmartId(data: any, nounType: string, index: number): string {
const mainText = this.extractMainText(data)
const cleanText = mainText.toLowerCase().replace(/[^a-z0-9]/g, '_').substring(0, 20)
return `${nounType}_${cleanText}_${index}`
}
private extractRelationshipContext(source: any, target: any, allData: any[]): string {
// Extract context for relationship detection
return [
this.extractMainText(source),
this.extractMainText(target),
// Add more contextual information
].join(' ')
}
private calculateTypeCompatibility(sourceType: string, targetType: string, verbType: string): number {
// Define type compatibility matrix for relationships
const compatibilityMatrix: Record<string, Record<string, string[]>> = {
[NounType.Person]: {
[NounType.Organization]: [VerbType.MemberOf, VerbType.WorksWith],
[NounType.Project]: [VerbType.WorksWith, VerbType.Creates],
[NounType.Person]: [VerbType.WorksWith, VerbType.Mentors, VerbType.ReportsTo]
}
// Add more compatibility rules
}
const sourceCompatibility = compatibilityMatrix[sourceType]
if (sourceCompatibility && sourceCompatibility[targetType]) {
return sourceCompatibility[targetType].includes(verbType) ? 1.0 : 0.3
}
return 0.5 // Default compatibility
}
private getVerbSpecificity(verbType: string): number {
// More specific verbs get higher scores
const specificityScores: Record<string, number> = {
[VerbType.RelatedTo]: 0.1, // Very generic
[VerbType.WorksWith]: 0.7, // Specific
[VerbType.Mentors]: 0.9, // Very specific
[VerbType.ReportsTo]: 0.9, // Very specific
}
return specificityScores[verbType] || 0.5
}
private getRelevantFields(data: any, nounType: string): string[] {
// Implementation for finding relevant fields
return []
}
private getMatchedPatterns(text: string, data: any, nounType: string): string[] {
// Implementation for finding matched patterns
return []
}
private pruneRelationships(relationships: DetectedRelationship[]): DetectedRelationship[] {
// Remove duplicates and low-confidence relationships
return relationships
.sort((a, b) => b.confidence - a.confidence)
.slice(0, 1000) // Limit to top 1000 relationships
}
private detectHierarchies(relationships: DetectedRelationship[]): any[] {
// Detect hierarchical structures
return []
}
private detectClusters(entities: DetectedEntity[], relationships: DetectedRelationship[]): any[] {
// Detect entity clusters
return []
}
private detectPatterns(relationships: DetectedRelationship[]): any[] {
// Detect relationship patterns
return []
}
private summarizeEntities(entities: DetectedEntity[]): Record<string, any> {
const summary: Record<string, any> = {}
entities.forEach(entity => {
if (!summary[entity.nounType]) {
summary[entity.nounType] = { count: 0, totalConfidence: 0 }
}
summary[entity.nounType].count++
summary[entity.nounType].totalConfidence += entity.confidence
})
Object.keys(summary).forEach(type => {
summary[type].avgConfidence = summary[type].totalConfidence / summary[type].count
})
return summary
}
private summarizeRelationships(relationships: DetectedRelationship[]): Record<string, any> {
const summary: Record<string, any> = {}
relationships.forEach(rel => {
if (!summary[rel.verbType]) {
summary[rel.verbType] = { count: 0, totalWeight: 0, totalConfidence: 0 }
}
summary[rel.verbType].count++
summary[rel.verbType].totalWeight += rel.weight
summary[rel.verbType].totalConfidence += rel.confidence
})
Object.keys(summary).forEach(type => {
const stats = summary[type]
stats.avgWeight = stats.totalWeight / stats.count
stats.avgConfidence = stats.totalConfidence / stats.count
})
return summary
}
private calculateOverallConfidence(entities: DetectedEntity[], relationships: DetectedRelationship[]): number {
const entityConfidence = entities.reduce((sum, e) => sum + e.confidence, 0) / entities.length
const relationshipConfidence = relationships.reduce((sum, r) => sum + r.confidence, 0) / relationships.length
return (entityConfidence + relationshipConfidence) / 2
}
private async generatePreview(entities: DetectedEntity[], relationships: DetectedRelationship[]): Promise<ProcessedData[]> {
return entities.slice(0, 5).map(entity => ({
id: entity.suggestedId,
nounType: entity.nounType,
data: entity.originalData,
relationships: relationships
.filter(r => r.sourceId === entity.suggestedId)
.slice(0, 3)
.map(r => ({
target: r.targetId,
verbType: r.verbType,
weight: r.weight,
confidence: r.confidence
}))
}))
}
private async confirmNeuralImport(result: NeuralAnalysisResult): Promise<boolean> {
const { confirm } = await prompts({
type: 'confirm',
name: 'confirm',
message: `${this.emojis.rocket} Execute neural import?`,
initial: true
})
return confirm
}
private async executeNeuralImport(result: NeuralAnalysisResult, options: NeuralImportOptions): Promise<void> {
const spinner = ora(`${this.emojis.gear} Executing neural import...`).start()
try {
// Add entities to Brainy. Subtype precedence: extractor-set → caller's
// `options.defaultSubtype` → Brainy default `'extracted'` so enforcement
// consumers don't get rejected on neural-extraction writes (added 7.30.1).
for (const entity of result.detectedEntities) {
await this.brainy.add({
data: this.extractMainText(entity.originalData),
type: entity.nounType as NounType,
subtype: (entity as any).subtype ?? options.defaultSubtype ?? 'extracted',
metadata: {
...entity.originalData,
confidence: entity.confidence,
id: entity.suggestedId
}
})
}
// Add relationships to Brainy. Same subtype precedence as the entity side.
for (const relationship of result.detectedRelationships) {
await this.brainy.relate({
from: relationship.sourceId,
to: relationship.targetId,
type: relationship.verbType as VerbType,
subtype: (relationship as any).subtype ?? options.defaultSubtype ?? 'extracted',
weight: relationship.weight,
confidence: relationship.confidence, // reserved field — dedicated param, not metadata
metadata: {
context: relationship.context,
...relationship.metadata
}
})
}
spinner.succeed(this.colors.success(
`${this.emojis.check} Neural import complete! ` +
`${result.detectedEntities.length} entities and ` +
`${result.detectedRelationships.length} relationships imported.`
))
} catch (error) {
spinner.fail('Neural import failed')
throw error
}
}
private async generateRelationshipReasoning(
source: DetectedEntity,
target: DetectedEntity,
verbType: string,
context: string
): Promise<string> {
return `Neural analysis detected ${verbType} relationship based on semantic context`
}
private extractRelationshipMetadata(sourceData: any, targetData: any, verbType: string): Record<string, any> {
return {
sourceType: typeof sourceData,
targetType: typeof targetData,
detectedBy: 'neural-import',
timestamp: new Date().toISOString()
}
}
}