bulltrackers-module 1.0.654 → 1.0.656
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
|
|
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
|
-
//
|
|
1238
|
-
|
|
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
|
-
//
|
|
1248
|
-
|
|
1249
|
-
|
|
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,98 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
|
|
|
1273
1277
|
}
|
|
1274
1278
|
};
|
|
1275
1279
|
|
|
1280
|
+
// 10.4. Find most recent available GlobalAumPerAsset30D data
|
|
1281
|
+
// Looks back up to 7 days to find the most recent available data
|
|
1282
|
+
const findMostRecentGlobalAumData = async (db, startDateStr, maxLookbackDays = 7) => {
|
|
1283
|
+
try {
|
|
1284
|
+
const startDate = new Date(startDateStr);
|
|
1285
|
+
|
|
1286
|
+
// Look back through dates to find the most recent available data
|
|
1287
|
+
for (let i = 0; i < maxLookbackDays; i++) {
|
|
1288
|
+
const checkDate = new Date(startDate);
|
|
1289
|
+
checkDate.setDate(startDate.getDate() - i);
|
|
1290
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
1291
|
+
|
|
1292
|
+
try {
|
|
1293
|
+
const computationData = await getComputationResults(db, 'GlobalAumPerAsset30D', dateKey);
|
|
1294
|
+
|
|
1295
|
+
// If we got valid data (not null/empty), return it
|
|
1296
|
+
if (computationData && typeof computationData === 'object' && Object.keys(computationData).length > 0) {
|
|
1297
|
+
return {
|
|
1298
|
+
date: dateKey,
|
|
1299
|
+
data: computationData
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
} catch (error) {
|
|
1303
|
+
// Missing data for this date - continue looking back
|
|
1304
|
+
console.log(`[GlobalAum] No data found for ${dateKey}, checking previous date...`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// No data found in the lookback period
|
|
1309
|
+
return null;
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
console.error(`[GlobalAum] Error finding most recent data:`, error);
|
|
1312
|
+
throw error;
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
// 10.5. Fetch GlobalAumPerAsset30D with 7-day lookback
|
|
1317
|
+
// Returns the last 7 days of successfully stored data for a specific ticker or all assets
|
|
1318
|
+
const fetchGlobalAumPerAssetWithLookback = async (db, dateStr, ticker = null, lookbackDays = 7) => {
|
|
1319
|
+
try {
|
|
1320
|
+
const endDate = new Date(dateStr);
|
|
1321
|
+
const results = [];
|
|
1322
|
+
|
|
1323
|
+
// Fetch data for the last 7 days, handling missing data gracefully
|
|
1324
|
+
for (let i = 0; i < lookbackDays; i++) {
|
|
1325
|
+
const checkDate = new Date(endDate);
|
|
1326
|
+
checkDate.setDate(endDate.getDate() - i);
|
|
1327
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
1328
|
+
|
|
1329
|
+
try {
|
|
1330
|
+
const computationData = await getComputationResults(db, 'GlobalAumPerAsset30D', dateKey);
|
|
1331
|
+
|
|
1332
|
+
// If we got valid data (not null/empty), add it to results
|
|
1333
|
+
if (computationData && typeof computationData === 'object' && Object.keys(computationData).length > 0) {
|
|
1334
|
+
if (ticker) {
|
|
1335
|
+
// If searching for a specific ticker, only include if it exists
|
|
1336
|
+
const tickerUpper = ticker.toUpperCase();
|
|
1337
|
+
if (computationData[tickerUpper] !== undefined) {
|
|
1338
|
+
results.push({
|
|
1339
|
+
date: dateKey,
|
|
1340
|
+
aum: computationData[tickerUpper],
|
|
1341
|
+
ticker: tickerUpper
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
} else {
|
|
1345
|
+
// Return all assets for this date
|
|
1346
|
+
results.push({
|
|
1347
|
+
date: dateKey,
|
|
1348
|
+
data: computationData
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
// Missing data for this date - skip it (graceful handling)
|
|
1354
|
+
console.log(`[GlobalAum] No data found for ${dateKey}, skipping`);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Sort by date descending (newest first)
|
|
1359
|
+
results.sort((a, b) => {
|
|
1360
|
+
const dateA = new Date(a.date);
|
|
1361
|
+
const dateB = new Date(b.date);
|
|
1362
|
+
return dateB.getTime() - dateA.getTime();
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
return results;
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
console.error(`[GlobalAum] Error fetching with lookback:`, error);
|
|
1368
|
+
throw error;
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1276
1372
|
// 11. Fetch User Notifications
|
|
1277
1373
|
const fetchNotifications = async (firestore, userId, options = {}) => {
|
|
1278
1374
|
const { limit = 20, unreadOnly = false, excludeTypes = ['watchlistAlerts'] } = options;
|
|
@@ -2913,6 +3009,9 @@ const subscribeToAllWatchlistPIs = async (db, userId, watchlistId, alertTypes =
|
|
|
2913
3009
|
module.exports = {
|
|
2914
3010
|
latestUserCentricSnapshot,
|
|
2915
3011
|
pageCollection,
|
|
3012
|
+
findMostRecentGlobalAumData,
|
|
3013
|
+
fetchGlobalAumPerAssetWithLookback,
|
|
3014
|
+
getComputationResults,
|
|
2916
3015
|
fetchPopularInvestorMasterList,
|
|
2917
3016
|
isDeveloper,
|
|
2918
3017
|
lookupCidByEmail,
|
|
@@ -8,10 +8,13 @@ const {
|
|
|
8
8
|
getComputationResults,
|
|
9
9
|
requestPopularInvestorAddition,
|
|
10
10
|
trackPopularInvestorView,
|
|
11
|
-
pageCollection
|
|
11
|
+
pageCollection,
|
|
12
|
+
findMostRecentGlobalAumData,
|
|
13
|
+
fetchGlobalAumPerAssetWithLookback
|
|
12
14
|
} = require('../helpers/data-fetchers/firestore.js');
|
|
13
15
|
const { sanitizeCid } = require('../helpers/security_utils.js');
|
|
14
16
|
const { timeouts } = require('../helpers/timeout_utils.js');
|
|
17
|
+
const { requireFirebaseAuth } = require('../middleware/firebase_auth_middleware.js');
|
|
15
18
|
|
|
16
19
|
const router = express.Router();
|
|
17
20
|
|
|
@@ -255,5 +258,93 @@ router.get('/:piId/analytics', async (req, res) => {
|
|
|
255
258
|
}
|
|
256
259
|
});
|
|
257
260
|
|
|
261
|
+
/**
|
|
262
|
+
* GLOBAL AUM PER ASSET - Public for all signed-in users
|
|
263
|
+
*
|
|
264
|
+
* These routes show how much $ is invested in each asset based on AUM totals of all Popular Investors.
|
|
265
|
+
* - Uses GlobalAumPerAsset30D computation
|
|
266
|
+
* - Access: Public (any signed-in user can view)
|
|
267
|
+
* - Requires Firebase authentication
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
// GET /popular-investors/global-aum - Get top assets by AUM (default: top 10)
|
|
271
|
+
router.get('/global-aum', requireFirebaseAuth, async (req, res, next) => {
|
|
272
|
+
try {
|
|
273
|
+
const { db } = req.dependencies;
|
|
274
|
+
const { date, limit = 10 } = req.query;
|
|
275
|
+
|
|
276
|
+
// Default to today if no date provided
|
|
277
|
+
const targetDate = date || new Date().toISOString().split('T')[0];
|
|
278
|
+
|
|
279
|
+
// Find the most recent available data (looks back up to 7 days if today's data doesn't exist)
|
|
280
|
+
const result = await findMostRecentGlobalAumData(db, targetDate, 7);
|
|
281
|
+
|
|
282
|
+
if (!result || !result.data || Object.keys(result.data).length === 0) {
|
|
283
|
+
return res.status(404).json({
|
|
284
|
+
success: false,
|
|
285
|
+
error: 'No AUM data available in the last 7 days'
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Convert to array and sort by AUM (descending)
|
|
290
|
+
const assetsArray = Object.entries(result.data)
|
|
291
|
+
.map(([ticker, aum]) => ({ ticker, aum: Number(aum) }))
|
|
292
|
+
.sort((a, b) => b.aum - a.aum)
|
|
293
|
+
.slice(0, parseInt(limit) || 10);
|
|
294
|
+
|
|
295
|
+
res.json({
|
|
296
|
+
success: true,
|
|
297
|
+
date: result.date, // Return the actual date of the data found
|
|
298
|
+
count: assetsArray.length,
|
|
299
|
+
data: assetsArray
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
next(error);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// GET /popular-investors/global-aum/:ticker - Get 7-day AUM history for a specific ticker
|
|
307
|
+
router.get('/global-aum/:ticker', requireFirebaseAuth, async (req, res, next) => {
|
|
308
|
+
try {
|
|
309
|
+
const { db } = req.dependencies;
|
|
310
|
+
const { ticker } = req.params;
|
|
311
|
+
const { date, lookback = 7 } = req.query;
|
|
312
|
+
|
|
313
|
+
// Validate ticker
|
|
314
|
+
if (!ticker || typeof ticker !== 'string' || ticker.length === 0) {
|
|
315
|
+
return res.status(400).json({
|
|
316
|
+
success: false,
|
|
317
|
+
error: 'Invalid ticker symbol'
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Default to today if no date provided
|
|
322
|
+
const targetDate = date || new Date().toISOString().split('T')[0];
|
|
323
|
+
|
|
324
|
+
// Validate lookback parameter
|
|
325
|
+
const lookbackDays = Math.min(Math.max(parseInt(lookback) || 7, 1), 30); // Between 1 and 30 days
|
|
326
|
+
|
|
327
|
+
// Fetch 7-day lookback data for the ticker
|
|
328
|
+
const results = await fetchGlobalAumPerAssetWithLookback(db, targetDate, ticker, lookbackDays);
|
|
329
|
+
|
|
330
|
+
if (results.length === 0) {
|
|
331
|
+
return res.status(404).json({
|
|
332
|
+
success: false,
|
|
333
|
+
error: `No AUM data found for ticker ${ticker.toUpperCase()} in the last ${lookbackDays} days`
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
res.json({
|
|
338
|
+
success: true,
|
|
339
|
+
ticker: ticker.toUpperCase(),
|
|
340
|
+
lookbackDays: lookbackDays,
|
|
341
|
+
count: results.length,
|
|
342
|
+
data: results
|
|
343
|
+
});
|
|
344
|
+
} catch (error) {
|
|
345
|
+
next(error);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
258
349
|
|
|
259
350
|
module.exports = router;
|