bulltrackers-module 1.0.731 → 1.0.733

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 (52) hide show
  1. package/functions/orchestrator/index.js +19 -17
  2. package/index.js +8 -29
  3. package/package.json +6 -5
  4. package/functions/computation-system/WorkflowOrchestrator.js +0 -213
  5. package/functions/computation-system/config/monitoring_config.js +0 -31
  6. package/functions/computation-system/config/validation_overrides.js +0 -10
  7. package/functions/computation-system/context/ContextFactory.js +0 -132
  8. package/functions/computation-system/context/ManifestBuilder.js +0 -379
  9. package/functions/computation-system/data/AvailabilityChecker.js +0 -236
  10. package/functions/computation-system/data/CachedDataLoader.js +0 -325
  11. package/functions/computation-system/data/DependencyFetcher.js +0 -455
  12. package/functions/computation-system/executors/MetaExecutor.js +0 -279
  13. package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
  14. package/functions/computation-system/executors/StandardExecutor.js +0 -465
  15. package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
  16. package/functions/computation-system/helpers/computation_worker.js +0 -375
  17. package/functions/computation-system/helpers/monitor.js +0 -64
  18. package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
  19. package/functions/computation-system/layers/extractors.js +0 -1097
  20. package/functions/computation-system/layers/index.js +0 -40
  21. package/functions/computation-system/layers/mathematics.js +0 -522
  22. package/functions/computation-system/layers/profiling.js +0 -537
  23. package/functions/computation-system/layers/validators.js +0 -170
  24. package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
  25. package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
  26. package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
  27. package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
  28. package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
  29. package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
  30. package/functions/computation-system/logger/logger.js +0 -297
  31. package/functions/computation-system/persistence/ContractValidator.js +0 -81
  32. package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
  33. package/functions/computation-system/persistence/ResultCommitter.js +0 -283
  34. package/functions/computation-system/persistence/ResultsValidator.js +0 -130
  35. package/functions/computation-system/persistence/RunRecorder.js +0 -142
  36. package/functions/computation-system/persistence/StatusRepository.js +0 -52
  37. package/functions/computation-system/reporter_epoch.js +0 -6
  38. package/functions/computation-system/scripts/UpdateContracts.js +0 -128
  39. package/functions/computation-system/services/SnapshotService.js +0 -148
  40. package/functions/computation-system/simulation/Fabricator.js +0 -285
  41. package/functions/computation-system/simulation/SeededRandom.js +0 -41
  42. package/functions/computation-system/simulation/SimRunner.js +0 -51
  43. package/functions/computation-system/system_epoch.js +0 -2
  44. package/functions/computation-system/tools/BuildReporter.js +0 -531
  45. package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
  46. package/functions/computation-system/tools/DeploymentValidator.js +0 -536
  47. package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
  48. package/functions/computation-system/topology/HashManager.js +0 -55
  49. package/functions/computation-system/topology/ManifestLoader.js +0 -47
  50. package/functions/computation-system/utils/data_loader.js +0 -597
  51. package/functions/computation-system/utils/schema_capture.js +0 -121
  52. package/functions/computation-system/utils/utils.js +0 -188
@@ -1,455 +0,0 @@
1
- /**
2
- * FILENAME: computation-system/data/DependencyFetcher.js
3
- * @fileoverview Fetches dependencies for computations.
4
- * REFACTORED: Unified fetch logic, streamlined decompression/sharding/GCS.
5
- * UPDATED: Properly checks isPage/isAlert flags to determine BigQuery vs Firestore routing.
6
- */
7
- const { normalizeName } = require('../utils/utils');
8
- const zlib = require('zlib');
9
- const { Storage } = require('@google-cloud/storage');
10
-
11
- const storage = new Storage(); // Singleton Client
12
-
13
- /**
14
- * Helper to check if a computation is isPage or isAlert by looking up manifest
15
- * @param {string} normalizedName - Normalized computation name
16
- * @param {object} config - Config object that may contain manifest info
17
- * @param {string} category - Computation category (fallback for alerts)
18
- * @returns {Promise<{isPage: boolean, isAlert: boolean}>}
19
- */
20
- async function checkComputationFlags(normalizedName, config, category) {
21
- let isPage = false;
22
- let isAlert = false;
23
-
24
- // Try to get manifest from config if available
25
- if (config.getCalculations && typeof config.getCalculations === 'function') {
26
- try {
27
- const calculations = config.getCalculations(config);
28
- const manifest = calculations.find(c => normalizeName(c.name) === normalizedName);
29
- if (manifest) {
30
- isPage = manifest.isPage === true;
31
- isAlert = manifest.isAlertComputation === true;
32
- return { isPage, isAlert };
33
- }
34
- } catch (e) {
35
- // Fall through to category-based detection
36
- }
37
- }
38
-
39
- // Fallback: infer from category (alerts category = alert computation)
40
- // Note: isPage cannot be inferred from category alone, so defaults to false
41
- isAlert = category === 'alerts';
42
-
43
- return { isPage, isAlert };
44
- }
45
-
46
- // =============================================================================
47
- // HELPERS
48
- // =============================================================================
49
-
50
- /** Checks if data is effectively empty (null or only metadata keys) */
51
- function isDataEmpty(data) {
52
- if (data == null) return true;
53
- if (Array.isArray(data)) return data.length === 0;
54
- if (typeof data === 'object') {
55
- // Return true if NO keys exist that DON'T start with '_'
56
- return !Object.keys(data).some(k => !k.startsWith('_'));
57
- }
58
- return false;
59
- }
60
-
61
- /** Robust decompression helper (Buffer, Base64, or Firestore Binary) */
62
- function tryDecompress(payload) {
63
- if (!payload) return null;
64
- try {
65
- const buffer = (payload instanceof Buffer) ? payload :
66
- (payload._byteString ? Buffer.from(payload._byteString, 'base64') :
67
- (payload.toDate ? payload.toDate() : Buffer.from(payload)));
68
-
69
- return JSON.parse(zlib.gunzipSync(buffer).toString('utf8'));
70
- } catch (e) {
71
- throw new Error(`Decompression failed: ${e.message}`);
72
- }
73
- }
74
-
75
- // =============================================================================
76
- // CORE FETCH LOGIC
77
- // =============================================================================
78
-
79
- /**
80
- * Fetches, decompresses, and reassembles (if sharded or on GCS) a single result document.
81
- * NEW: For non-alert, non-page computations, tries BigQuery first (cheaper, no sharding/compression).
82
- * @param {object} db - Firestore database instance
83
- * @param {object} config - Configuration object (may contain logger, manifestLookup, etc.)
84
- * @param {string} dateStr - Date string in YYYY-MM-DD format
85
- * @param {string} name - Computation name (normalized)
86
- * @param {string} category - Computation category
87
- * @returns {Promise<object|null>} Result data or null if not found
88
- */
89
- async function fetchSingleResult(db, config, dateStr, name, category) {
90
- const { resultsCollection = 'computation_results', resultsSubcollection = 'results', computationsSubcollection = 'computations' } = config;
91
- const log = config.logger || console;
92
-
93
- // Check if this is an alert or page computation by looking up manifest
94
- const { isPage: isPageComputation, isAlert: isAlertComputation } = await checkComputationFlags(name, config, category);
95
-
96
- // Try BigQuery first for non-alert, non-page computations (reduces Firestore reads)
97
- // isPage and isAlert computations are always written to Firestore (in addition to BigQuery),
98
- // so we should check Firestore for them, but can also try BigQuery as a fallback
99
- if (!isAlertComputation && !isPageComputation && process.env.BIGQUERY_ENABLED !== 'false') {
100
- try {
101
- const { queryComputationResult } = require('../../core/utils/bigquery_utils');
102
- const bigqueryResult = await queryComputationResult(name, category, dateStr, log);
103
-
104
- if (bigqueryResult && !isDataEmpty(bigqueryResult)) {
105
- log.log('INFO', `[DependencyFetcher] ✅ Using BigQuery for ${name} (${dateStr}, ${category})`);
106
- return bigqueryResult;
107
- }
108
- } catch (bqError) {
109
- log.log('WARN', `[DependencyFetcher] BigQuery fetch failed for ${name}, falling back to Firestore: ${bqError.message}`);
110
- // Fall through to Firestore
111
- }
112
- } else if (isAlertComputation || isPageComputation) {
113
- log.log('INFO', `[DependencyFetcher] 📄 Using Firestore for ${isAlertComputation ? 'alert' : 'page'} computation ${name} (${dateStr})`);
114
- }
115
-
116
- // Fallback to Firestore (for alerts, pages, or if BigQuery fails)
117
- const docRef = db.collection(resultsCollection).doc(dateStr)
118
- .collection(resultsSubcollection).doc(category)
119
- .collection(computationsSubcollection).doc(name);
120
-
121
- const snap = await docRef.get();
122
- if (!snap.exists) return null;
123
-
124
- let data = snap.data();
125
-
126
- // -------------------------------------------------------------------------
127
- // 1. GCS POINTER HANDLER (New)
128
- // -------------------------------------------------------------------------
129
- if (data.gcsUri || (data._gcs && data.gcsBucket && data.gcsPath)) {
130
- try {
131
- const bucketName = data.gcsBucket || data.gcsUri.split('/')[2];
132
- const fileName = data.gcsPath || data.gcsUri.split('/').slice(3).join('/');
133
-
134
- // Stream download is memory efficient for large files
135
- const [fileContent] = await storage.bucket(bucketName).file(fileName).download();
136
-
137
- // Assume Gzip (as writer does it), if fails try plain
138
- try {
139
- return JSON.parse(zlib.gunzipSync(fileContent).toString('utf8'));
140
- } catch (gzipErr) {
141
- // Fallback for uncompressed GCS files
142
- return JSON.parse(fileContent.toString('utf8'));
143
- }
144
- } catch (e) {
145
- log.log('ERROR', `[DependencyFetcher] ❌ GCS Fetch Failed for ${name}: ${e.message}`);
146
- // Depending on strictness, we might return null here or allow it to fail hard.
147
- // Returning null allows 'isDataEmpty' to catch it as "MISSING"
148
- return null;
149
- }
150
- }
151
-
152
- // -------------------------------------------------------------------------
153
- // 2. FIRESTORE COMPRESSED HANDLER
154
- // -------------------------------------------------------------------------
155
- if (data._compressed && data.payload) {
156
- try {
157
- const realData = tryDecompress(data.payload);
158
- data = { ...data, ...realData }; // Merge payload into base
159
- delete data.payload;
160
- } catch (e) {
161
- log.log('ERROR', `[DependencyFetcher] ❌ ${e.message} at ${docRef.path}`);
162
- return null;
163
- }
164
- }
165
-
166
- // -------------------------------------------------------------------------
167
- // 3. FIRESTORE SHARDED HANDLER
168
- // -------------------------------------------------------------------------
169
- if (data._sharded) {
170
- const shardSnaps = await docRef.collection('_shards').get();
171
- if (shardSnaps.empty) {
172
- log.log('ERROR', `[DependencyFetcher] ❌ Sharded doc has no shards: ${docRef.path}`);
173
- return null;
174
- }
175
-
176
- // Initialize merged data with any non-metadata fields from the pointer doc
177
- const merged = {};
178
- Object.entries(data).forEach(([k, v]) => { if (!k.startsWith('_')) merged[k] = v; });
179
-
180
- for (const shard of shardSnaps.docs) {
181
- let sData = shard.data();
182
-
183
- // Decompress Shard if needed
184
- if (sData._compressed && sData.payload) {
185
- try {
186
- const decomp = tryDecompress(sData.payload);
187
- sData = (typeof decomp === 'string') ? JSON.parse(decomp) : decomp;
188
- } catch (e) {
189
- log.log('ERROR', `[DependencyFetcher] ❌ Shard decompression failed (${shard.id}): ${e.message}`);
190
- continue;
191
- }
192
- }
193
-
194
- // Merge Shard Content
195
- Object.entries(sData).forEach(([k, v]) => {
196
- if (!k.startsWith('_')) merged[k] = v;
197
- });
198
- }
199
-
200
- // Return null if result is empty after merging
201
- if (Object.keys(merged).length === 0) return null;
202
- return merged;
203
- }
204
-
205
- // -------------------------------------------------------------------------
206
- // 4. STANDARD DOCUMENT HANDLER
207
- // -------------------------------------------------------------------------
208
- const clean = {};
209
- let hasContent = false;
210
- Object.entries(data).forEach(([k, v]) => {
211
- if (!k.startsWith('_')) { clean[k] = v; hasContent = true; }
212
- });
213
-
214
- return hasContent ? clean : null;
215
- }
216
-
217
- // =============================================================================
218
- // PUBLIC METHODS
219
- // =============================================================================
220
-
221
- /**
222
- * Fetches dependencies for a specific date (Standard pass).
223
- */
224
- async function fetchDependencies(date, calcs, config, deps, manifestLookup = {}, allowMissing = false) {
225
- const { db, logger } = deps;
226
- const dStr = date.toISOString().slice(0, 10);
227
-
228
- // 1. Resolve Dependencies (Normalize Name -> Original Name)
229
- const needed = new Map();
230
- calcs.forEach(c => {
231
- // Priority: class.getDependencies() > instance.getDependencies() > manifest.dependencies
232
- const getDeps = c.class?.getDependencies || c.getDependencies;
233
- const reqs = (typeof getDeps === 'function') ? getDeps.call(c.class || c) : c.dependencies;
234
-
235
- if (Array.isArray(reqs)) {
236
- reqs.forEach(r => needed.set(normalizeName(r), r));
237
- }
238
- });
239
-
240
- if (needed.size === 0) return {};
241
-
242
- logger.log('INFO', `[DependencyFetcher] 🔍 Fetching ${needed.size} dependencies for ${dStr}`);
243
-
244
- const results = {};
245
- const errors = [];
246
-
247
- // 2. Fetch All Dependencies in Parallel
248
- await Promise.all(Array.from(needed.entries()).map(async ([norm, orig]) => {
249
- const category = manifestLookup[norm] || 'analytics';
250
- const path = `${config.resultsCollection || 'computation_results'}/${dStr}/.../${category}/${norm}`;
251
-
252
- try {
253
- const data = await fetchSingleResult(db, { ...config, logger }, dStr, norm, category);
254
-
255
- if (!data || isDataEmpty(data)) {
256
- // Determine severity based on context
257
- const status = data ? 'EMPTY' : 'MISSING';
258
- errors.push({ name: orig, path, reason: status });
259
-
260
- // Log immediately for visibility
261
- const level = allowMissing ? 'INFO' : 'ERROR';
262
- logger.log(level, `[DependencyFetcher] ⚠️ Dependency '${orig}' ${status} at ${path}`);
263
- } else {
264
- results[orig] = data; // Store using Original Name
265
- }
266
- } catch (e) {
267
- errors.push({ name: orig, path, reason: e.message });
268
- logger.log('ERROR', `[DependencyFetcher] ❌ Error loading '${orig}': ${e.message}`);
269
- }
270
- }));
271
-
272
- // 3. Final Validation
273
- if (errors.length > 0 && !allowMissing) {
274
- throw new Error(`[DependencyFetcher] CRITICAL: Missing required dependencies: ${errors.map(e => e.name).join(', ')}`);
275
- } else if (errors.length > 0) {
276
- logger.log('INFO', `[DependencyFetcher] ⚠️ Allowed missing/empty dependencies in Historical context: ${errors.map(e => e.name).join(', ')}`);
277
- }
278
-
279
- return results;
280
- }
281
-
282
- /**
283
- * Fetches result series (Historical data) for lookbacks.
284
- * @param {string} endDateStr - The end date for the series
285
- * @param {string[]} calcNames - Original (case-sensitive) computation names
286
- * @param {Object} manifestLookup - Map of normalizedName -> category
287
- * @param {Object} config - Configuration object
288
- * @param {Object} deps - Dependencies (db, logger)
289
- * @param {number} lookbackDays - Number of days to look back
290
- */
291
- async function fetchResultSeries(endDateStr, calcNames, manifestLookup, config, deps, lookbackDays) {
292
- const { db, logger } = deps;
293
- const results = {}; // normalizedName -> { date -> data }
294
- const { resultsCollection = 'computation_results', resultsSubcollection = 'results', computationsSubcollection = 'computations' } = config;
295
-
296
- // Initialize results structure
297
- calcNames.forEach(n => results[normalizeName(n)] = {});
298
-
299
- // Generate Date List (going backwards from endDate)
300
- const dates = [];
301
- const d = new Date(endDateStr);
302
- for (let i = 0; i < lookbackDays; i++) {
303
- d.setUTCDate(d.getUTCDate() - 1);
304
- dates.push(d.toISOString().slice(0, 10));
305
- }
306
-
307
- const startDateStr = dates[dates.length - 1]; // Oldest date
308
- const queryEndDateStr = dates[0]; // Newest date (for BigQuery query)
309
-
310
- // [DEBUG] Log the manifest lookup and resolved categories
311
- logger.log('INFO', `[DependencyFetcher] 🔍 ManifestLookup has ${Object.keys(manifestLookup).length} entries`);
312
- for (const rawName of calcNames) {
313
- const norm = normalizeName(rawName);
314
- const category = manifestLookup[norm] || 'analytics';
315
- const samplePath = `${resultsCollection}/${dates[0]}/${resultsSubcollection}/${category}/${computationsSubcollection}/${rawName}`;
316
- logger.log('INFO', `[DependencyFetcher] 📍 '${rawName}' -> category='${category}' -> Path: ${samplePath}`);
317
- }
318
-
319
- // =========================================================================
320
- // BIGQUERY FIRST: Try batch query for all dates at once
321
- // =========================================================================
322
- if (process.env.BIGQUERY_ENABLED !== 'false') {
323
- try {
324
- const { queryComputationResultsRange } = require('../../core/utils/bigquery_utils');
325
-
326
- // Query each computation in parallel
327
- const bigqueryPromises = calcNames.map(async (rawName) => {
328
- const norm = normalizeName(rawName);
329
- const category = manifestLookup[norm] || 'analytics';
330
-
331
- const bigqueryRows = await queryComputationResultsRange(
332
- rawName,
333
- category,
334
- startDateStr,
335
- queryEndDateStr,
336
- logger
337
- );
338
-
339
- if (bigqueryRows && bigqueryRows.length > 0) {
340
- logger.log('INFO', `[DependencyFetcher] ✅ Using BigQuery for ${rawName} series: ${bigqueryRows.length} dates`);
341
-
342
- // Map BigQuery results to results structure
343
- for (const row of bigqueryRows) {
344
- if (row.data && !isDataEmpty(row.data)) {
345
- results[norm][row.date] = row.data;
346
- }
347
- }
348
-
349
- return { name: rawName, found: bigqueryRows.length };
350
- }
351
-
352
- return { name: rawName, found: 0 };
353
- });
354
-
355
- const bigqueryResults = await Promise.all(bigqueryPromises);
356
- const totalFound = bigqueryResults.reduce((sum, r) => sum + r.found, 0);
357
-
358
- if (totalFound > 0) {
359
- logger.log('INFO', `[DependencyFetcher] ✅ BigQuery retrieved ${totalFound} computation result records across ${calcNames.length} computations`);
360
-
361
- // Fill in any missing dates from Firestore (fallback)
362
- const missingOps = [];
363
- for (const dateStr of dates) {
364
- for (const rawName of calcNames) {
365
- const norm = normalizeName(rawName);
366
- // Only fetch if we don't have this date already
367
- if (!results[norm] || !results[norm][dateStr]) {
368
- const category = manifestLookup[norm] || 'analytics';
369
- missingOps.push(async () => {
370
- const val = await fetchSingleResult(db, { ...config, logger }, dateStr, rawName, category);
371
- if (val && !isDataEmpty(val)) {
372
- results[norm][dateStr] = val;
373
- }
374
- });
375
- }
376
- }
377
- }
378
-
379
- // Fetch missing dates from Firestore
380
- if (missingOps.length > 0) {
381
- logger.log('INFO', `[DependencyFetcher] 📂 Fetching ${missingOps.length} missing dates from Firestore (fallback)`);
382
- const BATCH_SIZE = 20;
383
- for (let i = 0; i < missingOps.length; i += BATCH_SIZE) {
384
- await Promise.all(missingOps.slice(i, i + BATCH_SIZE).map(fn => fn()));
385
- }
386
- }
387
-
388
- // Log final summary
389
- for (const rawName of calcNames) {
390
- const norm = normalizeName(rawName);
391
- const foundDates = Object.keys(results[norm] || {});
392
- logger.log('INFO', `[DependencyFetcher] ✅ '${rawName}' found data for ${foundDates.length}/${lookbackDays} days (BigQuery + Firestore)`);
393
- }
394
-
395
- return results;
396
- } else {
397
- logger.log('INFO', `[DependencyFetcher] ⚠️ BigQuery returned no results, falling back to Firestore`);
398
- }
399
- } catch (bqError) {
400
- logger.log('WARN', `[DependencyFetcher] BigQuery series query failed, falling back to Firestore: ${bqError.message}`);
401
- }
402
- }
403
-
404
- // =========================================================================
405
- // FIRESTORE FALLBACK: Original logic (backwards compatibility)
406
- // =========================================================================
407
- logger.log('INFO', `[DependencyFetcher] 📂 Using Firestore for computation result series: ${calcNames.length} calcs x ${lookbackDays} days`);
408
-
409
- // Build Fetch Operations
410
- const ops = [];
411
- for (const dateStr of dates) {
412
- for (const rawName of calcNames) {
413
- const norm = normalizeName(rawName);
414
- const category = manifestLookup[norm] || 'analytics';
415
-
416
- ops.push(async () => {
417
- const val = await fetchSingleResult(db, { ...config, logger }, dateStr, rawName, category);
418
- if (val && !isDataEmpty(val)) {
419
- results[norm][dateStr] = val;
420
- }
421
- });
422
- }
423
- }
424
-
425
- // Execute in Batches (Limit Concurrency)
426
- logger.log('INFO', `[DependencyFetcher] 📚 Loading series: ${calcNames.length} calcs x ${lookbackDays} days (${ops.length} ops)`);
427
- const BATCH_SIZE = 20;
428
- for (let i = 0; i < ops.length; i += BATCH_SIZE) {
429
- await Promise.all(ops.slice(i, i + BATCH_SIZE).map(fn => fn()));
430
- }
431
-
432
- // [DEBUG] Log results summary
433
- for (const rawName of calcNames) {
434
- const norm = normalizeName(rawName);
435
- const foundDates = Object.keys(results[norm] || {});
436
- logger.log('INFO', `[DependencyFetcher] ✅ '${rawName}' found data for ${foundDates.length}/${lookbackDays} days`);
437
- }
438
-
439
- return results;
440
- }
441
-
442
- /**
443
- * Bridge function for WorkflowOrchestrator compatibility.
444
- */
445
- async function fetchExistingResults(dateStr, calcs, fullManifest, config, deps, isHistoricalContext) {
446
- const lookup = {};
447
- if (Array.isArray(fullManifest)) {
448
- fullManifest.forEach(c => lookup[normalizeName(c.name)] = c.category || 'analytics');
449
- }
450
- // Ensure Date Object
451
- const dateObj = new Date(dateStr.includes('T') ? dateStr : dateStr + 'T00:00:00Z');
452
- return fetchDependencies(dateObj, calcs, config, deps, lookup, isHistoricalContext);
453
- }
454
-
455
- module.exports = { fetchDependencies, fetchResultSeries, fetchExistingResults };