@arclabs561/ai-visual-test 0.5.1

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.
Files changed (93) hide show
  1. package/.secretsignore.example +20 -0
  2. package/CHANGELOG.md +360 -0
  3. package/CONTRIBUTING.md +63 -0
  4. package/DEPLOYMENT.md +80 -0
  5. package/LICENSE +22 -0
  6. package/README.md +142 -0
  7. package/SECURITY.md +108 -0
  8. package/api/health.js +34 -0
  9. package/api/validate.js +252 -0
  10. package/index.d.ts +1221 -0
  11. package/package.json +112 -0
  12. package/public/index.html +149 -0
  13. package/src/batch-optimizer.mjs +451 -0
  14. package/src/bias-detector.mjs +370 -0
  15. package/src/bias-mitigation.mjs +233 -0
  16. package/src/cache.mjs +433 -0
  17. package/src/config.mjs +268 -0
  18. package/src/constants.mjs +80 -0
  19. package/src/context-compressor.mjs +350 -0
  20. package/src/convenience.mjs +617 -0
  21. package/src/cost-tracker.mjs +257 -0
  22. package/src/cross-modal-consistency.mjs +170 -0
  23. package/src/data-extractor.mjs +232 -0
  24. package/src/dynamic-few-shot.mjs +140 -0
  25. package/src/dynamic-prompts.mjs +361 -0
  26. package/src/ensemble/index.mjs +53 -0
  27. package/src/ensemble-judge.mjs +366 -0
  28. package/src/error-handler.mjs +67 -0
  29. package/src/errors.mjs +167 -0
  30. package/src/experience-propagation.mjs +128 -0
  31. package/src/experience-tracer.mjs +487 -0
  32. package/src/explanation-manager.mjs +299 -0
  33. package/src/feedback-aggregator.mjs +248 -0
  34. package/src/game-goal-prompts.mjs +478 -0
  35. package/src/game-player.mjs +548 -0
  36. package/src/hallucination-detector.mjs +155 -0
  37. package/src/helpers/playwright.mjs +80 -0
  38. package/src/human-validation-manager.mjs +516 -0
  39. package/src/index.mjs +364 -0
  40. package/src/judge.mjs +929 -0
  41. package/src/latency-aware-batch-optimizer.mjs +192 -0
  42. package/src/load-env.mjs +159 -0
  43. package/src/logger.mjs +55 -0
  44. package/src/metrics.mjs +187 -0
  45. package/src/model-tier-selector.mjs +221 -0
  46. package/src/multi-modal/index.mjs +36 -0
  47. package/src/multi-modal-fusion.mjs +190 -0
  48. package/src/multi-modal.mjs +524 -0
  49. package/src/natural-language-specs.mjs +1071 -0
  50. package/src/pair-comparison.mjs +277 -0
  51. package/src/persona/index.mjs +42 -0
  52. package/src/persona-enhanced.mjs +200 -0
  53. package/src/persona-experience.mjs +572 -0
  54. package/src/position-counterbalance.mjs +140 -0
  55. package/src/prompt-composer.mjs +375 -0
  56. package/src/render-change-detector.mjs +583 -0
  57. package/src/research-enhanced-validation.mjs +436 -0
  58. package/src/retry.mjs +152 -0
  59. package/src/rubrics.mjs +231 -0
  60. package/src/score-tracker.mjs +277 -0
  61. package/src/smart-validator.mjs +447 -0
  62. package/src/spec-config.mjs +106 -0
  63. package/src/spec-templates.mjs +347 -0
  64. package/src/specs/index.mjs +38 -0
  65. package/src/temporal/index.mjs +102 -0
  66. package/src/temporal-adaptive.mjs +163 -0
  67. package/src/temporal-batch-optimizer.mjs +222 -0
  68. package/src/temporal-constants.mjs +69 -0
  69. package/src/temporal-context.mjs +49 -0
  70. package/src/temporal-decision-manager.mjs +271 -0
  71. package/src/temporal-decision.mjs +669 -0
  72. package/src/temporal-errors.mjs +58 -0
  73. package/src/temporal-note-pruner.mjs +173 -0
  74. package/src/temporal-preprocessor.mjs +543 -0
  75. package/src/temporal-prompt-formatter.mjs +219 -0
  76. package/src/temporal-validation.mjs +159 -0
  77. package/src/temporal.mjs +415 -0
  78. package/src/type-guards.mjs +311 -0
  79. package/src/uncertainty-reducer.mjs +470 -0
  80. package/src/utils/index.mjs +175 -0
  81. package/src/validation-framework.mjs +321 -0
  82. package/src/validation-result-normalizer.mjs +64 -0
  83. package/src/validation.mjs +243 -0
  84. package/src/validators/accessibility-programmatic.mjs +345 -0
  85. package/src/validators/accessibility-validator.mjs +223 -0
  86. package/src/validators/batch-validator.mjs +143 -0
  87. package/src/validators/hybrid-validator.mjs +268 -0
  88. package/src/validators/index.mjs +34 -0
  89. package/src/validators/prompt-builder.mjs +218 -0
  90. package/src/validators/rubric.mjs +85 -0
  91. package/src/validators/state-programmatic.mjs +260 -0
  92. package/src/validators/state-validator.mjs +291 -0
  93. package/vercel.json +27 -0
@@ -0,0 +1,543 @@
1
+ /**
2
+ * Temporal Preprocessing Manager
3
+ *
4
+ * Implements preprocessing patterns from high-stakes, low-latency domains (driving, aviation)
5
+ * for browser temporal note-taking. The key insight: preprocess expensive operations during
6
+ * low-Hz periods (idle, reading, stable) and use preprocessed data during high-Hz periods
7
+ * (rapid interactions, fast state changes).
8
+ *
9
+ * Research context (inspired by, not exact implementations):
10
+ * - arXiv:2406.12125 - Temporal dependency concepts (loosely related, not implementing core algorithm)
11
+ * - arXiv:2505.13326 - Adaptive batching concepts (inspired by, not exact method)
12
+ * - Human perception time scales (NN/g, PMC) - 0.1s threshold for direct manipulation
13
+ *
14
+ * Pattern:
15
+ * - High-Hz (10-60Hz): Fast note capture, use preprocessed aggregations
16
+ * - Low-Hz (0.1-1Hz): Expensive preprocessing (multi-scale aggregation, coherence, pruning)
17
+ *
18
+ * CACHE ARCHITECTURE NOTE:
19
+ * - This has its OWN in-memory cache (object), separate from VLLM cache and BatchOptimizer cache
20
+ * - Purpose: Cache aggregated temporal notes to avoid recomputation during high-activity periods
21
+ * - Why separate: Different data type (AggregatedTemporalNotes vs ValidationResult), different lifecycle
22
+ * (instance-scoped vs process-scoped vs persistent), different invalidation strategy (activity-based vs TTL)
23
+ * - No coordination with other caches (by design - they serve different purposes with minimal data overlap)
24
+ * - See docs/CACHE_ARCHITECTURE_DEEP_DIVE.md for details
25
+ */
26
+
27
+ import { aggregateTemporalNotes } from './temporal.mjs';
28
+ import { aggregateMultiScale } from './temporal-decision.mjs';
29
+ import { pruneTemporalNotes, selectTopWeightedNotes } from './temporal-note-pruner.mjs';
30
+ import { log, warn } from './logger.mjs';
31
+
32
+ /**
33
+ * Activity Detector
34
+ *
35
+ * Detects activity level based on note frequency and user interactions.
36
+ */
37
+ class ActivityDetector {
38
+ /**
39
+ * Detect activity level from temporal notes
40
+ *
41
+ * Activity detection is critical for preprocessing routing:
42
+ * - High activity (>10 notes/sec): Use cache for speed
43
+ * - Medium activity (1-10 notes/sec): Hybrid (cache if valid, else compute)
44
+ * - Low activity (<1 note/sec): Background preprocessing (non-blocking)
45
+ *
46
+ * Thresholds are based on human perception time scales:
47
+ * - High-Hz (>10Hz): Rapid interactions, fast state changes (gaming, scrolling)
48
+ * - Medium-Hz (1-10Hz): Normal browsing, moderate interactions
49
+ * - Low-Hz (<1Hz): Idle, reading, stable state (can do expensive preprocessing)
50
+ *
51
+ * DON'T CHANGE THRESHOLDS without:
52
+ * - Testing with real browser interactions
53
+ * - Validating cache hit rates
54
+ * - Measuring latency impact
55
+ *
56
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
57
+ * @param {number} recentWindow - Time window in ms to analyze (default: 5000ms)
58
+ * @returns {'high' | 'medium' | 'low'} Activity level
59
+ */
60
+ detectActivityLevel(notes, recentWindow = 5000) {
61
+ if (notes.length === 0) return 'low';
62
+
63
+ const now = Date.now();
64
+ const recent = notes.filter(n => {
65
+ const timestamp = n.timestamp || n.elapsed || 0;
66
+ const noteTime = typeof timestamp === 'number' ? timestamp : now;
67
+ return now - noteTime < recentWindow;
68
+ });
69
+
70
+ if (recent.length === 0) return 'low';
71
+
72
+ // Calculate note rate (notes per second)
73
+ const oldestRecent = recent[0];
74
+ const newestRecent = recent[recent.length - 1];
75
+ const oldestTime = oldestRecent.timestamp || oldestRecent.elapsed || now;
76
+ const newestTime = newestRecent.timestamp || newestRecent.elapsed || now;
77
+ const timeSpan = Math.max(100, newestTime - oldestTime); // At least 100ms
78
+ const noteRate = recent.length / (timeSpan / 1000); // notes per second
79
+
80
+ // Thresholds based on human perception and interaction rates
81
+ // High: >10 notes/sec (rapid interactions, fast state changes)
82
+ // Medium: 1-10 notes/sec (normal browsing, moderate interactions)
83
+ // Low: <1 note/sec (idle, reading, stable state)
84
+ if (noteRate > 10) return 'high';
85
+ if (noteRate > 1) return 'medium';
86
+ return 'low';
87
+ }
88
+
89
+ /**
90
+ * Check if there are recent user interactions
91
+ *
92
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
93
+ * @param {number} recentWindow - Time window in ms (default: 2000ms)
94
+ * @returns {boolean} True if user interaction detected
95
+ */
96
+ hasUserInteraction(notes, recentWindow = 2000) {
97
+ if (notes.length === 0) return false;
98
+
99
+ const now = Date.now();
100
+ const recent = notes.slice(-5).filter(n => {
101
+ const timestamp = n.timestamp || n.elapsed || 0;
102
+ const noteTime = typeof timestamp === 'number' ? timestamp : now;
103
+ return now - noteTime < recentWindow;
104
+ });
105
+
106
+ return recent.some(note =>
107
+ note.step?.includes('interaction') ||
108
+ note.step?.includes('click') ||
109
+ note.step?.includes('action') ||
110
+ note.observation?.includes('user') ||
111
+ note.observation?.includes('clicked') ||
112
+ note.observation?.includes('interaction')
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Check if state is stable (low variance in scores/observations)
118
+ *
119
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
120
+ * @param {number} window - Time window in ms (default: 2000ms)
121
+ * @returns {boolean} True if state is stable
122
+ */
123
+ isStableState(notes, window = 2000) {
124
+ if (notes.length < 3) return true; // Not enough data
125
+
126
+ const now = Date.now();
127
+ const recent = notes.slice(-5).filter(n => {
128
+ const timestamp = n.timestamp || n.elapsed || 0;
129
+ const noteTime = typeof timestamp === 'number' ? timestamp : now;
130
+ return now - noteTime < window;
131
+ });
132
+
133
+ if (recent.length < 3) return true; // Not enough recent data
134
+
135
+ // Extract scores
136
+ const scores = recent.map(n => {
137
+ if (n.score !== undefined) return n.score;
138
+ if (n.gameState?.score !== undefined) return n.gameState.score;
139
+ return 0;
140
+ });
141
+
142
+ // Calculate variance
143
+ const mean = scores.reduce((a, b) => a + b, 0) / scores.length;
144
+ const variance = scores.reduce((sum, score) => sum + Math.pow(score - mean, 2), 0) / scores.length;
145
+ const stdDev = Math.sqrt(variance);
146
+
147
+ // Low variance = stable state
148
+ return stdDev < 0.5; // Threshold: low variance
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Temporal Preprocessing Manager
154
+ *
155
+ * Manages preprocessing of temporal notes during low-activity periods
156
+ * and provides fast access to preprocessed data during high-activity periods.
157
+ *
158
+ * CACHE ARCHITECTURE NOTE:
159
+ * - This is a THIRD cache system, completely separate from VLLM cache and BatchOptimizer cache
160
+ * - In-memory object (not a Map), single cache entry (most recent preprocessing)
161
+ * - No keys, no coordination with other caches
162
+ * - Cache validity based on age (5s) and note count change (>20%)
163
+ * - "Incremental aggregation" is currently a lie (does full recomputation)
164
+ * - See CACHE_ARCHITECTURE_DEEP_DIVE.md for details
165
+ */
166
+ export class TemporalPreprocessingManager {
167
+ constructor(options = {}) {
168
+ this.activityDetector = new ActivityDetector();
169
+ this.preprocessedCache = {
170
+ aggregated: null,
171
+ multiScale: null,
172
+ coherence: null,
173
+ prunedNotes: null,
174
+ patterns: null,
175
+ lastPreprocessTime: 0,
176
+ noteCount: 0
177
+ };
178
+ this.preprocessInterval = options.preprocessInterval || 2000; // Preprocess every 2s during low activity
179
+ this.cacheMaxAge = options.cacheMaxAge || 5000; // Cache valid for 5s
180
+ this.preprocessingInProgress = false;
181
+ this.preprocessQueue = [];
182
+ }
183
+
184
+ /**
185
+ * Fast path: Get aggregation using preprocessed data if available
186
+ *
187
+ * Performance note: Full recomputation is fast enough for typical use cases (10-50 notes).
188
+ * Incremental aggregation would only be beneficial for very large datasets (1000+ notes),
189
+ * which is not a current use case. Full recomputation is O(n) and typically completes in <1ms.
190
+ *
191
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
192
+ * @param {Object} options - Aggregation options
193
+ * @returns {import('./index.mjs').AggregatedTemporalNotes} Aggregated notes
194
+ */
195
+ getFastAggregation(notes, options = {}) {
196
+ const activity = this.activityDetector.detectActivityLevel(notes);
197
+
198
+ // During high activity, use cache if valid
199
+ if (activity === 'high' && this.isCacheValid(notes)) {
200
+ log('[Preprocessor] Using cached aggregation (high activity)');
201
+ return this.preprocessedCache.aggregated;
202
+ }
203
+
204
+ // Full synchronous computation
205
+ // Note: For typical datasets (10-50 notes), full recomputation is fast (<1ms)
206
+ // and simpler than incremental approaches. If use cases emerge with 1000+ notes,
207
+ // consider implementing true incremental aggregation at that time.
208
+ return aggregateTemporalNotes(notes, options);
209
+ }
210
+
211
+ /**
212
+ * Background preprocessing during low activity
213
+ *
214
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
215
+ * @param {Object} options - Preprocessing options
216
+ * @returns {Promise<void>}
217
+ */
218
+ async preprocessInBackground(notes, options = {}) {
219
+ // Skip if already preprocessing
220
+ if (this.preprocessingInProgress) {
221
+ return;
222
+ }
223
+
224
+ const activity = this.activityDetector.detectActivityLevel(notes);
225
+ const hasInteraction = this.activityDetector.hasUserInteraction(notes);
226
+ const isStable = this.activityDetector.isStableState(notes);
227
+
228
+ // Only preprocess during low/medium activity and stable state
229
+ if (activity === 'high' || (hasInteraction && !isStable)) {
230
+ return; // Skip preprocessing, use cache or compute synchronously
231
+ }
232
+
233
+ this.preprocessingInProgress = true;
234
+
235
+ try {
236
+ // Do expensive operations
237
+ const aggregated = aggregateTemporalNotes(notes, {
238
+ windowSize: options.windowSize || 10000,
239
+ decayFactor: options.decayFactor || 0.9
240
+ });
241
+
242
+ const multiScale = aggregateMultiScale(notes, {
243
+ attentionWeights: true
244
+ });
245
+
246
+ const prunedNotes = pruneTemporalNotes(notes, {
247
+ maxNotes: options.maxNotes || 20,
248
+ minWeight: options.minWeight || 0.1
249
+ });
250
+
251
+ const topWeighted = selectTopWeightedNotes(notes, {
252
+ maxNotes: options.maxNotes || 20
253
+ });
254
+
255
+ // Identify patterns (lightweight)
256
+ const patterns = this._identifyPatterns(notes);
257
+
258
+ // Update cache
259
+ this.preprocessedCache = {
260
+ aggregated,
261
+ multiScale,
262
+ coherence: aggregated.coherence || 0,
263
+ prunedNotes,
264
+ topWeighted,
265
+ patterns,
266
+ lastPreprocessTime: Date.now(),
267
+ noteCount: notes.length
268
+ };
269
+
270
+ log(`[Preprocessor] Background preprocessing complete (${notes.length} notes, activity: ${activity})`);
271
+ } catch (error) {
272
+ warn(`[Preprocessor] Background preprocessing failed: ${error.message}`);
273
+ } finally {
274
+ this.preprocessingInProgress = false;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Check if cache is valid for current notes
280
+ *
281
+ * NOTE: This only checks note COUNT, not note CONTENT!
282
+ *
283
+ * The problem:
284
+ * - Validates cache if note count changed <20%
285
+ * - But doesn't check if notes actually changed
286
+ * - Example: 10 notes, replace 2 with different notes = same count = cache valid (WRONG!)
287
+ *
288
+ * Why this exists:
289
+ * - Checking note content would require hashing/comparing all notes (expensive)
290
+ * - Count-based check is fast but imprecise
291
+ * - 20% threshold is arbitrary (why 20%? why not 10% or 30%?)
292
+ *
293
+ * What should happen:
294
+ * - Hash note content to detect actual changes
295
+ * - Or use more sophisticated diff
296
+ * - Or accept that cache might be stale (document the risk)
297
+ *
298
+ * Current impact:
299
+ * - Cache might be used when notes changed but count didn't
300
+ * - Stale aggregations returned
301
+ * - Coherence scores might be wrong
302
+ *
303
+ * @param {import('./index.mjs').TemporalNote[]} notes - Current notes
304
+ * @returns {boolean} True if cache is valid
305
+ */
306
+ isCacheValid(notes) {
307
+ if (!this.preprocessedCache.aggregated) return false;
308
+
309
+ // Age-based invalidation: cache expires after cacheMaxAge (default: 5s)
310
+ const age = Date.now() - this.preprocessedCache.lastPreprocessTime;
311
+ if (age > this.cacheMaxAge) return false;
312
+
313
+ // Count-based invalidation: cache invalid if note count changed >20%
314
+ // NOTE: Cache invalidation is based on note count, not content comparison
315
+ // This is a performance optimization - full content comparison would be expensive
316
+ // Trade-off: May miss cases where notes changed but count stayed same (rare)
317
+ // Same count but different notes = cache considered valid (might be stale)
318
+ const noteCountDiff = Math.abs(notes.length - this.preprocessedCache.noteCount);
319
+ if (noteCountDiff > notes.length * 0.2) return false; // >20% change
320
+
321
+ return true;
322
+ }
323
+
324
+
325
+ /**
326
+ * Identify patterns in notes (lightweight)
327
+ *
328
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
329
+ * @returns {Object} Pattern information
330
+ */
331
+ _identifyPatterns(notes) {
332
+ if (notes.length < 3) {
333
+ return { trends: [], conflicts: [] };
334
+ }
335
+
336
+ const trends = [];
337
+ const conflicts = [];
338
+
339
+ // Simple trend detection: score progression
340
+ const scores = notes.map(n => n.score || n.gameState?.score || 0);
341
+ if (scores.length >= 3) {
342
+ const first = scores[0];
343
+ const last = scores[scores.length - 1];
344
+ if (last > first * 1.1) {
345
+ trends.push({ type: 'increasing', metric: 'score', magnitude: (last - first) / first });
346
+ } else if (last < first * 0.9) {
347
+ trends.push({ type: 'decreasing', metric: 'score', magnitude: (first - last) / first });
348
+ }
349
+ }
350
+
351
+ // Simple conflict detection: contradictory observations
352
+ const observations = notes.map(n => n.observation || '').filter(o => o.length > 0);
353
+ if (observations.length >= 2) {
354
+ // Check for contradictory keywords (simplified)
355
+ const hasPositive = observations.some(o =>
356
+ /good|great|excellent|improved|better/i.test(o)
357
+ );
358
+ const hasNegative = observations.some(o =>
359
+ /bad|poor|worse|declined|problem/i.test(o)
360
+ );
361
+ if (hasPositive && hasNegative) {
362
+ conflicts.push({ type: 'contradictory_observations', count: observations.length });
363
+ }
364
+ }
365
+
366
+ return { trends, conflicts };
367
+ }
368
+
369
+ /**
370
+ * Get preprocessed multi-scale aggregation
371
+ *
372
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
373
+ * @returns {Object} Multi-scale aggregation
374
+ */
375
+ getFastMultiScale(notes) {
376
+ if (this.isCacheValid(notes) && this.preprocessedCache.multiScale) {
377
+ return this.preprocessedCache.multiScale;
378
+ }
379
+
380
+ // Fallback to synchronous computation
381
+ return aggregateMultiScale(notes, { attentionWeights: true });
382
+ }
383
+
384
+ /**
385
+ * Get preprocessed pruned notes
386
+ *
387
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
388
+ * @param {Object} options - Pruning options
389
+ * @returns {import('./index.mjs').TemporalNote[]} Pruned notes
390
+ */
391
+ getFastPrunedNotes(notes, options = {}) {
392
+ if (this.isCacheValid(notes) && this.preprocessedCache.prunedNotes) {
393
+ return this.preprocessedCache.prunedNotes;
394
+ }
395
+
396
+ // Fallback to synchronous computation
397
+ return pruneTemporalNotes(notes, options);
398
+ }
399
+
400
+ /**
401
+ * Get cache statistics
402
+ *
403
+ * @returns {Object} Cache statistics
404
+ */
405
+ getCacheStats() {
406
+ return {
407
+ hasCache: !!this.preprocessedCache.aggregated,
408
+ cacheAge: this.preprocessedCache.aggregated
409
+ ? Date.now() - this.preprocessedCache.lastPreprocessTime
410
+ : null,
411
+ noteCount: this.preprocessedCache.noteCount,
412
+ coherence: this.preprocessedCache.coherence,
413
+ preprocessingInProgress: this.preprocessingInProgress
414
+ };
415
+ }
416
+
417
+ /**
418
+ * Clear cache
419
+ */
420
+ clearCache() {
421
+ this.preprocessedCache = {
422
+ aggregated: null,
423
+ multiScale: null,
424
+ coherence: null,
425
+ prunedNotes: null,
426
+ patterns: null,
427
+ lastPreprocessTime: 0,
428
+ noteCount: 0
429
+ };
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Adaptive Temporal Processor
435
+ *
436
+ * Routes processing to fast or expensive path based on activity level.
437
+ */
438
+ export class AdaptiveTemporalProcessor {
439
+ constructor(options = {}) {
440
+ this.preprocessor = new TemporalPreprocessingManager(options);
441
+ this.activityDetector = new ActivityDetector();
442
+ }
443
+
444
+ /**
445
+ * Smart processing: route to fast or expensive path based on activity
446
+ *
447
+ * @param {import('./index.mjs').TemporalNote[]} notes - Temporal notes
448
+ * @param {Object} options - Processing options
449
+ * @returns {Promise<Object>} Processing result with metadata
450
+ */
451
+ async processNotes(notes, options = {}) {
452
+ const activity = this.activityDetector.detectActivityLevel(notes);
453
+ const hasInteraction = this.activityDetector.hasUserInteraction(notes);
454
+ const isStable = this.activityDetector.isStableState(notes);
455
+
456
+ // High activity + interaction = use cache, fast path only
457
+ if (activity === 'high' && hasInteraction) {
458
+ const aggregated = this.preprocessor.getFastAggregation(notes, options);
459
+ return {
460
+ aggregated,
461
+ multiScale: this.preprocessor.getFastMultiScale(notes),
462
+ prunedNotes: this.preprocessor.getFastPrunedNotes(notes, options),
463
+ source: 'cache',
464
+ latency: '<10ms',
465
+ activity,
466
+ metadata: {
467
+ noteCount: notes.length,
468
+ cacheAge: this.preprocessor.preprocessedCache.lastPreprocessTime
469
+ ? Date.now() - this.preprocessor.preprocessedCache.lastPreprocessTime
470
+ : null
471
+ }
472
+ };
473
+ }
474
+
475
+ // Low activity + stable = do expensive preprocessing
476
+ if (activity === 'low' && isStable) {
477
+ await this.preprocessor.preprocessInBackground(notes, options);
478
+ return {
479
+ aggregated: this.preprocessor.preprocessedCache.aggregated,
480
+ multiScale: this.preprocessor.preprocessedCache.multiScale,
481
+ prunedNotes: this.preprocessor.preprocessedCache.prunedNotes,
482
+ patterns: this.preprocessor.preprocessedCache.patterns,
483
+ source: 'preprocessed',
484
+ latency: '100-1000ms (background)',
485
+ activity,
486
+ metadata: {
487
+ noteCount: notes.length,
488
+ cacheAge: 0
489
+ }
490
+ };
491
+ }
492
+
493
+ // Medium activity = hybrid: use cache if valid, else compute
494
+ if (this.preprocessor.isCacheValid(notes)) {
495
+ return {
496
+ aggregated: this.preprocessor.getFastAggregation(notes, options),
497
+ multiScale: this.preprocessor.getFastMultiScale(notes),
498
+ prunedNotes: this.preprocessor.getFastPrunedNotes(notes, options),
499
+ source: 'cache',
500
+ latency: '<10ms',
501
+ activity,
502
+ metadata: {
503
+ noteCount: notes.length,
504
+ cacheAge: Date.now() - this.preprocessor.preprocessedCache.lastPreprocessTime
505
+ }
506
+ };
507
+ } else {
508
+ // Compute synchronously but lighter version
509
+ const aggregated = aggregateTemporalNotes(notes, {
510
+ windowSize: options.windowSize || 10000,
511
+ decayFactor: options.decayFactor || 0.9
512
+ });
513
+
514
+ return {
515
+ aggregated,
516
+ multiScale: aggregateMultiScale(notes, { attentionWeights: true }),
517
+ prunedNotes: pruneTemporalNotes(notes, { maxNotes: options.maxNotes || 20 }),
518
+ source: 'computed',
519
+ latency: '50-200ms',
520
+ activity,
521
+ metadata: {
522
+ noteCount: notes.length,
523
+ cacheAge: null
524
+ }
525
+ };
526
+ }
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Create a temporal preprocessing manager with default options
532
+ */
533
+ export function createTemporalPreprocessingManager(options = {}) {
534
+ return new TemporalPreprocessingManager(options);
535
+ }
536
+
537
+ /**
538
+ * Create an adaptive temporal processor with default options
539
+ */
540
+ export function createAdaptiveTemporalProcessor(options = {}) {
541
+ return new AdaptiveTemporalProcessor(options);
542
+ }
543
+