bulltrackers-module 1.0.439 → 1.0.441

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.
@@ -2483,6 +2483,7 @@ module.exports = {
2483
2483
  checkIfUserIsPopularInvestor,
2484
2484
  checkIfUserIsPI, // Export for use in review_helpers
2485
2485
  findLatestRankingsDate, // Export for use in on_demand_fetch_helpers
2486
+ tryDecompress, // Export for use in on_demand_fetch_helpers
2486
2487
  trackProfileView,
2487
2488
  getSignedInUserPIPersonalizedMetrics
2488
2489
  };
@@ -275,17 +275,52 @@ async function getPiFetchStatus(req, res, dependencies, config) {
275
275
 
276
276
  if (!requestsSnapshot.empty) {
277
277
  const latestRequest = requestsSnapshot.docs[0].data();
278
- const status = latestRequest.status || 'queued';
278
+ let status = latestRequest.status || 'queued';
279
+
280
+ // If status is 'indexing' or 'computing', check if computation results are now available
281
+ if (status === 'indexing' || status === 'computing') {
282
+ // Re-check if computation results exist
283
+ const checkDate = new Date();
284
+ for (let i = 0; i < 2; i++) { // Check today and yesterday
285
+ const dateStr = new Date(checkDate);
286
+ dateStr.setDate(checkDate.getDate() - i);
287
+ const dateStrFormatted = dateStr.toISOString().split('T')[0];
288
+
289
+ const docRef = db.collection(insightsCollection)
290
+ .doc(dateStrFormatted)
291
+ .collection(resultsSub)
292
+ .doc('popular-investor')
293
+ .collection(compsSub)
294
+ .doc('PopularInvestorProfileMetrics');
295
+
296
+ const doc = await docRef.get();
297
+ if (doc.exists) {
298
+ const { tryDecompress } = require('./data_helpers');
299
+ const data = tryDecompress(doc.data());
300
+
301
+ if (data && data[String(piCidNum)]) {
302
+ // Computation completed! Update status
303
+ status = 'completed';
304
+ await requestsSnapshot.docs[0].ref.update({
305
+ status: 'completed',
306
+ completedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
307
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
308
+ });
309
+ break;
310
+ }
311
+ }
312
+ }
313
+ }
279
314
 
280
315
  const response = {
281
316
  success: true,
282
- dataAvailable: false,
317
+ dataAvailable: status === 'completed',
283
318
  status,
284
319
  requestId: latestRequest.requestId,
285
320
  startedAt: latestRequest.startedAt?.toDate?.()?.toISOString() || null,
286
321
  createdAt: latestRequest.createdAt?.toDate?.()?.toISOString() || null,
287
322
  estimatedCompletion: latestRequest.startedAt
288
- ? new Date(new Date(latestRequest.startedAt.toDate()).getTime() + 5 * 60 * 1000).toISOString()
323
+ ? new Date(new Date(latestRequest.startedAt.toDate()).getTime() + 10 * 60 * 1000).toISOString() // 10 min for computation
289
324
  : null
290
325
  };
291
326
 
@@ -295,6 +330,12 @@ async function getPiFetchStatus(req, res, dependencies, config) {
295
330
  response.failedAt = latestRequest.failedAt?.toDate?.()?.toISOString() || null;
296
331
  }
297
332
 
333
+ // Include raw data status if computing
334
+ if (status === 'computing') {
335
+ response.rawDataStoredAt = latestRequest.rawDataStoredAt?.toDate?.()?.toISOString() || null;
336
+ response.message = 'Raw data stored, computation in progress...';
337
+ }
338
+
298
339
  return res.status(200).json(response);
299
340
  }
300
341
 
@@ -154,12 +154,26 @@ async function handleRequest(message, context, config, dependencies) {
154
154
  await handleUpdate(data, 'single-update', dependencies, config);
155
155
  break;
156
156
  case 'POPULAR_INVESTOR_UPDATE':
157
- // Validate data exists before passing to handler
158
- if (!data) {
159
- logger.log('ERROR', '[TaskEngine] POPULAR_INVESTOR_UPDATE received with no data', { payload });
157
+ // For POPULAR_INVESTOR_UPDATE, the entire payload IS the task data
158
+ // (not wrapped in a 'data' field like other task types)
159
+ // Extract task data from payload, excluding 'type'
160
+ const taskData = data || {
161
+ cid: payload.cid,
162
+ username: payload.username,
163
+ requestId: payload.requestId,
164
+ source: payload.source,
165
+ requestedBy: payload.requestedBy,
166
+ actualRequestedBy: payload.actualRequestedBy,
167
+ metadata: payload.metadata,
168
+ priority: payload.priority
169
+ };
170
+
171
+ if (!taskData || (!taskData.cid && !taskData.username)) {
172
+ logger.log('ERROR', '[TaskEngine] POPULAR_INVESTOR_UPDATE missing required fields (cid or username)', { payload, taskData });
160
173
  return;
161
174
  }
162
- await handlePopularInvestorUpdate(data, config, dependencies);
175
+
176
+ await handlePopularInvestorUpdate(taskData, config, dependencies);
163
177
  break;
164
178
  case 'ON_DEMAND_USER_UPDATE':
165
179
  const onDemandData = data || payload;
@@ -19,7 +19,7 @@ const { shouldTryProxy, recordProxyOutcome, getFailureCount, getMaxFailures } =
19
19
  * @param {object} dependencies - db, logger, proxyManager, batchManager, headerManager.
20
20
  */
21
21
  async function handlePopularInvestorUpdate(taskData, config, dependencies) {
22
- const { logger, proxyManager, batchManager, headerManager, db } = dependencies;
22
+ const { logger, proxyManager, batchManager, headerManager, db, pubsub } = dependencies;
23
23
 
24
24
  // Validate taskData exists and has required fields
25
25
  if (!taskData) {
@@ -299,7 +299,7 @@ async function handlePopularInvestorUpdate(taskData, config, dependencies) {
299
299
 
300
300
  logger.log('SUCCESS', `[PI Update] Completed full update for ${username}`);
301
301
 
302
- // Update request status to completed if this is an on-demand request
302
+ // Update request status and trigger computation if this is an on-demand request
303
303
  if (requestId && source === 'on_demand' && db) {
304
304
  try {
305
305
  const requestRef = db.collection('pi_fetch_requests')
@@ -307,13 +307,82 @@ async function handlePopularInvestorUpdate(taskData, config, dependencies) {
307
307
  .collection('requests')
308
308
  .doc(requestId);
309
309
 
310
+ // Update status to indicate raw data is stored, indexing and computation will be triggered
310
311
  await requestRef.update({
311
- status: 'completed',
312
- completedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
312
+ status: 'indexing',
313
+ rawDataStoredAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
313
314
  updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
314
315
  });
316
+
317
+ // CRITICAL: Trigger root data indexer FIRST so computation system knows data exists
318
+ try {
319
+ const { runRootDataIndexer } = require('../../root-data-indexer/index');
320
+ const indexerConfig = {
321
+ availabilityCollection: config.rootDataIndexer?.availabilityCollection || 'system_root_data_index',
322
+ earliestDate: config.rootDataIndexer?.earliestDate || '2025-08-01',
323
+ collections: config.rootDataIndexer?.collections || {},
324
+ targetDate: today // Index only today's date for speed
325
+ };
326
+
327
+ logger.log('INFO', `[PI Update] Triggering root data indexer for date ${today} before computation...`);
328
+ await runRootDataIndexer(indexerConfig, dependencies);
329
+ logger.log('INFO', `[PI Update] Root data indexer completed for date ${today}`);
330
+
331
+ // Update status to indicate indexing is done, computation is being triggered
332
+ await requestRef.update({
333
+ status: 'computing',
334
+ indexedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
335
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
336
+ });
337
+ } catch (indexerError) {
338
+ logger.log('ERROR', `[PI Update] Failed to run root data indexer for ${today}`, indexerError);
339
+ // Continue anyway - computation might still work if index already exists
340
+ // But update status to indicate we tried
341
+ await requestRef.update({
342
+ status: 'computing',
343
+ indexerError: indexerError.message,
344
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
345
+ });
346
+ }
347
+
348
+ // Trigger computation for PopularInvestorProfileMetrics
349
+ const { pubsub } = dependencies;
350
+ if (pubsub) {
351
+ const computationTopic = config.computationSystem?.computationTopicStandard || 'computation-tasks';
352
+ const topic = pubsub.topic(computationTopic);
353
+ const crypto = require('crypto');
354
+
355
+ const computationMessage = {
356
+ action: 'RUN_COMPUTATION_DATE',
357
+ computation: 'PopularInvestorProfileMetrics',
358
+ date: today,
359
+ pass: '1', // Standard pass
360
+ dispatchId: crypto.randomUUID(),
361
+ triggerReason: 'on_demand_pi_fetch',
362
+ resources: 'standard',
363
+ metadata: {
364
+ onDemand: true,
365
+ requestId: requestId,
366
+ piCid: cid,
367
+ piUsername: username
368
+ },
369
+ traceContext: {
370
+ traceId: crypto.randomBytes(16).toString('hex'),
371
+ spanId: crypto.randomBytes(8).toString('hex'),
372
+ sampled: true
373
+ }
374
+ };
375
+
376
+ await topic.publishMessage({
377
+ data: Buffer.from(JSON.stringify(computationMessage))
378
+ });
379
+
380
+ logger.log('INFO', `[PI Update] Triggered computation PopularInvestorProfileMetrics for PI ${cid} (${username}) for date ${today}`);
381
+ } else {
382
+ logger.log('WARN', `[PI Update] PubSub not available, cannot trigger computation`);
383
+ }
315
384
  } catch (err) {
316
- logger.log('WARN', `[PI Update] Failed to update request status to completed for ${requestId}`, err);
385
+ logger.log('WARN', `[PI Update] Failed to update request status or trigger computation for ${requestId}`, err);
317
386
  }
318
387
  }
319
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.439",
3
+ "version": "1.0.441",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [