bulltrackers-module 1.0.474 → 1.0.476

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.
@@ -7,14 +7,21 @@ const { handleDiscover } = require('./helpers/discover_helpers');
7
7
  const { handleVerify } = require('./helpers/verify_helpers');
8
8
  const { handleUpdate } = require('./helpers/update_helpers');
9
9
  const { handlePopularInvestorUpdate, handleOnDemandUserUpdate } = require('./helpers/popular_investor_helpers');
10
+ const { handleSocialFetch } = require('./helpers/social_helpers');
10
11
 
11
12
  // IMPORT THE UTILS TO HANDLE BATCHES
12
13
  const { executeTasks, prepareTaskBatches } = require('./utils/task_engine_utils');
13
14
 
14
15
  async function handleRequest(message, context, configObj, dependencies) {
15
- // Support both old format (single config) and new format (object with taskEngine and rootDataIndexer)
16
+ // Support both old format (single config) and new format (object with taskEngine, rootDataIndexer, and social)
16
17
  const config = configObj.taskEngine || configObj; // Backward compatibility
17
18
  const rootDataIndexerConfig = configObj.rootDataIndexer;
19
+ const socialConfig = configObj.social;
20
+
21
+ // Merge social config into main config for easy access
22
+ if (socialConfig) {
23
+ config.social = socialConfig;
24
+ }
18
25
  const { logger, batchManager, db } = dependencies;
19
26
 
20
27
  // [CRITICAL FIX] Max Age increased to 25m to match the larger dedup window.
@@ -118,10 +125,55 @@ async function handleRequest(message, context, configObj, dependencies) {
118
125
  });
119
126
 
120
127
  const taskId = context.eventId || 'batch-' + Date.now();
128
+ const today = new Date().toISOString().split('T')[0];
129
+ const { db } = dependencies;
130
+
131
+ // Initialize counter for batch processing (only for update tasks that need root data indexing)
132
+ const updateTasksCount = payload.tasks.filter(t => t.type === 'update').length;
133
+ let batchCounterRef = null;
134
+
135
+ if (updateTasksCount > 0) {
136
+ try {
137
+ batchCounterRef = db.collection('task_engine_batch_counters').doc(`${today}-${taskId}`);
138
+ await batchCounterRef.set({
139
+ totalTasks: updateTasksCount,
140
+ remainingTasks: updateTasksCount,
141
+ startedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
142
+ taskId: taskId
143
+ });
144
+ logger.log('INFO', `[TaskEngine] Initialized batch counter: ${updateTasksCount} update tasks for ${today}`);
145
+ } catch (counterError) {
146
+ logger.log('WARN', `[TaskEngine] Failed to initialize batch counter`, counterError);
147
+ }
148
+ }
121
149
 
122
150
  try {
123
- const { tasksToRun, otherTasks } = await prepareTaskBatches(payload.tasks, null, logger);
124
- await executeTasks(tasksToRun, otherTasks, dependencies, configObj, taskId);
151
+ const { tasksToRun, otherTasks, socialTasks } = await prepareTaskBatches(payload.tasks, null, logger);
152
+
153
+ // Initialize social batch counter if we have social tasks
154
+ let socialCounterRef = null;
155
+ const userSocialTasksCount = socialTasks.filter(t =>
156
+ t.type === 'SOCIAL_PI_FETCH' || t.type === 'SOCIAL_SIGNED_IN_USER_FETCH'
157
+ ).length;
158
+
159
+ if (userSocialTasksCount > 0) {
160
+ try {
161
+ const socialCounterId = `social-${today}-${taskId}`;
162
+ socialCounterRef = db.collection('social_batch_counters').doc(socialCounterId);
163
+ await socialCounterRef.set({
164
+ totalTasks: userSocialTasksCount,
165
+ remainingTasks: userSocialTasksCount,
166
+ startedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
167
+ taskId: taskId,
168
+ date: today
169
+ });
170
+ logger.log('INFO', `[TaskEngine] Initialized social batch counter: ${userSocialTasksCount} user social tasks for ${today}`);
171
+ } catch (counterError) {
172
+ logger.log('WARN', `[TaskEngine] Failed to initialize social batch counter`, counterError);
173
+ }
174
+ }
175
+
176
+ await executeTasks(tasksToRun, otherTasks, dependencies, configObj, taskId, batchCounterRef, today, socialTasks, socialCounterRef);
125
177
  } catch (batchError) {
126
178
  logger.log('ERROR', `[TaskEngine] Error processing batch. Message will be acknowledged to prevent retry loop.`, {
127
179
  error: batchError.message,
@@ -186,6 +238,24 @@ async function handleRequest(message, context, configObj, dependencies) {
186
238
  }
187
239
  await handleOnDemandUserUpdate(onDemandData, configObj, dependencies);
188
240
  break;
241
+ case 'SOCIAL_INSTRUMENT_FETCH':
242
+ case 'SOCIAL_PI_FETCH':
243
+ case 'SOCIAL_SIGNED_IN_USER_FETCH':
244
+ const socialData = data || payload;
245
+ if (!socialData.id) {
246
+ logger.log('ERROR', `[TaskEngine] Social fetch task missing required field 'id'`, { data: socialData });
247
+ return;
248
+ }
249
+ // Map task type to social handler type
250
+ const socialType = type === 'SOCIAL_INSTRUMENT_FETCH' ? 'INSTRUMENT' :
251
+ type === 'SOCIAL_PI_FETCH' ? 'POPULAR_INVESTOR' : 'SIGNED_IN_USER';
252
+ await handleSocialFetch({
253
+ type: socialType,
254
+ id: socialData.id,
255
+ username: socialData.username,
256
+ since: socialData.since
257
+ }, config, dependencies);
258
+ break;
189
259
  default:
190
260
  logger.log('WARN', `[TaskEngine] Unknown task type: ${type}`);
191
261
  }
@@ -27,7 +27,9 @@ async function handlePopularInvestorUpdate(taskData, config, dependencies) {
27
27
  throw new Error('taskData is required for POPULAR_INVESTOR_UPDATE');
28
28
  }
29
29
 
30
- const { cid, username, requestId, source } = taskData;
30
+ const { cid, username, requestId, source, metadata } = taskData;
31
+ // Extract targetCid from metadata if present (for optimization)
32
+ const targetCid = metadata?.targetCid || cid;
31
33
 
32
34
  // Validate required fields
33
35
  if (!cid && !username) {
@@ -388,39 +390,46 @@ async function handlePopularInvestorUpdate(taskData, config, dependencies) {
388
390
  });
389
391
  }
390
392
 
391
- // Trigger computation for PopularInvestorProfileMetrics
393
+ // Trigger computations with dependency chain resolution
392
394
  const { pubsub } = dependencies;
393
395
  if (pubsub) {
394
- const computationTopic = config.computationSystem?.computationTopicStandard || 'computation-tasks';
395
- const topic = pubsub.topic(computationTopic);
396
- const crypto = require('crypto');
396
+ const { triggerComputationWithDependencies } = require('../../computation-system/helpers/on_demand_helpers');
397
397
 
398
- const computationMessage = {
399
- action: 'RUN_COMPUTATION_DATE',
400
- computation: 'PopularInvestorProfileMetrics',
401
- date: today,
402
- pass: '1', // Standard pass
403
- dispatchId: crypto.randomUUID(),
404
- triggerReason: 'on_demand_pi_fetch',
405
- resources: 'standard',
406
- metadata: {
407
- onDemand: true,
408
- requestId: requestId,
409
- piCid: cid,
410
- piUsername: username
411
- },
412
- traceContext: {
413
- traceId: crypto.randomBytes(16).toString('hex'),
414
- spanId: crypto.randomBytes(8).toString('hex'),
415
- sampled: true
416
- }
417
- };
398
+ // For Popular Investors, we need:
399
+ // 1. PopularInvestorProfileMetrics (main profile)
400
+ // 2. SignedInUserPIPersonalizedMetrics (if PI is also a signed-in user)
401
+ const computationsToTrigger = ['PopularInvestorProfileMetrics'];
418
402
 
419
- await topic.publishMessage({
420
- data: Buffer.from(JSON.stringify(computationMessage))
421
- });
403
+ // Check if this PI is also a signed-in user
404
+ const signedInUserRef = db.collection('signed_in_users').doc(String(cid));
405
+ const signedInUserDoc = await signedInUserRef.get();
406
+ if (signedInUserDoc.exists) {
407
+ computationsToTrigger.push('SignedInUserPIPersonalizedMetrics');
408
+ }
409
+
410
+ const triggeredMessages = [];
411
+ for (const computation of computationsToTrigger) {
412
+ try {
413
+ const messages = await triggerComputationWithDependencies(
414
+ computation,
415
+ today,
416
+ dependencies,
417
+ config.computationSystem || {},
418
+ {
419
+ triggerReason: 'on_demand_pi_fetch',
420
+ requestId: requestId,
421
+ piCid: cid,
422
+ piUsername: username,
423
+ targetCid: targetCid // Pass targetCid for optimization
424
+ }
425
+ );
426
+ triggeredMessages.push(...messages);
427
+ } catch (compError) {
428
+ logger.log('ERROR', `[PI Update] Failed to trigger ${computation}`, compError);
429
+ }
430
+ }
422
431
 
423
- logger.log('INFO', `[PI Update] Triggered computation PopularInvestorProfileMetrics for PI ${cid} (${username}) for date ${today}`);
432
+ logger.log('INFO', `[PI Update] Triggered ${triggeredMessages.length} computation messages for PI ${cid} (${username}) for date ${today}`);
424
433
  } else {
425
434
  logger.log('WARN', `[PI Update] PubSub not available, cannot trigger computation`);
426
435
  }
@@ -478,7 +487,15 @@ async function handlePopularInvestorUpdate(taskData, config, dependencies) {
478
487
  * Handles the On-Demand update for a Signed-In User.
479
488
  */
480
489
  async function handleOnDemandUserUpdate(taskData, config, dependencies) {
481
- const { cid, username, requestId, source } = taskData;
490
+ const { cid, username, requestId, source, metadata } = taskData;
491
+ const data = taskData.data || {}; // Extract data object
492
+ const includeSocial = data.includeSocial === true; // Check if social should be fetched
493
+ const since = data.since || new Date(Date.now() - (7 * 24 * 60 * 60 * 1000)).toISOString(); // Default to 7 days
494
+ const portfolioOnly = data.portfolioOnly === false ? false : true; // Default to true (fetch portfolio)
495
+
496
+ // Extract targetCid from metadata if present (for optimization)
497
+ const targetCid = metadata?.targetCid || cid;
498
+ const isNewUser = metadata?.isNewUser === true; // Flag for new user signup
482
499
 
483
500
  // [FIX] Destructure dependencies first
484
501
  const { logger, proxyManager, batchManager, headerManager, db } = dependencies;
@@ -539,63 +556,71 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
539
556
  };
540
557
 
541
558
  let fetchSuccess = false;
559
+ let portfolioFetched = false;
560
+ let historyFetched = false;
542
561
 
543
562
  try {
544
- // Portfolio Fetch
545
- const portfolioUrl = `${ETORO_API_PORTFOLIO_URL}?cid=${cid}&client_request_id=${uuid}`;
546
- logger.log('INFO', `[On-Demand DEBUG] Fetching Portfolio URL: ${portfolioUrl}`);
563
+ // Portfolio Fetch (only if portfolioOnly is true)
564
+ if (portfolioOnly) {
565
+ const portfolioUrl = `${ETORO_API_PORTFOLIO_URL}?cid=${cid}&client_request_id=${uuid}`;
566
+ logger.log('INFO', `[On-Demand DEBUG] Fetching Portfolio URL: ${portfolioUrl}`);
547
567
 
548
- let portfolioRes = null;
549
- let portfolioSuccess = false;
550
-
551
- // [CIRCUIT BREAKER] Try proxy only if circuit is not open
552
- if (shouldTryProxy()) {
553
- try {
554
- portfolioRes = await proxyManager.fetch(portfolioUrl, requestOptions);
555
- if (portfolioRes.ok) {
556
- portfolioSuccess = true;
557
- recordProxyOutcome(true);
558
- } else {
559
- throw new Error(`Proxy returned non-ok status: ${portfolioRes.status}`);
568
+ let portfolioRes = null;
569
+ let portfolioSuccess = false;
570
+
571
+ // [CIRCUIT BREAKER] Try proxy only if circuit is not open
572
+ if (shouldTryProxy()) {
573
+ try {
574
+ portfolioRes = await proxyManager.fetch(portfolioUrl, requestOptions);
575
+ if (portfolioRes.ok) {
576
+ portfolioSuccess = true;
577
+ recordProxyOutcome(true);
578
+ } else {
579
+ throw new Error(`Proxy returned non-ok status: ${portfolioRes.status}`);
580
+ }
581
+ } catch (proxyError) {
582
+ recordProxyOutcome(false);
583
+ logger.log('WARN', `[On-Demand] Proxy failed (${getFailureCount()}/${getMaxFailures()}). Error: ${proxyError.message}. Trying direct fetch...`);
560
584
  }
561
- } catch (proxyError) {
562
- recordProxyOutcome(false);
563
- logger.log('WARN', `[On-Demand] Proxy failed (${getFailureCount()}/${getMaxFailures()}). Error: ${proxyError.message}. Trying direct fetch...`);
585
+ } else {
586
+ logger.log('INFO', `[On-Demand] Circuit breaker open (${getFailureCount()}/${getMaxFailures()} failures). Skipping proxy, using direct fetch.`);
564
587
  }
565
- } else {
566
- logger.log('INFO', `[On-Demand] Circuit breaker open (${getFailureCount()}/${getMaxFailures()} failures). Skipping proxy, using direct fetch.`);
567
- }
568
-
569
- // Fallback to direct fetch if proxy failed or circuit is open
570
- if (!portfolioSuccess) {
571
- try {
572
- const directFetch = typeof fetch !== 'undefined' ? fetch : require('node-fetch');
573
- portfolioRes = await directFetch(portfolioUrl, requestOptions);
574
- if (!portfolioRes.ok) {
575
- throw new Error(`Failed to fetch portfolio for ${cid}: ${portfolioRes.status}`);
588
+
589
+ // Fallback to direct fetch if proxy failed or circuit is open
590
+ if (!portfolioSuccess) {
591
+ try {
592
+ const directFetch = typeof fetch !== 'undefined' ? fetch : require('node-fetch');
593
+ portfolioRes = await directFetch(portfolioUrl, requestOptions);
594
+ if (!portfolioRes.ok) {
595
+ throw new Error(`Failed to fetch portfolio for ${cid}: ${portfolioRes.status}`);
596
+ }
597
+ portfolioSuccess = true;
598
+ } catch (fetchErr) {
599
+ logger.log('ERROR', `[On-Demand] Direct fetch also failed for ${username}`, fetchErr);
600
+ throw new Error(`Failed to fetch portfolio for ${cid}. Direct: ${fetchErr.message}`);
576
601
  }
577
- portfolioSuccess = true;
578
- } catch (fetchErr) {
579
- logger.log('ERROR', `[On-Demand] Direct fetch also failed for ${username}`, fetchErr);
580
- throw new Error(`Failed to fetch portfolio for ${cid}. Direct: ${fetchErr.message}`);
581
602
  }
582
- }
583
603
 
584
- const portfolioData = await portfolioRes.json();
585
- portfolioData.lastUpdated = new Date();
586
- portfolioData.username = username;
587
-
588
- fetchSuccess = true;
604
+ const portfolioData = await portfolioRes.json();
605
+ portfolioData.lastUpdated = new Date();
606
+ portfolioData.username = username;
607
+
608
+ fetchSuccess = true;
609
+ portfolioFetched = true;
589
610
 
590
- await batchManager.addToPortfolioBatch(String(cid), blockId, today, portfolioData, 'signed_in_user');
611
+ await batchManager.addToPortfolioBatch(String(cid), blockId, today, portfolioData, 'signed_in_user');
612
+ } else {
613
+ logger.log('INFO', `[On-Demand Update] Skipping portfolio fetch (portfolioOnly=false) for ${username}`);
614
+ }
591
615
 
592
- // History Fetch - Single Page with Large ItemsPerPage
593
- const oneYearAgo = new Date();
594
- oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
595
- const historyBaseUrl = ETORO_API_HISTORY_URL || 'https://www.etoro.com/sapi/trade-data-real/history/public/credit/flat';
596
- const historyUrl = `${historyBaseUrl}?StartTime=${oneYearAgo.toISOString()}&PageNumber=1&ItemsPerPage=30000&PublicHistoryPortfolioFilter=&CID=${cid}&client_request_id=${uuid}`;
616
+ // History Fetch - Single Page with Large ItemsPerPage (only if portfolioOnly is true)
617
+ if (portfolioOnly) {
618
+ const oneYearAgo = new Date();
619
+ oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
620
+ const historyBaseUrl = ETORO_API_HISTORY_URL || 'https://www.etoro.com/sapi/trade-data-real/history/public/credit/flat';
621
+ const historyUrl = `${historyBaseUrl}?StartTime=${oneYearAgo.toISOString()}&PageNumber=1&ItemsPerPage=30000&PublicHistoryPortfolioFilter=&CID=${cid}&client_request_id=${uuid}`;
597
622
 
598
- logger.log('INFO', `[On-Demand DEBUG] Fetching History URL: ${historyUrl}`);
623
+ logger.log('INFO', `[On-Demand DEBUG] Fetching History URL: ${historyUrl}`);
599
624
 
600
625
  let historyRes = null;
601
626
  let historySuccess = false;
@@ -644,15 +669,103 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
644
669
  p => VALID_REASONS.includes(p.CloseReason)
645
670
  );
646
671
  }
647
- await batchManager.addToTradingHistoryBatch(String(cid), blockId, today, historyData, 'signed_in_user');
672
+ await batchManager.addToTradingHistoryBatch(String(cid), blockId, today, historyData, 'signed_in_user');
673
+ historyFetched = true;
674
+ } else {
675
+ logger.log('WARN', `[On-Demand Update] History fetch failed for ${username} (${historyRes.status})`);
676
+ }
648
677
  } else {
649
- logger.log('WARN', `[On-Demand Update] History fetch failed for ${username} (${historyRes.status})`);
678
+ logger.log('INFO', `[On-Demand Update] Skipping history fetch (portfolioOnly=false) for ${username}`);
650
679
  }
651
680
 
652
- logger.log('SUCCESS', `[On-Demand Update] Complete for ${username}`);
681
+ // Fetch social data if requested (for user signup or explicit request)
682
+ let socialFetched = false;
683
+ if (includeSocial && username) {
684
+ try {
685
+ logger.log('INFO', `[On-Demand Update] Fetching social data for ${username} (${cid})...`);
686
+
687
+ // Import social helper functions
688
+ const { getGcidForUser } = require('../../social-task-handler/helpers/handler_helpers');
689
+ const { FieldValue, FieldPath } = require('@google-cloud/firestore');
690
+
691
+ // Get GCID for user
692
+ const gcid = await getGcidForUser(dependencies, config.social || {}, cid, username);
693
+
694
+ // Fetch social posts
695
+ const userFeedApiUrl = config.social?.userFeedApiUrl || 'https://www.etoro.com/api/edm-streams/v1/feed/user/top/';
696
+ const fetchUrl = `${userFeedApiUrl}${gcid}?take=10&offset=0&reactionsPageSize=20&badgesExperimentIsEnabled=false&client_request_id=${uuid}`;
697
+
698
+ const socialResponse = await proxyManager.fetch(fetchUrl, requestOptions);
699
+ if (socialResponse.ok) {
700
+ const socialData = await socialResponse.json();
701
+ const discussions = socialData?.discussions || [];
702
+
703
+ // Store social posts (simplified version - full logic would be in merged handler)
704
+ const signedInUserSocialCollection = config.social?.signedInUserSocialCollection || 'signed_in_users_social';
705
+ const processedPostsCollection = config.social?.processedPostsCollectionName || 'social_processed_registry';
706
+ const processedRef = db.collection(processedPostsCollection);
707
+
708
+ const MAX_POSTS = 30;
709
+ let saved = 0;
710
+
711
+ for (const discussion of discussions.slice(0, MAX_POSTS)) {
712
+ const post = discussion.post;
713
+ if (!post || !post.id) continue;
714
+
715
+ // Check if already processed
716
+ const existing = await processedRef.doc(post.id).get();
717
+ if (existing.exists) continue;
718
+
719
+ // Store post
720
+ const postData = {
721
+ postId: post.id,
722
+ text: post.message?.text || '',
723
+ ownerId: post.owner?.id,
724
+ username: post.owner?.username,
725
+ createdAt: post.created,
726
+ fetchedAt: FieldValue.serverTimestamp(),
727
+ snippet: (post.message?.text || '').substring(0, 500),
728
+ stats: {
729
+ likes: discussion.emotionsData?.like?.paging?.totalCount || 0,
730
+ comments: discussion.summary?.totalCommentsAndReplies || 0
731
+ },
732
+ aiAnalysis: {
733
+ overallSentiment: "Neutral",
734
+ topics: [],
735
+ isSpam: false,
736
+ qualityScore: 0.5
737
+ },
738
+ tags: post.tags?.map(t => t.market?.symbolName).filter(Boolean) || []
739
+ };
740
+
741
+ await db.collection(signedInUserSocialCollection).doc(String(cid)).collection('posts').doc(post.id).set(postData);
742
+ await processedRef.doc(post.id).set({ processedAt: FieldValue.serverTimestamp() });
743
+ saved++;
744
+ }
745
+
746
+ // Update date tracking document
747
+ const trackingDocRef = db.collection(signedInUserSocialCollection).doc('_dates');
748
+ await trackingDocRef.set({
749
+ [`fetchedDates.${today}`]: true,
750
+ lastUpdated: FieldValue.serverTimestamp()
751
+ }, { merge: true });
752
+
753
+ socialFetched = true;
754
+ logger.log('INFO', `[On-Demand Update] Fetched and stored ${saved} social posts for ${username}`);
755
+ }
756
+ } catch (socialError) {
757
+ logger.log('WARN', `[On-Demand Update] Social fetch failed for ${username}`, socialError);
758
+ // Continue - social failure shouldn't block portfolio/history processing
759
+ }
760
+ }
653
761
 
654
- // Update request status and trigger computation if this is a sync request
655
- if (requestId && source === 'on_demand_sync' && db) {
762
+ logger.log('SUCCESS', `[On-Demand Update] Complete for ${username} (Portfolio: ${portfolioFetched ? '✓' : '✗'}, History: ${historyFetched ? '✓' : '✗'}, Social: ${socialFetched ? '✓' : '✗'})`);
763
+
764
+ // Update root data indexer after all data is fetched
765
+ // Then trigger computations for both sync requests AND user signups
766
+ const shouldTriggerComputations = (requestId && (source === 'on_demand_sync' || source === 'user_signup')) || isNewUser;
767
+
768
+ if (shouldTriggerComputations && db) {
656
769
  try {
657
770
  const requestRef = db.collection('user_sync_requests')
658
771
  .doc(String(cid))
@@ -693,39 +806,44 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
693
806
  });
694
807
  }
695
808
 
696
- // Trigger computation for SignedInUserProfileMetrics
809
+ // Trigger computations with dependency chain resolution
697
810
  const { pubsub } = dependencies;
698
811
  if (pubsub) {
699
- const computationTopic = config.computationSystem?.computationTopicStandard || 'computation-tasks';
700
- const topic = pubsub.topic(computationTopic);
701
- const crypto = require('crypto');
812
+ const { triggerComputationWithDependencies } = require('../../computation-system/helpers/on_demand_helpers');
702
813
 
703
- const computationMessage = {
704
- action: 'RUN_COMPUTATION_DATE',
705
- computation: 'SignedInUserProfileMetrics',
706
- date: today,
707
- pass: '1', // Standard pass
708
- dispatchId: crypto.randomUUID(),
709
- triggerReason: 'on_demand_sync',
710
- resources: 'standard',
711
- metadata: {
712
- onDemand: true,
713
- requestId: requestId,
714
- userCid: cid,
715
- username: username
716
- },
717
- traceContext: {
718
- traceId: crypto.randomBytes(16).toString('hex'),
719
- spanId: crypto.randomBytes(8).toString('hex'),
720
- sampled: true
721
- }
722
- };
814
+ // For signed-in users, we need to trigger multiple computations:
815
+ // 1. SignedInUserProfileMetrics (main profile)
816
+ // 2. SignedInUserCopiedList, SignedInUserCopiedPIs, SignedInUserPastCopies (copied data)
817
+ const computationsToTrigger = [
818
+ 'SignedInUserProfileMetrics',
819
+ 'SignedInUserCopiedList',
820
+ 'SignedInUserCopiedPIs',
821
+ 'SignedInUserPastCopies'
822
+ ];
723
823
 
724
- await topic.publishMessage({
725
- data: Buffer.from(JSON.stringify(computationMessage))
726
- });
824
+ const triggeredMessages = [];
825
+ for (const computation of computationsToTrigger) {
826
+ try {
827
+ const messages = await triggerComputationWithDependencies(
828
+ computation,
829
+ today,
830
+ dependencies,
831
+ config.computationSystem || {},
832
+ {
833
+ triggerReason: source === 'user_signup' ? 'user_signup' : 'on_demand_sync',
834
+ requestId: requestId,
835
+ userCid: cid,
836
+ username: username,
837
+ targetCid: targetCid // Pass targetCid for optimization
838
+ }
839
+ );
840
+ triggeredMessages.push(...messages);
841
+ } catch (compError) {
842
+ logger.log('ERROR', `[On-Demand Update] Failed to trigger ${computation}`, compError);
843
+ }
844
+ }
727
845
 
728
- logger.log('INFO', `[On-Demand Update] Triggered computation SignedInUserProfileMetrics for user ${cid} (${username}) for date ${today}`);
846
+ logger.log('INFO', `[On-Demand Update] Triggered ${triggeredMessages.length} computation messages for user ${cid} (${username}) for date ${today}`);
729
847
 
730
848
  // Don't mark as completed yet - wait for computation to finish
731
849
  // The status will remain 'computing' until computation completes