bulltrackers-module 1.0.768 → 1.0.769

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 (51) hide show
  1. package/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
  2. package/functions/computation-system-v2/computations/BehavioralAnomaly.js +557 -337
  3. package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
  4. package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
  5. package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
  6. package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
  7. package/functions/computation-system-v2/computations/SignedInUserList.js +51 -0
  8. package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
  9. package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
  10. package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
  11. package/functions/computation-system-v2/config/bulltrackers.config.js +30 -128
  12. package/functions/computation-system-v2/core-api.js +17 -9
  13. package/functions/computation-system-v2/data_schema_reference.MD +108 -0
  14. package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
  15. package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
  16. package/functions/computation-system-v2/devtools/index.js +36 -0
  17. package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
  18. package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
  19. package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
  20. package/functions/computation-system-v2/devtools/shared/index.js +16 -0
  21. package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
  22. package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
  23. package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
  24. package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
  25. package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
  26. package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
  27. package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
  28. package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
  29. package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
  30. package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
  31. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
  32. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
  33. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
  34. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
  35. package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
  36. package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
  37. package/functions/computation-system-v2/framework/data/DataFetcher.js +250 -184
  38. package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
  39. package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
  40. package/functions/computation-system-v2/framework/execution/Orchestrator.js +215 -129
  41. package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
  42. package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
  43. package/functions/computation-system-v2/framework/storage/StorageManager.js +105 -67
  44. package/functions/computation-system-v2/framework/testing/ComputationTester.js +12 -6
  45. package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
  46. package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
  47. package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
  48. package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
  49. package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
  50. package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
  51. package/package.json +1 -1
@@ -0,0 +1,581 @@
1
+ /**
2
+ * @fileoverview Simulation Server
3
+ *
4
+ * HTTP server with WebSocket for live updates.
5
+ * Provides:
6
+ * - REST API for simulation control
7
+ * - File watcher for auto-reload
8
+ * - WebSocket for live result push
9
+ * - Dashboard serving
10
+ */
11
+
12
+ const http = require('http');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { promisify } = require('util');
16
+
17
+ // Try to load optional dependencies
18
+ let chokidar;
19
+ try {
20
+ chokidar = require('chokidar');
21
+ } catch (e) {
22
+ console.warn('chokidar not available - file watching disabled');
23
+ }
24
+
25
+ const { SimulationEngine } = require('./SimulationEngine');
26
+ const { DAGAnalyzer } = require('./DAGAnalyzer');
27
+ const { SystemIntrospector } = require('../shared/SystemIntrospector');
28
+
29
+ class SimulationServer {
30
+ /**
31
+ * @param {Object} config - The bulltrackers.config.js exports
32
+ * @param {Object} options
33
+ * @param {number} [options.port=3210] - Server port
34
+ * @param {boolean} [options.watch=true] - Enable file watching
35
+ * @param {string} [options.computationsDir] - Directory to watch
36
+ * @param {number} [options.entityCount=10] - Default entity count
37
+ */
38
+ constructor(config, options = {}) {
39
+ this.config = config;
40
+ this.port = options.port || 3210;
41
+ this.shouldWatch = options.watch !== false;
42
+ this.computationsDir = options.computationsDir ||
43
+ path.resolve(__dirname, '../../computations');
44
+ this.entityCount = options.entityCount || 10;
45
+
46
+ // Core components
47
+ this.engine = new SimulationEngine(config, {
48
+ entityCount: this.entityCount,
49
+ verbose: true
50
+ });
51
+ this.introspector = new SystemIntrospector(config);
52
+ this.dagAnalyzer = new DAGAnalyzer(this.introspector);
53
+
54
+ // Server state
55
+ this.server = null;
56
+ this.watcher = null;
57
+ this.clients = new Set(); // WebSocket-like clients (SSE)
58
+
59
+ // Current simulation state
60
+ this.lastRun = null;
61
+ this.activeComputation = null;
62
+ }
63
+
64
+ /**
65
+ * Start the server.
66
+ */
67
+ async start() {
68
+ await this.engine.initialize();
69
+
70
+ this.server = http.createServer((req, res) => this._handleRequest(req, res));
71
+
72
+ this.server.listen(this.port, () => {
73
+ console.log(`\n🚀 Simulation Server running at http://localhost:${this.port}`);
74
+ console.log(` Dashboard: http://localhost:${this.port}/`);
75
+ console.log(` API: http://localhost:${this.port}/api`);
76
+ console.log(`\n Watching: ${this.computationsDir}\n`);
77
+ });
78
+
79
+ // Start file watcher
80
+ if (this.shouldWatch && chokidar) {
81
+ this._startWatcher();
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Stop the server.
87
+ */
88
+ stop() {
89
+ if (this.watcher) {
90
+ this.watcher.close();
91
+ }
92
+ if (this.server) {
93
+ this.server.close();
94
+ }
95
+ }
96
+
97
+ // =========================================================================
98
+ // REQUEST HANDLING
99
+ // =========================================================================
100
+
101
+ _handleRequest(req, res) {
102
+ const url = new URL(req.url, `http://localhost:${this.port}`);
103
+ const pathname = url.pathname;
104
+
105
+ // CORS headers
106
+ res.setHeader('Access-Control-Allow-Origin', '*');
107
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
108
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
109
+
110
+ if (req.method === 'OPTIONS') {
111
+ res.writeHead(204);
112
+ res.end();
113
+ return;
114
+ }
115
+
116
+ // Route handling
117
+ if (pathname === '/' || pathname === '/index.html') {
118
+ this._serveDashboard(res);
119
+ } else if (pathname === '/dashboard.css') {
120
+ this._serveFile(res, 'dashboard.css', 'text/css');
121
+ } else if (pathname === '/dashboard.js') {
122
+ this._serveFile(res, 'dashboard.js', 'application/javascript');
123
+ } else if (pathname === '/events') {
124
+ this._handleSSE(req, res);
125
+ } else if (pathname.startsWith('/api/')) {
126
+ this._handleAPI(req, res, pathname, url);
127
+ } else {
128
+ res.writeHead(404);
129
+ res.end('Not Found');
130
+ }
131
+ }
132
+
133
+ async _handleAPI(req, res, pathname, url) {
134
+ res.setHeader('Content-Type', 'application/json');
135
+
136
+ try {
137
+ let result;
138
+
139
+ switch (pathname) {
140
+ case '/api/stats':
141
+ result = this.introspector.getStats();
142
+ break;
143
+
144
+ case '/api/computations':
145
+ result = this.introspector.getComputationNames().map(name => ({
146
+ name,
147
+ passLevel: this.introspector.getPassLevel(name),
148
+ config: this.introspector.getComputationConfig(name)
149
+ }));
150
+ break;
151
+
152
+ case '/api/dag':
153
+ result = this.dagAnalyzer.getVisualizationData();
154
+ break;
155
+
156
+ case '/api/dag/summary':
157
+ result = this.dagAnalyzer.getSummary();
158
+ break;
159
+
160
+ case '/api/tables':
161
+ result = this.introspector.getAvailableTables().map(name => ({
162
+ name,
163
+ ...this.introspector.getTableMetadata(name)
164
+ }));
165
+ break;
166
+
167
+ case '/api/rules':
168
+ result = {};
169
+ for (const mod of this.introspector.getRuleModules()) {
170
+ result[mod] = this.introspector.getRuleFunctions(mod);
171
+ }
172
+ break;
173
+
174
+ case '/api/simulate': {
175
+ // POST body contains computation name
176
+ const body = await this._readBody(req);
177
+ const { computation, entityCount, entityIds, targetDate } = JSON.parse(body);
178
+
179
+ this.activeComputation = computation;
180
+ this._broadcast({ type: 'simulationStarted', computation });
181
+
182
+ result = await this.engine.runComputation(computation, {
183
+ entityCount: entityCount || this.entityCount,
184
+ entityIds,
185
+ targetDate
186
+ });
187
+
188
+ this.lastRun = { computation, result, timestamp: new Date().toISOString() };
189
+ this._broadcast({ type: 'simulationComplete', ...this.lastRun });
190
+ this.activeComputation = null;
191
+ break;
192
+ }
193
+
194
+ case '/api/simulate-with-deps': {
195
+ const body = await this._readBody(req);
196
+ const { computation, entityCount, targetDate } = JSON.parse(body);
197
+
198
+ this.activeComputation = computation;
199
+ this._broadcast({ type: 'simulationStarted', computation, withDependencies: true });
200
+
201
+ result = await this.engine.runWithDependencies(computation, {
202
+ entityCount: entityCount || this.entityCount,
203
+ targetDate
204
+ });
205
+
206
+ this.lastRun = { computation, result, timestamp: new Date().toISOString() };
207
+ this._broadcast({ type: 'simulationComplete', ...this.lastRun });
208
+ this.activeComputation = null;
209
+ break;
210
+ }
211
+
212
+ case '/api/validate': {
213
+ const body = await this._readBody(req);
214
+ const { computation } = JSON.parse(body);
215
+ result = this.engine.validateComputation(computation);
216
+ break;
217
+ }
218
+
219
+ case '/api/info': {
220
+ const computation = url.searchParams.get('computation');
221
+ result = this.engine.getComputationInfo(computation);
222
+ break;
223
+ }
224
+
225
+ case '/api/results': {
226
+ const computation = url.searchParams.get('computation');
227
+ result = this.engine.getResults(computation);
228
+ break;
229
+ }
230
+
231
+ case '/api/reset':
232
+ this.engine.reset();
233
+ result = { success: true };
234
+ break;
235
+
236
+ case '/api/last-run':
237
+ result = this.lastRun || null;
238
+ break;
239
+
240
+ case '/api/execution-log':
241
+ result = this.engine.getExecutionLog();
242
+ break;
243
+
244
+ default:
245
+ res.writeHead(404);
246
+ result = { error: 'Unknown endpoint' };
247
+ }
248
+
249
+ res.writeHead(200);
250
+ res.end(JSON.stringify(result, null, 2));
251
+
252
+ } catch (error) {
253
+ res.writeHead(500);
254
+ res.end(JSON.stringify({ error: error.message, stack: error.stack }));
255
+ }
256
+ }
257
+
258
+ // =========================================================================
259
+ // SSE (Server-Sent Events)
260
+ // =========================================================================
261
+
262
+ _handleSSE(req, res) {
263
+ res.writeHead(200, {
264
+ 'Content-Type': 'text/event-stream',
265
+ 'Cache-Control': 'no-cache',
266
+ 'Connection': 'keep-alive'
267
+ });
268
+
269
+ const client = {
270
+ send: (data) => {
271
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
272
+ }
273
+ };
274
+
275
+ this.clients.add(client);
276
+
277
+ req.on('close', () => {
278
+ this.clients.delete(client);
279
+ });
280
+
281
+ // Send initial state
282
+ client.send({ type: 'connected', timestamp: new Date().toISOString() });
283
+ }
284
+
285
+ _broadcast(data) {
286
+ for (const client of this.clients) {
287
+ try {
288
+ client.send(data);
289
+ } catch (e) {
290
+ this.clients.delete(client);
291
+ }
292
+ }
293
+ }
294
+
295
+ // =========================================================================
296
+ // FILE WATCHING
297
+ // =========================================================================
298
+
299
+ _startWatcher() {
300
+ this.watcher = chokidar.watch(path.join(this.computationsDir, '**/*.js'), {
301
+ ignoreInitial: true
302
+ });
303
+
304
+ this.watcher.on('change', (filepath) => {
305
+ const filename = path.basename(filepath);
306
+ console.log(`📝 File changed: ${filename}`);
307
+
308
+ // Clear require cache for this file
309
+ delete require.cache[require.resolve(filepath)];
310
+
311
+ this._broadcast({
312
+ type: 'fileChanged',
313
+ file: filename,
314
+ path: filepath,
315
+ timestamp: new Date().toISOString()
316
+ });
317
+
318
+ // TODO: Auto-rerun if active computation matches changed file
319
+ });
320
+ }
321
+
322
+ // =========================================================================
323
+ // STATIC FILE SERVING
324
+ // =========================================================================
325
+
326
+ _serveDashboard(res) {
327
+ const dashboardPath = path.join(__dirname, 'ui', 'dashboard.html');
328
+ if (fs.existsSync(dashboardPath)) {
329
+ res.setHeader('Content-Type', 'text/html');
330
+ res.writeHead(200);
331
+ res.end(fs.readFileSync(dashboardPath, 'utf8'));
332
+ } else {
333
+ res.setHeader('Content-Type', 'text/html');
334
+ res.writeHead(200);
335
+ res.end(this._getInlineDashboard());
336
+ }
337
+ }
338
+
339
+ _serveFile(res, filename, contentType) {
340
+ const filePath = path.join(__dirname, 'ui', filename);
341
+ if (fs.existsSync(filePath)) {
342
+ res.setHeader('Content-Type', contentType);
343
+ res.writeHead(200);
344
+ res.end(fs.readFileSync(filePath, 'utf8'));
345
+ } else {
346
+ res.writeHead(404);
347
+ res.end('Not Found');
348
+ }
349
+ }
350
+
351
+ _getInlineDashboard() {
352
+ return `<!DOCTYPE html>
353
+ <html lang="en">
354
+ <head>
355
+ <meta charset="UTF-8">
356
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
357
+ <title>Computation Simulator</title>
358
+ <style>
359
+ * { box-sizing: border-box; margin: 0; padding: 0; }
360
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; }
361
+ .container { max-width: 1400px; margin: 0 auto; padding: 1.5rem; }
362
+ header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid #334155; }
363
+ h1 { font-size: 1.5rem; display: flex; align-items: center; gap: 0.5rem; }
364
+ h1::before { content: '🔄'; }
365
+ .status { padding: 0.5rem 1rem; border-radius: 9999px; font-size: 0.875rem; }
366
+ .status.connected { background: #064e3b; color: #6ee7b7; }
367
+ .status.disconnected { background: #7f1d1d; color: #fca5a5; }
368
+ .grid { display: grid; grid-template-columns: 300px 1fr; gap: 1.5rem; }
369
+ .sidebar { display: flex; flex-direction: column; gap: 1rem; }
370
+ .card { background: #1e293b; border-radius: 0.75rem; padding: 1.25rem; }
371
+ .card h2 { font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.05em; color: #94a3b8; margin-bottom: 1rem; }
372
+ .comp-list { max-height: 400px; overflow-y: auto; }
373
+ .comp-item { padding: 0.75rem; border-radius: 0.5rem; cursor: pointer; margin-bottom: 0.5rem; background: #334155; display: flex; justify-content: space-between; align-items: center; transition: background 0.2s; }
374
+ .comp-item:hover { background: #475569; }
375
+ .comp-item.active { background: #3b82f6; }
376
+ .pass-badge { background: #4338ca; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; }
377
+ .main { display: flex; flex-direction: column; gap: 1.5rem; }
378
+ .controls { display: flex; gap: 1rem; }
379
+ .btn { padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: none; cursor: pointer; font-weight: 600; transition: all 0.2s; }
380
+ .btn-primary { background: #3b82f6; color: white; }
381
+ .btn-primary:hover { background: #2563eb; }
382
+ .btn-secondary { background: #475569; color: white; }
383
+ .btn-secondary:hover { background: #64748b; }
384
+ .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
385
+ .stat { background: #334155; padding: 1rem; border-radius: 0.5rem; text-align: center; }
386
+ .stat-value { font-size: 1.5rem; font-weight: 700; color: #60a5fa; }
387
+ .stat-label { font-size: 0.75rem; color: #94a3b8; margin-top: 0.25rem; }
388
+ .output { background: #0f172a; border-radius: 0.5rem; padding: 1rem; max-height: 400px; overflow-y: auto; font-family: monospace; font-size: 0.875rem; white-space: pre-wrap; }
389
+ .log-entry { padding: 0.5rem; border-left: 3px solid #3b82f6; margin-bottom: 0.5rem; background: #1e293b; }
390
+ .log-entry.success { border-color: #22c55e; }
391
+ .log-entry.error { border-color: #ef4444; }
392
+ .validation { padding: 1rem; border-radius: 0.5rem; }
393
+ .validation.valid { background: #064e3b; }
394
+ .validation.invalid { background: #7f1d1d; }
395
+ #result-output { background: #0f172a; }
396
+ </style>
397
+ </head>
398
+ <body>
399
+ <div class="container">
400
+ <header>
401
+ <h1>Computation Simulator</h1>
402
+ <span id="status" class="status disconnected">Disconnected</span>
403
+ </header>
404
+ <div class="grid">
405
+ <aside class="sidebar">
406
+ <div class="card">
407
+ <h2>Computations</h2>
408
+ <div id="comp-list" class="comp-list"></div>
409
+ </div>
410
+ <div class="card">
411
+ <h2>System Stats</h2>
412
+ <div id="system-stats"></div>
413
+ </div>
414
+ </aside>
415
+ <main class="main">
416
+ <div class="card">
417
+ <h2>Controls</h2>
418
+ <div class="controls">
419
+ <button class="btn btn-primary" id="btn-run">Run Selected</button>
420
+ <button class="btn btn-secondary" id="btn-run-deps">Run with Dependencies</button>
421
+ <button class="btn btn-secondary" id="btn-validate">Validate</button>
422
+ <button class="btn btn-secondary" id="btn-reset">Reset</button>
423
+ </div>
424
+ </div>
425
+ <div class="card">
426
+ <h2>Results</h2>
427
+ <div class="stats-grid" id="result-stats"></div>
428
+ <div class="output" id="result-output">Select a computation and click Run</div>
429
+ </div>
430
+ <div class="card">
431
+ <h2>Execution Log</h2>
432
+ <div class="output" id="execution-log"></div>
433
+ </div>
434
+ </main>
435
+ </div>
436
+ </div>
437
+ <script>
438
+ let selectedComputation = null;
439
+ let eventSource = null;
440
+
441
+ async function init() {
442
+ await loadComputations();
443
+ await loadSystemStats();
444
+ connectSSE();
445
+ }
446
+
447
+ async function loadComputations() {
448
+ const res = await fetch('/api/computations');
449
+ const comps = await res.json();
450
+ const list = document.getElementById('comp-list');
451
+ list.innerHTML = comps.map(c => \`
452
+ <div class="comp-item" data-name="\${c.name}">
453
+ <span>\${c.name}</span>
454
+ <span class="pass-badge">Pass \${c.passLevel}</span>
455
+ </div>
456
+ \`).join('');
457
+
458
+ list.querySelectorAll('.comp-item').forEach(el => {
459
+ el.addEventListener('click', () => {
460
+ document.querySelectorAll('.comp-item').forEach(e => e.classList.remove('active'));
461
+ el.classList.add('active');
462
+ selectedComputation = el.dataset.name;
463
+ });
464
+ });
465
+ }
466
+
467
+ async function loadSystemStats() {
468
+ const res = await fetch('/api/stats');
469
+ const stats = await res.json();
470
+ document.getElementById('system-stats').innerHTML = \`
471
+ <div class="stat"><div class="stat-value">\${stats.computations}</div><div class="stat-label">Computations</div></div>
472
+ <div class="stat"><div class="stat-value">\${stats.tables}</div><div class="stat-label">Tables</div></div>
473
+ <div class="stat"><div class="stat-value">\${stats.totalPasses}</div><div class="stat-label">Passes</div></div>
474
+ \`;
475
+ }
476
+
477
+ function connectSSE() {
478
+ eventSource = new EventSource('/events');
479
+ eventSource.onopen = () => {
480
+ document.getElementById('status').className = 'status connected';
481
+ document.getElementById('status').textContent = 'Connected';
482
+ };
483
+ eventSource.onerror = () => {
484
+ document.getElementById('status').className = 'status disconnected';
485
+ document.getElementById('status').textContent = 'Disconnected';
486
+ };
487
+ eventSource.onmessage = (e) => {
488
+ const data = JSON.parse(e.data);
489
+ handleEvent(data);
490
+ };
491
+ }
492
+
493
+ function handleEvent(data) {
494
+ if (data.type === 'fileChanged') {
495
+ addLog(\`File changed: \${data.file}\`, 'info');
496
+ } else if (data.type === 'simulationComplete') {
497
+ displayResults(data.result);
498
+ }
499
+ }
500
+
501
+ async function runSimulation(withDeps = false) {
502
+ if (!selectedComputation) return alert('Select a computation first');
503
+ const endpoint = withDeps ? '/api/simulate-with-deps' : '/api/simulate';
504
+ const res = await fetch(endpoint, {
505
+ method: 'POST',
506
+ headers: { 'Content-Type': 'application/json' },
507
+ body: JSON.stringify({ computation: selectedComputation })
508
+ });
509
+ const result = await res.json();
510
+ displayResults(result);
511
+ }
512
+
513
+ async function validate() {
514
+ if (!selectedComputation) return alert('Select a computation first');
515
+ const res = await fetch('/api/validate', {
516
+ method: 'POST',
517
+ headers: { 'Content-Type': 'application/json' },
518
+ body: JSON.stringify({ computation: selectedComputation })
519
+ });
520
+ const result = await res.json();
521
+ const output = document.getElementById('result-output');
522
+ output.innerHTML = result.valid
523
+ ? '<div class="validation valid">✅ Valid configuration</div>'
524
+ : \`<div class="validation invalid">❌ Errors: \${result.errors.join(', ')}</div>\`;
525
+ }
526
+
527
+ function displayResults(result) {
528
+ const statsEl = document.getElementById('result-stats');
529
+ const outputEl = document.getElementById('result-output');
530
+
531
+ if (result.stats) {
532
+ statsEl.innerHTML = \`
533
+ <div class="stat"><div class="stat-value">\${result.stats.executionTimeMs}ms</div><div class="stat-label">Execution</div></div>
534
+ <div class="stat"><div class="stat-value">\${result.stats.entitiesProcessed}</div><div class="stat-label">Entities</div></div>
535
+ <div class="stat"><div class="stat-value">\${Math.round(result.stats.memoryUsedBytes / 1024)}KB</div><div class="stat-label">Memory</div></div>
536
+ <div class="stat"><div class="stat-value">\${result.passLevel || '?'}</div><div class="stat-label">Pass Level</div></div>
537
+ \`;
538
+ }
539
+
540
+ outputEl.textContent = JSON.stringify(result.results || result, null, 2);
541
+ addLog(\`Simulation complete: \${selectedComputation}\`, result.success ? 'success' : 'error');
542
+ }
543
+
544
+ function addLog(message, type = 'info') {
545
+ const log = document.getElementById('execution-log');
546
+ const entry = document.createElement('div');
547
+ entry.className = \`log-entry \${type}\`;
548
+ entry.textContent = \`[\${new Date().toLocaleTimeString()}] \${message}\`;
549
+ log.prepend(entry);
550
+ }
551
+
552
+ document.getElementById('btn-run').addEventListener('click', () => runSimulation(false));
553
+ document.getElementById('btn-run-deps').addEventListener('click', () => runSimulation(true));
554
+ document.getElementById('btn-validate').addEventListener('click', validate);
555
+ document.getElementById('btn-reset').addEventListener('click', async () => {
556
+ await fetch('/api/reset');
557
+ document.getElementById('result-output').textContent = 'Reset complete';
558
+ document.getElementById('result-stats').innerHTML = '';
559
+ });
560
+
561
+ init();
562
+ </script>
563
+ </body>
564
+ </html>`;
565
+ }
566
+
567
+ // =========================================================================
568
+ // HELPERS
569
+ // =========================================================================
570
+
571
+ _readBody(req) {
572
+ return new Promise((resolve, reject) => {
573
+ let body = '';
574
+ req.on('data', chunk => body += chunk);
575
+ req.on('end', () => resolve(body));
576
+ req.on('error', reject);
577
+ });
578
+ }
579
+ }
580
+
581
+ module.exports = { SimulationServer };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @fileoverview Simulation Module Index
3
+ */
4
+
5
+ const { SimulationEngine } = require('./SimulationEngine');
6
+ const { SimulationServer } = require('./SimulationServer');
7
+ const { MockDataFetcher } = require('./MockDataFetcher');
8
+ const { MockStorageManager } = require('./MockStorageManager');
9
+ const { DAGAnalyzer } = require('./DAGAnalyzer');
10
+
11
+ module.exports = {
12
+ SimulationEngine,
13
+ SimulationServer,
14
+ MockDataFetcher,
15
+ MockStorageManager,
16
+ DAGAnalyzer
17
+ };