bulltrackers-module 1.0.124 → 1.0.126

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.
@@ -13,10 +13,11 @@
13
13
  const { FieldPath } = require('@google-cloud/firestore');
14
14
  const {
15
15
  getPortfolioPartRefs,
16
- loadFullDayMap,
16
+ loadFullDayMap, // <-- Ensure loadFullDayMap is imported
17
17
  loadDataByRefs,
18
18
  loadDailyInsights,
19
- loadDailySocialPostInsights
19
+ loadDailySocialPostInsights,
20
+ getHistoryPartRefs // <-- IMPORT NEW FUNCTION
20
21
  } = require('../utils/data_loader.js');
21
22
 
22
23
  const {
@@ -53,47 +54,53 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
53
54
  if (dep === 'portfolio' && !rootDataStatus.hasPortfolio) return false;
54
55
  if (dep === 'insights' && !rootDataStatus.hasInsights) return false;
55
56
  if (dep === 'social' && !rootDataStatus.hasSocial) return false;
57
+ if (dep === 'history' && !rootDataStatus.hasHistory) return false; // <-- ADDED THIS LINE
56
58
  }
57
59
  return true; // All dependencies were met
58
60
  }
59
61
 
60
62
 
61
63
  /**
62
- * Checks if the root data (portfolios, insights, social) exists for a given day.
64
+ * Checks if the root data (portfolios, insights, social, history) exists for a given day.
63
65
  * @param {string} dateStr - The date string to check (YYYY-MM-DD).
64
66
  * @param {object} config - The computation system configuration object.
65
67
  * @param {object} dependencies - Contains db, logger, calculationUtils.
66
- * @returns {Promise<object>} { portfolioRefs, insightsData, socialData, hasPortfolio, hasInsights, hasSocial }
68
+ * @returns {Promise<object>} { portfolioRefs, insightsData, socialData, historyRefs, hasPortfolio, hasInsights, hasSocial, hasHistory }
67
69
  */
