bulltrackers-module 1.0.188 → 1.0.189

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.
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * FILENAME: bulltrackers-module/functions/computation-system/helpers/computation_pass_runner.js
3
+ * FIXED: Integrates 'runBatchPriceComputation' to prevent OOM on price calculations.
3
4
  */
4
5
 
5
6
  const {
@@ -10,10 +11,11 @@ const {
10
11
  updateComputationStatus,
11
12
  runStandardComputationPass,
12
13
  runMetaComputationPass,
13
- checkRootDependencies
14
+ checkRootDependencies,
15
+ runBatchPriceComputation // NEW IMPORT
14
16
  } = require('./orchestration_helpers.js');
15
17
 
16
- const { getExpectedDateStrings, normalizeName, getEarliestDataDates } = require('../utils/utils.js');
18
+ const { getExpectedDateStrings, normalizeName } = require('../utils/utils.js');
17
19
 
18
20
  const PARALLEL_BATCH_SIZE = 7;
19
21
 
@@ -31,12 +33,10 @@ async function runComputationPass(config, dependencies, computationManifest) {
31
33
  history: new Date('2025-11-05T00:00:00Z'),
32
34
  social: new Date('2025-10-30T00:00:00Z'),
33
35
  insights: new Date('2025-08-26T00:00:00Z'),
34
- price: new Date('2025-08-01T00:00:00Z') // A few weeks before insights (earliest other data)
35
-
36
+ price: new Date('2025-08-01T00:00:00Z')
36
37
  };
37
38
  earliestDates.absoluteEarliest = Object.values(earliestDates).reduce((a,b) => a < b ? a : b);
38
39
 
39
-
40
40
  const passes = groupByPass(computationManifest);
41
41
  const calcsInThisPass = passes[passToRun] || [];
42
42
 
@@ -47,46 +47,102 @@ async function runComputationPass(config, dependencies, computationManifest) {
47
47
  const endDateUTC = new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() - 1));
48
48
  const allExpectedDates = getExpectedDateStrings(passEarliestDate, endDateUTC);
49
49
 
50
- const standardCalcs = calcsInThisPass.filter(c => c.type === 'standard');
51
- const metaCalcs = calcsInThisPass.filter(c => c.type === 'meta');
50
+ // --- SEPARATION OF CONCERNS ---
51
+ // Identify calculations that require the Optimized Price Batch Runner
52
+ const priceBatchCalcs = calcsInThisPass.filter(c =>
53
+ c.type === 'meta' &&
54
+ c.rootDataDependencies &&
55
+ c.rootDataDependencies.includes('price')
56
+ );
57
+
58
+ // Identify calculations for the Standard Date-Loop Runner
59
+ const standardAndOtherMetaCalcs = calcsInThisPass.filter(c => !priceBatchCalcs.includes(c));
60
+
61
+
62
+ // ========================================================================
63
+ // 1. EXECUTE OPTIMIZED PRICE BATCH (Shard-First)
64
+ // ========================================================================
65
+ if (priceBatchCalcs.length > 0) {
66
+ logger.log('INFO', `[PassRunner] Detected ${priceBatchCalcs.length} Price-Meta calculations. Checking statuses...`);
67
+
68
+ // Filter dates that actually need these calculations
69
+ // We do a quick serial check of status docs to avoid re-running satisfied dates
70
+ const datesNeedingPriceCalc = [];
71
+
72
+ // Check statuses in chunks to avoid blowing up IO
73
+ const STATUS_CHECK_CHUNK = 20;
74
+ for (let i = 0; i < allExpectedDates.length; i += STATUS_CHECK_CHUNK) {
75
+ const dateChunk = allExpectedDates.slice(i, i + STATUS_CHECK_CHUNK);
76
+ await Promise.all(dateChunk.map(async (dateStr) => {
77
+ const status = await fetchComputationStatus(dateStr, config, dependencies);
78
+ // If ANY of the price calcs are missing/false, we run the batch for this date
79
+ const needsRun = priceBatchCalcs.some(c => status[normalizeName(c.name)] !== true);
80
+ if (needsRun) datesNeedingPriceCalc.push(dateStr);
81
+ }));
82
+ }
83
+
84
+ if (datesNeedingPriceCalc.length > 0) {
85
+ logger.log('INFO', `[PassRunner] >>> Starting Optimized Batch for ${datesNeedingPriceCalc.length} dates <<<`);
86
+
87
+ // Execute the Shard-First Logic
88
+ await runBatchPriceComputation(config, dependencies, datesNeedingPriceCalc, priceBatchCalcs);
89
+
90
+ // Manually update statuses for these dates/calcs upon completion
91
+ // (runBatchPriceComputation handles the results, but we must mark the status doc)
92
+ logger.log('INFO', `[PassRunner] Updating status documents for batch...`);
93
+
94
+ const BATCH_UPDATE_SIZE = 50;
95
+ for (let i = 0; i < datesNeedingPriceCalc.length; i += BATCH_UPDATE_SIZE) {
96
+ const updateChunk = datesNeedingPriceCalc.slice(i, i + BATCH_UPDATE_SIZE);
97
+ await Promise.all(updateChunk.map(async (dateStr) => {
98
+ const updates = {};
99
+ priceBatchCalcs.forEach(c => updates[normalizeName(c.name)] = true);
100
+ await updateComputationStatus(dateStr, updates, config, dependencies);
101
+ }));
102
+ }
103
+ logger.log('INFO', `[PassRunner] >>> Optimized Batch Complete <<<`);
104
+ } else {
105
+ logger.log('INFO', `[PassRunner] All Price-Meta calculations are up to date.`);
106
+ }
107
+ }
108
+
109
+
110
+ // ========================================================================
111
+ // 2. EXECUTE STANDARD DATE LOOP (Date-First)
112
+ // ========================================================================
113
+ if (standardAndOtherMetaCalcs.length === 0) {
114
+ logger.log('INFO', `[PassRunner] No other calculations remaining. Exiting.`);
115
+ return;
116
+ }
117
+
118
+ const standardCalcs = standardAndOtherMetaCalcs.filter(c => c.type === 'standard');
119
+ const metaCalcs = standardAndOtherMetaCalcs.filter(c => c.type === 'meta');
52
120
 
53
121
  // Process a single date
54
122
  const processDate = async (dateStr) => {
55
123
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
56
124
 
57
125
  // 1. Fetch Status for THIS specific date only
58
- // This ensures Pass 2 sees exactly what Pass 1 wrote for this date.
59
126
  const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
60
127
 
61
- // Helper: Check status using the fetched daily data
128
+ // Helper: Check status
62
129
  const shouldRun = (calc) => {
63
130
  const cName = normalizeName(calc.name);
64
-
65
- // A. If recorded as TRUE -> Ignore (already ran)
66
131
  if (dailyStatus[cName] === true) return false;
67
-
68
- // B. If recorded as FALSE or UNDEFINED -> Run it (retry or new)
69
- // But first, check if we have the necessary data dependencies.
70
132
 
71
133
  if (calc.dependencies && calc.dependencies.length > 0) {
72
- // Check if prerequisites (from previous passes on THIS date) are complete
73
134
  const missing = calc.dependencies.filter(depName => dailyStatus[normalizeName(depName)] !== true);
74
- if (missing.length > 0) {
75
- // Dependency missing: cannot run yet.
76
- return false;
77
- }
135
+ if (missing.length > 0) return false;
78
136
  }
79
-
80
- // If we are here, status is false/undefined AND dependencies are met.
81
137
  return true;
82
138
  };
83
139
 
84
140
  const standardToRun = standardCalcs.filter(shouldRun);
85
141
  const metaToRun = metaCalcs.filter(shouldRun);
86
142
 
87
- if (!standardToRun.length && !metaToRun.length) return null; // No work for this date
143
+ if (!standardToRun.length && !metaToRun.length) return null;
88
144
 
89
- // 2. Check Root Data Availability (Portfolio, History, etc.)
145
+ // 2. Check Root Data Availability
90
146
  const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
91
147
  if (!rootData) return null;
92
148
 
@@ -98,7 +154,7 @@ async function runComputationPass(config, dependencies, computationManifest) {
98
154
 
99
155
  logger.log('INFO', `[PassRunner] Running ${dateStr}: ${finalStandardToRun.length} std, ${finalMetaToRun.length} meta`);
100
156
 
101
- const dateUpdates = {}; // { calcName: true/false }
157
+ const dateUpdates = {};
102
158
 
103
159
  try {
104
160
  const calcsRunning = [...finalStandardToRun, ...finalMetaToRun];
@@ -107,7 +163,6 @@ async function runComputationPass(config, dependencies, computationManifest) {
107
163
  const prevDateStr = prevDate.toISOString().slice(0, 10);
108
164
  const previousResults = await fetchExistingResults(prevDateStr, calcsRunning, computationManifest, config, dependencies, true);
109
165
 
110
- // Note: We use skipStatusWrite=true because we want to batch write the status at the end of this function
111
166
  if (finalStandardToRun.length) {
112
167
  const updates = await runStandardComputationPass(dateToProcess, finalStandardToRun, `Pass ${passToRun} (Std)`, config, dependencies, rootData, existingResults, previousResults, true);
113
168
  Object.assign(dateUpdates, updates);
@@ -121,7 +176,6 @@ async function runComputationPass(config, dependencies, computationManifest) {
121
176
  [...finalStandardToRun, ...finalMetaToRun].forEach(c => dateUpdates[normalizeName(c.name)] = false);
122
177
  }
123
178
 
124
- // 4. Write "true" or "false" results for THIS specific date immediately
125
179
  if (Object.keys(dateUpdates).length > 0) {
126
180
  await updateComputationStatus(dateStr, dateUpdates, config, dependencies);
127
181
  }
@@ -138,4 +192,4 @@ async function runComputationPass(config, dependencies, computationManifest) {
138
192
  logger.log('INFO', `[PassRunner] Pass ${passToRun} orchestration finished.`);
139
193
  }
140
194
 
141
- module.exports = { runComputationPass };
195
+ module.exports = { runComputationPass };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.188",
3
+ "version": "1.0.189",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [