bulltrackers-module 1.0.152 → 1.0.154
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/appscript-api/index.js +8 -38
- package/functions/computation-system/helpers/computation_pass_runner.js +38 -183
- package/functions/computation-system/helpers/orchestration_helpers.js +105 -326
- package/functions/computation-system/utils/data_loader.js +38 -133
- package/functions/computation-system/utils/schema_capture.js +7 -41
- package/functions/computation-system/utils/utils.js +37 -124
- package/functions/core/utils/firestore_utils.js +8 -46
- package/functions/core/utils/intelligent_header_manager.js +26 -128
- package/functions/core/utils/intelligent_proxy_manager.js +33 -171
- package/functions/core/utils/pubsub_utils.js +7 -24
- package/functions/dispatcher/helpers/dispatch_helpers.js +9 -30
- package/functions/dispatcher/index.js +7 -30
- package/functions/etoro-price-fetcher/helpers/handler_helpers.js +12 -80
- package/functions/fetch-insights/helpers/handler_helpers.js +18 -70
- package/functions/generic-api/helpers/api_helpers.js +28 -167
- package/functions/generic-api/index.js +49 -188
- package/functions/invalid-speculator-handler/helpers/handler_helpers.js +10 -47
- package/functions/orchestrator/helpers/discovery_helpers.js +1 -5
- package/functions/orchestrator/index.js +1 -6
- package/functions/price-backfill/helpers/handler_helpers.js +13 -69
- package/functions/social-orchestrator/helpers/orchestrator_helpers.js +5 -37
- package/functions/social-task-handler/helpers/handler_helpers.js +29 -186
- package/functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers.js +19 -78
- package/functions/task-engine/handler_creator.js +2 -8
- package/functions/task-engine/helpers/update_helpers.js +74 -100
- package/functions/task-engine/helpers/verify_helpers.js +11 -56
- package/functions/task-engine/utils/firestore_batch_manager.js +29 -65
- package/functions/task-engine/utils/task_engine_utils.js +14 -37
- package/index.js +45 -43
- package/package.json +1 -1
|
@@ -13,46 +13,16 @@ const { createErrorResponse } = require('./helpers/errors');
|
|
|
13
13
|
*/
|
|
14
14
|
function handlePost(e) {
|
|
15
15
|
try {
|
|
16
|
-
// Parse the request details from the POST body
|
|
17
16
|
const requestDetails = JSON.parse(e.postData.contents);
|
|
18
17
|
const { url, headers, method, body } = requestDetails;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Prepare the external request options
|
|
25
|
-
const options = {
|
|
26
|
-
headers: headers,
|
|
27
|
-
muteHttpExceptions: true,
|
|
28
|
-
method: method || 'GET',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
if (body) {
|
|
32
|
-
options.payload = body;
|
|
33
|
-
if (!headers['Content-Type']) {
|
|
34
|
-
headers['Content-Type'] = 'application/json';
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Make the external request using UrlFetchApp
|
|
18
|
+
if (!url || !headers) { return createErrorResponse("Invalid request: 'url' and 'headers' are required.", 400); }
|
|
19
|
+
const options = { headers: headers, muteHttpExceptions: true, method: method || 'GET', };
|
|
20
|
+
if (body) { options.payload = body;
|
|
21
|
+
if (!headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } }
|
|
39
22
|
const response = UrlFetchApp.fetch(url, options);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
statusCode: response.getResponseCode(),
|
|
44
|
-
headers: response.getHeaders(),
|
|
45
|
-
body: response.getContentText(),
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return ContentService.createTextOutput(JSON.stringify(responseData))
|
|
49
|
-
.setMimeType(ContentService.MimeType.JSON);
|
|
50
|
-
|
|
51
|
-
} catch (error) {
|
|
52
|
-
return createErrorResponse(error.toString(), 500);
|
|
53
|
-
}
|
|
23
|
+
const responseData = { statusCode: response.getResponseCode(), headers: response.getHeaders(), body: response.getContentText(), };
|
|
24
|
+
return ContentService.createTextOutput(JSON.stringify(responseData)) .setMimeType(ContentService.MimeType.JSON);
|
|
25
|
+
} catch (error) { return createErrorResponse(error.toString(), 500); }
|
|
54
26
|
}
|
|
55
27
|
|
|
56
|
-
module.exports = {
|
|
57
|
-
handlePost,
|
|
58
|
-
};
|
|
28
|
+
module.exports = { handlePost };
|
|
@@ -3,209 +3,64 @@
|
|
|
3
3
|
* Now calculates earliest date PER CALCULATION, not per pass
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { groupByPass, checkRootDataAvailability, fetchExistingResults,
|
|
7
|
-
const { getExpectedDateStrings
|
|
8
|
-
|
|
6
|
+
const { groupByPass, checkRootDataAvailability, fetchExistingResults, runStandardComputationPass, runMetaComputationPass } = require('./orchestration_helpers.js');
|
|
7
|
+
const { getExpectedDateStrings } = require('../utils/utils.js');
|
|
9
8
|
const PARALLEL_BATCH_SIZE = 7;
|
|
10
9
|
|
|
11
10
|
async function runComputationPass(config, dependencies, computationManifest) {
|
|
12
11
|
const { logger } = dependencies;
|
|
13
12
|
const passToRun = String(config.COMPUTATION_PASS_TO_RUN);
|
|
14
13
|
if (!passToRun) return logger.log('ERROR', '[PassRunner] No pass defined. Aborting.');
|
|
15
|
-
|
|
16
14
|
logger.log('INFO', `🚀 Starting PASS ${passToRun}...`);
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Hardcoded earliest dates
|
|
23
|
-
logger.log('INFO', 'Using hardcoded earliest data dates to bypass faulty discovery.');
|
|
24
|
-
const earliestPortfolio = new Date('2025-09-25T00:00:00Z');
|
|
25
|
-
const earliestHistory = new Date('2025-11-05T00:00:00Z');
|
|
26
|
-
const earliestSocial = new Date('2025-10-30T00:00:00Z');
|
|
27
|
-
const earliestInsights = new Date('2025-08-26T00:00:00Z');
|
|
28
|
-
|
|
29
|
-
const earliestDates = {
|
|
30
|
-
portfolio: earliestPortfolio,
|
|
31
|
-
history: earliestHistory,
|
|
32
|
-
social: earliestSocial,
|
|
33
|
-
insights: earliestInsights,
|
|
34
|
-
absoluteEarliest: [earliestPortfolio, earliestHistory, earliestSocial, earliestInsights].reduce((a, b) => a < b ? a : b)
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
logger.log('INFO', `Hardcoded map: portfolio=${earliestDates.portfolio.toISOString().slice(0,10)}, history=${earliestDates.history.toISOString().slice(0,10)}, social=${earliestDates.social.toISOString().slice(0,10)}, insights=${earliestDates.insights.toISOString().slice(0,10)}`);
|
|
38
|
-
|
|
15
|
+
|
|
16
|
+
const earliestDates = { portfolio: new Date('2025-09-25T00:00:00Z'), history: new Date('2025-11-05T00:00:00Z'), social: new Date('2025-10-30T00:00:00Z'), insights: new Date('2025-08-26T00:00:00Z') };
|
|
17
|
+
earliestDates.absoluteEarliest = Object.values(earliestDates).reduce((a,b) => a < b ? a : b);
|
|
18
|
+
logger.log('INFO', `Hardcoded earliest dates: ${Object.entries(earliestDates).map(([k,v]) => `${k}=${v.toISOString().slice(0,10)}`).join(', ')}`);
|
|
19
|
+
|
|
39
20
|
const passes = groupByPass(computationManifest);
|
|
40
21
|
const calcsInThisPass = passes[passToRun] || [];
|
|
41
22
|
if (!calcsInThisPass.length) return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
|
|
42
|
-
|
|
43
|
-
// ============================================
|
|
44
|
-
// NEW: Calculate earliest date PER CALCULATION
|
|
45
|
-
// ============================================
|
|
46
23
|
const calcEarliestDates = new Map();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
calcEarliestDates.set(calc.name, earliestDates.absoluteEarliest);
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Find the LATEST earliest date among THIS calculation's dependencies
|
|
58
|
-
let latestEarliest = new Date(0);
|
|
59
|
-
for (const dep of deps) {
|
|
60
|
-
const depDate = earliestDates[dep];
|
|
61
|
-
if (depDate && depDate > latestEarliest) {
|
|
62
|
-
latestEarliest = depDate;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// If this is a historical calculation, add 1 day (needs yesterday)
|
|
67
|
-
if (calc.isHistorical) {
|
|
68
|
-
const adjusted = new Date(latestEarliest);
|
|
69
|
-
adjusted.setUTCDate(adjusted.getUTCDate() + 1);
|
|
70
|
-
calcEarliestDates.set(calc.name, adjusted);
|
|
71
|
-
logger.log('TRACE', `[PassRunner] ${calc.name}: earliest=${adjusted.toISOString().slice(0,10)} (historical, needs ${latestEarliest.toISOString().slice(0,10)} + 1 day)`);
|
|
72
|
-
} else {
|
|
73
|
-
calcEarliestDates.set(calc.name, latestEarliest);
|
|
74
|
-
logger.log('TRACE', `[PassRunner] ${calc.name}: earliest=${latestEarliest.toISOString().slice(0,10)}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// The pass can start from the EARLIEST calculation's start date
|
|
24
|
+
for (const calc of calcsInThisPass) { const deps = calc.rootDataDependencies || []; if (!deps.length) { calcEarliestDates.set(calc.name, earliestDates.absoluteEarliest); continue; }
|
|
25
|
+
const latestDep = new Date(Math.max(...deps.map(d => earliestDates[d]?.getTime() || 0)));
|
|
26
|
+
const calcDate = calc.isHistorical ? new Date(latestDep.getTime() + 86400000) : latestDep; // +1 day if historical
|
|
27
|
+
calcEarliestDates.set(calc.name, calcDate);
|
|
28
|
+
logger.log('TRACE', `[PassRunner] ${calc.name}: earliest=${calcDate.toISOString().slice(0,10)}`); }
|
|
29
|
+
|
|
79
30
|
const passEarliestDate = new Date(Math.min(...Array.from(calcEarliestDates.values()).map(d => d.getTime())));
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
logger.log('INFO', ` Pass can start from: ${passEarliestDate.toISOString().slice(0,10)}`);
|
|
84
|
-
logger.log('INFO', ` Individual calculation date ranges calculated.`);
|
|
85
|
-
|
|
86
|
-
// ============================================
|
|
87
|
-
// Generate date range from earliest to yesterday
|
|
88
|
-
// ============================================
|
|
89
|
-
const startDateUTC = new Date(Date.UTC(passEarliestDate.getUTCFullYear(), passEarliestDate.getUTCMonth(), passEarliestDate.getUTCDate()));
|
|
90
|
-
const allExpectedDates = getExpectedDateStrings(startDateUTC, endDateUTC);
|
|
91
|
-
|
|
92
|
-
logger.log('INFO', `[PassRunner] Processing ${allExpectedDates.length} total dates from ${allExpectedDates[0]} to ${allExpectedDates[allExpectedDates.length-1]}`);
|
|
93
|
-
|
|
31
|
+
logger.log('INFO', `[PassRunner] Pass ${passToRun} analysis: Total calcs=${calcsInThisPass.length}, can start from ${passEarliestDate.toISOString().slice(0,10)}`);
|
|
32
|
+
const endDateUTC = new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() - 1));
|
|
33
|
+
const allExpectedDates = getExpectedDateStrings(passEarliestDate, endDateUTC);
|
|
94
34
|
const standardCalcs = calcsInThisPass.filter(c => c.type === 'standard');
|
|
95
35
|
const metaCalcs = calcsInThisPass.filter(c => c.type === 'meta');
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
36
|
+
const checkDeps = (calc, rootData, existingResults, dateToProcess) => { if (existingResults[calc.name]) return false;
|
|
37
|
+
const earliest = calcEarliestDates.get(calc.name);
|
|
38
|
+
if (earliest && dateToProcess < earliest) return false;
|
|
39
|
+
|
|
40
|
+
const missingRoot = (calc.rootDataDependencies || []).filter(dep => !rootData.status[`has${dep[0].toUpperCase() + dep.slice(1)}`]);
|
|
41
|
+
if (missingRoot.length) return false;
|
|
42
|
+
|
|
43
|
+
if (calc.type === 'meta') { const missingComputed = (calc.dependencies || []).filter(d => !existingResults[d]); if (missingComputed.length) return false; } return true; };
|
|
44
|
+
|
|
100
45
|
const processDate = async (dateStr) => {
|
|
101
46
|
const dateToProcess = new Date(dateStr + 'T00:00:00Z');
|
|
102
|
-
|
|
103
47
|
try {
|
|
104
|
-
// Check root data availability
|
|
105
48
|
const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
|
|
106
|
-
if (!rootData) {
|
|
107
|
-
logger.log('WARN', `[PassRunner] Skipping ${dateStr} for Pass ${passToRun}: No root data.`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Fetch existing results
|
|
49
|
+
if (!rootData) return logger.log('WARN', `[PassRunner] Skipping ${dateStr}: No root data.`);
|
|
112
50
|
const existingResults = await fetchExistingResults(dateStr, calcsInThisPass, computationManifest, config, dependencies);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
logger.log('TRACE', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Result already exists.`);
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Skip if date is before this calculation's earliest date
|
|
125
|
-
const calcEarliestDate = calcEarliestDates.get(c.name);
|
|
126
|
-
if (calcEarliestDate && dateToProcess < calcEarliestDate) {
|
|
127
|
-
logger.log('TRACE', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Date before calc's earliest (${calcEarliestDate.toISOString().slice(0,10)}).`);
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Check root data dependencies
|
|
132
|
-
const missingDeps = (c.rootDataDependencies || []).filter(dep => {
|
|
133
|
-
if (dep === 'portfolio') return !rootData.status.hasPortfolio;
|
|
134
|
-
if (dep === 'insights') return !rootData.status.hasInsights;
|
|
135
|
-
if (dep === 'social') return !rootData.status.hasSocial;
|
|
136
|
-
if (dep === 'history') return !rootData.status.hasHistory;
|
|
137
|
-
return false;
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
if (missingDeps.length > 0) {
|
|
141
|
-
logger.log('INFO', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Missing deps: ${missingDeps.join(', ')}`);
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return true;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const metaCalcsToRun = metaCalcs.filter(c => {
|
|
149
|
-
if (existingResults[c.name]) return false;
|
|
150
|
-
|
|
151
|
-
const calcEarliestDate = calcEarliestDates.get(c.name);
|
|
152
|
-
if (calcEarliestDate && dateToProcess < calcEarliestDate) {
|
|
153
|
-
logger.log('TRACE', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Date before calc's earliest.`);
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Check root dependencies
|
|
158
|
-
const missingRootDeps = (c.rootDataDependencies || []).filter(dep => {
|
|
159
|
-
if (dep === 'portfolio') return !rootData.status.hasPortfolio;
|
|
160
|
-
if (dep === 'insights') return !rootData.status.hasInsights;
|
|
161
|
-
if (dep === 'social') return !rootData.status.hasSocial;
|
|
162
|
-
if (dep === 'history') return !rootData.status.hasHistory;
|
|
163
|
-
return false;
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
if (missingRootDeps.length > 0) {
|
|
167
|
-
logger.log('INFO', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Missing root deps: ${missingRootDeps.join(', ')}`);
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Check computed dependencies
|
|
172
|
-
const missingComputedDeps = (c.dependencies || []).filter(d => !existingResults[d]);
|
|
173
|
-
if (missingComputedDeps.length > 0) {
|
|
174
|
-
logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Missing computed deps: ${missingComputedDeps.join(', ')}`);
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return true;
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
if (standardCalcsToRun.length === 0 && metaCalcsToRun.length === 0) {
|
|
182
|
-
logger.log('INFO', `[PassRunner] All eligible calcs for ${dateStr} Pass ${passToRun} are already complete. Skipping.`);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
logger.log('INFO', `[PassRunner] Running ${dateStr}: ${standardCalcsToRun.length} standard, ${metaCalcsToRun.length} meta`);
|
|
187
|
-
|
|
188
|
-
if (standardCalcsToRun.length) {
|
|
189
|
-
await runStandardComputationPass(dateToProcess, standardCalcsToRun, `Pass ${passToRun} (Standard)`, config, dependencies, rootData);
|
|
190
|
-
}
|
|
191
|
-
if (metaCalcsToRun.length) {
|
|
192
|
-
await runMetaComputationPass(dateToProcess, metaCalcsToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, rootData);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
} catch (err) {
|
|
196
|
-
logger.log('ERROR', `[PassRunner] FAILED Pass ${passToRun} for ${dateStr}`, { errorMessage: err.message, stack: err.stack });
|
|
197
|
-
}
|
|
51
|
+
const standardToRun = standardCalcs.filter(c => checkDeps(c, rootData, existingResults, dateToProcess));
|
|
52
|
+
const metaToRun = metaCalcs.filter(c => checkDeps(c, rootData, existingResults, dateToProcess));
|
|
53
|
+
if (!standardToRun.length && !metaToRun.length) return logger.log('INFO', `[PassRunner] All calcs complete for ${dateStr}. Skipping.`);
|
|
54
|
+
logger.log('INFO', `[PassRunner] Running ${dateStr}: ${standardToRun.length} standard, ${metaToRun.length} meta`);
|
|
55
|
+
if (standardToRun.length) await runStandardComputationPass(dateToProcess, standardToRun, `Pass ${passToRun} (Standard)`, config, dependencies, rootData);
|
|
56
|
+
if (metaToRun.length) await runMetaComputationPass(dateToProcess, metaToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, rootData);
|
|
57
|
+
} catch (err) { logger.log('ERROR', `[PassRunner] FAILED Pass ${passToRun} for ${dateStr}`, { errorMessage: err.message, stack: err.stack }); }
|
|
198
58
|
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
logger.log('INFO', `[PassRunner] Processing ${allExpectedDates.length
|
|
202
|
-
|
|
203
|
-
const batch = allExpectedDates.slice(i, i + PARALLEL_BATCH_SIZE);
|
|
204
|
-
logger.log('INFO', `[PassRunner] Processing batch ${Math.floor(i / PARALLEL_BATCH_SIZE) + 1}/${Math.ceil(allExpectedDates.length / PARALLEL_BATCH_SIZE)} (Dates: ${batch[0]}...${batch[batch.length-1]})`);
|
|
205
|
-
await Promise.all(batch.map(dateStr => processDate(dateStr)));
|
|
206
|
-
}
|
|
207
|
-
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < allExpectedDates.length; i += PARALLEL_BATCH_SIZE) { const batch = allExpectedDates.slice(i, i + PARALLEL_BATCH_SIZE);
|
|
61
|
+
logger.log('INFO', `[PassRunner] Processing batch ${Math.floor(i / PARALLEL_BATCH_SIZE) + 1}/${Math.ceil(allExpectedDates.length / PARALLEL_BATCH_SIZE)} (Dates: ${batch[0]}...${batch[batch.length-1]})`);
|
|
62
|
+
await Promise.all(batch.map(processDate)); }
|
|
208
63
|
logger.log('INFO', `[PassRunner] Pass ${passToRun} orchestration finished.`);
|
|
209
64
|
}
|
|
210
65
|
|
|
211
|
-
module.exports = { runComputationPass };
|
|
66
|
+
module.exports = { runComputationPass };
|