bulltrackers-module 1.0.87 → 1.0.89
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.
|
@@ -14,12 +14,16 @@ const {
|
|
|
14
14
|
loadDailyInsights,
|
|
15
15
|
loadDailySocialPostInsights // <-- NEW
|
|
16
16
|
} = require('../utils/data_loader.js');
|
|
17
|
+
|
|
18
|
+
// --- MODIFIED: Import new meta calculation objects ---
|
|
17
19
|
const {
|
|
18
|
-
historicalCalculations, dailyCalculations,
|
|
20
|
+
historicalCalculations, dailyCalculations, metaCalculations,
|
|
21
|
+
HISTORICAL_CALC_NAMES, META_CALC_NAMES,
|
|
19
22
|
withRetry,
|
|
20
23
|
getExpectedDateStrings, processJobsInParallel, getFirstDateFromSourceData,
|
|
21
24
|
commitBatchInChunks, unifiedUtils
|
|
22
25
|
} = require('../utils/utils.js');
|
|
26
|
+
// --- END MODIFIED ---
|
|
23
27
|
|
|
24
28
|
/**
|
|
25
29
|
* Main pipe: pipe.computationSystem.runOrchestration
|
|
@@ -29,7 +33,9 @@ const {
|
|
|
29
33
|
*/
|
|
30
34
|
async function runComputationOrchestrator(config, dependencies) {
|
|
31
35
|
const { logger, db } = dependencies; // Added db here
|
|
32
|
-
|
|
36
|
+
// --- MODIFIED: Add pass3_results to summary ---
|
|
37
|
+
const summary = { pass1_results: [], pass2_results: [], pass3_results: [] };
|
|
38
|
+
// --- END MODIFIED ---
|
|
33
39
|
const yesterday = new Date();
|
|
34
40
|
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
35
41
|
const endDateUTC = new Date(Date.UTC(yesterday.getUTCFullYear(), yesterday.getUTCMonth(), yesterday.getUTCDate()));
|
|
@@ -40,20 +46,23 @@ async function runComputationOrchestrator(config, dependencies) {
|
|
|
40
46
|
const masterDailyList = Object.entries(dailyCalculations).flatMap(([cat, calcs]) =>
|
|
41
47
|
Object.keys(calcs).map(name => ({ category: cat, calcName: name }))
|
|
42
48
|
);
|
|
43
|
-
// --- (EXISTING) insights calculations ---
|
|
44
49
|
const insightsCalculations = require('aiden-shared-calculations-unified').calculations.insights || {};
|
|
45
50
|
const masterInsightsList = Object.keys(insightsCalculations).map(name => ({ category: 'insights', calcName: name }));
|
|
46
|
-
|
|
47
|
-
// --- NEW: Add social post calculations to the master lists ---
|
|
48
51
|
const socialPostCalculations = require('aiden-shared-calculations-unified').calculations.socialPosts || {};
|
|
49
52
|
const masterSocialPostList = Object.keys(socialPostCalculations).map(name => ({ category: 'socialPosts', calcName: name }));
|
|
53
|
+
|
|
54
|
+
// --- NEW: Create master list for meta calcs ---
|
|
55
|
+
const masterMetaList = Object.entries(metaCalculations).flatMap(([cat, calcs]) =>
|
|
56
|
+
Object.keys(calcs).map(name => ({ category: cat, calcName: name }))
|
|
57
|
+
);
|
|
50
58
|
// --- END NEW ---
|
|
51
59
|
|
|
52
60
|
const masterFullList = [
|
|
53
61
|
...masterHistoricalList,
|
|
54
62
|
...masterDailyList,
|
|
55
63
|
...masterInsightsList,
|
|
56
|
-
...masterSocialPostList
|
|
64
|
+
...masterSocialPostList,
|
|
65
|
+
...masterMetaList // <-- NEW
|
|
57
66
|
];
|
|
58
67
|
|
|
59
68
|
// Pass dependencies to sub-pipe
|
|
@@ -69,21 +78,16 @@ async function runComputationOrchestrator(config, dependencies) {
|
|
|
69
78
|
const existingDateIds = new Set(insightDocs.docs.map(d => d.id).filter(id => /^\d{4}-\d{2}-\d{2}$/.test(id)));
|
|
70
79
|
|
|
71
80
|
// --- PASS 1 ---
|
|
72
|
-
// --- MODIFIED: Include insights & social calcs in missing list ---
|
|
73
81
|
const missingDates = allExpectedDates.filter(dateStr => !existingDateIds.has(dateStr));
|
|
74
82
|
const pass1Jobs = missingDates.map(date => ({
|
|
75
83
|
date,
|
|
76
|
-
missing: [...masterDailyList, ...masterInsightsList, ...masterSocialPostList]
|
|
84
|
+
missing: [...masterDailyList, ...masterInsightsList, ...masterSocialPostList]
|
|
77
85
|
}));
|
|
78
|
-
// --- END MODIFIED ---
|
|
79
|
-
|
|
80
86
|
logger.log('INFO', `[Orchestrator] Pass 1: Found ${pass1Jobs.length} missing dates to process (daily, insights & social calcs).`);
|
|
81
|
-
// Pass dependencies to sub-pipe
|
|
82
|
-
// --- MODIFIED: Combine daily, insights, and social calcs for Pass 1 run ---
|
|
83
87
|
const pass1SourcePackage = {
|
|
84
88
|
...dailyCalculations,
|
|
85
89
|
insights: insightsCalculations,
|
|
86
|
-
socialPosts: socialPostCalculations
|
|
90
|
+
socialPosts: socialPostCalculations
|
|
87
91
|
};
|
|
88
92
|
const pass1Results = await processJobsInParallel(
|
|
89
93
|
pass1Jobs,
|
|
@@ -91,36 +95,32 @@ async function runComputationOrchestrator(config, dependencies) {
|
|
|
91
95
|
'Pass 1',
|
|
92
96
|
config
|
|
93
97
|
);
|
|
94
|
-
// --- END MODIFIED ---
|
|
95
98
|
summary.pass1_results = pass1Results.map((r, i) => r.status === 'fulfilled' ? r.value : { success: false, date: pass1Jobs[i].date, error: r.reason?.message });
|
|
96
99
|
|
|
97
100
|
|
|
98
101
|
// --- PASS 2 ---
|
|
99
102
|
const pass2Jobs = [];
|
|
100
|
-
// Use db from dependencies
|
|
101
103
|
const updatedInsightDocs = await withRetry(() => dependencies.db.collection(config.resultsCollection).get(), "ListAllInsightDocs-Pass2");
|
|
102
|
-
|
|
103
104
|
updatedInsightDocs.forEach(doc => {
|
|
104
105
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(doc.id)) return;
|
|
105
106
|
const data = doc.data();
|
|
106
|
-
const missingCalcs = masterFullList.filter(({ category, calcName }) =>
|
|
107
|
+
const missingCalcs = masterFullList.filter(({ category, calcName }) =>
|
|
108
|
+
!META_CALC_NAMES.has(calcName) && // <-- Exclude meta calcs from Pass 2
|
|
109
|
+
!data?.[category]?.[calcName]
|
|
110
|
+
);
|
|
107
111
|
|
|
108
112
|
if (missingCalcs.length > 0) {
|
|
109
113
|
pass2Jobs.push({ date: doc.id, missing: missingCalcs });
|
|
110
114
|
}
|
|
111
115
|
});
|
|
112
|
-
|
|
113
116
|
logger.log('INFO', `[Orchestrator] Pass 2: Found ${pass2Jobs.length} incomplete dates to process.`);
|
|
114
|
-
// Pass dependencies to sub-pipe
|
|
115
117
|
const pass2Results = await processJobsInParallel(
|
|
116
118
|
pass2Jobs,
|
|
117
119
|
async (date, missing) => {
|
|
118
120
|
const historicalMissing = missing.filter(c => HISTORICAL_CALC_NAMES.has(c.calcName));
|
|
119
|
-
// --- MODIFIED: Include insights & social in daily missing ---
|
|
120
121
|
const dailyMissing = missing.filter(c => !HISTORICAL_CALC_NAMES.has(c.calcName) && c.category !== 'insights' && c.category !== 'socialPosts');
|
|
121
122
|
const insightsMissing = missing.filter(c => c.category === 'insights');
|
|
122
|
-
const socialPostsMissing = missing.filter(c => c.category === 'socialPosts');
|
|
123
|
-
// --- END MODIFIED ---
|
|
123
|
+
const socialPostsMissing = missing.filter(c => c.category === 'socialPosts');
|
|
124
124
|
const results = [];
|
|
125
125
|
|
|
126
126
|
if (historicalMissing.length > 0) {
|
|
@@ -128,19 +128,17 @@ async function runComputationOrchestrator(config, dependencies) {
|
|
|
128
128
|
const histResult = await runUnifiedComputation(date, historicalMissing, 'Pass 2 (Historical)', historicalCalculations, config, dependencies);
|
|
129
129
|
results.push(histResult);
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
const dailyAndInsightsAndSocialMissing = [...dailyMissing, ...insightsMissing, ...socialPostsMissing]; // <-- NEW
|
|
131
|
+
const dailyAndInsightsAndSocialMissing = [...dailyMissing, ...insightsMissing, ...socialPostsMissing];
|
|
133
132
|
if (dailyAndInsightsAndSocialMissing.length > 0) {
|
|
134
133
|
logger.log('INFO', `[Pass 2] Running ${dailyAndInsightsAndSocialMissing.length} daily/insights/social calcs for ${date.toISOString().slice(0, 10)}.`);
|
|
135
134
|
const dailyAndInsightsSourcePackage = {
|
|
136
135
|
...dailyCalculations,
|
|
137
136
|
insights: insightsCalculations,
|
|
138
|
-
socialPosts: socialPostCalculations
|
|
137
|
+
socialPosts: socialPostCalculations
|
|
139
138
|
};
|
|
140
139
|
const dailyResult = await runUnifiedComputation(date, dailyAndInsightsAndSocialMissing, 'Pass 2 (Daily/Insights/Social)', dailyAndInsightsSourcePackage, config, dependencies);
|
|
141
140
|
results.push(dailyResult);
|
|
142
141
|
}
|
|
143
|
-
// --- END MODIFIED ---
|
|
144
142
|
return results.length === 1 ? results[0] : { date: date.toISOString().slice(0,10), results };
|
|
145
143
|
},
|
|
146
144
|
'Pass 2',
|
|
@@ -148,6 +146,49 @@ async function runComputationOrchestrator(config, dependencies) {
|
|
|
148
146
|
);
|
|
149
147
|
summary.pass2_results = pass2Results.map((r, i) => r.status === 'fulfilled' ? r.value : { success: false, date: pass2Jobs[i].date, error: r.reason?.message });
|
|
150
148
|
|
|
149
|
+
|
|
150
|
+
// --- NEW: PASS 3 (Meta) ---
|
|
151
|
+
// This pass runs *after* Pass 1 & 2, checking for missing *meta* calculations.
|
|
152
|
+
const pass3Jobs = [];
|
|
153
|
+
const finalInsightDocs = await withRetry(() => dependencies.db.collection(config.resultsCollection).get(), "ListAllInsightDocs-Pass3");
|
|
154
|
+
|
|
155
|
+
finalInsightDocs.forEach(doc => {
|
|
156
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(doc.id)) return;
|
|
157
|
+
|
|
158
|
+
// Only check dates that are expected to be processed
|
|
159
|
+
if (!allExpectedDates.includes(doc.id)) return;
|
|
160
|
+
|
|
161
|
+
const data = doc.data();
|
|
162
|
+
// Find meta calcs that are missing from the summary doc
|
|
163
|
+
const missingMetaCalcs = masterMetaList.filter(({ category, calcName }) =>
|
|
164
|
+
!data?.[category]?.[calcName]
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (missingMetaCalcs.length > 0) {
|
|
168
|
+
pass3Jobs.push({ date: doc.id, missing: missingMetaCalcs });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
logger.log('INFO', `[Orchestrator] Pass 3: Found ${pass3Jobs.length} dates to process for meta-calculations.`);
|
|
173
|
+
|
|
174
|
+
// Pass dependencies to the new sub-pipe `runMetaComputation`
|
|
175
|
+
const pass3Results = await processJobsInParallel(
|
|
176
|
+
pass3Jobs,
|
|
177
|
+
(date, missing) => runMetaComputation(
|
|
178
|
+
date,
|
|
179
|
+
missing,
|
|
180
|
+
'Pass 3 (Meta)',
|
|
181
|
+
metaCalculations, // Pass the meta calcs package
|
|
182
|
+
config,
|
|
183
|
+
dependencies
|
|
184
|
+
),
|
|
185
|
+
'Pass 3',
|
|
186
|
+
config
|
|
187
|
+
);
|
|
188
|
+
summary.pass3_results = pass3Results.map((r, i) => r.status === 'fulfilled' ? r.value : { success: false, date: pass3Jobs[i].date, error: r.reason?.message });
|
|
189
|
+
// --- END NEW PASS 3 ---
|
|
190
|
+
|
|
191
|
+
|
|
151
192
|
logger.log('INFO', '[Orchestrator] Computation orchestration finished.');
|
|
152
193
|
return summary;
|
|
153
194
|
}
|
|
@@ -189,7 +230,6 @@ async function streamAndProcess(
|
|
|
189
230
|
const { db, logger } = dependencies;
|
|
190
231
|
logger.log('INFO', `[${passName}] Streaming ${todayRefs.length} 'today' part docs for ${dateStr}...`);
|
|
191
232
|
|
|
192
|
-
// --- START MODIFICATION ---
|
|
193
233
|
// Calculate yesterday's date string
|
|
194
234
|
const yesterdayDate = new Date(dateStr + 'T00:00:00Z');
|
|
195
235
|
yesterdayDate.setUTCDate(yesterdayDate.getUTCDate() - 1);
|
|
@@ -203,7 +243,6 @@ async function streamAndProcess(
|
|
|
203
243
|
todayDateStr: dateStr,
|
|
204
244
|
yesterdayDateStr: yesterdayStr
|
|
205
245
|
};
|
|
206
|
-
// --- END MODIFICATION ---
|
|
207
246
|
|
|
208
247
|
const batchSize = config.partRefBatchSize || 10;
|
|
209
248
|
let isFirstUser = true; // Flag for insights/social calculations
|
|
@@ -234,7 +273,6 @@ async function streamAndProcess(
|
|
|
234
273
|
yesterdaySocialPostInsights // <-- NEW
|
|
235
274
|
];
|
|
236
275
|
|
|
237
|
-
// --- MODIFIED: Handle insights & social calculations ---
|
|
238
276
|
if (category === 'insights' || category === 'socialPosts') {
|
|
239
277
|
// Only process these once per day, using the first user as a trigger
|
|
240
278
|
if (isFirstUser) {
|
|
@@ -243,7 +281,6 @@ async function streamAndProcess(
|
|
|
243
281
|
continue; // Skip processing for subsequent users
|
|
244
282
|
}
|
|
245
283
|
} else if (HISTORICAL_CALC_NAMES.has(calcName)) {
|
|
246
|
-
// --- MODIFIED: Handle missing yesterday's portfolio gracefully ---
|
|
247
284
|
const pYesterday = yesterdayPortfolios[uid];
|
|
248
285
|
// Skip if yesterday's portfolio is required but missing
|
|
249
286
|
if (!pYesterday && calc.constructor.prototype.process.length >= 3) {
|
|
@@ -259,7 +296,6 @@ async function streamAndProcess(
|
|
|
259
296
|
}
|
|
260
297
|
processArgs = [p, null, uid, ...allContextArgs]; // Pass null for yesterdayPortfolio, add all context
|
|
261
298
|
}
|
|
262
|
-
// --- END MODIFIED ---
|
|
263
299
|
|
|
264
300
|
try {
|
|
265
301
|
await Promise.resolve(calc.process(...processArgs));
|
|
@@ -271,7 +307,7 @@ async function streamAndProcess(
|
|
|
271
307
|
}
|
|
272
308
|
}
|
|
273
309
|
|
|
274
|
-
//
|
|
310
|
+
// Handle case where there are no users but we still need to run insights/social calcs
|
|
275
311
|
if (todayRefs.length === 0 && isFirstUser) {
|
|
276
312
|
logger.log('INFO', `[${passName}] No user portfolios found for ${dateStr}. Running insights/social calcs once.`);
|
|
277
313
|
const allContextArgs = [
|
|
@@ -296,13 +332,11 @@ async function streamAndProcess(
|
|
|
296
332
|
}
|
|
297
333
|
}
|
|
298
334
|
}
|
|
299
|
-
// --- END NEW ---
|
|
300
335
|
}
|
|
301
336
|
|
|
302
337
|
|
|
303
338
|
/**
|
|
304
339
|
* Internal sub-pipe: Runs computations for a single date.
|
|
305
|
-
* --- MODIFIED: Load and pass social post insights data ---
|
|
306
340
|
*/
|
|
307
341
|
async function runUnifiedComputation(dateToProcess, calculationsToRun, passName, sourcePackage, config, dependencies) {
|
|
308
342
|
const { db, logger } = dependencies;
|
|
@@ -310,35 +344,26 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
|
|
|
310
344
|
logger.log('INFO', `[${passName}] Starting run for ${dateStr} with ${calculationsToRun.length} calcs.`);
|
|
311
345
|
|
|
312
346
|
try {
|
|
313
|
-
// --- (EXISTING) Load today's instrument insights ---
|
|
314
347
|
const todayInsightsData = await loadDailyInsights(config, dependencies, dateStr);
|
|
315
|
-
// --- NEW: Load today's social post insights ---
|
|
316
348
|
const todaySocialPostInsightsData = await loadDailySocialPostInsights(config, dependencies, dateStr);
|
|
317
|
-
// --- END NEW ---
|
|
318
349
|
|
|
319
|
-
// Pass dependencies to sub-pipe
|
|
320
350
|
const todayRefs = await getPortfolioPartRefs(config, dependencies, dateStr);
|
|
321
|
-
// --- MODIFIED: Check if *any* data exists (portfolio OR insights OR social) ---
|
|
322
351
|
if (todayRefs.length === 0 && !todayInsightsData && !todaySocialPostInsightsData) {
|
|
323
352
|
logger.log('WARN', `[${passName}] No portfolio, instrument insights, OR social post data found for ${dateStr}. Skipping.`);
|
|
324
353
|
return { success: true, date: dateStr, message: "No source data for today." };
|
|
325
354
|
}
|
|
326
|
-
// --- END MODIFIED ---
|
|
327
355
|
|
|
328
356
|
|
|
329
357
|
let yesterdayPortfolios = {};
|
|
330
358
|
let yesterdayInsightsData = null;
|
|
331
|
-
let yesterdaySocialPostInsightsData = null;
|
|
359
|
+
let yesterdaySocialPostInsightsData = null;
|
|
332
360
|
|
|
333
|
-
//
|
|
334
|
-
const requiresYesterdayPortfolio = calculationsToRun.some(c => sourcePackage[c.category]?.[c.calcName]?.prototype?.process.length >= 3);
|
|
361
|
+
const requiresYesterdayPortfolio = calculationsToRun.some(c => HISTORICAL_CALC_NAMES.has(c.calcName)); // Use historical cals to determine need FIX from const requiresYesterdayPortfolio = calculationsToRun.some(c => sourcePackage[c.category]?.[c.calcName]?.prototype?.process.length >= 3);
|
|
335
362
|
const requiresYesterdayInsights = calculationsToRun.some(c => c.category === 'insights');
|
|
336
|
-
const requiresYesterdaySocialPosts = calculationsToRun.some(c => c.category === 'socialPosts');
|
|
337
|
-
// --- END MODIFIED ---
|
|
363
|
+
const requiresYesterdaySocialPosts = calculationsToRun.some(c => c.category === 'socialPosts');
|
|
338
364
|
|
|
339
|
-
if (requiresYesterdayPortfolio || requiresYesterdayInsights || requiresYesterdaySocialPosts) {
|
|
365
|
+
if (requiresYesterdayPortfolio || requiresYesterdayInsights || requiresYesterdaySocialPosts) {
|
|
340
366
|
|
|
341
|
-
// --- (EXISTING) Load yesterday's instrument insights ---
|
|
342
367
|
if(requiresYesterdayInsights) {
|
|
343
368
|
let daysAgo = 1;
|
|
344
369
|
const maxLookback = 30; // Or from config
|
|
@@ -360,9 +385,7 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
|
|
|
360
385
|
logger.log('WARN', `[${passName}] Could not find any 'yesterday' instrument insights data within a ${maxLookback} day lookback.`);
|
|
361
386
|
}
|
|
362
387
|
}
|
|
363
|
-
// --- END (EXISTING) ---
|
|
364
388
|
|
|
365
|
-
// --- NEW: Load yesterday's social post insights ---
|
|
366
389
|
if(requiresYesterdaySocialPosts) {
|
|
367
390
|
let daysAgo = 1;
|
|
368
391
|
const maxLookback = 30;
|
|
@@ -384,9 +407,7 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
|
|
|
384
407
|
logger.log('WARN', `[${passName}] Could not find any 'yesterday' social post insights data within a ${maxLookback} day lookback.`);
|
|
385
408
|
}
|
|
386
409
|
}
|
|
387
|
-
// --- END NEW ---
|
|
388
410
|
|
|
389
|
-
// --- (EXISTING) Load yesterday's portfolio data ---
|
|
390
411
|
if (requiresYesterdayPortfolio) {
|
|
391
412
|
const prev = new Date(dateToProcess);
|
|
392
413
|
prev.setUTCDate(prev.getUTCDate() - 1);
|
|
@@ -400,7 +421,6 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
|
|
|
400
421
|
logger.log('WARN', `[${passName}] Yesterday's (${prevStr}) portfolio data not found. Historical calcs requiring it will be skipped.`);
|
|
401
422
|
}
|
|
402
423
|
}
|
|
403
|
-
// --- END (EXISTING) ---
|
|
404
424
|
}
|
|
405
425
|
|
|
406
426
|
|
|
@@ -416,8 +436,8 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
|
|
|
416
436
|
yesterdayPortfolios,
|
|
417
437
|
todayInsightsData,
|
|
418
438
|
yesterdayInsightsData,
|
|
419
|
-
todaySocialPostInsightsData,
|
|
420
|
-
yesterdaySocialPostInsightsData
|
|
439
|
+
todaySocialPostInsightsData,
|
|
440
|
+
yesterdaySocialPostInsightsData
|
|
421
441
|
);
|
|
422
442
|
|
|
423
443
|
let successCount = 0;
|
|
@@ -471,13 +491,7 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
|
|
|
471
491
|
successCount++;
|
|
472
492
|
}
|
|
473
493
|
} else {
|
|
474
|
-
// --- START MODIFICATION ---
|
|
475
|
-
// REASON: Do not count a null/empty result as a success.
|
|
476
|
-
// This makes the failure visible in the summary log and ensures
|
|
477
|
-
// Pass 2 will detect the missing calculation for backfilling.
|
|
478
494
|
logger.log('WARN', `[${passName}] Calculation ${key} produced no results for ${dateStr}. Skipping write to allow backfill.`);
|
|
479
|
-
// DO NOT increment successCount here.
|
|
480
|
-
// --- END MODIFICATION ---
|
|
481
495
|
}
|
|
482
496
|
} catch (e) {
|
|
483
497
|
logger.log('ERROR', `[${passName}] getResult/Commit failed for ${key} on ${dateStr}`, { err: e.message });
|
|
@@ -496,6 +510,83 @@ async function runUnifiedComputation(dateToProcess, calculationsToRun, passName,
|
|
|
496
510
|
}
|
|
497
511
|
|
|
498
512
|
|
|
513
|
+
// --- NEW: Add the handler for meta-calculations ---
|
|
514
|
+
/**
|
|
515
|
+
* Internal sub-pipe: Runs "meta" computations for a single date.
|
|
516
|
+
* These calculations do NOT stream user data, but rather use the
|
|
517
|
+
* special `process(dateStr, dependencies, config)` signature.
|
|
518
|
+
*/
|
|
519
|
+
async function runMetaComputation(dateToProcess, calculationsToRun, passName, sourcePackage, config, dependencies) {
|
|
520
|
+
const { db, logger } = dependencies;
|
|
521
|
+
const dateStr = dateToProcess.toISOString().slice(0, 10);
|
|
522
|
+
logger.log('INFO', `[${passName}] Starting run for ${dateStr} with ${calculationsToRun.length} meta-calcs.`);
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
const state = initializeCalculators(calculationsToRun, sourcePackage, logger);
|
|
526
|
+
let successCount = 0;
|
|
527
|
+
const resultsCollectionRef = db.collection(config.resultsCollection).doc(dateStr).collection(config.resultsSubcollection);
|
|
528
|
+
|
|
529
|
+
for (const key in state) {
|
|
530
|
+
const calc = state[key];
|
|
531
|
+
// Check for the unique meta-calc signature
|
|
532
|
+
if (!calc || typeof calc.process !== 'function') continue;
|
|
533
|
+
|
|
534
|
+
const [category, calcName] = key.split('/');
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
// Call the special process method that returns the result directly
|
|
538
|
+
const result = await Promise.resolve(calc.process(dateStr, dependencies, config));
|
|
539
|
+
|
|
540
|
+
const pendingWrites = [];
|
|
541
|
+
const summaryData = {};
|
|
542
|
+
|
|
543
|
+
// Check if the result is valid
|
|
544
|
+
if (result && Object.keys(result).length > 0) {
|
|
545
|
+
// Meta calcs are not expected to be sharded, but we handle the standard case
|
|
546
|
+
const computationDocRef = resultsCollectionRef.doc(category)
|
|
547
|
+
.collection(config.computationsSubcollection)
|
|
548
|
+
.doc(calcName);
|
|
549
|
+
pendingWrites.push({ ref: computationDocRef, data: result });
|
|
550
|
+
|
|
551
|
+
if (!summaryData[category]) summaryData[category] = {};
|
|
552
|
+
summaryData[category][calcName] = true;
|
|
553
|
+
|
|
554
|
+
// Add the summary flag to the top-level date doc
|
|
555
|
+
if (Object.keys(summaryData).length > 0) {
|
|
556
|
+
const topLevelDocRef = db.collection(config.resultsCollection).doc(dateStr);
|
|
557
|
+
pendingWrites.push({ ref: topLevelDocRef, data: summaryData });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Commit the writes
|
|
561
|
+
if (pendingWrites.length > 0) {
|
|
562
|
+
await commitBatchInChunks(
|
|
563
|
+
config,
|
|
564
|
+
dependencies,
|
|
565
|
+
pendingWrites,
|
|
566
|
+
`Commit ${passName} ${dateStr} [${key}]`
|
|
567
|
+
);
|
|
568
|
+
successCount++;
|
|
569
|
+
}
|
|
570
|
+
} else {
|
|
571
|
+
logger.log('WARN', `[${passName}] Meta-calculation ${key} produced no results for ${dateStr}. Skipping write.`);
|
|
572
|
+
}
|
|
573
|
+
} catch (e) {
|
|
574
|
+
logger.log('ERROR', `[${passName}] Meta-calc process/commit failed for ${key} on ${dateStr}`, { err: e.message, stack: e.stack });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const completionStatus = successCount === calculationsToRun.length ? 'SUCCESS' : 'WARN';
|
|
579
|
+
logger.log(completionStatus, `[${passName}] Completed ${dateStr}. Success: ${successCount}/${calculationsToRun.length}.`);
|
|
580
|
+
return { success: true, date: dateStr, successful: successCount, failed: calculationsToRun.length - successCount };
|
|
581
|
+
|
|
582
|
+
} catch (err) {
|
|
583
|
+
logger.log('ERROR', `[${passName}] Fatal error for ${dateStr}`, { errorMessage: err.message, stack: err.stack });
|
|
584
|
+
return { success: false, date: dateStr, error: err.message };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// --- END NEW FUNCTION ---
|
|
588
|
+
|
|
589
|
+
|
|
499
590
|
module.exports = {
|
|
500
591
|
runComputationOrchestrator,
|
|
501
|
-
};
|
|
592
|
+
};
|
|
@@ -13,7 +13,6 @@ const { FieldPath } = require('@google-cloud/firestore'); // <<< --- ADD FieldPa
|
|
|
13
13
|
* @returns {Promise<Firestore.DocumentReference[]>} An array of DocumentReferences.
|
|
14
14
|
*/
|
|
15
15
|
async function getPortfolioPartRefs(config, dependencies, dateString) {
|
|
16
|
-
// ... (existing code unchanged) ...
|
|
17
16
|
const { db, logger } = dependencies;
|
|
18
17
|
logger.log('INFO', `Getting portfolio part references for date: ${dateString}`);
|
|
19
18
|
const allPartRefs = [];
|
|
@@ -53,7 +52,6 @@ async function getPortfolioPartRefs(config, dependencies, dateString) {
|
|
|
53
52
|
* @returns {Promise<object>} A single map of { [userId]: portfolioData }.
|
|
54
53
|
*/
|
|
55
54
|
async function loadDataByRefs(config, dependencies, refs) {
|
|
56
|
-
// ... (existing code unchanged) ...
|
|
57
55
|
const { db, logger } = dependencies;
|
|
58
56
|
if (!refs || refs.length === 0) { return {}; }
|
|
59
57
|
const mergedPortfolios = {};
|
|
@@ -88,7 +86,6 @@ async function loadDataByRefs(config, dependencies, refs) {
|
|
|
88
86
|
* @returns {Promise<object>} A single map of { [userId]: portfolioData }.
|
|
89
87
|
*/
|
|
90
88
|
async function loadFullDayMap(config, dependencies, partRefs) {
|
|
91
|
-
// ... (existing code unchanged) ...
|
|
92
89
|
const { logger } = dependencies;
|
|
93
90
|
if (partRefs.length === 0) return {};
|
|
94
91
|
logger.log('TRACE', `Loading full day map from ${partRefs.length} references...`);
|
|
@@ -100,7 +97,6 @@ async function loadFullDayMap(config, dependencies, partRefs) {
|
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
/**
|
|
103
|
-
* --- (EXISTING) ---
|
|
104
100
|
* Sub-pipe: pipe.computationSystem.dataLoader.loadDailyInsights
|
|
105
101
|
* Fetches the daily instrument insights document for a specific date.
|
|
106
102
|
* @param {object} config - The computation system configuration object.
|
|
@@ -173,5 +169,5 @@ module.exports = {
|
|
|
173
169
|
loadDataByRefs,
|
|
174
170
|
loadFullDayMap,
|
|
175
171
|
loadDailyInsights,
|
|
176
|
-
loadDailySocialPostInsights,
|
|
172
|
+
loadDailySocialPostInsights,
|
|
177
173
|
};
|
|
@@ -14,19 +14,30 @@ const HISTORICAL_CALC_NAMES = new Set([
|
|
|
14
14
|
'risk-appetite-change', 'drawdown-response', 'gain-response',
|
|
15
15
|
'tsl-effectiveness', 'position-count-pnl', 'diversification-pnl',
|
|
16
16
|
|
|
17
|
-
// --- ADD THESE THREE LINES ---
|
|
18
17
|
'deposit-withdrawal-percentage',
|
|
19
18
|
'new-allocation-percentage',
|
|
20
19
|
'reallocation-increase-percentage',
|
|
21
|
-
'asset-crowd-flow'
|
|
20
|
+
'asset-crowd-flow'
|
|
22
21
|
]);
|
|
22
|
+
|
|
23
|
+
// --- Set for meta calculation names ---
|
|
24
|
+
const META_CALC_NAMES = new Set([
|
|
25
|
+
'cash-flow-deployment'
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
|
|
23
29
|
const historicalCalculations = {};
|
|
24
30
|
const dailyCalculations = {};
|
|
31
|
+
const metaCalculations = {};
|
|
32
|
+
|
|
25
33
|
for (const category in calculations) {
|
|
26
34
|
for (const calcName in calculations[category]) {
|
|
27
35
|
if (HISTORICAL_CALC_NAMES.has(calcName)) {
|
|
28
36
|
if (!historicalCalculations[category]) historicalCalculations[category] = {};
|
|
29
37
|
historicalCalculations[category][calcName] = calculations[category][calcName];
|
|
38
|
+
} else if (META_CALC_NAMES.has(calcName)) {
|
|
39
|
+
if (!metaCalculations[category]) metaCalculations[category] = {};
|
|
40
|
+
metaCalculations[category][calcName] = calculations[category][calcName];
|
|
30
41
|
} else {
|
|
31
42
|
if (!dailyCalculations[category]) dailyCalculations[category] = {};
|
|
32
43
|
dailyCalculations[category][calcName] = calculations[category][calcName];
|
|
@@ -182,7 +193,9 @@ async function getFirstDateFromSourceData(config, dependencies) {
|
|
|
182
193
|
|
|
183
194
|
module.exports = {
|
|
184
195
|
FieldValue, FieldPath,
|
|
185
|
-
historicalCalculations, dailyCalculations,
|
|
196
|
+
historicalCalculations, dailyCalculations, metaCalculations,
|
|
197
|
+
unifiedUtils: utils,
|
|
198
|
+
HISTORICAL_CALC_NAMES, META_CALC_NAMES,
|
|
186
199
|
withRetry, commitBatchInChunks,
|
|
187
200
|
getExpectedDateStrings, processJobsInParallel, getFirstDateFromSourceData,
|
|
188
|
-
};
|
|
201
|
+
};
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// --- Core Utilities (Classes and Stateless Helpers) ---
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
const core = {
|
|
10
10
|
IntelligentHeaderManager: require('./functions/core/utils/intelligent_header_manager').IntelligentHeaderManager,
|
|
11
11
|
IntelligentProxyManager: require('./functions/core/utils/intelligent_proxy_manager').IntelligentProxyManager,
|
|
@@ -15,7 +15,7 @@ const core = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// --- Pipe 1: Orchestrator ---
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
const orchestrator = {
|
|
20
20
|
// Main Pipes (Entry points for Cloud Functions)
|
|
21
21
|
runDiscoveryOrchestrator: require('./functions/orchestrator/index').runDiscoveryOrchestrator,
|
|
@@ -32,7 +32,7 @@ const orchestrator = {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
// --- Pipe 2: Dispatcher ---
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
const dispatcher = {
|
|
37
37
|
// Main Pipe
|
|
38
38
|
handleRequest: require('./functions/dispatcher/index').handleRequest,
|
|
@@ -42,7 +42,7 @@ const dispatcher = {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// --- Pipe 3: Task Engine ---
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
const taskEngine = {
|
|
47
47
|
// Main Pipe
|
|
48
48
|
handleRequest: require('./functions/task-engine/handler_creator').handleRequest,
|
|
@@ -54,7 +54,6 @@ const taskEngine = {
|
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
// --- Pipe 4: Computation System ---
|
|
57
|
-
// ... (no changes here) ...
|
|
58
57
|
const computationSystem = {
|
|
59
58
|
// Main Pipe
|
|
60
59
|
runOrchestration: require('./functions/computation-system/helpers/orchestration_helpers').runComputationOrchestrator,
|
|
@@ -65,7 +64,7 @@ const computationSystem = {
|
|
|
65
64
|
};
|
|
66
65
|
|
|
67
66
|
// --- Pipe 5: API ---
|
|
68
|
-
|
|
67
|
+
|
|
69
68
|
const api = {
|
|
70
69
|
// Main Pipe
|
|
71
70
|
createApiApp: require('./functions/generic-api/index').createApiApp,
|
|
@@ -75,27 +74,21 @@ const api = {
|
|
|
75
74
|
};
|
|
76
75
|
|
|
77
76
|
// --- Pipe 6: Maintenance ---
|
|
78
|
-
// Standalone cleanup and utility functions
|
|
77
|
+
// Standalone cleanup and utility functions TODO -- Some of these are not really maintenance pipes, socials could do with its own pipe..?
|
|
79
78
|
const maintenance = {
|
|
80
79
|
runSpeculatorCleanup: require('./functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers').runCleanup,
|
|
81
80
|
handleInvalidSpeculator: require('./functions/invalid-speculator-handler/helpers/handler_helpers').handleInvalidSpeculator,
|
|
82
81
|
runFetchInsights: require('./functions/fetch-insights/helpers/handler_helpers').fetchAndStoreInsights,
|
|
83
82
|
runFetchPrices: require('./functions/etoro-price-fetcher/helpers/handler_helpers').fetchAndStorePrices,
|
|
84
|
-
|
|
85
|
-
// --- UPDATED ---
|
|
86
83
|
runUserActivitySamplerOrchestrator: require('./functions/user-activity-sampler/helpers/sampler_helpers').runUserActivitySamplerOrchestrator,
|
|
87
84
|
handleSampleBlockTask: require('./functions/user-activity-sampler/helpers/sampler_helpers').handleSampleBlockTask,
|
|
88
|
-
// --- END UPDATE ---
|
|
89
|
-
|
|
90
|
-
// --- NEW SOCIAL SENTIMENT FUNCTIONS ---
|
|
91
85
|
runSocialOrchestrator: require('./functions/social-orchestrator/helpers/orchestrator_helpers').runSocialOrchestrator,
|
|
92
86
|
handleSocialTask: require('./functions/social-task-handler/helpers/handler_helpers').handleSocialTask,
|
|
93
87
|
runBackfillAssetPrices: require('./functions/price-backfill/helpers/handler_helpers').runBackfillAssetPrices,
|
|
94
|
-
// --- END NEW ---
|
|
95
88
|
};
|
|
96
89
|
|
|
97
90
|
// --- Pipe 7: Proxy ---
|
|
98
|
-
|
|
91
|
+
|
|
99
92
|
const proxy = {
|
|
100
93
|
handlePost: require('./functions/appscript-api/index').handlePost,
|
|
101
94
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bulltrackers-module",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.89",
|
|
4
4
|
"description": "Helper Functions for Bulltrackers.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@google-cloud/firestore": "^7.11.3",
|
|
33
33
|
"sharedsetup": "latest",
|
|
34
34
|
"require-all": "^3.0.0",
|
|
35
|
-
"aiden-shared-calculations-unified": "1.0.
|
|
35
|
+
"aiden-shared-calculations-unified": "1.0.13",
|
|
36
36
|
"@google-cloud/pubsub": "latest",
|
|
37
37
|
"express": "^4.19.2",
|
|
38
38
|
"cors": "^2.8.5",
|