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, HISTORICAL_CALC_NAMES,
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
- const summary = { pass1_results: [], pass2_results: [] };
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 // <-- NEW
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] // <-- NEW
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 // <-- NEW
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 }) => !data?.[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'); // <-- NEW
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
- // --- MODIFIED: Run daily, insights, and social ---
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 // <-- NEW
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
- // --- NEW: Handle case where there are no users but we still need to run insights/social calcs ---
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; // <-- NEW
359
+ let yesterdaySocialPostInsightsData = null;
332
360
 
333
- // --- MODIFIED: Check if *any* calc requires yesterday data ---
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'); // <-- NEW
337
- // --- END MODIFIED ---
363
+ const requiresYesterdaySocialPosts = calculationsToRun.some(c => c.category === 'socialPosts');
338
364
 
339
- if (requiresYesterdayPortfolio || requiresYesterdayInsights || requiresYesterdaySocialPosts) { // <-- NEW
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, // <-- NEW
420
- yesterdaySocialPostInsightsData // <-- NEW
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, // Export the new function
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' // <-- ADD THIS LINE
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, unifiedUtils: utils, HISTORICAL_CALC_NAMES,
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
- // ... (no changes here) ...
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
- // ... (no changes here) ...
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
- // ... (no changes here) ...
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
- // ... (no changes here) ...
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
- // ... (no changes here) ...
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
- // ... (no changes here) ...
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.87",
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.12",
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",