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.
- package/functions/orchestrator/helpers/update_helpers.js +36 -6
- package/functions/task-engine/handler_creator.js +1 -1
- package/functions/task-engine/helpers/popular_investor_helpers.js +1 -1
- package/functions/task-engine/utils/task_engine_utils.js +8 -6
- package/package.json +1 -1
- package/functions/task-engine/taskengine_workflow.yaml +0 -83
|
@@ -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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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}]
|
|
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
|
|
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
|
|
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:
|
|
116
|
+
const handler = { discover: handleDiscover, verify: handleVerify }[normalizedType];
|
|
115
117
|
|
|
116
118
|
if (handler) {
|
|
117
119
|
allTaskPromises.push(
|
package/package.json
CHANGED
|
@@ -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."
|