bulltrackers-module 1.0.453 → 1.0.455

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.
@@ -735,30 +735,42 @@ async function getUserSocialPosts(req, res, dependencies, config) {
735
735
 
736
736
  /**
737
737
  * GET /user/me/instrument-mappings
738
- * Fetches instrument ID to ticker mapping for the frontend
738
+ * Fetches instrument ID to ticker and sector mappings for the frontend
739
739
  */
740
740
  async function getInstrumentMappings(req, res, dependencies, config) {
741
741
  const { db, logger } = dependencies;
742
742
 
743
743
  try {
744
744
  // Fetch from Firestore (same source as computation system)
745
- const tickerToIdDoc = await db.collection('instrument_mappings').doc('etoro_to_ticker').get();
745
+ const [tickerToIdDoc, tickerToSectorDoc] = await Promise.all([
746
+ db.collection('instrument_mappings').doc('etoro_to_ticker').get(),
747
+ db.collection('instrument_sector_mappings').doc('sector_mappings').get()
748
+ ]);
746
749
 
747
750
  if (!tickerToIdDoc.exists) {
748
751
  return res.status(404).json({ error: "Instrument mappings not found" });
749
752
  }
750
753
 
751
754
  const tickerToId = tickerToIdDoc.data();
755
+ const tickerToSector = tickerToSectorDoc.exists ? tickerToSectorDoc.data() : {};
752
756
 
753
757
  // Convert to ID -> Ticker mapping (reverse the mapping)
754
758
  const idToTicker = {};
759
+ const idToSector = {};
760
+
755
761
  for (const [id, ticker] of Object.entries(tickerToId)) {
756
762
  idToTicker[String(id)] = ticker;
763
+ // Map ID -> Sector via ticker
764
+ if (tickerToSector[ticker]) {
765
+ idToSector[String(id)] = tickerToSector[ticker];
766
+ }
757
767
  }
758
768
 
759
769
  return res.status(200).json({
760
770
  instrumentToTicker: idToTicker,
761
- count: Object.keys(idToTicker).length
771
+ instrumentToSector: idToSector,
772
+ count: Object.keys(idToTicker).length,
773
+ sectorCount: Object.keys(idToSector).length
762
774
  });
763
775
 
764
776
  } catch (error) {
@@ -1847,11 +1859,16 @@ async function getPiProfile(req, res, dependencies, config) {
1847
1859
  });
1848
1860
  }
1849
1861
 
1862
+ // Get username from rankings
1863
+ const { getPiUsername } = require('./on_demand_fetch_helpers');
1864
+ const username = await getPiUsername(db, cid, config, logger);
1865
+
1850
1866
  logger.log('SUCCESS', `[getPiProfile] Returning profile data for CID ${cid} from date ${foundDate} (requested: ${today}, latest available: ${latestDate})`);
1851
1867
 
1852
1868
  return res.status(200).json({
1853
1869
  status: 'success',
1854
1870
  cid: cidStr,
1871
+ username: username || null,
1855
1872
  data: profileData,
1856
1873
  isFallback: foundDate !== latestDate || foundDate !== today,
1857
1874
  dataDate: foundDate,
@@ -234,6 +234,47 @@ async function getUserSyncStatus(req, res, dependencies, config) {
234
234
  const latestRequest = requestsSnapshot.docs[0].data();
235
235
  let status = latestRequest.status || 'queued';
236
236
 
237
+ // Check if request is stale (stuck in processing state for too long)
238
+ const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
239
+ const processingStates = ['processing', 'dispatched', 'indexing', 'computing', 'queued'];
240
+ const isProcessingState = processingStates.includes(status);
241
+
242
+ let isStale = false;
243
+ if (isProcessingState) {
244
+ const now = Date.now();
245
+ const createdAt = latestRequest.createdAt?.toDate?.()?.getTime() ||
246
+ latestRequest.createdAt?.toMillis?.() || null;
247
+ const dispatchedAt = latestRequest.dispatchedAt?.toDate?.()?.getTime() ||
248
+ latestRequest.dispatchedAt?.toMillis?.() || null;
249
+ const updatedAt = latestRequest.updatedAt?.toDate?.()?.getTime() ||
250
+ latestRequest.updatedAt?.toMillis?.() || null;
251
+
252
+ // Use the most recent timestamp to determine age
253
+ const referenceTime = dispatchedAt || createdAt || updatedAt;
254
+
255
+ if (referenceTime && (now - referenceTime) > STALE_THRESHOLD_MS) {
256
+ isStale = true;
257
+ logger.log('WARN', `[getUserSyncStatus] Detected stale request ${latestRequest.requestId} for user ${targetCidNum}. Status: ${status}, Age: ${Math.round((now - referenceTime) / 60000)} minutes`);
258
+ }
259
+ }
260
+
261
+ // If stale, mark as failed to stop polling
262
+ if (isStale) {
263
+ status = 'failed';
264
+ const requestDocRef = requestsSnapshot.docs[0].ref;
265
+ try {
266
+ await requestDocRef.update({
267
+ status: 'failed',
268
+ error: 'Request timed out - task may have failed to process. Please try again.',
269
+ failedAt: FieldValue.serverTimestamp(),
270
+ updatedAt: FieldValue.serverTimestamp()
271
+ });
272
+ logger.log('INFO', `[getUserSyncStatus] Marked stale request ${latestRequest.requestId} as failed`);
273
+ } catch (updateErr) {
274
+ logger.log('WARN', `[getUserSyncStatus] Failed to update stale request status`, updateErr);
275
+ }
276
+ }
277
+
237
278
  const response = {
238
279
  success: true,
239
280
  status,
@@ -249,8 +290,11 @@ async function getUserSyncStatus(req, res, dependencies, config) {
249
290
 
250
291
  // Include error details if status is failed
251
292
  if (status === 'failed') {
252
- response.error = latestRequest.error || 'Unknown error occurred';
253
- response.failedAt = latestRequest.failedAt?.toDate?.()?.toISOString() || null;
293
+ response.error = isStale
294
+ ? 'Request timed out - task may have failed to process. Please try again.'
295
+ : (latestRequest.error || 'Unknown error occurred');
296
+ response.failedAt = latestRequest.failedAt?.toDate?.()?.toISOString() ||
297
+ (isStale ? new Date().toISOString() : null);
254
298
  }
255
299
 
256
300
  // Include raw data status if processing
@@ -646,7 +646,7 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
646
646
 
647
647
  logger.log('SUCCESS', `[On-Demand Update] Complete for ${username}`);
648
648
 
649
- // Update request status to completed if this is a sync request
649
+ // Update request status and trigger computation if this is a sync request
650
650
  if (requestId && source === 'on_demand_sync' && db) {
651
651
  try {
652
652
  const requestRef = db.collection('user_sync_requests')
@@ -654,14 +654,87 @@ async function handleOnDemandUserUpdate(taskData, config, dependencies) {
654
654
  .collection('requests')
655
655
  .doc(requestId);
656
656
 
657
- await requestRef.update({
658
- status: 'completed',
659
- completedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
660
- updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
661
- });
662
- logger.log('INFO', `[On-Demand Update] Updated sync request ${requestId} to completed`);
657
+ // CRITICAL: Trigger root data indexer FIRST so computation system knows data exists
658
+ try {
659
+ const { runRootDataIndexer } = require('../../root-data-indexer/index');
660
+
661
+ // Get rootDataIndexer config from injected config object
662
+ const rootDataIndexerConfig = config.rootDataIndexer || {};
663
+
664
+ // Build indexer config with targetDate for single-date indexing
665
+ const indexerConfig = {
666
+ ...rootDataIndexerConfig, // Use injected config (has all collection names)
667
+ targetDate: today // Index only today's date for speed
668
+ };
669
+
670
+ logger.log('INFO', `[On-Demand Update] Triggering root data indexer for date ${today} before computation...`);
671
+ await runRootDataIndexer(indexerConfig, dependencies);
672
+ logger.log('INFO', `[On-Demand Update] Root data indexer completed for date ${today}`);
673
+
674
+ // Update status to indicate indexing is done, computation is being triggered
675
+ await requestRef.update({
676
+ status: 'computing',
677
+ indexedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
678
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
679
+ });
680
+ } catch (indexerError) {
681
+ logger.log('ERROR', `[On-Demand Update] Failed to run root data indexer for ${today}`, indexerError);
682
+ // Continue anyway - computation might still work if index already exists
683
+ // But update status to indicate we tried
684
+ await requestRef.update({
685
+ status: 'computing',
686
+ indexerError: indexerError.message,
687
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
688
+ });
689
+ }
690
+
691
+ // Trigger computation for SignedInUserProfileMetrics
692
+ const { pubsub } = dependencies;
693
+ if (pubsub) {
694
+ const computationTopic = config.computationSystem?.computationTopicStandard || 'computation-tasks';
695
+ const topic = pubsub.topic(computationTopic);
696
+ const crypto = require('crypto');
697
+
698
+ const computationMessage = {
699
+ action: 'RUN_COMPUTATION_DATE',
700
+ computation: 'SignedInUserProfileMetrics',
701
+ date: today,
702
+ pass: '1', // Standard pass
703
+ dispatchId: crypto.randomUUID(),
704
+ triggerReason: 'on_demand_sync',
705
+ resources: 'standard',
706
+ metadata: {
707
+ onDemand: true,
708
+ requestId: requestId,
709
+ userCid: cid,
710
+ username: username
711
+ },
712
+ traceContext: {
713
+ traceId: crypto.randomBytes(16).toString('hex'),
714
+ spanId: crypto.randomBytes(8).toString('hex'),
715
+ sampled: true
716
+ }
717
+ };
718
+
719
+ await topic.publishMessage({
720
+ data: Buffer.from(JSON.stringify(computationMessage))
721
+ });
722
+
723
+ logger.log('INFO', `[On-Demand Update] Triggered computation SignedInUserProfileMetrics for user ${cid} (${username}) for date ${today}`);
724
+
725
+ // Don't mark as completed yet - wait for computation to finish
726
+ // The status will remain 'computing' until computation completes
727
+ } else {
728
+ logger.log('WARN', `[On-Demand Update] PubSub not available, cannot trigger computation`);
729
+ // Mark as completed if we can't trigger computation
730
+ await requestRef.update({
731
+ status: 'completed',
732
+ completedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp(),
733
+ updatedAt: require('@google-cloud/firestore').FieldValue.serverTimestamp()
734
+ });
735
+ }
663
736
  } catch (err) {
664
- logger.log('WARN', `[On-Demand Update] Failed to update request status to completed for ${requestId}`, err);
737
+ logger.log('WARN', `[On-Demand Update] Failed to update request status or trigger computation for ${requestId}`, err);
665
738
  }
666
739
  }
667
740
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.453",
3
+ "version": "1.0.455",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [