bulltrackers-module 1.0.654 → 1.0.655

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.
@@ -1222,7 +1222,7 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
1222
1222
  const shardCount = pointerData._shardCount || 0;
1223
1223
  console.log(`[Computation] Reassembling ${shardCount} shards for ${computationName}`);
1224
1224
 
1225
- if (shardCount === 0) return []; // Or empty object
1225
+ if (shardCount === 0) return {}; // Return empty object for object-based results
1226
1226
 
1227
1227
  // Create an array of promises to fetch all shards in parallel
1228
1228
  const shardPromises = [];
@@ -1233,10 +1233,9 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
1233
1233
 
1234
1234
  const shardSnaps = await Promise.all(shardPromises);
1235
1235
 
1236
- // Reassemble data.
1237
- // Assuming shards contain lists of data that need concatenation.
1238
- // Adjust logic if shards contain maps that need merging.
1239
- let reassembledData = [];
1236
+ // Reassemble data by merging objects (for object-based results like GlobalAumPerAsset30D)
1237
+ // Shards contain partial objects that need to be merged together
1238
+ let reassembledData = {};
1240
1239
 
1241
1240
  shardSnaps.forEach((snap, index) => {
1242
1241
  if (snap.exists) {
@@ -1244,9 +1243,14 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
1244
1243
  // If the shard itself is compressed (common in big data), decompress it
1245
1244
  const content = (data._compressed) ? tryDecompress(data) : data;
1246
1245
 
1247
- // Push to master list. Assuming standard structure { items: [...] } or Array
1248
- // If specific structure isn't known, we push the whole object.
1249
- reassembledData.push(content);
1246
+ // Merge shard contents, ignoring internal metadata fields
1247
+ if (content && typeof content === 'object') {
1248
+ Object.entries(content).forEach(([k, v]) => {
1249
+ if (!k.startsWith('_')) {
1250
+ reassembledData[k] = v;
1251
+ }
1252
+ });
1253
+ }
1250
1254
  } else {
1251
1255
  console.warn(`[Computation] Missing shard_${index} for ${computationName}`);
1252
1256
  }
@@ -1273,6 +1277,62 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
1273
1277
  }
1274
1278
  };
1275
1279
 
1280
+ // 10.5. Fetch GlobalAumPerAsset30D with 7-day lookback
1281
+ // Returns the last 7 days of successfully stored data for a specific ticker or all assets
1282
+ const fetchGlobalAumPerAssetWithLookback = async (db, dateStr, ticker = null, lookbackDays = 7) => {
1283
+ try {
1284
+ const endDate = new Date(dateStr);
1285
+ const results = [];
1286
+
1287
+ // Fetch data for the last 7 days, handling missing data gracefully
1288
+ for (let i = 0; i < lookbackDays; i++) {
1289
+ const checkDate = new Date(endDate);
1290
+ checkDate.setDate(endDate.getDate() - i);
1291
+ const dateKey = checkDate.toISOString().split('T')[0];
1292
+
1293
+ try {
1294
+ const computationData = await getComputationResults(db, 'GlobalAumPerAsset30D', dateKey);
1295
+
1296
+ // If we got valid data (not null/empty), add it to results
1297
+ if (computationData && typeof computationData === 'object' && Object.keys(computationData).length > 0) {
1298
+ if (ticker) {
1299
+ // If searching for a specific ticker, only include if it exists
1300
+ const tickerUpper = ticker.toUpperCase();
1301
+ if (computationData[tickerUpper] !== undefined) {
1302
+ results.push({
1303
+ date: dateKey,
1304
+ aum: computationData[tickerUpper],
1305
+ ticker: tickerUpper
1306
+ });
1307
+ }
1308
+ } else {
1309
+ // Return all assets for this date
1310
+ results.push({
1311
+ date: dateKey,
1312
+ data: computationData
1313
+ });
1314
+ }
1315
+ }
1316
+ } catch (error) {
1317
+ // Missing data for this date - skip it (graceful handling)
1318
+ console.log(`[GlobalAum] No data found for ${dateKey}, skipping`);
1319
+ }
1320
+ }
1321
+
1322
+ // Sort by date descending (newest first)
1323
+ results.sort((a, b) => {
1324
+ const dateA = new Date(a.date);
1325
+ const dateB = new Date(b.date);
1326
+ return dateB.getTime() - dateA.getTime();
1327
+ });
1328
+
1329
+ return results;
1330
+ } catch (error) {
1331
+ console.error(`[GlobalAum] Error fetching with lookback:`, error);
1332
+ throw error;
1333
+ }
1334
+ };
1335
+
1276
1336
  // 11. Fetch User Notifications
1277
1337
  const fetchNotifications = async (firestore, userId, options = {}) => {
1278
1338
  const { limit = 20, unreadOnly = false, excludeTypes = ['watchlistAlerts'] } = options;
@@ -2913,6 +2973,8 @@ const subscribeToAllWatchlistPIs = async (db, userId, watchlistId, alertTypes =
2913
2973
  module.exports = {
2914
2974
  latestUserCentricSnapshot,
2915
2975
  pageCollection,
2976
+ fetchGlobalAumPerAssetWithLookback,
2977
+ getComputationResults,
2916
2978
  fetchPopularInvestorMasterList,
2917
2979
  isDeveloper,
2918
2980
  lookupCidByEmail,
@@ -8,10 +8,12 @@ const {
8
8
  getComputationResults,
9
9
  requestPopularInvestorAddition,
10
10
  trackPopularInvestorView,
11
- pageCollection
11
+ pageCollection,
12
+ fetchGlobalAumPerAssetWithLookback
12
13
  } = require('../helpers/data-fetchers/firestore.js');
13
14
  const { sanitizeCid } = require('../helpers/security_utils.js');
14
15
  const { timeouts } = require('../helpers/timeout_utils.js');
16
+ const { requireFirebaseAuth } = require('../middleware/firebase_auth_middleware.js');
15
17
 
16
18
  const router = express.Router();
17
19
 
@@ -255,5 +257,93 @@ router.get('/:piId/analytics', async (req, res) => {
255
257
  }
256
258
  });
257
259
 
260
+ /**
261
+ * GLOBAL AUM PER ASSET - Public for all signed-in users
262
+ *
263
+ * These routes show how much $ is invested in each asset based on AUM totals of all Popular Investors.
264
+ * - Uses GlobalAumPerAsset30D computation
265
+ * - Access: Public (any signed-in user can view)
266
+ * - Requires Firebase authentication
267
+ */
268
+
269
+ // GET /popular-investors/global-aum - Get top assets by AUM (default: top 10)
270
+ router.get('/global-aum', requireFirebaseAuth, async (req, res, next) => {
271
+ try {
272
+ const { db } = req.dependencies;
273
+ const { date, limit = 10 } = req.query;
274
+
275
+ // Default to today if no date provided
276
+ const targetDate = date || new Date().toISOString().split('T')[0];
277
+
278
+ // Fetch the latest computation result
279
+ const computationData = await getComputationResults(db, 'GlobalAumPerAsset30D', targetDate);
280
+
281
+ if (!computationData || typeof computationData !== 'object' || Object.keys(computationData).length === 0) {
282
+ return res.status(404).json({
283
+ success: false,
284
+ error: 'No AUM data available for the specified date'
285
+ });
286
+ }
287
+
288
+ // Convert to array and sort by AUM (descending)
289
+ const assetsArray = Object.entries(computationData)
290
+ .map(([ticker, aum]) => ({ ticker, aum: Number(aum) }))
291
+ .sort((a, b) => b.aum - a.aum)
292
+ .slice(0, parseInt(limit) || 10);
293
+
294
+ res.json({
295
+ success: true,
296
+ date: targetDate,
297
+ count: assetsArray.length,
298
+ data: assetsArray
299
+ });
300
+ } catch (error) {
301
+ next(error);
302
+ }
303
+ });
304
+
305
+ // GET /popular-investors/global-aum/:ticker - Get 7-day AUM history for a specific ticker
306
+ router.get('/global-aum/:ticker', requireFirebaseAuth, async (req, res, next) => {
307
+ try {
308
+ const { db } = req.dependencies;
309
+ const { ticker } = req.params;
310
+ const { date, lookback = 7 } = req.query;
311
+
312
+ // Validate ticker
313
+ if (!ticker || typeof ticker !== 'string' || ticker.length === 0) {
314
+ return res.status(400).json({
315
+ success: false,
316
+ error: 'Invalid ticker symbol'
317
+ });
318
+ }
319
+
320
+ // Default to today if no date provided
321
+ const targetDate = date || new Date().toISOString().split('T')[0];
322
+
323
+ // Validate lookback parameter
324
+ const lookbackDays = Math.min(Math.max(parseInt(lookback) || 7, 1), 30); // Between 1 and 30 days
325
+
326
+ // Fetch 7-day lookback data for the ticker
327
+ const results = await fetchGlobalAumPerAssetWithLookback(db, targetDate, ticker, lookbackDays);
328
+
329
+ if (results.length === 0) {
330
+ return res.status(404).json({
331
+ success: false,
332
+ error: `No AUM data found for ticker ${ticker.toUpperCase()} in the last ${lookbackDays} days`
333
+ });
334
+ }
335
+
336
+ res.json({
337
+ success: true,
338
+ ticker: ticker.toUpperCase(),
339
+ lookbackDays: lookbackDays,
340
+ count: results.length,
341
+ data: results
342
+ });
343
+ } catch (error) {
344
+ next(error);
345
+ }
346
+ });
347
+
258
348
 
259
349
  module.exports = router;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.654",
3
+ "version": "1.0.655",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [