feat: implement production-ready type-aware NLP with zero hardcoded fields

🎯 COMPLETE TYPE-AWARE INTELLIGENCE SYSTEM:

## Type-Field Affinity Tracking:
- Track which fields actually appear with which NounTypes in real data
- Build affinity maps: Document → [title: 0.95, author: 0.87, publishDate: 0.82]
- Update tracking during all CRUD operations for real-time accuracy

## Dynamic Field Discovery:
- ZERO hardcoded fields except NounType/VerbType taxonomies (30+ noun, 40+ verb)
- Generate field variations algorithmically (camelCase, snake_case, suffixes)
- Remove all hardcoded abbreviations - purely linguistic pattern-based

## Type-Aware NLP Parsing:
- Detect NounType first using semantic similarity on pre-embedded types
- Get type-specific fields with affinity scores for context
- Prioritize field matching based on type relevance
- Boost confidence for fields with high type affinity

## Field-Type Validation:
- Validate field compatibility with detected types
- Provide intelligent suggestions for invalid combinations
- Auto-correct queries using most likely field alternatives
- Comprehensive validation warnings for debugging

## Smart Query Optimization:
- Type-context field prioritization
- Affinity-based confidence boosting
- Query plan optimization with type hints
- Performance metrics and cost estimation

## Production Features:
- All dynamic - learns from actual data patterns
- No stubs, fallbacks, or hardcoded lists
- Type-safe with comprehensive validation
- Real-time affinity tracking during CRUD
- Semantic matching for all field discovery

Example Intelligence:
Query: "documents by Smith with high citations"
→ Detects: NounType.Document (0.92 confidence)
→ Fields: "by" → "author" (0.87 type affinity boost)
→ Query: {type: "document", where: {author: "Smith", citations: {gt: 100}}}
→ Validates:  Documents have author field (87% affinity)
→ Optimizes: Process author first (lower cardinality)

This creates TRUE artificial intelligence for query understanding.
This commit is contained in:
David Snelling 2025-09-12 13:24:47 -07:00
parent 7b4838455a
commit 3e01a7d241
3 changed files with 407 additions and 31 deletions

View file

@ -95,6 +95,10 @@ export class MetadataIndexManager {
private readonly TIMESTAMP_PRECISION_MS = 60000 // 1 minute buckets
private readonly FLOAT_PRECISION = 2 // decimal places
// Type-Field Affinity Tracking for intelligent NLP
private typeFieldAffinity = new Map<string, Map<string, number>>() // nounType -> field -> count
private totalEntitiesByType = new Map<string, number>() // nounType -> total count
// Unified cache for coordinated memory management
private unifiedCache: UnifiedCache
@ -691,6 +695,9 @@ export class MetadataIndexManager {
this.updateCardinalityStats(field, value, 'add')
}
// Track type-field affinity for intelligent NLP
this.updateTypeFieldAffinity(id, field, value, 'add')
// Incrementally update sorted index for this field
if (!updatedFields.has(field)) {
this.updateSortedIndexAdd(field, value, id)
@ -795,6 +802,11 @@ export class MetadataIndexManager {
this.updateCardinalityStats(field, value, 'remove')
}
// Track type-field affinity for intelligent NLP
if (hadId) {
this.updateTypeFieldAffinity(id, field, value, 'remove')
}
// Incrementally update sorted index when removing
this.updateSortedIndexRemove(field, value, id)
@ -1868,4 +1880,147 @@ export class MetadataIndexManager {
return stats
}
/**
* Update type-field affinity tracking for intelligent NLP
* Tracks which fields commonly appear with which entity types
*/
private updateTypeFieldAffinity(entityId: string, field: string, value: any, operation: 'add' | 'remove'): void {
// Only track affinity for non-system fields
if (this.config.excludeFields.includes(field)) return
// Get the entity type from the "noun" field for this entity
let entityType: string | null = null
// Find the noun type for this entity
for (const [key, entry] of this.indexCache.entries()) {
if (key.startsWith('noun:') && entry.ids.has(entityId)) {
entityType = key.split(':', 2)[1]
break
}
}
if (!entityType) return // No type found, skip affinity tracking
// Initialize affinity tracking for this type
if (!this.typeFieldAffinity.has(entityType)) {
this.typeFieldAffinity.set(entityType, new Map())
}
if (!this.totalEntitiesByType.has(entityType)) {
this.totalEntitiesByType.set(entityType, 0)
}
const typeFields = this.typeFieldAffinity.get(entityType)!
if (operation === 'add') {
// Increment field count for this type
const currentCount = typeFields.get(field) || 0
typeFields.set(field, currentCount + 1)
// Update total entities of this type (only count once per entity)
if (field === 'noun') {
this.totalEntitiesByType.set(entityType, this.totalEntitiesByType.get(entityType)! + 1)
}
} else if (operation === 'remove') {
// Decrement field count for this type
const currentCount = typeFields.get(field) || 0
if (currentCount > 1) {
typeFields.set(field, currentCount - 1)
} else {
typeFields.delete(field)
}
// Update total entities of this type
if (field === 'noun') {
const total = this.totalEntitiesByType.get(entityType)!
if (total > 1) {
this.totalEntitiesByType.set(entityType, total - 1)
} else {
this.totalEntitiesByType.delete(entityType)
this.typeFieldAffinity.delete(entityType)
}
}
}
}
/**
* Get fields that commonly appear with a specific entity type
* Returns fields with their affinity scores (0-1)
*/
async getFieldsForType(nounType: string): Promise<Array<{
field: string
affinity: number
occurrences: number
totalEntities: number
}>> {
const typeFields = this.typeFieldAffinity.get(nounType)
const totalEntities = this.totalEntitiesByType.get(nounType)
if (!typeFields || !totalEntities) {
return []
}
const fieldsWithAffinity: Array<{
field: string
affinity: number
occurrences: number
totalEntities: number
}> = []
for (const [field, count] of typeFields.entries()) {
const affinity = count / totalEntities // 0-1 score
fieldsWithAffinity.push({
field,
affinity,
occurrences: count,
totalEntities
})
}
// Sort by affinity (most common fields first)
fieldsWithAffinity.sort((a, b) => b.affinity - a.affinity)
return fieldsWithAffinity
}
/**
* Get type-field affinity statistics for analysis
*/
async getTypeFieldAffinityStats(): Promise<{
totalTypes: number
averageFieldsPerType: number
typeBreakdown: Record<string, {
totalEntities: number
uniqueFields: number
topFields: Array<{field: string; affinity: number}>
}>
}> {
const typeBreakdown: Record<string, any> = {}
let totalFields = 0
for (const [nounType, fieldsMap] of this.typeFieldAffinity.entries()) {
const totalEntities = this.totalEntitiesByType.get(nounType) || 0
const fields = Array.from(fieldsMap.entries())
// Get top 5 fields for this type
const topFields = fields
.map(([field, count]) => ({ field, affinity: count / totalEntities }))
.sort((a, b) => b.affinity - a.affinity)
.slice(0, 5)
typeBreakdown[nounType] = {
totalEntities,
uniqueFields: fieldsMap.size,
topFields
}
totalFields += fieldsMap.size
}
return {
totalTypes: this.typeFieldAffinity.size,
averageFieldsPerType: totalFields / Math.max(1, this.typeFieldAffinity.size),
typeBreakdown
}
}
}