bulltrackers-module 1.0.207 → 1.0.208

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.
@@ -12,18 +12,30 @@
12
12
  */
13
13
  async function checkDiscoveryNeed(userType, config, dependencies) {
14
14
  const { logger, firestoreUtils } = dependencies;
15
+ const { targetUsersPerBlock, normalBlockCountsDocPath, allHighValueBlocks } = config;
16
+
17
+ // --- REFACTOR: ORGANIC SPECULATOR DISCOVERY ---
18
+ // We no longer "hunt" for speculators using block targets.
19
+ // Speculators are now discovered organically via the Normal User Update Loop (Feedback Loop).
20
+ if (userType === 'speculator') {
21
+ logger.log('INFO', '[Orchestrator Helpers] Speculator discovery is Organic/Feedback-Driven. Skipping active discovery checks.');
22
+ return { needsDiscovery: false, blocksToFill: [] };
23
+ }
24
+
15
25
  logger.log('INFO', `[Orchestrator Helpers] Checking discovery need for ${userType}...`);
16
- const { targetUsersPerBlock, speculatorBlockCountsDocPath, normalBlockCountsDocPath,speculatorInstruments,allHighValueBlocks} = config;
17
- const isSpeculator = userType === 'speculator';
18
- const blockCounts = await firestoreUtils.getBlockCapacities(dependencies, { speculatorBlockCountsDocPath, normalBlockCountsDocPath }, userType);
19
- let underPopulatedBlocks = [];
20
- if (isSpeculator) {for (const instrument of speculatorInstruments) { for (const block of allHighValueBlocks)
21
- {const instrumentBlockKey = `${instrument}_${block.startId}`;
22
- if ((blockCounts[instrumentBlockKey] || 0) < targetUsersPerBlock) {underPopulatedBlocks.push({ ...block, instrument });}}}}
23
- else {underPopulatedBlocks = allHighValueBlocks.filter(block => (blockCounts[block.startId] || 0) < targetUsersPerBlock);}
24
- const needsDiscovery = underPopulatedBlocks.length > 0;
25
- if (!needsDiscovery) {logger.log('SUCCESS', `✅ All blocks are at target capacity for ${userType} users.`);}
26
- else {logger.log('INFO', `Found ${underPopulatedBlocks.length} underpopulated blocks/instruments for ${userType}.`);}
26
+
27
+ // Logic for Normal Users remains (fill blocks to target)
28
+ const blockCounts = await firestoreUtils.getBlockCapacities(dependencies, { normalBlockCountsDocPath }, userType);
29
+
30
+ const underPopulatedBlocks = allHighValueBlocks.filter(block => (blockCounts[block.startId] || 0) < targetUsersPerBlock);
31
+ const needsDiscovery = underPopulatedBlocks.length > 0;
32
+
33
+ if (!needsDiscovery) {
34
+ logger.log('SUCCESS', `✅ All blocks are at target capacity for ${userType} users.`);
35
+ } else {
36
+ logger.log('INFO', `Found ${underPopulatedBlocks.length} underpopulated blocks for ${userType}.`);
37
+ }
38
+
27
39
  return { needsDiscovery, blocksToFill: underPopulatedBlocks };
28
40
  }
29
41
 
@@ -38,29 +50,70 @@ async function checkDiscoveryNeed(userType, config, dependencies) {
38
50
  async function getDiscoveryCandidates(userType, blocksToFill, config, dependencies) {
39
51
  const { logger, firestoreUtils } = dependencies;
40
52
  logger.log('INFO', `[Orchestrator Helpers] Getting discovery candidates for ${userType}...`);
41
- const {maxTasksPerRun,discoveryBatchSize,maxRandomCidsToDiscover,specBlocksCollection,pendingSpecCollection,normalUserCollection,invalidSpecCollection,speculatorInstrumentSet,snapshotsSubCollectionName,partsSubCollectionName} = config;
42
- const isSpeculator = userType === 'speculator';
53
+
54
+ const {
55
+ maxTasksPerRun,
56
+ discoveryBatchSize,
57
+ maxRandomCidsToDiscover,
58
+ specBlocksCollection,
59
+ pendingSpecCollection,
60
+ normalUserCollection,
61
+ invalidSpecCollection,
62
+ snapshotsSubCollectionName,
63
+ partsSubCollectionName
64
+ } = config;
65
+
43
66
  const dispatchedCids = new Set();
44
- const latestNormalPortfolios = await firestoreUtils.getLatestNormalUserPortfolios(dependencies,{ normalUserCollectionName: normalUserCollection, snapshotsSubCollectionName, partsSubCollectionName });
67
+
68
+ // Fetch exclusion list to avoid re-discovering existing users
69
+ // Note: For Normal users, we check against existing Normal users.
70
+ const latestNormalPortfolios = await firestoreUtils.getLatestNormalUserPortfolios(dependencies, {
71
+ normalUserCollectionName: normalUserCollection,
72
+ snapshotsSubCollectionName,
73
+ partsSubCollectionName
74
+ });
75
+
45
76
  const existingNormalUserIds = new Set(Object.keys(latestNormalPortfolios));
46
- const exclusionIds = await firestoreUtils.getExclusionIds(dependencies, {specBlocksCollection, pendingSpecCollection, normalUserCollection,invalidSpecCollection,existingNormalUserIds}, userType);
47
- if (isSpeculator) {const prioritizedCandidates = await firestoreUtils.getPrioritizedSpeculators(dependencies,exclusionIds, speculatorInstrumentSet,latestNormalPortfolios);
48
- logger.log('INFO', `Found ${prioritizedCandidates.length} potential new speculators from existing user pool.`);
49
- prioritizedCandidates.forEach(cidStr => {if (dispatchedCids.size < maxTasksPerRun) {dispatchedCids.add(cidStr);}});
50
- logger.log('INFO', `Added ${dispatchedCids.size} prioritized speculators.`);}
77
+ const exclusionIds = await firestoreUtils.getExclusionIds(dependencies, {
78
+ specBlocksCollection,
79
+ pendingSpecCollection,
80
+ normalUserCollection,
81
+ invalidSpecCollection,
82
+ existingNormalUserIds
83
+ }, userType);
84
+
85
+ // --- RANDOM DISCOVERY LOOP (For Normal Users) ---
51
86
  let dispatchedRandomCidCount = 0;
52
- const initialDispatchedCount = dispatchedCids.size;
53
- while ((dispatchedRandomCidCount + initialDispatchedCount) < maxRandomCidsToDiscover &&dispatchedCids.size < maxTasksPerRun && blocksToFill.length > 0){
87
+
88
+ while ((dispatchedRandomCidCount + dispatchedCids.size) < maxRandomCidsToDiscover &&
89
+ dispatchedCids.size < maxTasksPerRun &&
90
+ blocksToFill.length > 0) {
91
+
54
92
  const blockIndex = dispatchedCids.size % blocksToFill.length;
55
93
  const block = blocksToFill[blockIndex];
56
94
  if (!block) break;
95
+
57
96
  for (let j = 0; j < discoveryBatchSize; j++) {
58
- if ((dispatchedRandomCidCount + initialDispatchedCount) >= maxRandomCidsToDiscover || dispatchedCids.size >= maxTasksPerRun) break;
59
- let randomId;
60
- let retryCount = 0;
61
- const MAX_RETRIES = 50;
62
- do {if (++retryCount > MAX_RETRIES) break;randomId = String(Math.floor(Math.random() * 1000000) + block.startId);} while (exclusionIds.has(randomId) ||dispatchedCids.has(randomId));
63
- if (retryCount <= MAX_RETRIES) {dispatchedCids.add(randomId);dispatchedRandomCidCount++;}}}
97
+ if ((dispatchedRandomCidCount + dispatchedCids.size) >= maxRandomCidsToDiscover ||
98
+ dispatchedCids.size >= maxTasksPerRun) break;
99
+
100
+ let randomId;
101
+ let retryCount = 0;
102
+ const MAX_RETRIES = 50;
103
+
104
+ do {
105
+ if (++retryCount > MAX_RETRIES) break;
106
+ // Generate random CID within the block range
107
+ randomId = String(Math.floor(Math.random() * 1000000) + block.startId);
108
+ } while (exclusionIds.has(randomId) || dispatchedCids.has(randomId));
109
+
110
+ if (retryCount <= MAX_RETRIES) {
111
+ dispatchedCids.add(randomId);
112
+ dispatchedRandomCidCount++;
113
+ }
114
+ }
115
+ }
116
+
64
117
  logger.log('INFO', `Generated ${dispatchedRandomCidCount} random CIDs for ${userType} discovery.`);
65
118
  logger.log('INFO', `Total candidates for dispatch: ${dispatchedCids.size}`);
66
119
  return dispatchedCids;
@@ -75,34 +128,54 @@ async function getDiscoveryCandidates(userType, blocksToFill, config, dependenci
75
128
  * @returns {Promise<void>}
76
129
  */
77
130
  async function dispatchDiscovery(userType, candidates, config, dependencies) {
78
- const { logger, firestoreUtils, pubsubUtils, pubsub } = dependencies;
79
- const {topicName,dispatchBatchSize,pubsubBatchSize, pendingSpecCollection, pendingMaxFieldsPerDoc,pendingMaxWritesPerBatch} = config;
80
- if (candidates.size === 0) {logger.log('INFO', `[Orchestrator Helpers] No ${userType} candidates to dispatch.`);return;}
131
+ const { logger, pubsub } = dependencies;
132
+ const { topicName, dispatchBatchSize } = config;
133
+
134
+ if (candidates.size === 0) {
135
+ logger.log('INFO', `[Orchestrator Helpers] No ${userType} candidates to dispatch.`);
136
+ return;
137
+ }
138
+
81
139
  logger.log('INFO', `[Orchestrator Helpers] Dispatching ${candidates.size} discovery tasks for ${userType}...`);
82
- const isSpeculator = userType === 'speculator';
140
+
83
141
  const cidsArray = Array.from(candidates);
84
- if (isSpeculator)
85
- {await firestoreUtils.clearCollection(dependencies, pendingSpecCollection);
86
- await firestoreUtils.batchWriteShardedIds(dependencies, {collectionPath: pendingSpecCollection,items: cidsArray, timestamp: new Date(),maxFieldsPerDoc: pendingMaxFieldsPerDoc,maxWritesPerBatch: pendingMaxWritesPerBatch});}
87
142
  const tasks = [];
88
- for (let i = 0; i < cidsArray.length; i += dispatchBatchSize) {const batchCids = cidsArray.slice(i, i + dispatchBatchSize).map(cid => parseInt(cid));
89
- if (batchCids.length > 0) {
90
- const blockId = Math.floor(batchCids[0] / 1000000) * 1000000;
91
- tasks.push({type: 'discover',cids: batchCids,blockId, userType});}}
143
+
144
+ for (let i = 0; i < cidsArray.length; i += dispatchBatchSize) {
145
+ const batchCids = cidsArray.slice(i, i + dispatchBatchSize).map(cid => parseInt(cid));
146
+ if (batchCids.length > 0) {
147
+ const blockId = Math.floor(batchCids[0] / 1000000) * 1000000;
148
+ tasks.push({
149
+ type: 'discover',
150
+ cids: batchCids,
151
+ blockId,
152
+ userType
153
+ });
154
+ }
155
+ }
156
+
92
157
  const topic = pubsub.topic(topicName);
93
158
  let totalCidsPublished = 0;
94
159
  let messagesPublished = 0;
160
+
95
161
  for (let i = 0; i < tasks.length; i += dispatchBatchSize) {
96
162
  const batchOfTasks = tasks.slice(i, i + dispatchBatchSize);
97
163
  const messagePayload = { tasks: batchOfTasks };
98
- try {await topic.publishMessage({ json: messagePayload });
99
- const cidsInThisMessage = batchOfTasks.reduce((acc, task) => acc + task.cids.length, 0);
100
- totalCidsPublished += cidsInThisMessage;
101
- messagesPublished++;
102
- logger.log('INFO', `[Orchestrator Helpers] Dispatched batch ${messagesPublished} with ${batchOfTasks.length} discover tasks (${cidsInThisMessage} CIDs) as 1 Pub/Sub message.`);}
103
- catch (publishError) {
104
- logger.log('ERROR', `[Orchestrator Helpers] Failed to publish discover batch ${messagesPublished + 1}.`, { error: publishError.message });}}
105
- logger.log('SUCCESS', `[Orchestrator Helpers] Dispatched ${totalCidsPublished} CIDs in ${tasks.length} tasks, grouped into ${messagesPublished} Pub/Sub messages for ${userType} discovery.`);
164
+
165
+ try {
166
+ await topic.publishMessage({ json: messagePayload });
167
+
168
+ const cidsInThisMessage = batchOfTasks.reduce((acc, task) => acc + task.cids.length, 0);
169
+ totalCidsPublished += cidsInThisMessage;
170
+ messagesPublished++;
171
+
172
+ logger.log('INFO', `[Orchestrator Helpers] Dispatched batch ${messagesPublished} with ${batchOfTasks.length} discover tasks (${cidsInThisMessage} CIDs) as 1 Pub/Sub message.`);
173
+ } catch (publishError) {
174
+ logger.log('ERROR', `[Orchestrator Helpers] Failed to publish discover batch ${messagesPublished + 1}.`, { error: publishError.message });
175
+ }
176
+ }
177
+
178
+ logger.log('SUCCESS', `[Orchestrator Helpers] Dispatched ${totalCidsPublished} CIDs in ${tasks.length} tasks for ${userType} discovery.`);
106
179
  }
107
180
 
108
181
  module.exports = { checkDiscoveryNeed, getDiscoveryCandidates, dispatchDiscovery };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.207",
3
+ "version": "1.0.208",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [