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,597 +0,0 @@
1
- /**
2
- * @fileoverview Data Loading Layer.
3
- * REFACTORED:
4
- * 1. Routes 90% of data fetching to BigQuery (Portfolios, History, Prices, Insights, Rankings, etc.).
5
- * 2. Retains Firestore logic ONLY for:
6
- * - Verifications (user_verifications)
7
- * - Retail Users (Normal/Speculator Portfolios/History)
8
- * - Generic Social Feed (Legacy compatibility)
9
- */
10
- const {
11
- queryPortfolioData,
12
- queryHistoryData,
13
- querySocialData,
14
- queryAssetPrices,
15
- queryAllPricesForDate,
16
- queryPricesForTickers,
17
- queryInstrumentInsights,
18
- queryPIRankings,
19
- queryTickerMappings,
20
- queryPIMasterList,
21
- queryPIRatings,
22
- queryPIPageViews,
23
- queryWatchlistMembership,
24
- queryPIAlertHistory
25
- } = require('../../core/utils/bigquery_utils');
26
-
27
- const { normalizeName } = require('./utils');
28
-
29
- // =============================================================================
30
- // 1. PORTFOLIOS
31
- // =============================================================================
32
- exports.loadDailyPortfolios = async (config, deps, dateStr, userTypes = []) => {
33
- const { db, logger } = deps;
34
-
35
- // Normalize user types
36
- const types = Array.isArray(userTypes) ? userTypes : [userTypes];
37
- const isRetail = types.some(t => ['NORMAL', 'SPECULATOR'].includes(t.toUpperCase()));
38
- const isMigrated = types.some(t => ['POPULAR_INVESTOR', 'SIGNED_IN_USER'].includes(t.toUpperCase()));
39
-
40
- let results = {};
41
-
42
- // A. BigQuery (PIs & SignedIn)
43
- if (isMigrated && process.env.BIGQUERY_ENABLED !== 'false') {
44
- const bqData = await queryPortfolioData(dateStr, null, types, logger);
45
- if (bqData) Object.assign(results, bqData);
46
- }
47
-
48
- // B. Firestore (Retail / Fallback)
49
- // Note: If we need Retail data, we MUST check Firestore as it wasn't migrated.
50
- if (isRetail) {
51
- if (types.includes('NORMAL')) {
52
- const normalData = await loadRetailFirestore(db, 'NormalUserPortfolios', dateStr);
53
- Object.assign(results, normalData);
54
- }
55
- if (types.includes('SPECULATOR')) {
56
- const specData = await loadRetailFirestore(db, 'SpeculatorPortfolios', dateStr);
57
- Object.assign(results, specData);
58
- }
59
- }
60
-
61
- return results;
62
- };
63
-
64
- // =============================================================================
65
- // 2. TRADE HISTORY
66
- // =============================================================================
67
- exports.loadDailyHistory = async (config, deps, dateStr, userTypes = []) => {
68
- const { db, logger } = deps;
69
- const types = Array.isArray(userTypes) ? userTypes : [userTypes];
70
- const isRetail = types.some(t => ['NORMAL', 'SPECULATOR'].includes(t.toUpperCase()));
71
- const isMigrated = types.some(t => ['POPULAR_INVESTOR', 'SIGNED_IN_USER'].includes(t.toUpperCase()));
72
-
73
- let results = {};
74
-
75
- if (isMigrated && process.env.BIGQUERY_ENABLED !== 'false') {
76
- const bqData = await queryHistoryData(dateStr, null, types, logger);
77
- if (bqData) Object.assign(results, bqData);
78
- }
79
-
80
- if (isRetail) {
81
- if (types.includes('NORMAL')) {
82
- const normalData = await loadRetailFirestore(db, 'NormalUserTradeHistory', dateStr);
83
- Object.assign(results, normalData);
84
- }
85
- if (types.includes('SPECULATOR')) {
86
- const specData = await loadRetailFirestore(db, 'SpeculatorTradeHistory', dateStr);
87
- Object.assign(results, specData);
88
- }
89
- }
90
- return results;
91
- };
92
-
93
- // =============================================================================
94
- // 3. SOCIAL
95
- // =============================================================================
96
- exports.loadDailySocialPostInsights = async (config, deps, dateStr, userTypes = []) => {
97
- const { db, logger } = deps;
98
- const types = Array.isArray(userTypes) ? userTypes : (userTypes ? [userTypes] : []);
99
-
100
- // A. BigQuery (User-Specific Social)
101
- if (types.length > 0 && process.env.BIGQUERY_ENABLED !== 'false') {
102
- return querySocialData(dateStr, null, types, logger);
103
- }
104
-
105
- // B. Firestore (Generic Feed - Legacy)
106
- // If no user types specified, assume generic feed fetch
107
- const collection = config.socialInsightsCollection || 'daily_social_insights';
108
- try {
109
- const snap = await db.collection(collection).doc(dateStr).collection('posts').get();
110
- if (snap.empty) return {};
111
- const data = {};
112
- snap.forEach(doc => data[doc.id] = doc.data());
113
- return data;
114
- } catch (e) {
115
- logger.log('WARN', `[DataLoader] Failed to load generic social for ${dateStr}: ${e.message}`);
116
- return {};
117
- }
118
- };
119
-
120
- // =============================================================================
121
- // 4. MARKET DATA (Prices)
122
- // =============================================================================
123
- exports.getPriceShardRefs = async (config, deps) => {
124
- // Legacy Shard Helper - In BQ world, we don't use shards but CachedDataLoader expects this structure.
125
- // We return a "virtual" shard array that signals CachedDataLoader to load from BQ.
126
- if (process.env.BIGQUERY_ENABLED !== 'false') {
127
- return [ { _bigquery: true } ];
128
- }
129
- // Fallback to Firestore Logic - return array of doc refs
130
- const { db } = deps;
131
- const collection = config.assetPricesCollection || 'asset_prices';
132
- const snapshot = await db.collection(collection).listDocuments();
133
- const refs = [];
134
- snapshot.forEach(doc => refs.push(doc));
135
- return refs;
136
- };
137
-
138
- exports.getRelevantShardRefs = async (config, deps, targetIds) => {
139
- // In BQ mode, we don't shard by instrument; return single virtual shard
140
- if (process.env.BIGQUERY_ENABLED !== 'false') {
141
- return [ { _bigquery: true, targetIds: targetIds || [] } ];
142
- }
143
- // Firestore behavior - return array of doc refs (same as getPriceShardRefs for now)
144
- return exports.getPriceShardRefs(config, deps);
145
- };
146
-
147
- // =============================================================================
148
- // 5. ROOT DATA TYPES (Simple Mappings)
149
- // =============================================================================
150
-
151
- exports.loadDailyInsights = async (config, deps, dateStr) => {
152
- const { logger } = deps;
153
-
154
- if (process.env.BIGQUERY_ENABLED !== 'false') {
155
- try {
156
- const rows = await queryInstrumentInsights(dateStr, logger);
157
- if (Array.isArray(rows) && rows.length > 0) {
158
- logger.log('INFO', `[DataLoader] ✅ Using BigQuery for instrument insights (${dateStr}): ${rows.length} instruments`);
159
- // Wrap in Firestore-shaped document format for InsightsExtractor compatibility
160
- return { insights: rows };
161
- }
162
- } catch (e) {
163
- logger.log('WARN', `[DataLoader] BigQuery insights query failed for ${dateStr}: ${e.message}`);
164
- }
165
- }
166
-
167
- // No Firestore fallback by design – return empty but correctly shaped
168
- return { insights: [] };
169
- };
170
-
171
- exports.loadPopularInvestorRankings = async (config, deps, dateStr) => {
172
- const data = await queryPIRankings(dateStr, deps.logger);
173
- return data ? data.Items : [];
174
- };
175
-
176
- exports.loadPIRatings = async (config, deps, dateStr) => {
177
- return queryPIRatings(dateStr, deps.logger);
178
- };
179
-
180
- exports.loadPIPageViews = async (config, deps, dateStr) => {
181
- return queryPIPageViews(dateStr, deps.logger);
182
- };
183
-
184
- exports.loadWatchlistMembership = async (config, deps, dateStr) => {
185
- return queryWatchlistMembership(dateStr, deps.logger);
186
- };
187
-
188
- exports.loadPIAlertHistory = async (config, deps, dateStr) => {
189
- return queryPIAlertHistory(dateStr, deps.logger);
190
- };
191
-
192
- exports.loadPopularInvestorMasterList = async (config, deps) => {
193
- return queryPIMasterList(deps.logger);
194
- };
195
-
196
- exports.loadPIWatchlistData = async (config, deps, piCid) => {
197
- // Watchlist data is time-series in BQ. For "Current State" (ID based),
198
- // we query the most recent date available for this PI.
199
- // This is a specialized query not in standard utils, so we implement it here or assume caller passes date.
200
- // However, CachedDataLoader expects (cid) -> Data.
201
- // We'll return null here as WatchlistMembership (by date) is the preferred method now.
202
- deps.logger.log('WARN', '[DataLoader] loadPIWatchlistData (by CID) is deprecated in favor of loadWatchlistMembership (by Date).');
203
- return null;
204
- };
205
-
206
- // =============================================================================
207
- // 6. EXCEPTIONS (Firestore Only)
208
- // =============================================================================
209
-
210
- exports.loadVerificationProfiles = async (config, deps, dateStr) => {
211
- const { db, logger } = deps;
212
- try {
213
- // Verifications are a single collection, not date-partitioned snapshots
214
- const snap = await db.collection('user_verifications').get();
215
- const verifications = {};
216
- snap.forEach(doc => verifications[doc.id] = doc.data());
217
- return verifications;
218
- } catch (e) {
219
- logger.log('ERROR', `[DataLoader] Failed to load verifications: ${e.message}`);
220
- return {};
221
- }
222
- };
223
-
224
- // =============================================================================
225
- // HELPERS
226
- // =============================================================================
227
-
228
- // =============================================================================
229
- // 7. PRICE DATA BY REFS (For PriceBatchExecutor)
230
- // =============================================================================
231
-
232
- /**
233
- * Load price data from an array of shard references (virtual or Firestore doc refs).
234
- * Used by PriceBatchExecutor for batch price computations.
235
- * @param {object} config - Configuration object
236
- * @param {object} deps - Dependencies (db, logger, etc.)
237
- * @param {Array} shardRefs - Array of shard references (virtual BigQuery objects or Firestore doc refs)
238
- * @returns {Promise<object>} Combined price data object keyed by instrument ID
239
- */
240
- exports.loadDataByRefs = async (config, deps, shardRefs) => {
241
- const { logger } = deps;
242
-
243
- if (!Array.isArray(shardRefs) || shardRefs.length === 0) {
244
- return {};
245
- }
246
-
247
- // Check if we're in BigQuery mode (virtual shards)
248
- const isBigQuery = shardRefs.some(ref => ref && ref._bigquery === true);
249
-
250
- if (isBigQuery && process.env.BIGQUERY_ENABLED !== 'false') {
251
- try {
252
- // Extract targetIds from virtual shards if present
253
- const targetIds = shardRefs
254
- .filter(ref => ref._bigquery && ref.targetIds && ref.targetIds.length > 0)
255
- .flatMap(ref => ref.targetIds);
256
-
257
- // Query BigQuery for prices
258
- // queryAssetPrices signature: (startDateStr, endDateStr, instrumentIds, logger)
259
- const { queryAssetPrices } = require('../../core/utils/bigquery_utils');
260
- const pricesData = await queryAssetPrices(null, null, targetIds.length > 0 ? targetIds : null, logger);
261
-
262
- // Filter by targetIds if specified
263
- if (targetIds.length > 0 && pricesData) {
264
- const targetSet = new Set(targetIds.map(id => String(id)));
265
- const filtered = {};
266
- for (const [instrumentId, priceData] of Object.entries(pricesData)) {
267
- if (targetSet.has(String(instrumentId))) {
268
- filtered[instrumentId] = priceData;
269
- }
270
- }
271
- return filtered;
272
- }
273
-
274
- return pricesData || {};
275
- } catch (e) {
276
- logger.log('ERROR', `[DataLoader] BigQuery price load failed: ${e.message}`);
277
- return {};
278
- }
279
- }
280
-
281
- // Firestore fallback - load from doc refs
282
- const combined = {};
283
- try {
284
- const loadPromises = shardRefs.map(async (docRef) => {
285
- try {
286
- const snap = await docRef.get();
287
- if (snap.exists) {
288
- const data = snap.data();
289
- // Firestore price shards are nested: { instrumentId: { prices: {...} } }
290
- Object.assign(combined, data);
291
- }
292
- } catch (e) {
293
- logger.log('WARN', `[DataLoader] Failed to load price shard: ${e.message}`);
294
- }
295
- });
296
-
297
- await Promise.all(loadPromises);
298
- } catch (e) {
299
- logger.log('ERROR', `[DataLoader] Failed to load price data from refs: ${e.message}`);
300
- }
301
-
302
- return combined;
303
- };
304
-
305
- // =============================================================================
306
- // HELPERS
307
- // =============================================================================
308
-
309
- async function loadRetailFirestore(db, collectionName, dateStr) {
310
- const CANARY_ID = '19M'; // Legacy Block
311
- try {
312
- const partsRef = db.collection(collectionName).doc(CANARY_ID)
313
- .collection('snapshots').doc(dateStr).collection('parts');
314
-
315
- const snap = await partsRef.get();
316
- if (snap.empty) return {};
317
-
318
- const combined = {};
319
- snap.forEach(doc => Object.assign(combined, doc.data()));
320
- return combined;
321
- } catch (e) {
322
- return {};
323
- }
324
- }
325
-
326
- // =============================================================================
327
- // 8. STREAMING DATA (For StandardExecutor)
328
- // =============================================================================
329
-
330
- /**
331
- * Get portfolio part references for streaming.
332
- * In BigQuery mode, returns virtual refs that signal to load from BQ.
333
- * @param {object} config - Configuration object
334
- * @param {object} deps - Dependencies (db, logger)
335
- * @param {string} dateStr - Date string (YYYY-MM-DD)
336
- * @param {Array<string>|null} userTypes - User types to filter (null = all)
337
- * @returns {Promise<Array>} Array of part references
338
- */
339
- exports.getPortfolioPartRefs = async (config, deps, dateStr, userTypes = null) => {
340
- const { logger } = deps;
341
-
342
- // In BigQuery mode, return virtual refs with metadata
343
- if (process.env.BIGQUERY_ENABLED !== 'false') {
344
- logger.log('INFO', `[DataLoader] Using BigQuery virtual refs for portfolios (${dateStr})`);
345
- return [{
346
- _bigquery: true,
347
- type: 'portfolio',
348
- date: dateStr,
349
- userTypes: userTypes
350
- }];
351
- }
352
-
353
- // Firestore fallback - return actual document references
354
- const { db } = deps;
355
- const refs = [];
356
-
357
- // Check both PI and SignedIn collections
358
- const collections = [
359
- { name: 'PopularInvestorPortfolios', userType: 'POPULAR_INVESTOR' },
360
- { name: 'SignedInUserPortfolios', userType: 'SIGNED_IN_USER' }
361
- ];
362
-
363
- for (const col of collections) {
364
- if (userTypes && !userTypes.includes(col.userType)) continue;
365
-
366
- try {
367
- const partsSnap = await db.collection(col.name)
368
- .doc('latest')
369
- .collection('snapshots')
370
- .doc(dateStr)
371
- .collection('parts')
372
- .listDocuments();
373
-
374
- for (const docRef of partsSnap) {
375
- refs.push({ ref: docRef, userType: col.userType, collection: col.name });
376
- }
377
- } catch (e) {
378
- logger.log('WARN', `[DataLoader] Failed to get portfolio refs from ${col.name}: ${e.message}`);
379
- }
380
- }
381
-
382
- return refs;
383
- };
384
-
385
- /**
386
- * Get history part references for streaming.
387
- * @param {object} config - Configuration object
388
- * @param {object} deps - Dependencies (db, logger)
389
- * @param {string} dateStr - Date string (YYYY-MM-DD)
390
- * @param {Array<string>|null} userTypes - User types to filter (null = all)
391
- * @returns {Promise<Array>} Array of part references
392
- */
393
- exports.getHistoryPartRefs = async (config, deps, dateStr, userTypes = null) => {
394
- const { logger } = deps;
395
-
396
- // In BigQuery mode, return virtual refs with metadata
397
- if (process.env.BIGQUERY_ENABLED !== 'false') {
398
- logger.log('INFO', `[DataLoader] Using BigQuery virtual refs for history (${dateStr})`);
399
- return [{
400
- _bigquery: true,
401
- type: 'history',
402
- date: dateStr,
403
- userTypes: userTypes
404
- }];
405
- }
406
-
407
- // Firestore fallback - return actual document references
408
- const { db } = deps;
409
- const refs = [];
410
-
411
- const collections = [
412
- { name: 'PopularInvestorTradeHistory', userType: 'POPULAR_INVESTOR' },
413
- { name: 'SignedInUserTradeHistory', userType: 'SIGNED_IN_USER' }
414
- ];
415
-
416
- for (const col of collections) {
417
- if (userTypes && !userTypes.includes(col.userType)) continue;
418
-
419
- try {
420
- const partsSnap = await db.collection(col.name)
421
- .doc('latest')
422
- .collection('snapshots')
423
- .doc(dateStr)
424
- .collection('parts')
425
- .listDocuments();
426
-
427
- for (const docRef of partsSnap) {
428
- refs.push({ ref: docRef, userType: col.userType, collection: col.name });
429
- }
430
- } catch (e) {
431
- logger.log('WARN', `[DataLoader] Failed to get history refs from ${col.name}: ${e.message}`);
432
- }
433
- }
434
-
435
- return refs;
436
- };
437
-
438
- /**
439
- * Stream portfolio data in chunks (async generator).
440
- * Yields objects of { cid: portfolioData } for memory efficiency.
441
- * @param {object} config - Configuration object
442
- * @param {object} deps - Dependencies (db, logger)
443
- * @param {string} dateStr - Date string (YYYY-MM-DD)
444
- * @param {Array} refs - Part references from getPortfolioPartRefs
445
- * @param {Array<string>|null} userTypes - User types to filter
446
- * @yields {object} Chunk of portfolio data { cid: data }
447
- */
448
- exports.streamPortfolioData = async function* (config, deps, dateStr, refs, userTypes = null) {
449
- const { logger } = deps;
450
-
451
- if (!refs || refs.length === 0) {
452
- logger.log('WARN', `[DataLoader] No portfolio refs provided for streaming (${dateStr})`);
453
- return;
454
- }
455
-
456
- // BigQuery mode - load all at once and yield in chunks
457
- const isBigQuery = refs.some(r => r && r._bigquery === true);
458
-
459
- if (isBigQuery && process.env.BIGQUERY_ENABLED !== 'false') {
460
- try {
461
- const types = userTypes || ['POPULAR_INVESTOR', 'SIGNED_IN_USER'];
462
- const rawData = await queryPortfolioData(dateStr, null, types, logger);
463
-
464
- if (rawData && Object.keys(rawData).length > 0) {
465
- // Transform BigQuery format to computation system format
466
- // BigQuery returns: { cid: { portfolio_data: {...}, user_type: '...', fetched_at: '...' } }
467
- // Computation system expects: { cid: { ...portfolioFields..., _userType: '...' } }
468
- const data = {};
469
- for (const [cid, record] of Object.entries(rawData)) {
470
- // Unwrap portfolio_data and add _userType metadata
471
- const portfolioData = record.portfolio_data || {};
472
- data[cid] = {
473
- ...portfolioData,
474
- _userType: record.user_type
475
- };
476
- }
477
-
478
- logger.log('INFO', `[DataLoader] ✅ Streaming ${Object.keys(data).length} portfolios from BigQuery (${dateStr})`);
479
-
480
- // Yield in chunks of 100 users for memory efficiency
481
- const CHUNK_SIZE = 100;
482
- const entries = Object.entries(data);
483
-
484
- for (let i = 0; i < entries.length; i += CHUNK_SIZE) {
485
- const chunk = {};
486
- entries.slice(i, i + CHUNK_SIZE).forEach(([cid, portfolio]) => {
487
- chunk[cid] = portfolio;
488
- });
489
- yield chunk;
490
- }
491
- return;
492
- }
493
- } catch (e) {
494
- logger.log('ERROR', `[DataLoader] BigQuery portfolio stream failed: ${e.message}`);
495
- }
496
- }
497
-
498
- // Firestore fallback - stream from refs
499
- for (const refInfo of refs) {
500
- if (!refInfo.ref) continue;
501
-
502
- try {
503
- const snap = await refInfo.ref.get();
504
- if (snap.exists) {
505
- const data = snap.data();
506
- // Add user type metadata
507
- const enriched = {};
508
- for (const [cid, portfolio] of Object.entries(data)) {
509
- enriched[cid] = { ...portfolio, _userType: refInfo.userType };
510
- }
511
- yield enriched;
512
- }
513
- } catch (e) {
514
- logger.log('WARN', `[DataLoader] Failed to stream portfolio part: ${e.message}`);
515
- }
516
- }
517
- };
518
-
519
- /**
520
- * Stream history data in chunks (async generator).
521
- * @param {object} config - Configuration object
522
- * @param {object} deps - Dependencies (db, logger)
523
- * @param {string} dateStr - Date string (YYYY-MM-DD)
524
- * @param {Array} refs - Part references from getHistoryPartRefs
525
- * @param {Array<string>|null} userTypes - User types to filter
526
- * @yields {object} Chunk of history data { cid: data }
527
- */
528
- exports.streamHistoryData = async function* (config, deps, dateStr, refs, userTypes = null) {
529
- const { logger } = deps;
530
-
531
- if (!refs || refs.length === 0) {
532
- logger.log('WARN', `[DataLoader] No history refs provided for streaming (${dateStr})`);
533
- return;
534
- }
535
-
536
- // BigQuery mode - load all at once and yield in chunks
537
- const isBigQuery = refs.some(r => r && r._bigquery === true);
538
-
539
- if (isBigQuery && process.env.BIGQUERY_ENABLED !== 'false') {
540
- try {
541
- const types = userTypes || ['POPULAR_INVESTOR', 'SIGNED_IN_USER'];
542
- const rawData = await queryHistoryData(dateStr, null, types, logger);
543
-
544
- if (rawData && Object.keys(rawData).length > 0) {
545
- // Transform BigQuery format to computation system format
546
- // BigQuery returns: { cid: { history_data: {...}, user_type: '...', fetched_at: '...' } }
547
- // Computation system expects: { cid: { ...historyFields..., _userType: '...' } }
548
- const data = {};
549
- for (const [cid, record] of Object.entries(rawData)) {
550
- // Unwrap history_data and add _userType metadata
551
- const historyData = record.history_data || {};
552
- data[cid] = {
553
- ...historyData,
554
- _userType: record.user_type
555
- };
556
- }
557
-
558
- logger.log('INFO', `[DataLoader] ✅ Streaming ${Object.keys(data).length} history records from BigQuery (${dateStr})`);
559
-
560
- // Yield in chunks of 100 users
561
- const CHUNK_SIZE = 100;
562
- const entries = Object.entries(data);
563
-
564
- for (let i = 0; i < entries.length; i += CHUNK_SIZE) {
565
- const chunk = {};
566
- entries.slice(i, i + CHUNK_SIZE).forEach(([cid, history]) => {
567
- chunk[cid] = history;
568
- });
569
- yield chunk;
570
- }
571
- return;
572
- }
573
- } catch (e) {
574
- logger.log('ERROR', `[DataLoader] BigQuery history stream failed: ${e.message}`);
575
- }
576
- }
577
-
578
- // Firestore fallback - stream from refs
579
- for (const refInfo of refs) {
580
- if (!refInfo.ref) continue;
581
-
582
- try {
583
- const snap = await refInfo.ref.get();
584
- if (snap.exists) {
585
- const data = snap.data();
586
- // Add user type metadata
587
- const enriched = {};
588
- for (const [cid, history] of Object.entries(data)) {
589
- enriched[cid] = { ...history, _userType: refInfo.userType };
590
- }
591
- yield enriched;
592
- }
593
- } catch (e) {
594
- logger.log('WARN', `[DataLoader] Failed to stream history part: ${e.message}`);
595
- }
596
- }
597
- };