brainy/tests/unit/neural/signals/PatternSignal.test.ts
David Snelling 52782898a3 feat: implement progressive flush intervals for streaming imports
Progressive intervals adjust dynamically based on current entity count
(not total), making them work for both known and unknown totals.

**Key Features:**
- 0-999 entities: Flush every 100 (frequent early updates for UX)
- 1K-9.9K: Flush every 1000 (balanced performance)
- 10K+: Flush every 5000 (minimal overhead ~0.3%)

**Benefits:**
- Works with known totals (file imports)
- Works with unknown totals (streaming APIs, database cursors)
- Adapts automatically as import grows
- Zero configuration required

**Implementation:**
- Replaced adaptive intervals (requires total count) with progressive
- Added interval transition logging for observability
- Enhanced documentation to highlight engineering sophistication
- Final flush with statistics reporting

**Documentation:**
- Added "Engineering Insight" section showcasing advanced approach
- Updated all interval references from "adaptive" to "progressive"
- Added comprehensive examples in streaming-imports.md

Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 17:36:27 -07:00

700 lines
21 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest'
import { PatternSignal } from '../../../../src/neural/signals/PatternSignal.js'
import { NounType } from '../../../../src/types/graphTypes.js'
import type { Brainy } from '../../../../src/brainy.js'
// Mock minimal Brainy instance for testing
function createMockBrain(): Brainy {
return {
embed: async (text: string) => {
const hash = text.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0)
return Array(384).fill(0).map((_, i) => (hash + i) % 100 / 100)
}
} as any
}
describe('PatternSignal', () => {
let brain: Brainy
let signal: PatternSignal
beforeEach(() => {
brain = createMockBrain()
signal = new PatternSignal(brain)
})
describe('initialization', () => {
it('should initialize with default options', () => {
const defaultSignal = new PatternSignal(brain)
const stats = defaultSignal.getStats()
expect(stats).toBeDefined()
expect(stats.calls).toBe(0)
expect(stats.patternCount).toBeGreaterThan(50) // 56 patterns
})
it('should initialize with custom options', () => {
const customSignal = new PatternSignal(brain, {
minConfidence: 0.70,
cacheSize: 5000
})
expect(customSignal).toBeDefined()
})
it('should precompile patterns on initialization', () => {
const stats = signal.getStats()
expect(stats.patternCount).toBeGreaterThan(50)
})
})
describe('regex pattern matching - Person', () => {
it('should detect person titles', async () => {
const result = await signal.classify('Dr Smith')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Person)
expect(result?.source).toBe('pattern-regex')
})
it('should detect full names', async () => {
const result = await signal.classify('John Michael Doe')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Person)
})
it('should detect job titles', async () => {
const result = await signal.classify('Senior Software Engineer')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Person)
})
it('should detect CEO/CTO/CFO titles', async () => {
const result = await signal.classify('Our CEO announced')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Person)
})
it('should detect author/creator roles', async () => {
const result = await signal.classify('The author of this book')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Person)
})
})
describe('regex pattern matching - Location', () => {
it('should detect cities and towns', async () => {
const result = await signal.classify('The city of Paris')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Location)
})
it('should detect street keyword', async () => {
const result = await signal.classify('walk down the street today')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Location)
})
it('should detect City, State format', async () => {
const result = await signal.classify('Austin, TX')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Location)
})
it('should detect city pattern', async () => {
const result = await signal.classify('the city center')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Location)
})
})
describe('regex pattern matching - Organization', () => {
it('should detect Inc/LLC/Corp suffixes', async () => {
const result = await signal.classify('TechCorp Inc')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Organization)
})
it('should detect company keyword', async () => {
const result = await signal.classify('local university')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Organization)
})
it('should detect universities', async () => {
const result = await signal.classify('state university system')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Organization)
})
it('should detect departments', async () => {
const result = await signal.classify('marketing department team')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Organization)
})
it('should detect government agencies', async () => {
const result = await signal.classify('government agency office')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Organization)
})
})
describe('regex pattern matching - Technology', () => {
it('should detect programming languages', async () => {
const result = await signal.classify('Written in JavaScript')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should detect frameworks', async () => {
const result = await signal.classify('Built with React')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should detect cloud platforms', async () => {
const result = await signal.classify('Deployed on AWS')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should detect databases', async () => {
const result = await signal.classify('Stored in PostgreSQL')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should detect APIs', async () => {
const result = await signal.classify('REST API endpoint')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
})
describe('regex pattern matching - Event', () => {
it('should detect conferences', async () => {
const result = await signal.classify('attending the conference')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Event)
})
it('should detect meetings', async () => {
const result = await signal.classify('schedule a meeting')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Event)
})
it('should detect deployment events', async () => {
const result = await signal.classify('upcoming deployment window')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Event)
})
it('should detect workshops', async () => {
const result = await signal.classify('Workshop on AI')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Event)
})
})
describe('regex pattern matching - Concept', () => {
it('should detect theories', async () => {
const result = await signal.classify('Theory of Relativity')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Concept)
})
it('should detect patterns', async () => {
const result = await signal.classify('Singleton pattern implementation')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Concept)
})
it('should detect algorithms', async () => {
const result = await signal.classify('Sorting algorithm')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Concept)
})
it('should detect architectures', async () => {
const result = await signal.classify('Microservices architecture')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Concept)
})
})
describe('regex pattern matching - Object', () => {
it('should detect devices', async () => {
const result = await signal.classify('Mobile device')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should detect vehicles', async () => {
const result = await signal.classify('Electric car')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should detect computers', async () => {
const result = await signal.classify('Laptop computer')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should detect tools', async () => {
const result = await signal.classify('Power tool')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
})
describe('regex pattern matching - Document', () => {
it('should detect documents', async () => {
const result = await signal.classify('Technical document')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Document)
})
it('should detect reports', async () => {
const result = await signal.classify('Annual report 2024')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Document)
})
it('should detect specifications', async () => {
const result = await signal.classify('technical specification document')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Document)
})
it('should detect file extensions', async () => {
const result = await signal.classify('readme.pdf')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Document)
})
})
describe('regex pattern matching - File', () => {
it('should detect source files', async () => {
const result = await signal.classify('index.ts')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.File)
})
it('should detect config files', async () => {
const result = await signal.classify('config.yaml')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.File)
})
it('should detect image files', async () => {
const result = await signal.classify('logo.png')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.File)
})
})
describe('naming convention patterns', () => {
it('should detect PascalCase as Concept', async () => {
const result = await signal.classify('UserInterface')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Concept)
expect(result?.source).toBe('pattern-naming')
expect(result?.metadata?.matchedPattern).toBe('PascalCase')
})
it('should detect camelCase as Attribute', async () => {
const result = await signal.classify('userName')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Attribute)
expect(result?.metadata?.matchedPattern).toBe('camelCase')
})
it('should detect UPPER_CASE as Attribute', async () => {
const result = await signal.classify('MAX_CONNECTIONS')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Attribute)
expect(result?.metadata?.matchedPattern).toBe('UPPER_CASE')
})
it('should detect snake_case as Attribute', async () => {
const result = await signal.classify('user_name')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Attribute)
expect(result?.metadata?.matchedPattern).toBe('snake_case')
})
it('should detect kebab-case as File', async () => {
const result = await signal.classify('my-component')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.File)
expect(result?.metadata?.matchedPattern).toBe('kebab-case')
})
})
describe('structural patterns', () => {
it('should detect email as Person', async () => {
const result = await signal.classify('john.doe@example.com')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Person)
expect(result?.source).toBe('pattern-structural')
expect(result?.metadata?.matchedPattern).toBe('email')
})
it('should detect URL as Object', async () => {
const result = await signal.classify('https://example.com')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
expect(result?.metadata?.matchedPattern).toBe('url')
})
it('should detect UUID as Object', async () => {
const result = await signal.classify('550e8400-e29b-41d4-a716-446655440000')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
expect(result?.metadata?.matchedPattern).toBe('uuid')
})
it('should detect semantic version as Project', async () => {
const result = await signal.classify('v1.2.3')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Project)
// Can match via regex or structural pattern
expect(['pattern-regex', 'pattern-structural']).toContain(result?.source)
})
it('should detect semantic version without v prefix', async () => {
const result = await signal.classify('2.0.1')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Project)
})
it('should detect ISO date as Event', async () => {
const result = await signal.classify('2024-01-15T10:30:00')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Event)
expect(result?.metadata?.matchedPattern).toBe('iso_date')
})
it('should detect phone number as Person', async () => {
const result = await signal.classify('+1-555-123-4567')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Person)
expect(result?.metadata?.matchedPattern).toBe('phone')
})
})
describe('definition context matching', () => {
it('should use definition text for better matching', async () => {
const result = await signal.classify('Apollo', {
definition: 'A JavaScript framework for building GraphQL APIs'
})
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should match candidate even without definition', async () => {
const result = await signal.classify('JavaScript')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should combine candidate and definition for matching', async () => {
const result = await signal.classify('React', {
definition: 'A JavaScript library for building user interfaces with components'
})
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
// Multiple JavaScript/React/library matches possible
expect(result?.metadata?.matchCount).toBeGreaterThanOrEqual(1)
})
})
describe('caching', () => {
it('should cache successful lookups', async () => {
const result1 = await signal.classify('JavaScript')
const result2 = await signal.classify('JavaScript')
expect(result1).toEqual(result2)
const stats = signal.getStats()
expect(stats.cacheHits).toBe(1)
expect(stats.cacheHitRate).toBe(0.5)
})
it('should cache null results', async () => {
const result1 = await signal.classify('!@#$%^&*()') // Special chars, won't match
const result2 = await signal.classify('!@#$%^&*()')
expect(result1).toBeNull()
expect(result2).toBeNull()
const stats = signal.getStats()
expect(stats.cacheHits).toBe(1)
})
it('should respect cache size limit', async () => {
const smallCacheSignal = new PatternSignal(brain, {
cacheSize: 2
})
await smallCacheSignal.classify('JavaScript')
await smallCacheSignal.classify('Python')
await smallCacheSignal.classify('TypeScript') // Evicts JavaScript
const stats = smallCacheSignal.getStats()
expect(stats.cacheSize).toBeLessThanOrEqual(2)
})
it('should clear cache on demand', async () => {
await signal.classify('JavaScript')
expect(signal.getStats().cacheSize).toBe(1)
signal.clearCache()
expect(signal.getStats().cacheSize).toBe(0)
})
})
describe('statistics', () => {
it('should track all statistics', async () => {
const newSignal = new PatternSignal(brain) // Fresh signal for clean stats
await newSignal.classify('JavaScript') // Regex match
await newSignal.classify('JavaScript') // Cache hit
await newSignal.classify('userName') // Naming match
await newSignal.classify('hello@email.com') // Structural match (unique)
const stats = newSignal.getStats()
expect(stats.calls).toBe(4)
// Email might match regex pattern too, so be lenient
expect(stats.regexMatches).toBeGreaterThanOrEqual(1)
expect(stats.namingMatches).toBeGreaterThanOrEqual(1)
expect(stats.structuralMatches).toBeGreaterThanOrEqual(1)
expect(stats.cacheHits).toBe(1)
expect(stats.patternCount).toBeGreaterThan(50)
})
it('should calculate match rates', async () => {
await signal.classify('JavaScript')
await signal.classify('Python')
await signal.classify('unknown')
const stats = signal.getStats()
expect(stats.regexMatchRate).toBeCloseTo(2/3, 2)
})
it('should reset statistics', async () => {
await signal.classify('JavaScript')
signal.resetStats()
const stats = signal.getStats()
expect(stats.calls).toBe(0)
expect(stats.regexMatches).toBe(0)
expect(stats.patternCount).toBeGreaterThan(50) // Patterns not cleared
})
})
describe('edge cases', () => {
it('should handle empty string', async () => {
const result = await signal.classify('')
expect(result).toBeNull()
})
it('should handle whitespace-only string', async () => {
const result = await signal.classify(' ')
expect(result).toBeNull()
})
it('should handle very long strings', async () => {
const longString = 'JavaScript '.repeat(1000)
const result = await signal.classify(longString)
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should handle special characters', async () => {
const result = await signal.classify('C++')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
it('should handle mixed content', async () => {
const result = await signal.classify('Built with JavaScript and React')
expect(result).toBeDefined()
expect(result?.type).toBe(NounType.Thing)
})
})
describe('real-world scenarios', () => {
it('should classify software developer', async () => {
const result = await signal.classify('Sarah Johnson, Senior Software Engineer')
expect(result?.type).toBe(NounType.Person)
})
it('should classify company', async () => {
const result = await signal.classify('Google LLC')
expect(result?.type).toBe(NounType.Organization)
})
it('should classify tech stack', async () => {
const result = await signal.classify('Node.js application')
expect(result?.type).toBe(NounType.Thing)
})
it('should classify event', async () => {
const result = await signal.classify('Annual conference meeting')
expect(result?.type).toBe(NounType.Event)
})
it('should classify design pattern', async () => {
const result = await signal.classify('Observer pattern')
expect(result?.type).toBe(NounType.Concept)
})
it('should classify file path', async () => {
const result = await signal.classify('src/index.ts')
expect(result?.type).toBe(NounType.File)
})
it('should classify API endpoint', async () => {
const result = await signal.classify('REST API')
expect(result?.type).toBe(NounType.Thing)
})
it('should classify documentation', async () => {
const result = await signal.classify('Technical specification document')
expect(result?.type).toBe(NounType.Document)
})
})
describe('priority and confidence', () => {
it('should prefer regex matches over naming', async () => {
// "React" matches both technology regex and PascalCase naming
const result = await signal.classify('React')
expect(result?.source).toBe('pattern-regex') // Regex checked first
expect(result?.type).toBe(NounType.Thing)
})
it('should cap confidence at 0.85', async () => {
const result = await signal.classify('JavaScript')
expect(result?.confidence).toBeLessThanOrEqual(0.85)
})
it('should respect minConfidence threshold', async () => {
const strictSignal = new PatternSignal(brain, {
minConfidence: 0.90
})
// Most patterns have confidence < 0.90
const result = await strictSignal.classify('JavaScript')
expect(result).toBeNull() // Below threshold
})
})
describe('performance', () => {
it('should handle 1000 classifications quickly', async () => {
const terms = [
'JavaScript', 'Python', 'React', 'Angular', 'Vue',
'Dr. Smith', 'John Doe', 'CEO', 'Manager', 'Engineer',
'New York', 'Paris', 'London', 'Tokyo', 'Berlin'
]
const start = Date.now()
for (let i = 0; i < 1000; i++) {
await signal.classify(terms[i % terms.length])
}
const elapsed = Date.now() - start
// Should complete in < 300ms (mostly cached after first 15)
expect(elapsed).toBeLessThan(300)
})
it('should have fast pattern matching', async () => {
const start = Date.now()
for (let i = 0; i < 100; i++) {
await signal.classify('JavaScript framework')
}
const elapsed = Date.now() - start
// First call compiles, rest are cached
expect(elapsed).toBeLessThan(50)
})
})
})