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
|
|
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')
|
|
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
|
-
|
|
51
|
-
|
|
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
|
|
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;
|
|
143
|
+
if (!standardToRun.length && !metaToRun.length) return null;
|
|
88
144
|
|
|
89
|
-
// 2. Check Root Data Availability
|
|
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 = {};
|
|
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 };
|