@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.
- package/.secretsignore.example +20 -0
- package/CHANGELOG.md +360 -0
- package/CONTRIBUTING.md +63 -0
- package/DEPLOYMENT.md +80 -0
- package/LICENSE +22 -0
- package/README.md +142 -0
- package/SECURITY.md +108 -0
- package/api/health.js +34 -0
- package/api/validate.js +252 -0
- package/index.d.ts +1221 -0
- package/package.json +112 -0
- package/public/index.html +149 -0
- package/src/batch-optimizer.mjs +451 -0
- package/src/bias-detector.mjs +370 -0
- package/src/bias-mitigation.mjs +233 -0
- package/src/cache.mjs +433 -0
- package/src/config.mjs +268 -0
- package/src/constants.mjs +80 -0
- package/src/context-compressor.mjs +350 -0
- package/src/convenience.mjs +617 -0
- package/src/cost-tracker.mjs +257 -0
- package/src/cross-modal-consistency.mjs +170 -0
- package/src/data-extractor.mjs +232 -0
- package/src/dynamic-few-shot.mjs +140 -0
- package/src/dynamic-prompts.mjs +361 -0
- package/src/ensemble/index.mjs +53 -0
- package/src/ensemble-judge.mjs +366 -0
- package/src/error-handler.mjs +67 -0
- package/src/errors.mjs +167 -0
- package/src/experience-propagation.mjs +128 -0
- package/src/experience-tracer.mjs +487 -0
- package/src/explanation-manager.mjs +299 -0
- package/src/feedback-aggregator.mjs +248 -0
- package/src/game-goal-prompts.mjs +478 -0
- package/src/game-player.mjs +548 -0
- package/src/hallucination-detector.mjs +155 -0
- package/src/helpers/playwright.mjs +80 -0
- package/src/human-validation-manager.mjs +516 -0
- package/src/index.mjs +364 -0
- package/src/judge.mjs +929 -0
- package/src/latency-aware-batch-optimizer.mjs +192 -0
- package/src/load-env.mjs +159 -0
- package/src/logger.mjs +55 -0
- package/src/metrics.mjs +187 -0
- package/src/model-tier-selector.mjs +221 -0
- package/src/multi-modal/index.mjs +36 -0
- package/src/multi-modal-fusion.mjs +190 -0
- package/src/multi-modal.mjs +524 -0
- package/src/natural-language-specs.mjs +1071 -0
- package/src/pair-comparison.mjs +277 -0
- package/src/persona/index.mjs +42 -0
- package/src/persona-enhanced.mjs +200 -0
- package/src/persona-experience.mjs +572 -0
- package/src/position-counterbalance.mjs +140 -0
- package/src/prompt-composer.mjs +375 -0
- package/src/render-change-detector.mjs +583 -0
- package/src/research-enhanced-validation.mjs +436 -0
- package/src/retry.mjs +152 -0
- package/src/rubrics.mjs +231 -0
- package/src/score-tracker.mjs +277 -0
- package/src/smart-validator.mjs +447 -0
- package/src/spec-config.mjs +106 -0
- package/src/spec-templates.mjs +347 -0
- package/src/specs/index.mjs +38 -0
- package/src/temporal/index.mjs +102 -0
- package/src/temporal-adaptive.mjs +163 -0
- package/src/temporal-batch-optimizer.mjs +222 -0
- package/src/temporal-constants.mjs +69 -0
- package/src/temporal-context.mjs +49 -0
- package/src/temporal-decision-manager.mjs +271 -0
- package/src/temporal-decision.mjs +669 -0
- package/src/temporal-errors.mjs +58 -0
- package/src/temporal-note-pruner.mjs +173 -0
- package/src/temporal-preprocessor.mjs +543 -0
- package/src/temporal-prompt-formatter.mjs +219 -0
- package/src/temporal-validation.mjs +159 -0
- package/src/temporal.mjs +415 -0
- package/src/type-guards.mjs +311 -0
- package/src/uncertainty-reducer.mjs +470 -0
- package/src/utils/index.mjs +175 -0
- package/src/validation-framework.mjs +321 -0
- package/src/validation-result-normalizer.mjs +64 -0
- package/src/validation.mjs +243 -0
- package/src/validators/accessibility-programmatic.mjs +345 -0
- package/src/validators/accessibility-validator.mjs +223 -0
- package/src/validators/batch-validator.mjs +143 -0
- package/src/validators/hybrid-validator.mjs +268 -0
- package/src/validators/index.mjs +34 -0
- package/src/validators/prompt-builder.mjs +218 -0
- package/src/validators/rubric.mjs +85 -0
- package/src/validators/state-programmatic.mjs +260 -0
- package/src/validators/state-validator.mjs +291 -0
- 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
|
+
|