bulltrackers-module 1.0.211 → 1.0.212

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,186 @@
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: 'runDateComputation' now executes ALL calculation types (Standard, Meta, AND Price).
4
+ * UPDATED: Uses Code Hash for smart versioning and invalidation.
5
+ */
6
+
7
+ const {
8
+ groupByPass,
9
+ checkRootDataAvailability,
10
+ fetchExistingResults,
11
+ fetchComputationStatus,
12
+ updateComputationStatus,
13
+ runStandardComputationPass,
14
+ runMetaComputationPass,
15
+ checkRootDependencies,
16
+ runBatchPriceComputation
17
+ } = require('./orchestration_helpers.js');
18
+
19
+ const { getExpectedDateStrings, normalizeName } = require('../utils/utils.js');
20
+
21
+ const PARALLEL_BATCH_SIZE = 7;
22
+
23
+ /**
24
+ * LEGACY / MANUAL RUNNER
25
+ * (Kept for backward compatibility if you run the old HTTP endpoint directly)
26
+ */
27
+ async function runComputationPass(config, dependencies, computationManifest) {
28
+ const { logger } = dependencies;
29
+ const passToRun = String(config.COMPUTATION_PASS_TO_RUN);
30
+ if (!passToRun) return logger.log('ERROR', '[PassRunner] No pass defined. Aborting.');
31
+
32
+ logger.log('INFO', `🚀 Starting PASS ${passToRun} (Legacy Mode)...`);
33
+
34
+ // Hardcoded earliest dates
35
+ const earliestDates = {
36
+ portfolio: new Date('2025-09-25T00:00:00Z'),
37
+ history: new Date('2025-11-05T00:00:00Z'),
38
+ social: new Date('2025-10-30T00:00:00Z'),
39
+ insights: new Date('2025-08-26T00:00:00Z'),
40
+ price: new Date('2025-08-01T00:00:00Z')
41
+ };
42
+ earliestDates.absoluteEarliest = Object.values(earliestDates).reduce((a, b) => a < b ? a : b);
43
+
44
+ const passes = groupByPass(computationManifest);
45
+ const calcsInThisPass = passes[passToRun] || [];
46
+
47
+ if (!calcsInThisPass.length)
48
+ return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
49
+
50
+ const passEarliestDate = earliestDates.absoluteEarliest;
51
+ const endDateUTC = new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() - 1));
52
+ const allExpectedDates = getExpectedDateStrings(passEarliestDate, endDateUTC);
53
+
54
+ // Legacy Batch Optimization for Price (Only used in legacy loop)
55
+ const priceBatchCalcs = calcsInThisPass.filter(c => c.type === 'meta' && c.rootDataDependencies?.includes('price'));
56
+ const standardAndOtherMetaCalcs = calcsInThisPass.filter(c => !priceBatchCalcs.includes(c));
57
+
58
+ if (priceBatchCalcs.length > 0) {
59
+ try {
60
+ await runBatchPriceComputation(config, dependencies, allExpectedDates, priceBatchCalcs); // Simplified for legacy
61
+ } catch (e) { logger.log('ERROR', 'Legacy Batch Price failed', e); }
62
+ }
63
+
64
+ if (standardAndOtherMetaCalcs.length === 0) return;
65
+
66
+ for (let i = 0; i < allExpectedDates.length; i += PARALLEL_BATCH_SIZE) {
67
+ const batch = allExpectedDates.slice(i, i + PARALLEL_BATCH_SIZE);
68
+ await Promise.all(batch.map(dateStr => runDateComputation(dateStr, passToRun, standardAndOtherMetaCalcs, config, dependencies, computationManifest)));
69
+ }
70
+ }
71
+
72
+ /**
73
+ * UPDATED: Isolated function to run computations for a single date.
74
+ * Uses Code Hash to determine if a re-run is necessary.
75
+ */
76
+ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, dependencies, computationManifest) {
77
+ const { logger } = dependencies;
78
+ const dateToProcess = new Date(dateStr + 'T00:00:00Z');
79
+
80
+ // 1. Fetch Status for THIS specific date only
81
+ const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
82
+
83
+ // Helper: Check status using HASH comparison
84
+ const shouldRun = (calc) => {
85
+ const cName = normalizeName(calc.name);
86
+ const storedStatus = dailyStatus[cName];
87
+ const currentHash = calc.hash;
88
+
89
+ // If dependency logic is needed, check dependencies are 'complete'
90
+ // 'Complete' means truthy (hash or true)
91
+ if (calc.dependencies && calc.dependencies.length > 0) {
92
+ const missing = calc.dependencies.filter(depName => {
93
+ const depStatus = dailyStatus[normalizeName(depName)];
94
+ return !depStatus; // Run if dependency is missing entirely
95
+ });
96
+ if (missing.length > 0) return false; // Wait for dependency
97
+ }
98
+
99
+ // Logic A: No previous run
100
+ if (!storedStatus) {
101
+ logger.log('INFO', `[Versioning] ${cName}: New run needed (No prior status).`);
102
+ return true;
103
+ }
104
+
105
+ // Logic B: Hash Mismatch (Code Changed or Layer Changed)
106
+ if (currentHash && storedStatus !== currentHash) {
107
+ logger.log('INFO', `[Versioning] ${cName}: Code/Layer Changed. Re-running. (Old: ${storedStatus.substring(0,6)}... New: ${currentHash.substring(0,6)}...)`);
108
+ return true;
109
+ }
110
+
111
+ // Logic C: Legacy boolean check (Stored=true) vs New Hash
112
+ // If stored is strictly boolean true, but we have a hash, we upgrade (re-run) to stamp the hash.
113
+ if (storedStatus === true && currentHash) {
114
+ logger.log('INFO', `[Versioning] ${cName}: Upgrading legacy status to Hash. Re-running.`);
115
+ return true;
116
+ }
117
+
118
+ return false; // Skip
119
+ };
120
+
121
+ // --- FIX: Run ALL calc types (Standard, Meta, Price) ---
122
+ const calcsToAttempt = calcsInThisPass.filter(shouldRun);
123
+
124
+ if (!calcsToAttempt.length) return null;
125
+
126
+ // 2. Check Root Data Availability
127
+ const earliestDates = {
128
+ portfolio: new Date('2025-09-25T00:00:00Z'),
129
+ history: new Date('2025-11-05T00:00:00Z'),
130
+ social: new Date('2025-10-30T00:00:00Z'),
131
+ insights: new Date('2025-08-26T00:00:00Z'),
132
+ price: new Date('2025-08-01T00:00:00Z')
133
+ };
134
+
135
+ const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
136
+ if (!rootData) {
137
+ logger.log('INFO', `[DateRunner] Root data missing for ${dateStr}. Skipping.`);
138
+ return null;
139
+ }
140
+
141
+ // 3. Filter again based on Root Data availability
142
+ const runnableCalcs = calcsToAttempt.filter(c => checkRootDependencies(c, rootData.status).canRun);
143
+
144
+ if (!runnableCalcs.length) return null;
145
+
146
+ // Split into Standard (Streaming) and Meta (Once-Per-Day/Price)
147
+ const standardToRun = runnableCalcs.filter(c => c.type === 'standard');
148
+ // Note: Meta includes Price calcs in this flow
149
+ const metaToRun = runnableCalcs.filter(c => c.type === 'meta');
150
+
151
+ logger.log('INFO', `[DateRunner] Running ${dateStr}: ${standardToRun.length} std, ${metaToRun.length} meta`);
152
+
153
+ const dateUpdates = {};
154
+
155
+ try {
156
+ const calcsRunning = [...standardToRun, ...metaToRun];
157
+
158
+ // Fetch dependencies (results from this day or yesterday)
159
+ const existingResults = await fetchExistingResults(dateStr, calcsRunning, computationManifest, config, dependencies, false);
160
+ const prevDate = new Date(dateToProcess); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
161
+ const prevDateStr = prevDate.toISOString().slice(0, 10);
162
+ const previousResults = await fetchExistingResults(prevDateStr, calcsRunning, computationManifest, config, dependencies, true);
163
+
164
+ if (standardToRun.length) {
165
+ const updates = await runStandardComputationPass(dateToProcess, standardToRun, `Pass ${passToRun} (Std)`, config, dependencies, rootData, existingResults, previousResults, false);
166
+ Object.assign(dateUpdates, updates);
167
+ }
168
+ if (metaToRun.length) {
169
+ // runMetaComputationPass uses the Controller, which handles Price Sharding logic internally for single dates.
170
+ const updates = await runMetaComputationPass(dateToProcess, metaToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, previousResults, rootData, false);
171
+ Object.assign(dateUpdates, updates);
172
+ }
173
+ } catch (err) {
174
+ logger.log('ERROR', `[DateRunner] FAILED Pass ${passToRun} for ${dateStr}`, { errorMessage: err.message });
175
+ [...standardToRun, ...metaToRun].forEach(c => dateUpdates[normalizeName(c.name)] = false);
176
+ throw err; // Re-throw to trigger Pub/Sub retry
177
+ }
178
+
179
+ if (Object.keys(dateUpdates).length > 0) {
180
+ await updateComputationStatus(dateStr, dateUpdates, config, dependencies);
181
+ }
182
+
183
+ return { date: dateStr, updates: dateUpdates };
184
+ }
185
+
158
186
  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 };