bulltrackers-module 1.0.151 → 1.0.153

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.
Files changed (30) hide show
  1. package/functions/appscript-api/index.js +8 -38
  2. package/functions/computation-system/helpers/computation_pass_runner.js +38 -183
  3. package/functions/computation-system/helpers/orchestration_helpers.js +120 -314
  4. package/functions/computation-system/utils/data_loader.js +47 -132
  5. package/functions/computation-system/utils/schema_capture.js +7 -41
  6. package/functions/computation-system/utils/utils.js +37 -124
  7. package/functions/core/utils/firestore_utils.js +8 -46
  8. package/functions/core/utils/intelligent_header_manager.js +26 -128
  9. package/functions/core/utils/intelligent_proxy_manager.js +33 -171
  10. package/functions/core/utils/pubsub_utils.js +7 -24
  11. package/functions/dispatcher/helpers/dispatch_helpers.js +9 -30
  12. package/functions/dispatcher/index.js +7 -30
  13. package/functions/etoro-price-fetcher/helpers/handler_helpers.js +12 -80
  14. package/functions/fetch-insights/helpers/handler_helpers.js +18 -70
  15. package/functions/generic-api/helpers/api_helpers.js +28 -167
  16. package/functions/generic-api/index.js +49 -188
  17. package/functions/invalid-speculator-handler/helpers/handler_helpers.js +10 -47
  18. package/functions/orchestrator/helpers/discovery_helpers.js +1 -5
  19. package/functions/orchestrator/index.js +1 -6
  20. package/functions/price-backfill/helpers/handler_helpers.js +13 -69
  21. package/functions/social-orchestrator/helpers/orchestrator_helpers.js +5 -37
  22. package/functions/social-task-handler/helpers/handler_helpers.js +29 -186
  23. package/functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers.js +19 -78
  24. package/functions/task-engine/handler_creator.js +2 -8
  25. package/functions/task-engine/helpers/update_helpers.js +17 -83
  26. package/functions/task-engine/helpers/verify_helpers.js +11 -56
  27. package/functions/task-engine/utils/firestore_batch_manager.js +16 -67
  28. package/functions/task-engine/utils/task_engine_utils.js +6 -35
  29. package/index.js +45 -43
  30. 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
- if (!url || !headers) {
21
- return createErrorResponse("Invalid request: 'url' and 'headers' are required.", 400);
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
- // Package the response to send back
42
- const responseData = {
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, filterCalculations, runStandardComputationPass, runMetaComputationPass } = require('./orchestration_helpers.js');
7
- const { getExpectedDateStrings, getEarliestDataDates } = require('../utils/utils.js');
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 yesterday = new Date();
19
- yesterday.setUTCDate(yesterday.getUTCDate() - 1);
20
- const endDateUTC = new Date(Date.UTC(yesterday.getUTCFullYear(), yesterday.getUTCMonth(), yesterday.getUTCDate()));
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
- for (const calc of calcsInThisPass) {
49
- const deps = calc.rootDataDependencies || [];
50
-
51
- if (deps.length === 0) {
52
- // No dependencies = can run from the absolute earliest
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
- logger.log('INFO', `[PassRunner] Pass ${passToRun} analysis:`);
82
- logger.log('INFO', ` Total calculations: ${calcsInThisPass.length}`);
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
- // Process each date
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
- // NEW: Filter based on per-calculation earliest dates
116
- // ============================================
117
- const standardCalcsToRun = standardCalcs.filter(c => {
118
- // Skip if already exists
119
- if (existingResults[c.name]) {
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
- // Process in batches
201
- logger.log('INFO', `[PassRunner] Processing ${allExpectedDates.length} total dates in batches of ${PARALLEL_BATCH_SIZE}...`);
202
- for (let i = 0; i < allExpectedDates.length; i += PARALLEL_BATCH_SIZE) {
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 };