bulltrackers-module 1.0.108 → 1.0.110

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.
@@ -41,13 +41,37 @@ function groupByPass(manifest) {
41
41
  }, {});
42
42
  }
43
43
 
44
+ /**
45
+ * --- NEW HELPER ---
46
+ * Checks if a calculation's root data dependencies are met.
47
+ * @param {object} calcManifest - The manifest entry for the calculation.
48
+ * @param {object} rootDataStatus - The status object from checkRootDataAvailability.
49
+ * @returns {boolean} True if dependencies are met, false otherwise.
50
+ */
51
+ function checkRootDependencies(calcManifest, rootDataStatus) {
52
+ // Default to true if property is missing (legacy support)
53
+ if (!calcManifest.rootDataDependencies || calcManifest.rootDataDependencies.length === 0) {
54
+ return true;
55
+ }
56
+
57
+ // Check each required dependency
58
+ for (const dep of calcManifest.rootDataDependencies) {
59
+ if (dep === 'portfolio' && !rootDataStatus.hasPortfolio) return false;
60
+ if (dep === 'insights' && !rootDataStatus.hasInsights) return false;
61
+ if (dep === 'social' && !rootDataStatus.hasSocial) return false;
62
+ }
63
+
64
+ return true; // All dependencies were met
65
+ }
66
+
67
+
44
68
  /**
45
69
  * --- NEW HELPER ---
46
70
  * Checks if the root data (portfolios, insights, social) exists for a given day.
47
71
  * @param {string} dateStr - The date string to check (YYYY-MM-DD).
48
72
  * @param {object} config - The computation system configuration object.
49
73
  * @param {object} dependencies - Contains db, logger, calculationUtils.
50
- * @returns {Promise<object>} { portfolioRefs, insightsData, socialData, isAvailable }
74
+ * @returns {Promise<object>} { portfolioRefs, insightsData, socialData, isAvailable, hasPortfolio, hasInsights, hasSocial }
51
75
  */
52
76
  async function checkRootDataAvailability(dateStr, config, dependencies) {
53
77
  const { logger } = dependencies;
@@ -60,22 +84,33 @@ async function checkRootDataAvailability(dateStr, config, dependencies) {
60
84
  loadDailySocialPostInsights(config, dependencies, dateStr)
61
85
  ]);
62
86
 
63
- const isAvailable = (portfolioRefs && portfolioRefs.length > 0) || !!insightsData || !!socialData;
87
+ const hasPortfolio = (portfolioRefs && portfolioRefs.length > 0);
88
+ const hasInsights = !!insightsData;
89
+ const hasSocial = !!socialData;
90
+ const isAvailable = hasPortfolio || hasInsights || hasSocial;
64
91
 
65
92
  if (isAvailable) {
66
- logger.log('INFO', `[Orchestrator] Root data found for ${dateStr}. (Portfolio parts: ${portfolioRefs.length}, Insights: ${!!insightsData}, Social: ${!!socialData})`);
93
+ logger.log('INFO', `[Orchestrator] Root data found for ${dateStr}. (Portfolio: ${hasPortfolio}, Insights: ${hasInsights}, Social: ${hasSocial})`);
67
94
  }
68
95
 
69
96
  return {
70
97
  portfolioRefs: portfolioRefs || [],
71
98
  insightsData: insightsData || null,
72
99
  socialData: socialData || null,
73
- isAvailable: isAvailable
100
+ isAvailable: isAvailable,
101
+ // --- MODIFIED: Return granular status ---
102
+ hasPortfolio: hasPortfolio,
103
+ hasInsights: hasInsights,
104
+ hasSocial: hasSocial
105
+ // --- END MODIFICATION ---
74
106
  };
75
107
 
76
108
  } catch (err) {
77
109
  logger.log('ERROR', `[Orchestrator] Error checking data availability for ${dateStr}`, { errorMessage: err.message });
78
- return { portfolioRefs: [], insightsData: null, socialData: null, isAvailable: false };
110
+ return {
111
+ portfolioRefs: [], insightsData: null, socialData: null, isAvailable: false,
112
+ hasPortfolio: false, hasInsights: false, hasSocial: false
113
+ };
79
114
  }
80
115
  }
81
116
 
@@ -111,24 +146,26 @@ async function runComputationOrchestrator(config, dependencies, computationManif
111
146
  for (const dateStr of allExpectedDates) {
112
147
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
113
148
 
114
- // --- NEW: Check for root data *before* processing the day ---
149
+ // --- MODIFIED: Check for root data *before* processing the day ---
115
150
  const rootData = await checkRootDataAvailability(dateStr, config, dependencies);
151
+ // The 'isAvailable' flag now means "is there *any* data"
116
152
  if (!rootData.isAvailable) {
117
153
  logger.log('WARN', `[Orchestrator] Skipping all computations for ${dateStr} due to missing root data (no portfolios, insights, or social data found).`);
118
154
  continue; // Skip to the next day
119
155
  }
120
- // --- END NEW CHECK ---
156
+ // --- END MODIFIED CHECK ---
121
157
 
122
158
  logger.log('INFO', `[Orchestrator] Processing all passes for ${dateStr}...`);
123
159
 
124
- // This cache will hold results *for this day only*
125
160
  const dailyResultsCache = new Map();
161
+ // --- NEW: Keep track of skipped calcs ---
162
+ const skippedCalculations = new Set();
126
163
  let passSuccess = true;
127
164
 
128
165
  for (const passNum of passNumbers) {
129
166
  if (!passSuccess) {
130
167
  logger.log('WARN', `[Orchestrator] Skipping Pass ${passNum} for ${dateStr} due to previous pass failure.`);
131
- break; // Skip subsequent passes if a previous one failed
168
+ break;
132
169
  }
133
170
 
134
171
  const calcsInPass = passes[passNum] || [];
@@ -139,38 +176,87 @@ async function runComputationOrchestrator(config, dependencies, computationManif
139
176
  logger.log('INFO', `[Orchestrator] Starting Pass ${passNum} for ${dateStr} (${standardCalcs.length} standard, ${metaCalcs.length} meta).`);
140
177
 
141
178
  try {
142
- // 1. Run standard calcs for this pass
179
+ // --- 1. Run standard calcs for this pass ---
143
180
  if (standardCalcs.length > 0) {
144
- const standardResults = await runUnifiedComputation(
145
- dateToProcess,
146
- standardCalcs, // Pass the manifest objects
147
- `Pass ${passNum} (Standard)`,
148
- config,
149
- dependencies,
150
- rootData // <-- Pass pre-fetched root data
151
- );
152
181
 
153
- // Add results to cache
154
- for (const [calcName, result] of Object.entries(standardResults)) {
155
- dailyResultsCache.set(calcName, result);
182
+ // --- NEW: Filter calcs based on root data availability ---
183
+ const standardCalcsToRun = [];
184
+ for (const calcManifest of standardCalcs) {
185
+ if (checkRootDependencies(calcManifest, rootData)) {
186
+ standardCalcsToRun.push(calcManifest);
187
+ } else {
188
+ logger.log('INFO', `[Pass ${passNum}] Skipping standard calc "${calcManifest.name}" for ${dateStr} due to missing root data.`);
189
+ skippedCalculations.add(calcManifest.name);
190
+ }
191
+ }
192
+ // --- END NEW FILTER ---
193
+
194
+ if (standardCalcsToRun.length > 0) {
195
+ const standardResults = await runUnifiedComputation(
196
+ dateToProcess,
197
+ standardCalcsToRun, // Pass the filtered list
198
+ `Pass ${passNum} (Standard)`,
199
+ config,
200
+ dependencies,
201
+ rootData
202
+ );
203
+
204
+ for (const [calcName, result] of Object.entries(standardResults)) {
205
+ dailyResultsCache.set(calcName, result);
206
+ }
156
207
  }
157
208
  }
158
209
 
159
- // 2. Run meta calcs for this pass
210
+ // --- 2. Run meta calcs for this pass ---
160
211
  if (metaCalcs.length > 0) {
161
- const metaResults = await runMetaComputation(
162
- dateToProcess,
163
- metaCalcs, // Pass the manifest objects
164
- `Pass ${passNum} (Meta)`,
165
- config,
166
- dependencies,
167
- dailyResultsCache, // <-- PASS THE CACHE
168
- rootData // <--- !! MODIFICATION: PASS rootData !!
169
- );
170
-
171
- // Add results to cache
172
- for (const [calcName, result] of Object.entries(metaResults)) {
173
- dailyResultsCache.set(calcName, result);
212
+
213
+ // --- NEW: Filter calcs based on root data AND computation dependencies ---
214
+ const metaCalcsToRun = [];
215
+ for (const calcManifest of metaCalcs) {
216
+ const calcName = calcManifest.name;
217
+
218
+ // Check 1: Are root data dependencies met?
219
+ const rootCheck = checkRootDependencies(calcManifest, rootData);
220
+ if (!rootCheck) {
221
+ logger.log('INFO', `[Pass ${passNum} (Meta)] Skipping meta calc "${calcName}" for ${dateStr} due to missing root data.`);
222
+ skippedCalculations.add(calcName);
223
+ continue;
224
+ }
225
+
226
+ // Check 2: Are computation dependencies met (i.e., not skipped)?
227
+ let depCheck = true;
228
+ let missingDepName = '';
229
+ for (const depName of (calcManifest.dependencies || [])) {
230
+ if (skippedCalculations.has(normalizeName(depName))) {
231
+ depCheck = false;
232
+ missingDepName = normalizeName(depName);
233
+ break;
234
+ }
235
+ }
236
+
237
+ if (depCheck) {
238
+ metaCalcsToRun.push(calcManifest);
239
+ } else {
240
+ logger.log('INFO', `[Pass ${passNum} (Meta)] Skipping meta calc "${calcName}" for ${dateStr} due to missing computation dependency "${missingDepName}".`);
241
+ skippedCalculations.add(calcName);
242
+ }
243
+ }
244
+ // --- END NEW FILTER ---
245
+
246
+ if (metaCalcsToRun.length > 0) {
247
+ const metaResults = await runMetaComputation(
248
+ dateToProcess,
249
+ metaCalcsToRun, // Pass the filtered list
250
+ `Pass ${passNum} (Meta)`,
251
+ config,
252
+ dependencies,
253
+ dailyResultsCache,
254
+ rootData
255
+ );
256
+
257
+ for (const [calcName, result] of Object.entries(metaResults)) {
258
+ dailyResultsCache.set(calcName, result);
259
+ }
174
260
  }
175
261
  }
176
262
  logger.log('SUCCESS', `[Orchestrator] Completed Pass ${passNum} for ${dateStr}.`);
@@ -179,7 +265,7 @@ async function runComputationOrchestrator(config, dependencies, computationManif
179
265
  passSuccess = false;
180
266
  }
181
267
  } // End passes loop
182
- logger.log('INFO', `[Orchestrator] Finished processing for ${dateStr}.`);
268
+ logger.log('INFO', `[Orchestrator] Finished processing for ${dateStr}. Total skipped calculations: ${skippedCalculations.size}`);
183
269
  } // End dates loop
184
270
 
185
271
  logger.log('INFO', '[Orchestrator] Computation orchestration finished.');
@@ -255,6 +341,8 @@ async function streamAndProcess(
255
341
  if (!p) continue;
256
342
 
257
343
  const userType = p.PublicPositions ? 'speculator' : 'normal';
344
+ // Add userType to context
345
+ context.userType = userType;
258
346
 
259
347
  for (const calcName in state) { // calcName is already normalized
260
348
  const calc = state[calcName];
@@ -364,14 +452,10 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
364
452
  } = rootData;
365
453
  // --- END NEW ---
366
454
 
367
- // (The check for data availability is now done in the orchestrator)
368
-
369
455
  let yesterdayPortfolios = {};
370
456
  let yesterdayInsightsData = null;
371
457
  let yesterdaySocialPostInsightsData = null;
372
458
 
373
- // Check if any calc needs yesterday's data
374
- // --- MODIFIED: Use the manifest entry ---
375
459
  const requiresYesterdayPortfolio = calculationsToRun.some(c => c.isHistorical === true);
376
460
  const requiresYesterdayInsights = calculationsToRun.some(c => c.class.prototype.process.toString().includes('yesterdayInsights'));
377
461
  const requiresYesterdaySocialPosts = calculationsToRun.some(c => c.class.prototype.process.toString().includes('yesterdaySocialPostInsights'));
@@ -450,13 +534,11 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
450
534
  const calc = state[calcName];
451
535
  if (!calc || typeof calc.getResult !== 'function') continue;
452
536
 
453
- // --- MODIFIED: Get category from the attached manifest ---
454
537
  const category = calc.manifest.category || 'unknown';
455
538
 
456
539
  try {
457
540
  const result = await Promise.resolve(calc.getResult());
458
541
 
459
- // Add to results map for in-memory cache
460
542
  if (result) {
461
543
  passResults[calcName] = result;
462
544
  }
@@ -465,7 +547,6 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
465
547
  const summaryData = {};
466
548
 
467
549
  if (result && Object.keys(result).length > 0) {
468
- // (Special handling for sharded calcs remains the same)
469
550
  let isSharded = false;
470
551
  const shardedCollections = {
471
552
  'sharded_user_profile': config.shardedUserProfileCollection,
@@ -485,7 +566,6 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
485
566
  pendingWrites.push({ ref: shardRef, data: shardedData[shardId] });
486
567
  }
487
568
  }
488
- // Get results *not* in the shard key
489
569
  const { [resultKey]: _, ...otherResults } = result;
490
570
  if (Object.keys(otherResults).length > 0) {
491
571
  const computationDocRef = resultsCollectionRef.doc(category)
@@ -502,7 +582,6 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
502
582
  .doc(calcName);
503
583
  pendingWrites.push({ ref: computationDocRef, data: result });
504
584
  }
505
- // --- END SHARDED HANDLING ---
506
585
 
507
586
  if (!summaryData[category]) summaryData[category] = {};
508
587
  summaryData[category][calcName] = true;
@@ -522,7 +601,15 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
522
601
  successCount++;
523
602
  }
524
603
  } else {
525
- logger.log('WARN', `[${passName}] Calculation ${calcName} produced no results for ${dateStr}. Skipping write to allow backfill.`);
604
+ // --- MODIFICATION: Do not log a warning if the result is null. ---
605
+ // This is now expected behavior if a calc (like user-profitability-tracker)
606
+ // has no data to process.
607
+ // We *do* still need to log if a result is empty.
608
+ if (result === null) {
609
+ logger.log('INFO', `[${passName}] Calculation ${calcName} returned null for ${dateStr}. This is expected if no data was processed.`);
610
+ } else {
611
+ logger.log('WARN', `[${passName}] Calculation ${calcName} produced empty results {} for ${dateStr}. Skipping write.`);
612
+ }
526
613
  }
527
614
  } catch (e) {
528
615
  logger.log('ERROR', `[${passName}] getResult/Commit failed for ${calcName} on ${dateStr}`, { err: e.message });
@@ -532,7 +619,7 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
532
619
  const completionStatus = successCount === calculationsToRun.length ? 'SUCCESS' : 'WARN';
533
620
  logger.log(completionStatus, `[${passName}] Completed ${dateStr}. Success: ${successCount}/${calculationsToRun.length}.`);
534
621
 
535
- return passResults; // --- RETURN THE RESULTS ---
622
+ return passResults;
536
623
 
537
624
  } catch (err) {
538
625
  logger.log('ERROR', `[${passName}] Fatal error for ${dateStr}`, { errorMessage: err.message, stack: err.stack });
@@ -543,39 +630,33 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
543
630
 
544
631
  /**
545
632
  * Internal sub-pipe: Runs "meta" or "backtest" computations for a single date.
546
- * MODIFIED: Accepts in-memory cache and passes dependencies to calcs.
547
- * MODIFIED: Accepts rootData and adds it to the dependencies object.
548
- * Returns a map of results.
549
633
  */
550
634
  async function runMetaComputation(
551
635
  dateToProcess,
552
- calculationsToRun,
636
+ calculationsToRun, // This is the *filtered* list
553
637
  passName,
554
638
  config,
555
639
  dependencies,
556
640
  dailyResultsCache,
557
- rootData // <--- !! MODIFICATION: ACCEPT rootData !!
641
+ rootData
558
642
  ) {
559
643
  const { db, logger } = dependencies;
560
644
  const dateStr = dateToProcess.toISOString().slice(0, 10);
561
645
  logger.log('INFO', `[${passName}] Starting run for ${dateStr} with ${calculationsToRun.length} calcs.`);
562
646
 
563
- const passResults = {}; // Map to store and return results
647
+ const passResults = {};
564
648
 
565
649
  try {
566
650
  const resultsCollectionRef = db.collection(config.resultsCollection).doc(dateStr).collection(config.resultsSubcollection);
567
651
  let successCount = 0;
568
652
 
569
- // --- !! MODIFICATION: Add rootData to the dependencies object !! ---
570
653
  const dependenciesForMetaCalc = {
571
654
  ...dependencies,
572
655
  rootData: rootData
573
656
  };
574
- // --- END MODIFICATION ---
575
657
 
576
658
  for (const manifestCalc of calculationsToRun) {
577
659
  const calcName = normalizeName(manifestCalc.name);
578
- // --- MODIFIED: Get category from manifest ---
579
660
  const category = manifestCalc.category || 'unknown';
580
661
  const CalcClass = manifestCalc.class;
581
662
 
@@ -588,12 +669,17 @@ async function runMetaComputation(
588
669
 
589
670
  try {
590
671
  // --- Gather dependencies from the cache ---
672
+ // This check is now more robust, as the orchestrator has already
673
+ // logged the *reason* for a skip. We just need to check the cache.
591
674
  const computedDependencies = {};
592
675
  let missingDep = false;
593
676
  if (manifestCalc.dependencies) {
594
677
  for (const depName of manifestCalc.dependencies) {
595
678
  const normalizedDepName = normalizeName(depName);
596
679
  if (!dailyResultsCache.has(normalizedDepName)) {
680
+ // This log is still important, as it indicates a logic error
681
+ // if a calc *wasn't* skipped by the orchestrator but its
682
+ // dependency is *still* missing (e.g., the dep returned null).
597
683
  logger.log('ERROR', `[${passName}] Missing required dependency "${normalizedDepName}" for calculation "${calcName}". This should not happen. Skipping calc.`);
598
684
  missingDep = true;
599
685
  break;
@@ -606,15 +692,12 @@ async function runMetaComputation(
606
692
  // --- Call process with the dependencies ---
607
693
  const result = await Promise.resolve(instance.process(
608
694
  dateStr,
609
- dependenciesForMetaCalc, // <-- PASS MODIFIED DEPS
695
+ dependenciesForMetaCalc,
610
696
  config,
611
- computedDependencies // <-- PASS IN-MEMORY DEPS
697
+ computedDependencies
612
698
  ));
613
699
 
614
- // Add to results map
615
- if (result) {
616
- passResults[calcName] = result;
617
- }
700
+ passResults[calcName] = result;
618
701
 
619
702
  const pendingWrites = [];
620
703
  const summaryData = {};
@@ -647,18 +730,17 @@ async function runMetaComputation(
647
730
  }
648
731
  } catch (e) {
649
732
  logger.log('ERROR', `[${passName}] Meta-calc process/commit failed for ${calcName} on ${dateStr}`, { err: e.message, stack: e.stack });
650
- // Don't re-throw, allow other meta-calcs in the pass to run
651
733
  }
652
734
  }
653
735
 
654
736
  const completionStatus = successCount === calculationsToRun.length ? 'SUCCESS' : 'WARN';
655
737
  logger.log(completionStatus, `[${passName}] Completed ${dateStr}. Success: ${successCount}/${calculationsToRun.length}.`);
656
738
 
657
- return passResults; // --- RETURN THE RESULTS ---
739
+ return passResults;
658
740
 
659
741
  } catch (err) {
660
742
  logger.log('ERROR', `[${passName}] Fatal error for ${dateStr}`, { errorMessage: err.message, stack: err.stack });
661
- throw err; // Re-throw to stop the orchestrator for this day
743
+ throw err;
662
744
  }
663
745
  }
664
746
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.108",
3
+ "version": "1.0.110",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [