bulltrackers-module 1.0.211 → 1.0.213

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,158 +1,169 @@
1
- /**
2
- * FILENAME: bulltrackers-module/functions/computation-system/helpers/computation_pass_runner.js
3
- * FIXED: 'runDateComputation' now executes ALL calculation types (Standard, Meta, AND Price).
4
- */
5
-
6
- const {
7
- groupByPass,
8
- checkRootDataAvailability,
9
- fetchExistingResults,
10
- fetchComputationStatus,
11
- updateComputationStatus,
12
- runStandardComputationPass,
13
- runMetaComputationPass,
14
- checkRootDependencies,
15
- runBatchPriceComputation
16
- } = require('./orchestration_helpers.js');
17
-
18
- const { getExpectedDateStrings, normalizeName } = require('../utils/utils.js');
19
-
20
- const PARALLEL_BATCH_SIZE = 7;
21
-
22
- /**
23
- * LEGACY / MANUAL RUNNER
24
- * (Kept for backward compatibility if you run the old HTTP endpoint directly)
25
- */
26
- async function runComputationPass(config, dependencies, computationManifest) {
27
- const { logger } = dependencies;
28
- const passToRun = String(config.COMPUTATION_PASS_TO_RUN);
29
- if (!passToRun) return logger.log('ERROR', '[PassRunner] No pass defined. Aborting.');
30
-
31
- logger.log('INFO', `🚀 Starting PASS ${passToRun} (Legacy Mode)...`);
32
-
33
- // Hardcoded earliest dates
34
- const earliestDates = {
35
- portfolio: new Date('2025-09-25T00:00:00Z'),
36
- history: new Date('2025-11-05T00:00:00Z'),
37
- social: new Date('2025-10-30T00:00:00Z'),
38
- insights: new Date('2025-08-26T00:00:00Z'),
39
- price: new Date('2025-08-01T00:00:00Z')
40
- };
41
- earliestDates.absoluteEarliest = Object.values(earliestDates).reduce((a, b) => a < b ? a : b);
42
-
43
- const passes = groupByPass(computationManifest);
44
- const calcsInThisPass = passes[passToRun] || [];
45
-
46
- if (!calcsInThisPass.length)
47
- return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
48
-
49
- const passEarliestDate = earliestDates.absoluteEarliest;
50
- const endDateUTC = new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() - 1));
51
- const allExpectedDates = getExpectedDateStrings(passEarliestDate, endDateUTC);
52
-
53
- // Legacy Batch Optimization for Price (Only used in legacy loop)
54
- const priceBatchCalcs = calcsInThisPass.filter(c => c.type === 'meta' && c.rootDataDependencies?.includes('price'));
55
- const standardAndOtherMetaCalcs = calcsInThisPass.filter(c => !priceBatchCalcs.includes(c));
56
-
57
- if (priceBatchCalcs.length > 0) {
58
- try {
59
- await runBatchPriceComputation(config, dependencies, allExpectedDates, priceBatchCalcs); // Simplified for legacy
60
- } catch (e) { logger.log('ERROR', 'Legacy Batch Price failed', e); }
61
- }
62
-
63
- if (standardAndOtherMetaCalcs.length === 0) return;
64
-
65
- for (let i = 0; i < allExpectedDates.length; i += PARALLEL_BATCH_SIZE) {
66
- const batch = allExpectedDates.slice(i, i + PARALLEL_BATCH_SIZE);
67
- await Promise.all(batch.map(dateStr => runDateComputation(dateStr, passToRun, standardAndOtherMetaCalcs, config, dependencies, computationManifest)));
68
- }
69
- }
70
-
71
- /**
72
- * UPDATED: Isolated function to run computations for a single date.
73
- * Used by the Pub/Sub Worker.
74
- */
75
- async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, dependencies, computationManifest) {
76
- const { logger } = dependencies;
77
- const dateToProcess = new Date(dateStr + 'T00:00:00Z');
78
-
79
- // 1. Fetch Status for THIS specific date only
80
- const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
81
-
82
- // Helper: Check status
83
- const shouldRun = (calc) => {
84
- const cName = normalizeName(calc.name);
85
- if (dailyStatus[cName] === true) return false;
86
- if (calc.dependencies && calc.dependencies.length > 0) {
87
- const missing = calc.dependencies.filter(depName => dailyStatus[normalizeName(depName)] !== true);
88
- if (missing.length > 0) return false;
89
- }
90
- return true;
91
- };
92
-
93
- // --- FIX: Run ALL calc types (Standard, Meta, Price) ---
94
- const calcsToAttempt = calcsInThisPass.filter(shouldRun);
95
-
96
- if (!calcsToAttempt.length) return null;
97
-
98
- // 2. Check Root Data Availability
99
- const earliestDates = {
100
- portfolio: new Date('2025-09-25T00:00:00Z'),
101
- history: new Date('2025-11-05T00:00:00Z'),
102
- social: new Date('2025-10-30T00:00:00Z'),
103
- insights: new Date('2025-08-26T00:00:00Z'),
104
- price: new Date('2025-08-01T00:00:00Z')
105
- };
106
-
107
- const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
108
- if (!rootData) {
109
- logger.log('INFO', `[DateRunner] Root data missing for ${dateStr}. Skipping.`);
110
- return null;
111
- }
112
-
113
- // 3. Filter again based on Root Data availability
114
- const runnableCalcs = calcsToAttempt.filter(c => checkRootDependencies(c, rootData.status).canRun);
115
-
116
- if (!runnableCalcs.length) return null;
117
-
118
- // Split into Standard (Streaming) and Meta (Once-Per-Day/Price)
119
- const standardToRun = runnableCalcs.filter(c => c.type === 'standard');
120
- // Note: Meta includes Price calcs in this flow
121
- const metaToRun = runnableCalcs.filter(c => c.type === 'meta');
122
-
123
- logger.log('INFO', `[DateRunner] Running ${dateStr}: ${standardToRun.length} std, ${metaToRun.length} meta`);
124
-
125
- const dateUpdates = {};
126
-
127
- try {
128
- const calcsRunning = [...standardToRun, ...metaToRun];
129
-
130
- // Fetch dependencies (results from this day or yesterday)
131
- const existingResults = await fetchExistingResults(dateStr, calcsRunning, computationManifest, config, dependencies, false);
132
- const prevDate = new Date(dateToProcess); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
133
- const prevDateStr = prevDate.toISOString().slice(0, 10);
134
- const previousResults = await fetchExistingResults(prevDateStr, calcsRunning, computationManifest, config, dependencies, true);
135
-
136
- if (standardToRun.length) {
137
- const updates = await runStandardComputationPass(dateToProcess, standardToRun, `Pass ${passToRun} (Std)`, config, dependencies, rootData, existingResults, previousResults, false);
138
- Object.assign(dateUpdates, updates);
139
- }
140
- if (metaToRun.length) {
141
- // runMetaComputationPass uses the Controller, which handles Price Sharding logic internally for single dates.
142
- const updates = await runMetaComputationPass(dateToProcess, metaToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, previousResults, rootData, false);
143
- Object.assign(dateUpdates, updates);
144
- }
145
- } catch (err) {
146
- logger.log('ERROR', `[DateRunner] FAILED Pass ${passToRun} for ${dateStr}`, { errorMessage: err.message });
147
- [...standardToRun, ...metaToRun].forEach(c => dateUpdates[normalizeName(c.name)] = false);
148
- throw err; // Re-throw to trigger Pub/Sub retry
149
- }
150
-
151
- if (Object.keys(dateUpdates).length > 0) {
152
- await updateComputationStatus(dateStr, dateUpdates, config, dependencies);
153
- }
154
-
155
- return { date: dateStr, updates: dateUpdates };
156
- }
157
-
1
+ /**
2
+ * FILENAME: bulltrackers-module/functions/computation-system/helpers/computation_pass_runner.js
3
+ * FIXED: 'storedStatus.substring' crash and 'missing dependency' log clarity.
4
+ */
5
+
6
+ const {
7
+ groupByPass,
8
+ checkRootDataAvailability,
9
+ fetchExistingResults,
10
+ fetchComputationStatus,
11
+ updateComputationStatus,
12
+ runStandardComputationPass,
13
+ runMetaComputationPass,
14
+ checkRootDependencies,
15
+ runBatchPriceComputation
16
+ } = require('./orchestration_helpers.js');
17
+
18
+ const { getExpectedDateStrings, normalizeName } = require('../utils/utils.js');
19
+
20
+ const PARALLEL_BATCH_SIZE = 7;
21
+
22
+ async function runComputationPass(config, dependencies, computationManifest) {
23
+ const { logger } = dependencies;
24
+ const passToRun = String(config.COMPUTATION_PASS_TO_RUN);
25
+ if (!passToRun) return logger.log('ERROR', '[PassRunner] No pass defined. Aborting.');
26
+
27
+ logger.log('INFO', `🚀 Starting PASS ${passToRun} (Legacy Mode)...`);
28
+
29
+ const earliestDates = {
30
+ portfolio: new Date('2025-09-25T00:00:00Z'),
31
+ history: new Date('2025-11-05T00:00:00Z'),
32
+ social: new Date('2025-10-30T00:00:00Z'),
33
+ insights: new Date('2025-08-26T00:00:00Z'),
34
+ price: new Date('2025-08-01T00:00:00Z')
35
+ };
36
+ earliestDates.absoluteEarliest = Object.values(earliestDates).reduce((a, b) => a < b ? a : b);
37
+
38
+ const passes = groupByPass(computationManifest);
39
+ const calcsInThisPass = passes[passToRun] || [];
40
+
41
+ if (!calcsInThisPass.length)
42
+ return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
43
+
44
+ const passEarliestDate = earliestDates.absoluteEarliest;
45
+ const endDateUTC = new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() - 1));
46
+ const allExpectedDates = getExpectedDateStrings(passEarliestDate, endDateUTC);
47
+
48
+ const priceBatchCalcs = calcsInThisPass.filter(c => c.type === 'meta' && c.rootDataDependencies?.includes('price'));
49
+ const standardAndOtherMetaCalcs = calcsInThisPass.filter(c => !priceBatchCalcs.includes(c));
50
+
51
+ if (priceBatchCalcs.length > 0) {
52
+ try {
53
+ await runBatchPriceComputation(config, dependencies, allExpectedDates, priceBatchCalcs);
54
+ } catch (e) { logger.log('ERROR', 'Legacy Batch Price failed', e); }
55
+ }
56
+
57
+ if (standardAndOtherMetaCalcs.length === 0) return;
58
+
59
+ for (let i = 0; i < allExpectedDates.length; i += PARALLEL_BATCH_SIZE) {
60
+ const batch = allExpectedDates.slice(i, i + PARALLEL_BATCH_SIZE);
61
+ await Promise.all(batch.map(dateStr => runDateComputation(dateStr, passToRun, standardAndOtherMetaCalcs, config, dependencies, computationManifest)));
62
+ }
63
+ }
64
+
65
+ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, dependencies, computationManifest) {
66
+ const { logger } = dependencies;
67
+ const dateToProcess = new Date(dateStr + 'T00:00:00Z');
68
+
69
+ const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
70
+
71
+ // Filter AND Log reason for skipping
72
+ const calcsToAttempt = [];
73
+
74
+ for (const calc of calcsInThisPass) {
75
+ const cName = normalizeName(calc.name);
76
+ const storedStatus = dailyStatus[cName];
77
+ const currentHash = calc.hash;
78
+
79
+ // 1. Dependency Check
80
+ if (calc.dependencies && calc.dependencies.length > 0) {
81
+ const missing = calc.dependencies.filter(depName => !dailyStatus[normalizeName(depName)]);
82
+ if (missing.length > 0) {
83
+ // Too noisy to log every skip, but useful for debugging if needed.
84
+ // Only logging if it's NOT a bulk skip.
85
+ // logger.log('TRACE', `[Skip] ${cName} missing deps: ${missing.join(', ')}`);
86
+ continue;
87
+ }
88
+ }
89
+
90
+ // 2. Logic A: No previous run
91
+ if (!storedStatus) {
92
+ logger.log('INFO', `[Versioning] ${cName}: New run needed (No prior status).`);
93
+ calcsToAttempt.push(calc);
94
+ continue;
95
+ }
96
+
97
+ // 3. Logic B: Hash Mismatch
98
+ // FIX: Ensure storedStatus is a string before calling substring
99
+ if (typeof storedStatus === 'string' && currentHash && storedStatus !== currentHash) {
100
+ logger.log('INFO', `[Versioning] ${cName}: Code Changed. (Old: ${storedStatus.substring(0,6)}... New: ${currentHash.substring(0,6)}...)`);
101
+ calcsToAttempt.push(calc);
102
+ continue;
103
+ }
104
+
105
+ // 4. Logic C: Upgrade Legacy Boolean -> Hash
106
+ if (storedStatus === true && currentHash) {
107
+ logger.log('INFO', `[Versioning] ${cName}: Upgrading legacy status to Hash.`);
108
+ calcsToAttempt.push(calc);
109
+ continue;
110
+ }
111
+ }
112
+
113
+ if (!calcsToAttempt.length) return null;
114
+
115
+ const earliestDates = {
116
+ portfolio: new Date('2025-09-25T00:00:00Z'),
117
+ history: new Date('2025-11-05T00:00:00Z'),
118
+ social: new Date('2025-10-30T00:00:00Z'),
119
+ insights: new Date('2025-08-26T00:00:00Z'),
120
+ price: new Date('2025-08-01T00:00:00Z')
121
+ };
122
+
123
+ const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
124
+ if (!rootData) {
125
+ logger.log('INFO', `[DateRunner] Root data missing for ${dateStr}. Skipping.`);
126
+ return null;
127
+ }
128
+
129
+ const runnableCalcs = calcsToAttempt.filter(c => checkRootDependencies(c, rootData.status).canRun);
130
+
131
+ if (!runnableCalcs.length) return null;
132
+
133
+ const standardToRun = runnableCalcs.filter(c => c.type === 'standard');
134
+ const metaToRun = runnableCalcs.filter(c => c.type === 'meta');
135
+
136
+ logger.log('INFO', `[DateRunner] Running ${dateStr}: ${standardToRun.length} std, ${metaToRun.length} meta`);
137
+
138
+ const dateUpdates = {};
139
+
140
+ try {
141
+ const calcsRunning = [...standardToRun, ...metaToRun];
142
+
143
+ const existingResults = await fetchExistingResults(dateStr, calcsRunning, computationManifest, config, dependencies, false);
144
+ const prevDate = new Date(dateToProcess); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
145
+ const prevDateStr = prevDate.toISOString().slice(0, 10);
146
+ const previousResults = await fetchExistingResults(prevDateStr, calcsRunning, computationManifest, config, dependencies, true);
147
+
148
+ if (standardToRun.length) {
149
+ const updates = await runStandardComputationPass(dateToProcess, standardToRun, `Pass ${passToRun} (Std)`, config, dependencies, rootData, existingResults, previousResults, false);
150
+ Object.assign(dateUpdates, updates);
151
+ }
152
+ if (metaToRun.length) {
153
+ const updates = await runMetaComputationPass(dateToProcess, metaToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, previousResults, rootData, false);
154
+ Object.assign(dateUpdates, updates);
155
+ }
156
+ } catch (err) {
157
+ logger.log('ERROR', `[DateRunner] FAILED Pass ${passToRun} for ${dateStr}`, { errorMessage: err.message });
158
+ [...standardToRun, ...metaToRun].forEach(c => dateUpdates[normalizeName(c.name)] = false);
159
+ throw err;
160
+ }
161
+
162
+ if (Object.keys(dateUpdates).length > 0) {
163
+ await updateComputationStatus(dateStr, dateUpdates, config, dependencies);
164
+ }
165
+
166
+ return { date: dateStr, updates: dateUpdates };
167
+ }
168
+
158
169
  module.exports = { runComputationPass, runDateComputation };
@@ -1,86 +1,86 @@
1
- /**
2
- * FILENAME: bulltrackers-module/functions/computation-system/helpers/computation_worker.js
3
- * PURPOSE: Consumes computation tasks from Pub/Sub and executes them.
4
- * FIXED: Added robust payload parsing to handle Cloud Functions Gen 2 (CloudEvents).
5
- */
6
-
7
- const { runDateComputation } = require('./computation_pass_runner.js');
8
- const { groupByPass } = require('./orchestration_helpers.js');
9
-
10
- /**
11
- * Handles a single Pub/Sub message for a computation task.
12
- * Supports both Gen 1 (Message) and Gen 2 (CloudEvent) formats.
13
- */
14
- async function handleComputationTask(message, config, dependencies, computationManifest) {
15
- const { logger } = dependencies;
16
-
17
- let data;
18
- try {
19
- // 1. Handle Cloud Functions Gen 2 (CloudEvent)
20
- // Structure: event.data.message.data (base64)
21
- if (message.data && message.data.message && message.data.message.data) {
22
- const buffer = Buffer.from(message.data.message.data, 'base64');
23
- data = JSON.parse(buffer.toString());
24
- }
25
- // 2. Handle Cloud Functions Gen 1 / Legacy PubSub
26
- // Structure: message.data (base64) or message.json
27
- else if (message.data && typeof message.data === 'string') {
28
- const buffer = Buffer.from(message.data, 'base64');
29
- data = JSON.parse(buffer.toString());
30
- }
31
- // 3. Handle Direct JSON (Test harness or simulator)
32
- else if (message.json) {
33
- data = message.json;
34
- }
35
- // 4. Fallback: Assume message is the payload
36
- else {
37
- data = message;
38
- }
39
- } catch (parseError) {
40
- logger.log('ERROR', `[Worker] Failed to parse Pub/Sub payload.`, { error: parseError.message });
41
- return;
42
- }
43
-
44
- try {
45
- // Validate Action
46
- if (!data || data.action !== 'RUN_COMPUTATION_DATE') {
47
- // Only log if data exists but action is wrong, prevents log spam on empty messages
48
- if (data) logger.log('WARN', `[Worker] Unknown or missing action: ${data?.action}. Ignoring.`);
49
- return;
50
- }
51
-
52
- const { date, pass } = data;
53
-
54
- if (!date || !pass) {
55
- logger.log('ERROR', `[Worker] Missing date or pass in payload: ${JSON.stringify(data)}`);
56
- return;
57
- }
58
-
59
- logger.log('INFO', `[Worker] Received task: Date=${date}, Pass=${pass}`);
60
-
61
- // Resolve calculations for this pass
62
- const passes = groupByPass(computationManifest);
63
- const calcsInThisPass = passes[pass] || [];
64
-
65
- if (!calcsInThisPass.length) {
66
- logger.log('WARN', `[Worker] No calculations found for Pass ${pass}.`);
67
- return;
68
- }
69
-
70
- // Execute the computation for this specific date
71
- // The runner internally checks dependencies (Pass 1, 2, 3 status) and skips if not ready.
72
- const result = await runDateComputation(date, pass, calcsInThisPass, config, dependencies, computationManifest);
73
-
74
- if (result) {
75
- logger.log('INFO', `[Worker] Successfully processed ${date} (Pass ${pass}). Updates: ${Object.keys(result.updates || {}).length}`);
76
- } else {
77
- logger.log('INFO', `[Worker] Processed ${date} (Pass ${pass}) - Skipped (Dependencies missing or already done).`);
78
- }
79
-
80
- } catch (err) {
81
- logger.log('ERROR', `[Worker] Fatal error processing task: ${err.message}`, { stack: err.stack });
82
- throw err; // Re-throw to trigger Pub/Sub retry
83
- }
84
- }
85
-
1
+ /**
2
+ * FILENAME: bulltrackers-module/functions/computation-system/helpers/computation_worker.js
3
+ * PURPOSE: Consumes computation tasks from Pub/Sub and executes them.
4
+ * FIXED: Added robust payload parsing to handle Cloud Functions Gen 2 (CloudEvents).
5
+ */
6
+
7
+ const { runDateComputation } = require('./computation_pass_runner.js');
8
+ const { groupByPass } = require('./orchestration_helpers.js');
9
+
10
+ /**
11
+ * Handles a single Pub/Sub message for a computation task.
12
+ * Supports both Gen 1 (Message) and Gen 2 (CloudEvent) formats.
13
+ */
14
+ async function handleComputationTask(message, config, dependencies, computationManifest) {
15
+ const { logger } = dependencies;
16
+
17
+ let data;
18
+ try {
19
+ // 1. Handle Cloud Functions Gen 2 (CloudEvent)
20
+ // Structure: event.data.message.data (base64)
21
+ if (message.data && message.data.message && message.data.message.data) {
22
+ const buffer = Buffer.from(message.data.message.data, 'base64');
23
+ data = JSON.parse(buffer.toString());
24
+ }
25
+ // 2. Handle Cloud Functions Gen 1 / Legacy PubSub
26
+ // Structure: message.data (base64) or message.json
27
+ else if (message.data && typeof message.data === 'string') {
28
+ const buffer = Buffer.from(message.data, 'base64');
29
+ data = JSON.parse(buffer.toString());
30
+ }
31
+ // 3. Handle Direct JSON (Test harness or simulator)
32
+ else if (message.json) {
33
+ data = message.json;
34
+ }
35
+ // 4. Fallback: Assume message is the payload
36
+ else {
37
+ data = message;
38
+ }
39
+ } catch (parseError) {
40
+ logger.log('ERROR', `[Worker] Failed to parse Pub/Sub payload.`, { error: parseError.message });
41
+ return;
42
+ }
43
+
44
+ try {
45
+ // Validate Action
46
+ if (!data || data.action !== 'RUN_COMPUTATION_DATE') {
47
+ // Only log if data exists but action is wrong, prevents log spam on empty messages
48
+ if (data) logger.log('WARN', `[Worker] Unknown or missing action: ${data?.action}. Ignoring.`);
49
+ return;
50
+ }
51
+
52
+ const { date, pass } = data;
53
+
54
+ if (!date || !pass) {
55
+ logger.log('ERROR', `[Worker] Missing date or pass in payload: ${JSON.stringify(data)}`);
56
+ return;
57
+ }
58
+
59
+ logger.log('INFO', `[Worker] Received task: Date=${date}, Pass=${pass}`);
60
+
61
+ // Resolve calculations for this pass
62
+ const passes = groupByPass(computationManifest);
63
+ const calcsInThisPass = passes[pass] || [];
64
+
65
+ if (!calcsInThisPass.length) {
66
+ logger.log('WARN', `[Worker] No calculations found for Pass ${pass}.`);
67
+ return;
68
+ }
69
+
70
+ // Execute the computation for this specific date
71
+ // The runner internally checks dependencies (Pass 1, 2, 3 status) and skips if not ready.
72
+ const result = await runDateComputation(date, pass, calcsInThisPass, config, dependencies, computationManifest);
73
+
74
+ if (result) {
75
+ logger.log('INFO', `[Worker] Successfully processed ${date} (Pass ${pass}). Updates: ${Object.keys(result.updates || {}).length}`);
76
+ } else {
77
+ logger.log('INFO', `[Worker] Processed ${date} (Pass ${pass}) - Skipped (Dependencies missing or already done).`);
78
+ }
79
+
80
+ } catch (err) {
81
+ logger.log('ERROR', `[Worker] Fatal error processing task: ${err.message}`, { stack: err.stack });
82
+ throw err; // Re-throw to trigger Pub/Sub retry
83
+ }
84
+ }
85
+
86
86
  module.exports = { handleComputationTask };