bulltrackers-module 1.0.766 → 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 (76) hide show
  1. package/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
  2. package/functions/computation-system-v2/computations/BehavioralAnomaly.js +559 -227
  3. package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
  4. package/functions/computation-system-v2/computations/NewSectorExposure.js +82 -35
  5. package/functions/computation-system-v2/computations/NewSocialPost.js +52 -24
  6. package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
  7. package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
  8. package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
  9. package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +354 -641
  10. package/functions/computation-system-v2/computations/SignedInUserList.js +51 -0
  11. package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
  12. package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
  13. package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
  14. package/functions/computation-system-v2/config/bulltrackers.config.js +40 -126
  15. package/functions/computation-system-v2/core-api.js +17 -9
  16. package/functions/computation-system-v2/data_schema_reference.MD +108 -0
  17. package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
  18. package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
  19. package/functions/computation-system-v2/devtools/index.js +36 -0
  20. package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
  21. package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
  22. package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
  23. package/functions/computation-system-v2/devtools/shared/index.js +16 -0
  24. package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
  25. package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
  26. package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
  27. package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
  28. package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
  29. package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
  30. package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
  31. package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
  32. package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
  33. package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
  34. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
  35. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
  36. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
  37. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
  38. package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
  39. package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
  40. package/functions/computation-system-v2/framework/core/Manifest.js +9 -16
  41. package/functions/computation-system-v2/framework/core/RunAnalyzer.js +2 -1
  42. package/functions/computation-system-v2/framework/data/DataFetcher.js +330 -126
  43. package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
  44. package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
  45. package/functions/computation-system-v2/framework/execution/Orchestrator.js +226 -153
  46. package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
  47. package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
  48. package/functions/computation-system-v2/framework/storage/StorageManager.js +111 -83
  49. package/functions/computation-system-v2/framework/testing/ComputationTester.js +161 -66
  50. package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
  51. package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
  52. package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
  53. package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
  54. package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
  55. package/functions/computation-system-v2/scripts/test-computation-dag.js +109 -0
  56. package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
  57. package/functions/task-engine/helpers/data_storage_helpers.js +6 -6
  58. package/package.json +1 -1
  59. package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +0 -176
  60. package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +0 -294
  61. package/functions/computation-system-v2/computations/UserPortfolioSummary.js +0 -172
  62. package/functions/computation-system-v2/scripts/migrate-sectors.js +0 -73
  63. package/functions/computation-system-v2/test/analyze-results.js +0 -238
  64. package/functions/computation-system-v2/test/other/test-dependency-cascade.js +0 -150
  65. package/functions/computation-system-v2/test/other/test-dispatcher.js +0 -317
  66. package/functions/computation-system-v2/test/other/test-framework.js +0 -500
  67. package/functions/computation-system-v2/test/other/test-real-execution.js +0 -166
  68. package/functions/computation-system-v2/test/other/test-real-integration.js +0 -194
  69. package/functions/computation-system-v2/test/other/test-refactor-e2e.js +0 -131
  70. package/functions/computation-system-v2/test/other/test-results.json +0 -31
  71. package/functions/computation-system-v2/test/other/test-risk-metrics-computation.js +0 -329
  72. package/functions/computation-system-v2/test/other/test-scheduler.js +0 -204
  73. package/functions/computation-system-v2/test/other/test-storage.js +0 -449
  74. package/functions/computation-system-v2/test/run-pipeline-test.js +0 -554
  75. package/functions/computation-system-v2/test/test-full-pipeline.js +0 -227
  76. package/functions/computation-system-v2/test/test-worker-pool.js +0 -266
@@ -0,0 +1,525 @@
1
+ /**
2
+ * @fileoverview Simulation Engine
3
+ * * The core of the local simulation platform.
4
+ * Wraps the actual Orchestrator and injects mock data/storage layers.
5
+ * * Key Features:
6
+ * - 1:1 simulation of DAG execution
7
+ * - Multi-pass support for dependency chains
8
+ * - Real-time result inspection
9
+ * - No modifications to actual DAG code
10
+ */
11
+
12
+ const path = require('path');
13
+
14
+ // Import the actual framework components
15
+ let Orchestrator, ManifestBuilder;
16
+ try {
17
+ ({ Orchestrator } = require('../../framework/execution/Orchestrator'));
18
+ ({ ManifestBuilder } = require('../../framework/core/Manifest'));
19
+ } catch (e) {
20
+ // Handle case where we're being loaded from a different directory
21
+ console.warn('Could not load framework components directly:', e.message);
22
+ }
23
+
24
+ // Import our mock components
25
+ const { MockDataFetcher } = require('./MockDataFetcher');
26
+ const { MockStorageManager } = require('./MockStorageManager');
27
+ const { SystemIntrospector } = require('../shared/SystemIntrospector');
28
+ const { MockDataFactory } = require('../shared/MockDataFactory');
29
+
30
+ // **NEW**: Import MOCK_INSTRUMENTS to populate reference tables
31
+ const { MOCK_INSTRUMENTS } = require('../shared/SchemaTemplates');
32
+
33
+ // Helper to generate IDs (duplicate of logic in MockDataFactory, but needed here for consistency)
34
+ function generateEntityIds(count) {
35
+ const ids = [];
36
+ for (let i = 0; i < count; i++) {
37
+ // Use realistic PI-like IDs (8 digits)
38
+ ids.push(String(Math.floor(Math.random() * 40000000) + 10000000));
39
+ }
40
+ return ids;
41
+ }
42
+
43
+ class SimulationEngine {
44
+ /**
45
+ * @param {Object} config - The bulltrackers.config.js exports
46
+ * @param {Object} options
47
+ * @param {number} [options.entityCount=10] - Number of mock entities
48
+ * @param {string[]} [options.entityIds] - Specific entity IDs to use
49
+ * @param {Date|string} [options.targetDate] - Simulation date
50
+ * @param {Object} [options.preloadedData] - Pre-generated mock data
51
+ * @param {boolean} [options.verbose=false] - Enable verbose logging
52
+ */
53
+ constructor(config, options = {}) {
54
+ this.config = config;
55
+ this.options = options;
56
+ this.entityCount = options.entityCount || 10;
57
+
58
+ // FIX: Ensure we have a consistent set of Entity IDs immediately.
59
+ // If we leave this null, MockDataFactory generates different random IDs for every table, breaking joins.
60
+ this.entityIds = options.entityIds || generateEntityIds(this.entityCount);
61
+
62
+ this.targetDate = options.targetDate || new Date().toISOString().split('T')[0];
63
+ this.verbose = options.verbose || false;
64
+
65
+ // Initialize shared services
66
+ this.introspector = new SystemIntrospector(config);
67
+ this.mockDataFactory = new MockDataFactory({ introspector: this.introspector });
68
+
69
+ // **KEY FIX**: Preload Global Reference Data (Mappings)
70
+ // This ensures that ALL mock instruments exist in the ticker_mappings and sector_mappings tables.
71
+ const preloadedData = options.preloadedData || {};
72
+
73
+ // 1. Preload Ticker Mappings (InstrumentID -> Ticker)
74
+ // We replicate the FULL list of instruments for EVERY entity ID.
75
+ // This satisfies the MockDataFetcher's sharding logic while acting like a global table.
76
+ if (!preloadedData['ticker_mappings'] && MOCK_INSTRUMENTS) {
77
+ const mappingRows = MOCK_INSTRUMENTS.map(instr => ({
78
+ instrument_id: instr.id,
79
+ ticker: instr.ticker,
80
+ last_updated: new Date().toISOString()
81
+ }));
82
+
83
+ preloadedData['ticker_mappings'] = {};
84
+ for (const id of this.entityIds) {
85
+ preloadedData['ticker_mappings'][id] = mappingRows;
86
+ }
87
+ }
88
+
89
+ // 2. Preload Sector Mappings (Ticker -> Sector)
90
+ if (!preloadedData['sector_mappings'] && MOCK_INSTRUMENTS) {
91
+ const sectorRows = MOCK_INSTRUMENTS.map(instr => ({
92
+ symbol: instr.ticker,
93
+ sector: instr.sector
94
+ }));
95
+
96
+ preloadedData['sector_mappings'] = {};
97
+ for (const id of this.entityIds) {
98
+ preloadedData['sector_mappings'][id] = sectorRows;
99
+ }
100
+ }
101
+
102
+ // Initialize mock components
103
+ this.mockDataFetcher = new MockDataFetcher({
104
+ mockDataFactory: this.mockDataFactory,
105
+ introspector: this.introspector,
106
+ preloadedData: preloadedData,
107
+ entityCount: this.entityCount,
108
+ entityIds: this.entityIds // Pass the consistent IDs here
109
+ });
110
+
111
+ this.mockStorageManager = new MockStorageManager({
112
+ config
113
+ });
114
+
115
+ // Orchestrator will be created lazily
116
+ this._orchestrator = null;
117
+ this._manifest = null;
118
+
119
+ // Simulation state
120
+ this.simulationResults = {};
121
+ this.executionLog = [];
122
+ }
123
+
124
+ /**
125
+ * Initialize the simulation engine.
126
+ * Creates an Orchestrator with mocked services.
127
+ */
128
+ async initialize() {
129
+ if (this._orchestrator) return;
130
+
131
+ this._log('Initializing simulation engine...');
132
+
133
+ // Create a custom logger that captures output
134
+ const logger = this._createLogger();
135
+
136
+ // Create the real Orchestrator
137
+ this._orchestrator = new Orchestrator(this.config, logger);
138
+
139
+ // **KEY INJECTION**: Replace the data/storage components with mocks
140
+ this._orchestrator.dataFetcher = this.mockDataFetcher;
141
+ this._orchestrator.storageManager = this.mockStorageManager;
142
+
143
+ // **MOCK SchemaRegistry**: Prevent BigQuery connections during simulation
144
+ this._orchestrator.schemaRegistry = {
145
+ warmCache: async () => { this._log('Mock SchemaRegistry: warmCache skipped'); },
146
+ getSchema: (table) => ({ columns: [] }),
147
+ hasTable: () => true
148
+ };
149
+
150
+ // **MOCK StateRepository**: Prevent BigQuery state lookups
151
+ this._orchestrator.stateRepository = {
152
+ getDailyStatus: async () => new Map(),
153
+ getRunDates: async () => [],
154
+ updateStatusCache: () => { },
155
+ getResult: async () => ({}),
156
+ getEntityResult: async () => null,
157
+ getBatchEntityResults: async () => ({}),
158
+ cacheResult: () => { },
159
+ _log: () => { }
160
+ };
161
+
162
+ // Initialize (builds manifest etc)
163
+ await this._orchestrator.initialize();
164
+ this._manifest = this._orchestrator.manifest;
165
+
166
+ this._log(`Initialized with ${this._manifest.length} computations`);
167
+ }
168
+
169
+ /**
170
+ * Run a single computation in simulation mode.
171
+ * * @param {string} computationName - Name of the computation to simulate
172
+ * @param {Object} options
173
+ * @param {string[]} [options.entityIds] - Specific entities to process
174
+ * @param {string} [options.targetDate] - Override target date
175
+ * @returns {Object} Simulation result with metrics
176
+ */
177
+ async runComputation(computationName, options = {}) {
178
+ await this.initialize();
179
+
180
+ const targetDate = options.targetDate || this.targetDate;
181
+
182
+ // Use the consistent IDs unless overridden
183
+ const entityIds = options.entityIds || this.entityIds;
184
+
185
+ this._log(`Running simulation: ${computationName} for ${targetDate}`);
186
+
187
+ // Find the manifest entry (case-insensitive)
188
+ const normalizedName = computationName.toLowerCase();
189
+ const entry = this._manifest.find(e =>
190
+ e.name === computationName ||
191
+ e.name.toLowerCase() === normalizedName
192
+ );
193
+ if (!entry) {
194
+ throw new Error(`Computation not found: ${computationName}`);
195
+ }
196
+
197
+ // Load dependency results into mock DataFetcher
198
+ await this._loadDependencyResults(entry, targetDate);
199
+
200
+ // Configure entity IDs if specified (update the fetcher)
201
+ if (entityIds) {
202
+ this.mockDataFetcher.entityIds = entityIds;
203
+ }
204
+
205
+ // Run the computation through the real Orchestrator
206
+ const startTime = Date.now();
207
+ const startMemory = process.memoryUsage().heapUsed;
208
+
209
+ try {
210
+ await this._orchestrator.runSingle(entry, targetDate, {
211
+ entityIds,
212
+ dryRun: false,
213
+ forceLocal: true, // Disable remote workers in simulation
214
+ allowPartialCommit: true // Enable result capture for partial simulations
215
+ });
216
+
217
+ const endTime = Date.now();
218
+ const endMemory = process.memoryUsage().heapUsed;
219
+
220
+ // Gather results
221
+ const results = this.mockStorageManager.getResults(computationName);
222
+ const stats = {
223
+ executionTimeMs: endTime - startTime,
224
+ memoryUsedBytes: endMemory - startMemory,
225
+ entitiesProcessed: Object.keys(results).length,
226
+ dataFetcher: this.mockDataFetcher.getStats(),
227
+ storage: this.mockStorageManager.getStats()
228
+ };
229
+
230
+ // Store in simulation results for dependent computations
231
+ this.simulationResults[computationName] = {
232
+ results,
233
+ stats,
234
+ entry,
235
+ date: targetDate
236
+ };
237
+
238
+ // Log execution
239
+ this.executionLog.push({
240
+ computation: computationName,
241
+ date: targetDate,
242
+ success: true,
243
+ stats,
244
+ timestamp: new Date().toISOString()
245
+ });
246
+
247
+ this._log(`✓ ${computationName} completed in ${stats.executionTimeMs}ms, ${stats.entitiesProcessed} entities`);
248
+
249
+ return {
250
+ success: true,
251
+ results,
252
+ stats,
253
+ passLevel: this.introspector.getPassLevel(computationName),
254
+ dependencies: entry.dependencies || []
255
+ };
256
+
257
+ } catch (error) {
258
+ this.executionLog.push({
259
+ computation: computationName,
260
+ date: targetDate,
261
+ success: false,
262
+ error: error.message,
263
+ stack: error.stack,
264
+ timestamp: new Date().toISOString()
265
+ });
266
+
267
+ this._log(`✗ ${computationName} failed: ${error.message}`);
268
+ if (this.verbose) {
269
+ console.error(error.stack);
270
+ }
271
+
272
+ return {
273
+ success: false,
274
+ error: error.message,
275
+ stack: error.stack,
276
+ stack: error.stack
277
+ };
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Run a full simulation of the DAG up to a specific computation.
283
+ * Executes all dependencies in topological order first.
284
+ * * @param {string} computationName - Target computation
285
+ * @param {Object} options
286
+ * @returns {Object} Full simulation result
287
+ */
288
+ async runWithDependencies(computationName, options = {}) {
289
+ await this.initialize();
290
+
291
+ const targetDate = options.targetDate || this.targetDate;
292
+ const executionOrder = this._getExecutionOrder(computationName);
293
+
294
+ this._log(`Running ${computationName} with ${executionOrder.length} total computations`);
295
+
296
+ const results = [];
297
+
298
+ for (const compName of executionOrder) {
299
+ const result = await this.runComputation(compName, { targetDate, ...options });
300
+ results.push({ name: compName, ...result });
301
+
302
+ if (!result.success) {
303
+ this._log(`Pipeline stopped due to failure in ${compName}`);
304
+ break;
305
+ }
306
+ }
307
+
308
+ // Get the target computation result
309
+ const targetResult = results.find(r => r.name === computationName);
310
+
311
+ return {
312
+ targetComputation: computationName,
313
+ executionOrder,
314
+ results,
315
+ targetResult,
316
+ totalTimeMs: results.reduce((sum, r) => sum + (r.stats?.executionTimeMs || 0), 0)
317
+ };
318
+ }
319
+
320
+ /**
321
+ * Get DAG analysis for a computation.
322
+ * * @param {string} computationName
323
+ * @returns {Object} DAG info
324
+ */
325
+ getComputationInfo(computationName) {
326
+ const passLevel = this.introspector.getPassLevel(computationName);
327
+ const dependencies = this.introspector.getDependencyGraph().get(computationName) || [];
328
+ const config = this.introspector.getComputationConfig(computationName);
329
+
330
+ // Find dependents (computations that depend on this one)
331
+ const dependents = [];
332
+ for (const [name, deps] of this.introspector.getDependencyGraph()) {
333
+ if (deps.includes(computationName)) {
334
+ dependents.push(name);
335
+ }
336
+ }
337
+
338
+ return {
339
+ name: computationName,
340
+ passLevel,
341
+ totalPasses: this.introspector.getTotalPasses(),
342
+ dependencies,
343
+ dependents,
344
+ config,
345
+ validation: config ? this.introspector.validateComputationConfig(config) : null
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Validate a computation without running it.
351
+ * * @param {string} computationName
352
+ * @returns {Object} Validation result
353
+ */
354
+ validateComputation(computationName) {
355
+ const config = this.introspector.getComputationConfig(computationName);
356
+ if (!config) {
357
+ return { valid: false, errors: [`Computation not found: ${computationName}`] };
358
+ }
359
+
360
+ return this.introspector.validateComputationConfig(config);
361
+ }
362
+
363
+ /**
364
+ * Get sample results for an entity.
365
+ * * @param {string} computationName
366
+ * @param {string} entityId
367
+ * @returns {Object|null}
368
+ */
369
+ getResult(computationName, entityId) {
370
+ const results = this.mockStorageManager.getResults(computationName);
371
+ return results[entityId] || null;
372
+ }
373
+
374
+ /**
375
+ * Get all results for a computation.
376
+ * * @param {string} computationName
377
+ * @returns {Object}
378
+ */
379
+ getResults(computationName) {
380
+ return this.mockStorageManager.getResults(computationName);
381
+ }
382
+
383
+ /**
384
+ * Compare two simulation runs.
385
+ * * @param {Object} result1 - First simulation result
386
+ * @param {Object} result2 - Second simulation result
387
+ * @returns {Object} Diff
388
+ */
389
+ compareResults(result1, result2) {
390
+ const diff = {
391
+ added: [],
392
+ removed: [],
393
+ changed: [],
394
+ unchanged: []
395
+ };
396
+
397
+ const entities1 = new Set(Object.keys(result1.results || {}));
398
+ const entities2 = new Set(Object.keys(result2.results || {}));
399
+
400
+ // Find added/removed
401
+ for (const id of entities2) {
402
+ if (!entities1.has(id)) diff.added.push(id);
403
+ }
404
+ for (const id of entities1) {
405
+ if (!entities2.has(id)) diff.removed.push(id);
406
+ }
407
+
408
+ // Find changed/unchanged
409
+ for (const id of entities1) {
410
+ if (entities2.has(id)) {
411
+ const r1 = JSON.stringify(result1.results[id]);
412
+ const r2 = JSON.stringify(result2.results[id]);
413
+ if (r1 === r2) {
414
+ diff.unchanged.push(id);
415
+ } else {
416
+ diff.changed.push({
417
+ entityId: id,
418
+ before: result1.results[id],
419
+ after: result2.results[id]
420
+ });
421
+ }
422
+ }
423
+ }
424
+
425
+ return diff;
426
+ }
427
+
428
+ /**
429
+ * Reset simulation state.
430
+ */
431
+ reset() {
432
+ this.mockDataFetcher.resetStats();
433
+ this.mockStorageManager.clear();
434
+ this.simulationResults = {};
435
+ this.executionLog = [];
436
+ }
437
+
438
+ /**
439
+ * Get execution log.
440
+ */
441
+ getExecutionLog() {
442
+ return [...this.executionLog];
443
+ }
444
+
445
+ /**
446
+ * Get system statistics.
447
+ */
448
+ getSystemStats() {
449
+ return this.introspector.getStats();
450
+ }
451
+
452
+ // =========================================================================
453
+ // PRIVATE METHODS
454
+ // =========================================================================
455
+
456
+ /**
457
+ * Load dependency results into mock fetcher for multi-pass simulation.
458
+ */
459
+ async _loadDependencyResults(entry, targetDate) {
460
+ if (!entry.dependencies || entry.dependencies.length === 0) return;
461
+
462
+ for (const depName of entry.dependencies) {
463
+ // Check if we've already simulated this dependency
464
+ if (this.simulationResults[depName]) {
465
+ const depResults = this.simulationResults[depName].results;
466
+ this.mockDataFetcher.addComputationResults(depName, depResults);
467
+ this._log(`Loaded cached results for dependency: ${depName}`);
468
+ } else {
469
+ // Dependency hasn't been run yet - need to run it first
470
+ this._log(`Dependency ${depName} not yet simulated, running...`);
471
+ await this.runComputation(depName, { targetDate });
472
+
473
+ // Now load its results
474
+ const depResults = this.mockStorageManager.getResultsForDependency(depName);
475
+ this.mockDataFetcher.addComputationResults(depName, depResults);
476
+ }
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Get execution order for a computation (topological sort).
482
+ */
483
+ _getExecutionOrder(computationName) {
484
+ const graph = this.introspector.getDependencyGraph();
485
+ const order = [];
486
+ const visited = new Set();
487
+
488
+ const visit = (name) => {
489
+ if (visited.has(name)) return;
490
+ visited.add(name);
491
+
492
+ const deps = graph.get(name) || [];
493
+ for (const dep of deps) {
494
+ visit(dep);
495
+ }
496
+
497
+ order.push(name);
498
+ };
499
+
500
+ visit(computationName);
501
+ return order;
502
+ }
503
+
504
+ /**
505
+ * Create a logger that captures output.
506
+ */
507
+ _createLogger() {
508
+ const verbose = this.verbose;
509
+ return {
510
+ log: (...args) => verbose && console.log('[SIM]', ...args),
511
+ info: (...args) => verbose && console.log('[SIM]', ...args),
512
+ warn: (...args) => console.warn('[SIM:WARN]', ...args),
513
+ error: (...args) => console.error('[SIM:ERROR]', ...args),
514
+ debug: (...args) => verbose && console.debug('[SIM:DEBUG]', ...args)
515
+ };
516
+ }
517
+
518
+ _log(message) {
519
+ if (this.verbose) {
520
+ console.log(`[SimulationEngine] ${message}`);
521
+ }
522
+ }
523
+ }
524
+
525
+ module.exports = { SimulationEngine };