bulltrackers-module 1.0.567 → 1.0.569

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.
@@ -42,17 +42,47 @@ async function getUpdateTargets(userType, thresholds, config, dependencies) {
42
42
 
43
43
  /**
44
44
  * Sub-pipe: pipe.orchestrator.dispatchUpdates
45
+ * UPDATED: Added real-time validation to skip users already updated today.
45
46
  */
46
47
  async function dispatchUpdates(targets, userType, config, dependencies) {
47
- const { logger, pubsubUtils } = dependencies;
48
+ const { logger, pubsubUtils, db } = dependencies;
48
49
  const { dispatcherTopicName, taskBatchSize, pubsubBatchSize } = config;
49
50
 
50
- if (targets.length === 0) {
51
- logger.log('INFO', `[Orchestrator Helpers] No ${userType} update targets to dispatch.`);
52
- return;
53
- }
51
+ if (targets.length === 0) return;
52
+
53
+ // --- NEW VALIDATION BLOCK ---
54
+ const startOfToday = new Date();
55
+ startOfToday.setUTCHours(0, 0, 0, 0);
56
+
57
+ logger.log('INFO', `[Orchestrator Validation] Verifying ${targets.length} targets for ${userType}...`);
54
58
 
55
- logger.log('INFO', `[Orchestrator Helpers] Dispatching ${targets.length} update tasks for ${userType} to ${dispatcherTopicName}...`);
59
+ const validTargets = [];
60
+ for (const target of targets) {
61
+ const cid = target.userId || target.cid || (typeof target === 'string' ? target : null);
62
+ if (!cid) { validTargets.push(target); continue; }
63
+
64
+ // Check the actual Firestore record for today's completion
65
+ const collection = userType === 'popular_investor' ? 'PopularInvestors' :
66
+ userType === 'signed_in_user' ? 'SignedInUsers' : 'NormalUsers';
67
+
68
+ const doc = await db.collection(collection).doc(String(cid)).get();
69
+ const lastUpdated = doc.data()?.lastUpdated?.updatedAt?.toDate?.() ||
70
+ doc.data()?.lastUpdate?.toDate?.();
71
+
72
+ if (lastUpdated && lastUpdated >= startOfToday) {
73
+ logger.log('TRACE', `[Orchestrator Validation] Skipping ${cid} - Already updated today.`);
74
+ continue;
75
+ }
76
+ validTargets.push(target);
77
+ }
78
+
79
+ if (validTargets.length === 0) {
80
+ logger.log('INFO', `[Orchestrator Helpers] All ${targets.length} targets already updated. Skipping dispatch.`);
81
+ return;
82
+ }
83
+ // --- END VALIDATION BLOCK ---
84
+
85
+ logger.log('INFO', `[Orchestrator Helpers] Dispatching ${validTargets.length} validated update tasks for ${userType}...`);
56
86
 
57
87
  const individualTasks = targets.map(target => {
58
88
  let task = { userType };
@@ -133,7 +133,7 @@ async function handleRequest(message, context, configObj, dependencies) {
133
133
  if (updateTasksCount > 0) {
134
134
  try {
135
135
  // This batch counter is critical for triggering the Root Data Indexer
136
- batchCounterRef = db.collection('task_engine_batch_counters').doc(`${today}-${taskId}`);
136
+ batchCounterRef = db.collection('system_task_counts').doc(`${today}-${taskId}`);
137
137
  await batchCounterRef.set({
138
138
  totalTasks: updateTasksCount,
139
139
  remainingTasks: updateTasksCount,
@@ -308,7 +308,7 @@ async function handleGenericUserUpdate(taskData, config, dependencies, isPI) {
308
308
 
309
309
  // 5. Batch Counter Decrement (Critical for Scheduled Runs)
310
310
  if (batchCounterId) {
311
- const counterRef = db.collection('task_engine_batch_counters').doc(batchCounterId);
311
+ const counterRef = db.collection('system_task_counts').doc(batchCounterId);
312
312
  await counterRef.update({ remainingTasks: FieldValue.increment(-1) });
313
313
 
314
314
  // Check if we need to trigger root indexer for the batch
@@ -2,6 +2,7 @@
2
2
  * FILENAME: CloudFunctions/NpmWrappers/bulltrackers-module/functions/task-engine/utils/task_engine_utils.js
3
3
  * (REFACTORED: Concurrency limit increased to 5)
4
4
  * FIXED: Increased concurrency to prevent timeouts on large batches.
5
+ * FIXED: Added support for SIGNED_IN_USER_UPDATE in batch execution loop.
5
6
  */
6
7
 
7
8
  /**
@@ -45,7 +46,7 @@ async function prepareTaskBatches(tasks, batchManager, logger) {
45
46
  // Social fetch tasks
46
47
  socialTasks.push(task);
47
48
  } else {
48
- // Discover, Verify, Popular Investor (POPULAR_INVESTOR_UPDATE), Signed-In User (ON_DEMAND_USER_UPDATE)
49
+ // Discover, Verify, Popular Investor (POPULAR_INVESTOR_UPDATE), Signed-In User (ON_DEMAND_USER_UPDATE / SIGNED_IN_USER_UPDATE)
49
50
  otherTasks.push(task);
50
51
  }
51
52
  }
@@ -90,19 +91,20 @@ async function executeTasks(tasksToRun, otherTasks, dependencies, config, taskId
90
91
  continue;
91
92
  }
92
93
 
93
- if (task.type === 'ON_DEMAND_USER_UPDATE') {
94
+ // --- FIXED: Added SIGNED_IN_USER_UPDATE check ---
95
+ if (task.type === 'ON_DEMAND_USER_UPDATE' || task.type === 'SIGNED_IN_USER_UPDATE') {
94
96
  const taskData = task.data || task;
95
97
  if (!taskData.cid || !taskData.username) {
96
- logger.log('ERROR', `[TaskEngine/${taskId}] ON_DEMAND_USER_UPDATE task missing required fields`, { task, taskData });
98
+ logger.log('ERROR', `[TaskEngine/${taskId}] ${task.type} task missing required fields`, { task, taskData });
97
99
  taskCounters.failed++;
98
100
  continue;
99
101
  }
100
- logger.log('INFO', `[TaskEngine/${taskId}] Processing ON_DEMAND_USER_UPDATE for CID: ${taskData.cid}, Username: ${taskData.username}`);
102
+ logger.log('INFO', `[TaskEngine/${taskId}] Processing ${task.type} for CID: ${taskData.cid}, Username: ${taskData.username}`);
101
103
  allTaskPromises.push(limit(() =>
102
104
  handleOnDemandUserUpdate(taskData, config, dependencies)
103
105
  .then(() => taskCounters.on_demand++)
104
106
  .catch(err => {
105
- logger.log('ERROR', `[TaskEngine/${taskId}] Error in ON_DEMAND_USER_UPDATE for ${taskData.cid || taskData.username}`, { errorMessage: err.message });
107
+ logger.log('ERROR', `[TaskEngine/${taskId}] Error in ${task.type} for ${taskData.cid || taskData.username}`, { errorMessage: err.message });
106
108
  taskCounters.failed++;
107
109
  })
108
110
  ));
@@ -111,7 +113,7 @@ async function executeTasks(tasksToRun, otherTasks, dependencies, config, taskId
111
113
 
112
114
  // Handle legacy types (discover/verify)
113
115
  const normalizedType = task.type.toLowerCase();
114
- const handler = { discover: handleDiscover, verify: handleVerify }[normalizedType];
116
+ const handler = { discover: handleDiscover, verify: handleVerify }[normalizedType];
115
117
 
116
118
  if (handler) {
117
119
  allTaskPromises.push(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.567",
3
+ "version": "1.0.569",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,83 +0,0 @@
1
- # bulltrackers-module/workflows/daily_update_pipeline.yaml
2
- # Cloud Workflows: Slow-Trickle Daily Update Orchestrator
3
- # Triggers the Orchestrator to PLAN updates, then EXECUTES them in timed windows.
4
-
5
- main:
6
- params: [input]
7
- steps:
8
- - init:
9
- assign:
10
- - project: '${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}'
11
- - location: "europe-west1"
12
- # Replace with your actual Orchestrator HTTP trigger URL
13
- - orchestrator_url: '${"https://" + location + "-" + project + ".cloudfunctions.net/orchestrator-http"}'
14
- - today: '${text.split(time.format(sys.now()), "T")[0]}'
15
- # User types to process (can be passed in input or defaulted)
16
- - user_types: '${default(map.get(input, "userTypes"), ["normal", "speculator"])}'
17
- - default_windows: 10
18
-
19
- - process_user_types_loop:
20
- for:
21
- value: user_type
22
- in: ${user_types}
23
- steps:
24
- - log_start:
25
- call: sys.log
26
- args:
27
- text: '${"Starting update cycle for: " + user_type}'
28
-
29
- # --- PHASE 1: PLAN ---
30
- - plan_updates:
31
- call: http.post
32
- args:
33
- url: '${orchestrator_url}'
34
- body:
35
- action: 'PLAN'
36
- userType: '${user_type}'
37
- date: '${today}'
38
- windows: '${default_windows}'
39
- auth: { type: OIDC }
40
- timeout: 300 # 5 minutes to query and split users
41
- result: plan_res
42
-
43
- - log_plan:
44
- call: sys.log
45
- args:
46
- text: '${"📅 PLAN CREATED: " + user_type + " | PlanID: " + plan_res.body.planId + " | Users: " + plan_res.body.totalUsers + " | Windows: " + plan_res.body.windowCount}'
47
-
48
- # --- PHASE 2: EXECUTE WINDOWS ---
49
- - run_windows_loop:
50
- for:
51
- value: window_id
52
- in: '${plan_res.body.windowIds}'
53
- steps:
54
- - execute_window:
55
- call: http.post
56
- args:
57
- url: '${orchestrator_url}'
58
- body:
59
- action: 'EXECUTE_WINDOW'
60
- planId: '${plan_res.body.planId}'
61
- windowId: '${window_id}'
62
- userType: '${user_type}'
63
- auth: { type: OIDC }
64
- result: exec_res
65
-
66
- - log_execution:
67
- call: sys.log
68
- args:
69
- text: '${"🚀 WINDOW EXECUTED: " + user_type + " Window " + window_id + "/" + plan_res.body.windowCount + ". Dispatched: " + exec_res.body.dispatchedCount}'
70
-
71
- # --- PACING: Sleep between windows ---
72
- # We skip the sleep after the very last window of the loop
73
- - check_pacing_needed:
74
- switch:
75
- - condition: '${window_id < plan_res.body.windowCount}'
76
- steps:
77
- - wait_pacing:
78
- call: sys.sleep
79
- args:
80
- seconds: 3600 # 1 Hour wait between blocks
81
-
82
- - finish:
83
- return: "Daily Slow-Trickle Update Completed."