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.
- package/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
- package/functions/computation-system-v2/computations/BehavioralAnomaly.js +557 -337
- package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
- package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
- package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
- package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
- package/functions/computation-system-v2/computations/SignedInUserList.js +51 -0
- package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
- package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
- package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
- package/functions/computation-system-v2/config/bulltrackers.config.js +30 -128
- package/functions/computation-system-v2/core-api.js +17 -9
- package/functions/computation-system-v2/data_schema_reference.MD +108 -0
- package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
- package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
- package/functions/computation-system-v2/devtools/index.js +36 -0
- package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
- package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
- package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
- package/functions/computation-system-v2/devtools/shared/index.js +16 -0
- package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
- package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
- package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
- package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
- package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
- package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
- package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
- package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
- package/functions/computation-system-v2/framework/data/DataFetcher.js +250 -184
- package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
- package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +215 -129
- package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
- package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
- package/functions/computation-system-v2/framework/storage/StorageManager.js +105 -67
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +12 -6
- package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
- package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
- package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
- package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
- package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
- package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
- package/package.json +1 -1
|
@@ -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 };
|