68
70
  async function checkRootDataAvailability(dateStr, config, dependencies) {
69
71
  const { logger } = dependencies;
70
72
  logger.log('INFO', `[PassRunner] Checking root data availability for ${dateStr}...`);
71
73
 
72
74
  try {
73
- const [portfolioRefs, insightsData, socialData] = await Promise.all([
75
+ // --- MODIFIED: Add getHistoryPartRefs to the parallel load ---
76
+ const [portfolioRefs, insightsData, socialData, historyRefs] = await Promise.all([
74
77
  getPortfolioPartRefs(config, dependencies, dateStr),
75
78
  loadDailyInsights(config, dependencies, dateStr),
76
- loadDailySocialPostInsights(config, dependencies, dateStr)
79
+ loadDailySocialPostInsights(config, dependencies, dateStr),
80
+ getHistoryPartRefs(config, dependencies, dateStr) // <-- ADD THIS
77
81
  ]);
78
82
 
79
83
  const hasPortfolio = (portfolioRefs && portfolioRefs.length > 0);
80
84
  const hasInsights = !!insightsData;
81
85
  const hasSocial = !!socialData;
86
+ const hasHistory = (historyRefs && historyRefs.length > 0); // <-- ADD THIS
82
87
 
83
88
  return {
84
89
  portfolioRefs: portfolioRefs || [],
85
90
  insightsData: insightsData || null,
86
91
  socialData: socialData || null,
92
+ historyRefs: historyRefs || [], // <-- ADD THIS
87
93
  hasPortfolio: hasPortfolio,
88
94
  hasInsights: hasInsights,
89
- hasSocial: hasSocial
95
+ hasSocial: hasSocial,
96
+ hasHistory: hasHistory // <-- ADD THIS
90
97
  };
91
98
 
92
99
  } catch (err) {
93
100
  logger.log('ERROR', `[PassRunner] Error checking data availability for ${dateStr}`, { errorMessage: err.message });
94
101
  return {
95
- portfolioRefs: [], insightsData: null, socialData: null,
96
- hasPortfolio: false, hasInsights: false, hasSocial: false
102
+ portfolioRefs: [], insightsData: null, socialData: null, historyRefs: [], // <-- ADD historyRefs
103
+ hasPortfolio: false, hasInsights: false, hasSocial: false, hasHistory: false // <-- ADD hasHistory
97
104
  };
98
105
  }
99
106
  }
@@ -223,15 +230,16 @@ async function runComputationPass(config, dependencies, computationManifest) {
223
230
  for (const dateStr of allExpectedDates) {
224
231
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
225
232
 
226
- // 1. Check for root data (portfolios, insights, social)
233
+ // 1. Check for root data (portfolios, insights, social, AND HISTORY)
227
234
  const rootData = await checkRootDataAvailability(dateStr, config, dependencies);
228
235
  const rootDataStatus = {
229
236
  hasPortfolio: rootData.hasPortfolio,
230
237
  hasInsights: rootData.hasInsights,
231
- hasSocial: rootData.hasSocial
238
+ hasSocial: rootData.hasSocial,
239
+ hasHistory: rootData.hasHistory // <-- ADD THIS
232
240
  };
233
241
 
234
- if (!rootData.hasPortfolio && !rootData.hasInsights && !rootData.hasSocial) {
242
+ if (!rootData.hasPortfolio && !rootData.hasInsights && !rootData.hasSocial && !rootData.hasHistory) {
235
243
  logger.log('WARN', `[PassRunner] Skipping Pass ${passToRun} for ${dateStr} due to missing all root data.`);
236
244
  continue; // Skip to the next day
237
245
  }
@@ -363,7 +371,9 @@ async function streamAndProcess(
363
371
  todayInsights = null,
364
372
  yesterdayInsights = null,
365
373
  todaySocialPostInsights = null,
366
- yesterdaySocialPostInsights = null
374
+ yesterdaySocialPostInsights = null,
375
+ todayHistoryData = null, // <-- ADD THIS
376
+ yesterdayHistoryData = null // <-- ADD THIS
367
377
  ) {
368
378
  const { logger, calculationUtils } = dependencies;
369
379
  logger.log('INFO', `[${passName}] Streaming ${todayRefs.length} 'today' part docs for ${dateStr}...`);
@@ -405,12 +415,16 @@ async function streamAndProcess(
405
415
  const isHistoricalCalc = manifestCalc.isHistorical === true;
406
416
  const isSpeculatorCalc = manifestCalc.category === 'speculators';
407
417
  let processArgs;
418
+
419
+ // --- MODIFIED: Add history data to the context args ---
408
420
  const allContextArgs = [
409
421
  context,
410
422
  todayInsights,
411
423
  yesterdayInsights,
412
424
  todaySocialPostInsights,
413
- yesterdaySocialPostInsights
425
+ yesterdaySocialPostInsights,
426
+ todayHistoryData, // <-- ADD THIS
427
+ yesterdayHistoryData // <-- ADD THIS
414
428
  ];
415
429
 
416
430
  if (isSocialOrInsights) {
@@ -447,7 +461,18 @@ async function streamAndProcess(
447
461
  // Handle case where there are no users but we still need to run insights/social calcs
448
462
  if (todayRefs.length === 0 && isFirstUser) {
449
463
  logger.log('INFO', `[${passName}] No user portfolios found for ${dateStr}. Running insights/social calcs once.`);
450
- const allContextArgs = [ context, todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights ];
464
+
465
+ // --- MODIFIED: Add null history data ---
466
+ const allContextArgs = [
467
+ context,
468
+ todayInsights,
469
+ yesterdayInsights,
470
+ todaySocialPostInsights,
471
+ yesterdaySocialPostInsights,
472
+ todayHistoryData, // <-- ADD THIS
473
+ yesterdayHistoryData // <-- ADD THIS
474
+ ];
475
+
451
476
  for (const calcName in state) {
452
477
  const calc = state[calcName];
453
478
  if (!calc || typeof calc.process !== 'function') continue;
@@ -474,23 +499,32 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
474
499
  logger.log('INFO', `[${passName}] Starting run for ${dateStr} with ${calculationsToRun.length} calcs.`);
475
500
 
476
501
  try {
502
+ // --- NEW: Get history root data from the orchestrator ---
477
503
  const {
478
504
  portfolioRefs: todayRefs,
479
505
  insightsData: todayInsightsData,
480
- socialData: todaySocialPostInsightsData
506
+ socialData: todaySocialPostInsightsData,
507
+ historyRefs: todayHistoryRefs // <-- ADD THIS
481
508
  } = rootData;
482
509
 
483
510
  let yesterdayPortfolios = {};
484
511
  let yesterdayInsightsData = null;
485
512
  let yesterdaySocialPostInsightsData = null;
513
+ let todayHistoryData = null; // <-- ADD THIS
514
+ let yesterdayHistoryData = null; // <-- ADD THIS
486
515
 
516
+ // --- MODIFIED: Add checks for history data ---
487
517
  const requiresYesterdayPortfolio = calculationsToRun.some(c => c.isHistorical === true);
488
518
  const requiresYesterdayInsights = calculationsToRun.some(c => c.class.prototype.process.toString().includes('yesterdayInsights'));
489
519
  const requiresYesterdaySocialPosts = calculationsToRun.some(c => c.class.prototype.process.toString().includes('yesterdaySocialPostInsights'));
520
+ const requiresHistory = calculationsToRun.some(c => c.rootDataDependencies.includes('history'));
521
+ const requiresYesterdayHistory = calculationsToRun.some(c => c.isHistorical === true && c.class.prototype.process.toString().includes('yesterdayHistoryData'));
522
+ // --- END MODIFICATION ---
523
+
490
524
 
491
- if (requiresYesterdayPortfolio || requiresYesterdayInsights || requiresYesterdaySocialPosts) {
492
- // ... [Identical logic for fetching yesterday's data as in original file] ...
493
- // This logic is complex and correct, so it is preserved.
525
+ // --- (This block is now much larger, handling all "yesterday" and "history" data) ---
526
+ if (requiresYesterdayPortfolio || requiresYesterdayInsights || requiresYesterdaySocialPosts || requiresHistory || requiresYesterdayHistory) {
527
+
494
528
  if(requiresYesterdayInsights) {
495
529
  let daysAgo = 1; const maxLookback = 30;
496
530
  while (!yesterdayInsightsData && daysAgo <= maxLookback) {
@@ -524,9 +558,32 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
524
558
  logger.log('WARN', `[${passName}] Yesterday's (${prevStr}) portfolio data not found. Historical calcs requiring it will be skipped.`);
525
559
  }
526
560
  }
561
+
562
+ // --- NEW: Add logic to load Today's History Data (if needed) ---
563
+ if (requiresHistory && todayHistoryRefs.length > 0) {
564
+ logger.log('INFO', `[${passName}] Loading today's (${dateStr}) history map...`);
565
+ todayHistoryData = await loadFullDayMap(config, dependencies, todayHistoryRefs);
566
+ logger.log('INFO', `[${passName}] Loaded today's history map with ${Object.keys(todayHistoryData).length} users.`);
567
+ }
568
+
569
+ // --- NEW: Add logic to load Yesterday's History Data (if needed) ---
570
+ if (requiresYesterdayHistory) {
571
+ const prev = new Date(dateToProcess);
572
+ prev.setUTCDate(prev.getUTCDate() - 1);
573
+ const prevStr = prev.toISOString().slice(0, 10);
574
+ const yesterdayHistoryRefs = await getHistoryPartRefs(config, dependencies, prevStr);
575
+ if (yesterdayHistoryRefs.length > 0) {
576
+ yesterdayHistoryData = await loadFullDayMap(config, dependencies, yesterdayHistoryRefs);
577
+ logger.log('INFO', `[${passName}] Loaded yesterday's (${prevStr}) history map for historical calcs.`);
578
+ } else {
579
+ logger.log('WARN', `[${passName}] Yesterday's (${prevStr}) history data not found. Historical calcs requiring it will be skipped.`);
580
+ }
581
+ }
527
582
  }
528
583
 
529
584
  const state = initializeCalculators(calculationsToRun, logger);
585
+
586
+ // --- MODIFIED: Pass new history data ---
530
587
  await streamAndProcess(
531
588
  dateStr,
532
589
  todayRefs,
@@ -538,7 +595,9 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
538
595
  todayInsightsData,
539
596
  yesterdayInsightsData,
540
597
  todaySocialPostInsightsData,
541
- yesterdaySocialPostInsightsData
598
+ yesterdaySocialPostInsightsData,
599
+ todayHistoryData, // <-- ADD THIS
600
+ yesterdayHistoryData // <-- ADD THIS
542
601
  );
543
602
 
544
603
  let successCount = 0;
@@ -183,10 +183,60 @@ async function loadDailySocialPostInsights(config, dependencies, dateString) {
183
183
  // --- END NEW ---
184
184
 
185
185
 
186
+
187
+ /**
188
+ * --- NEW ---
189
+ * Sub-pipe: pipe.computationSystem.dataLoader.getHistoryPartRefs
190
+ * @param {object} config - The computation system configuration object.
191
+ * @param {object} dependencies - Contains db, logger, calculationUtils.
192
+ * @param {string} dateString - The date in YYYY-MM-DD format.
193
+ * @returns {Promise<Firestore.DocumentReference[]>} An array of DocumentReferences.
194
+ */
195
+ async function getHistoryPartRefs(config, dependencies, dateString) {
196
+ const { db, logger, calculationUtils } = dependencies;
197
+ const { withRetry } = calculationUtils;
198
+
199
+ logger.log('INFO', `Getting history part references for date: ${dateString}`);
200
+ const allPartRefs = [];
201
+ // --- MODIFIED: Use new history collection config keys ---
202
+ const collectionsToQuery = [config.normalUserHistoryCollection, config.speculatorHistoryCollection];
203
+
204
+ for (const collectionName of collectionsToQuery) {
205
+ if (!collectionName) { // Add a check in case config is missing
206
+ logger.log('WARN', `History collection name is undefined. Skipping.`);
207
+ continue;
208
+ }
209
+ const blockDocsQuery = db.collection(collectionName);
210
+ const blockDocRefs = await withRetry(
211
+ () => blockDocsQuery.listDocuments(),
212
+ `listDocuments(${collectionName})`
213
+ );
214
+
215
+ if (blockDocRefs.length === 0) {
216
+ logger.log('WARN', `No block documents found in collection: ${collectionName}`);
217
+ continue;
218
+ }
219
+
220
+ for (const blockDocRef of blockDocRefs) {
221
+ const partsCollectionRef = blockDocRef.collection(config.snapshotsSubcollection).doc(dateString).collection(config.partsSubcollection);
222
+ const partDocs = await withRetry(
223
+ () => partsCollectionRef.listDocuments(),
224
+ `listDocuments(${partsCollectionRef.path})`
225
+ );
226
+ allPartRefs.push(...partDocs);
227
+ }
228
+ }
229
+
230
+ logger.log('INFO', `Found ${allPartRefs.length} history part document references for ${dateString}.`);
231
+ return allPartRefs;
232
+ }
233
+
234
+
186
235
  module.exports = {
187
236
  getPortfolioPartRefs,
188
237
  loadDataByRefs,
189
238
  loadFullDayMap,
190
239
  loadDailyInsights,
191
240
  loadDailySocialPostInsights,
241
+ getHistoryPartRefs, // <-- EXPORT NEW FUNCTION
192
242
  };
@@ -120,10 +120,11 @@ class FirestoreBatchManager {
120
120
  this.tradingHistoryBatch[basePath][userId] = historyData;
121
121
 
122
122
  // Trigger flush based on portfolio batch size, assuming they'll be roughly equal
123
- const totalUsersInBatch = Object.values(this.portfolioBatch).reduce((sum, users) => sum + Object.keys(users).length, 0);
123
+ const totalUsersInBatch = Object.values(this.tradingHistoryBatch).reduce((sum, users) => sum + Object.keys(users).length, 0);
124
124
 
125
125
  if (totalUsersInBatch >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
126
- await this.flushBatches();
126
+ // DO NOT await a flush. Just schedule one.
127
+ this._scheduleFlush();
127
128
  } else {
128
129
  this._scheduleFlush();
129
130
  }
@@ -153,7 +154,8 @@ class FirestoreBatchManager {
153
154
  const totalUsersInBatch = Object.values(this.portfolioBatch).reduce((sum, users) => sum + Object.keys(users).length, 0);
154
155
 
155
156
  if (totalUsersInBatch >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
156
- await this.flushBatches();
157
+ // DO NOT await a flush. Just schedule one.
158
+ this._scheduleFlush();
157
159
  } else {
158
160
  this._scheduleFlush();
159
161
  }
@@ -180,7 +182,8 @@ class FirestoreBatchManager {
180
182
  this.timestampBatch[docPath][timestampKey] = new Date();
181
183
 
182
184
  if (Object.keys(this.timestampBatch[docPath]).length >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
183
- await this.flushBatches();
185
+ // DO NOT await a flush. Just schedule one.
186
+ this._scheduleFlush();
184
187
  } else {
185
188
  this._scheduleFlush();
186
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.124",
3
+ "version": "1.0.126",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [