bulltrackers-module 1.0.210 → 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.
- package/functions/computation-system/controllers/computation_controller.js +199 -188
- package/functions/computation-system/helpers/computation_dispatcher.js +90 -90
- package/functions/computation-system/helpers/computation_manifest_builder.js +323 -283
- package/functions/computation-system/helpers/computation_pass_runner.js +185 -157
- package/functions/computation-system/helpers/computation_worker.js +85 -85
- package/functions/computation-system/helpers/orchestration_helpers.js +542 -558
- package/functions/computation-system/layers/extractors.js +415 -0
- package/functions/computation-system/layers/index.js +40 -0
- package/functions/computation-system/layers/math_primitives.js +743 -743
- package/functions/computation-system/layers/mathematics.js +397 -0
- package/functions/computation-system/layers/profiling.js +287 -0
- package/functions/computation-system/layers/validators.js +170 -0
- package/functions/computation-system/utils/schema_capture.js +63 -63
- package/functions/computation-system/utils/utils.js +22 -1
- package/functions/task-engine/helpers/update_helpers.js +34 -12
- package/package.json +1 -1
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
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 };
|