bulltrackers-module 1.0.573 → 1.0.575
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.
|
@@ -212,14 +212,14 @@ async function findSubscriptionsForPI(db, logger, piCid, alertTypeId, computatio
|
|
|
212
212
|
return subscriptions;
|
|
213
213
|
}
|
|
214
214
|
} else {
|
|
215
|
-
logger.log('WARN', `[findSubscriptionsForPI] WatchlistMembershipData/${computationDate} not found,
|
|
216
|
-
//
|
|
217
|
-
return
|
|
215
|
+
logger.log('WARN', `[findSubscriptionsForPI] WatchlistMembershipData/${computationDate} not found, returning existing subscriptions (dev overrides only)`);
|
|
216
|
+
// Return subscriptions array which may contain dev overrides
|
|
217
|
+
return subscriptions;
|
|
218
218
|
}
|
|
219
219
|
} catch (error) {
|
|
220
220
|
logger.log('ERROR', `[findSubscriptionsForPI] Error loading WatchlistMembershipData: ${error.message}`);
|
|
221
|
-
//
|
|
222
|
-
return
|
|
221
|
+
// Return subscriptions array which may contain dev overrides
|
|
222
|
+
return subscriptions;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
// Step 2: For each user, read their watchlists from SignedInUsers/{cid}/watchlists
|
|
@@ -298,51 +298,6 @@ async function findSubscriptionsForPI(db, logger, piCid, alertTypeId, computatio
|
|
|
298
298
|
return subscriptions;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
/**
|
|
302
|
-
* Legacy fallback method - scans all watchlists (used if WatchlistMembershipData is not available)
|
|
303
|
-
*/
|
|
304
|
-
async function findSubscriptionsForPILegacy(db, logger, piCid, alertTypeId, configKey) {
|
|
305
|
-
const subscriptions = [];
|
|
306
|
-
|
|
307
|
-
logger.log('INFO', `[findSubscriptionsForPILegacy] Using legacy method to find subscriptions for PI ${piCid}`);
|
|
308
|
-
|
|
309
|
-
// Get all watchlists from legacy path
|
|
310
|
-
const watchlistsCollection = db.collection('watchlists');
|
|
311
|
-
const watchlistsSnapshot = await watchlistsCollection.get();
|
|
312
|
-
|
|
313
|
-
for (const userDoc of watchlistsSnapshot.docs) {
|
|
314
|
-
const userCid = Number(userDoc.id);
|
|
315
|
-
const userListsSnapshot = await userDoc.ref.collection('lists').get();
|
|
316
|
-
|
|
317
|
-
for (const listDoc of userListsSnapshot.docs) {
|
|
318
|
-
const listData = listDoc.data();
|
|
319
|
-
|
|
320
|
-
// Check static watchlists
|
|
321
|
-
if (listData.type === 'static' && listData.items && Array.isArray(listData.items)) {
|
|
322
|
-
for (const item of listData.items) {
|
|
323
|
-
if (Number(item.cid) === Number(piCid)) {
|
|
324
|
-
const isTestProbe = alertTypeId === 'TestSystemProbe';
|
|
325
|
-
const isEnabled = item.alertConfig && item.alertConfig[configKey] === true;
|
|
326
|
-
|
|
327
|
-
if (isTestProbe || isEnabled) {
|
|
328
|
-
subscriptions.push({
|
|
329
|
-
userCid: userCid,
|
|
330
|
-
piCid: piCid,
|
|
331
|
-
piUsername: item.username || `PI-${piCid}`,
|
|
332
|
-
watchlistId: listDoc.id,
|
|
333
|
-
watchlistName: listData.name || 'Unnamed Watchlist',
|
|
334
|
-
alertConfig: item.alertConfig
|
|
335
|
-
});
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return subscriptions;
|
|
345
|
-
}
|
|
346
301
|
|
|
347
302
|
/**
|
|
348
303
|
* Check if alert should trigger based on thresholds
|
|
@@ -9,6 +9,7 @@ const { getEffectiveCid, getDevOverride } = require('../dev/dev_helpers');
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Check if a PI exists in a computation date
|
|
12
|
+
* Reads from the new path format: /unified_insights/YYYY-MM-DD/results/popular-investor/computations/PopularInvestorProfileMetrics/pages/{cid}
|
|
12
13
|
* Returns { found: boolean, profileData: object | null, computationData: object | null }
|
|
13
14
|
* @param {object} db - Firestore instance
|
|
14
15
|
* @param {string} insightsCollection - Insights collection name
|
|
@@ -23,72 +24,45 @@ const { getEffectiveCid, getDevOverride } = require('../dev/dev_helpers');
|
|
|
23
24
|
*/
|
|
24
25
|
async function checkPiInComputationDate(db, insightsCollection, resultsSub, compsSub, category, computationName, dateStr, cidStr, logger) {
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
+
// New path format: /unified_insights/YYYY-MM-DD/results/popular-investor/computations/PopularInvestorProfileMetrics/pages/{cid}
|
|
28
|
+
const pageRef = db.collection(insightsCollection)
|
|
27
29
|
.doc(dateStr)
|
|
28
30
|
.collection(resultsSub)
|
|
29
31
|
.doc(category)
|
|
30
32
|
.collection(compsSub)
|
|
31
|
-
.doc(computationName)
|
|
33
|
+
.doc(computationName)
|
|
34
|
+
.collection('pages')
|
|
35
|
+
.doc(cidStr);
|
|
32
36
|
|
|
33
|
-
const
|
|
37
|
+
const pageDoc = await pageRef.get();
|
|
34
38
|
|
|
35
|
-
if (!
|
|
39
|
+
if (!pageDoc.exists) {
|
|
40
|
+
logger.log('INFO', `[checkPiInComputationDate] Page document not found for CID ${cidStr} in date ${dateStr}`);
|
|
36
41
|
return { found: false, profileData: null, computationData: null };
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
const rawData =
|
|
40
|
-
let
|
|
44
|
+
const rawData = pageDoc.data();
|
|
45
|
+
let profileData = null;
|
|
41
46
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
for (let i = 0; i < shardCount; i++) {
|
|
53
|
-
const shardDoc = await shardsCol.doc(`shard_${i}`).get();
|
|
54
|
-
if (shardDoc.exists) {
|
|
55
|
-
const shardData = shardDoc.data();
|
|
56
|
-
Object.assign(computationData, shardData);
|
|
57
|
-
} else {
|
|
58
|
-
logger.log('WARN', `[checkPiInComputationDate] Shard shard_${i} missing for date ${dateStr}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
// Data is in the main document (compressed or raw)
|
|
63
|
-
computationData = tryDecompress(rawData);
|
|
64
|
-
|
|
65
|
-
// Handle string decompression result
|
|
66
|
-
if (typeof computationData === 'string') {
|
|
67
|
-
try {
|
|
68
|
-
computationData = JSON.parse(computationData);
|
|
69
|
-
} catch (e) {
|
|
70
|
-
logger.log('WARN', `[checkPiInComputationDate] Failed to parse decompressed string for date ${dateStr}:`, e.message);
|
|
71
|
-
return { found: false, profileData: null, computationData: null };
|
|
72
|
-
}
|
|
47
|
+
// Decompress if needed
|
|
48
|
+
profileData = tryDecompress(rawData);
|
|
49
|
+
|
|
50
|
+
// Handle string decompression result
|
|
51
|
+
if (typeof profileData === 'string') {
|
|
52
|
+
try {
|
|
53
|
+
profileData = JSON.parse(profileData);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
logger.log('WARN', `[checkPiInComputationDate] Failed to parse decompressed string for CID ${cidStr} on date ${dateStr}:`, e.message);
|
|
56
|
+
return { found: false, profileData: null, computationData: null };
|
|
73
57
|
}
|
|
74
58
|
}
|
|
75
59
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Check if the requested CID exists
|
|
82
|
-
const profileData = computationData[cidStr];
|
|
83
|
-
if (profileData) {
|
|
84
|
-
logger.log('INFO', `[checkPiInComputationDate] Found CID ${cidStr} in date ${dateStr} (total CIDs: ${cids.length})`);
|
|
85
|
-
return { found: true, profileData, computationData };
|
|
86
|
-
} else {
|
|
87
|
-
logger.log('INFO', `[checkPiInComputationDate] CID ${cidStr} not found in date ${dateStr} (total CIDs: ${cids.length})`);
|
|
88
|
-
return { found: false, profileData: null, computationData };
|
|
89
|
-
}
|
|
60
|
+
if (profileData && typeof profileData === 'object') {
|
|
61
|
+
logger.log('INFO', `[checkPiInComputationDate] Found profile data for CID ${cidStr} in date ${dateStr}`);
|
|
62
|
+
// Return the profile data - computationData is set to null since we're reading individual pages
|
|
63
|
+
return { found: true, profileData, computationData: null };
|
|
90
64
|
} else {
|
|
91
|
-
logger.log('WARN', `[checkPiInComputationDate]
|
|
65
|
+
logger.log('WARN', `[checkPiInComputationDate] Profile data is not an object for CID ${cidStr} on date ${dateStr}`);
|
|
92
66
|
return { found: false, profileData: null, computationData: null };
|
|
93
67
|
}
|
|
94
68
|
} catch (error) {
|
|
@@ -174,18 +148,38 @@ async function getUserComputations(req, res, dependencies, config) {
|
|
|
174
148
|
const dateStr = checkDate.toISOString().split('T')[0];
|
|
175
149
|
|
|
176
150
|
try {
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.collection(resultsSub)
|
|
181
|
-
.doc(category)
|
|
182
|
-
.collection(compsSub)
|
|
183
|
-
.doc(firstCompName);
|
|
151
|
+
// For PopularInvestorProfileMetrics, check pages subcollection directly
|
|
152
|
+
// For other computations, check main computation document
|
|
153
|
+
let shouldCheck = false;
|
|
184
154
|
|
|
185
|
-
|
|
155
|
+
if (firstCompName === 'PopularInvestorProfileMetrics') {
|
|
156
|
+
// Check if page document exists for this user
|
|
157
|
+
const pageRef = db.collection(insightsCollection)
|
|
158
|
+
.doc(dateStr)
|
|
159
|
+
.collection(resultsSub)
|
|
160
|
+
.doc(category)
|
|
161
|
+
.collection(compsSub)
|
|
162
|
+
.doc(firstCompName)
|
|
163
|
+
.collection('pages')
|
|
164
|
+
.doc(String(effectiveCid));
|
|
165
|
+
|
|
166
|
+
const pageDoc = await pageRef.get();
|
|
167
|
+
shouldCheck = pageDoc.exists;
|
|
168
|
+
} else {
|
|
169
|
+
// Check if computation document exists for this date
|
|
170
|
+
const computationRef = db.collection(insightsCollection)
|
|
171
|
+
.doc(dateStr)
|
|
172
|
+
.collection(resultsSub)
|
|
173
|
+
.doc(category)
|
|
174
|
+
.collection(compsSub)
|
|
175
|
+
.doc(firstCompName);
|
|
176
|
+
|
|
177
|
+
const computationDoc = await computationRef.get();
|
|
178
|
+
shouldCheck = computationDoc.exists;
|
|
179
|
+
}
|
|
186
180
|
|
|
187
|
-
if (
|
|
188
|
-
// Computation exists, verify user is in it (same as data-status)
|
|
181
|
+
if (shouldCheck) {
|
|
182
|
+
// Computation/page exists, verify user is in it (same as data-status)
|
|
189
183
|
const { found } = await checkPiInComputationDate(
|
|
190
184
|
db,
|
|
191
185
|
insightsCollection,
|
|
@@ -249,94 +243,128 @@ async function getUserComputations(req, res, dependencies, config) {
|
|
|
249
243
|
|
|
250
244
|
for (const compName of computationNames) {
|
|
251
245
|
try {
|
|
252
|
-
|
|
253
|
-
.doc(date)
|
|
254
|
-
.collection(resultsSub)
|
|
255
|
-
.doc(category)
|
|
256
|
-
.collection(compsSub)
|
|
257
|
-
.doc(compName);
|
|
258
|
-
|
|
259
|
-
const doc = await docRef.get();
|
|
246
|
+
let userResult = null;
|
|
260
247
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
248
|
+
// Special handling for PopularInvestorProfileMetrics - read from pages subcollection
|
|
249
|
+
if (compName === 'PopularInvestorProfileMetrics') {
|
|
250
|
+
const pageRef = db.collection(insightsCollection)
|
|
251
|
+
.doc(date)
|
|
252
|
+
.collection(resultsSub)
|
|
253
|
+
.doc(category)
|
|
254
|
+
.collection(compsSub)
|
|
255
|
+
.doc(compName)
|
|
256
|
+
.collection('pages')
|
|
257
|
+
.doc(String(effectiveCid));
|
|
264
258
|
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
data = JSON.parse(data);
|
|
268
|
-
} catch (e) {
|
|
269
|
-
logger.log('WARN', `[getUserComputations] Failed to parse decompressed string for ${compName} on ${date}:`, e.message);
|
|
270
|
-
data = null;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
259
|
+
const pageDoc = await pageRef.get();
|
|
273
260
|
|
|
274
|
-
if (
|
|
275
|
-
const
|
|
276
|
-
|
|
261
|
+
if (pageDoc.exists) {
|
|
262
|
+
const rawData = pageDoc.data();
|
|
263
|
+
let data = tryDecompress(rawData);
|
|
277
264
|
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
265
|
+
if (typeof data === 'string') {
|
|
266
|
+
try {
|
|
267
|
+
data = JSON.parse(data);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
logger.log('WARN', `[getUserComputations] Failed to parse decompressed string for ${compName} page on ${date}:`, e.message);
|
|
270
|
+
data = null;
|
|
283
271
|
}
|
|
284
|
-
} else {
|
|
285
|
-
data = null;
|
|
286
272
|
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// [FIX] Handle meta computations (global results) vs standard computations (user-specific)
|
|
290
|
-
// Use metadata from schema collection to determine computation type
|
|
291
|
-
let userResult = null;
|
|
292
|
-
if (data && typeof data === 'object') {
|
|
293
|
-
const metadata = computationMetadata[compName];
|
|
294
|
-
const isMetaComputation = metadata && metadata.type === 'meta';
|
|
295
273
|
|
|
296
|
-
if (
|
|
297
|
-
// Meta computation: return entire data object (global results)
|
|
274
|
+
if (data && typeof data === 'object') {
|
|
298
275
|
userResult = data;
|
|
299
|
-
} else {
|
|
300
|
-
// Standard computation: extract user-specific result
|
|
301
|
-
const userCidKey = String(effectiveCid);
|
|
302
|
-
userResult = data.hasOwnProperty(userCidKey) ? data[userCidKey] : null;
|
|
303
276
|
}
|
|
304
277
|
}
|
|
278
|
+
} else {
|
|
279
|
+
// Standard path for other computations
|
|
280
|
+
const docRef = db.collection(insightsCollection)
|
|
281
|
+
.doc(date)
|
|
282
|
+
.collection(resultsSub)
|
|
283
|
+
.doc(category)
|
|
284
|
+
.collection(compsSub)
|
|
285
|
+
.doc(compName);
|
|
305
286
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
287
|
+
const doc = await docRef.get();
|
|
288
|
+
|
|
289
|
+
if (doc.exists) {
|
|
290
|
+
const rawData = doc.data();
|
|
291
|
+
let data = tryDecompress(rawData);
|
|
292
|
+
|
|
293
|
+
if (typeof data === 'string') {
|
|
294
|
+
try {
|
|
295
|
+
data = JSON.parse(data);
|
|
296
|
+
} catch (e) {
|
|
297
|
+
logger.log('WARN', `[getUserComputations] Failed to parse decompressed string for ${compName} on ${date}:`, e.message);
|
|
298
|
+
data = null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (data && data._sharded === true && data._shardCount) {
|
|
303
|
+
const shardsCol = docRef.collection('_shards');
|
|
304
|
+
const shardsSnapshot = await shardsCol.get();
|
|
324
305
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
data
|
|
306
|
+
if (!shardsSnapshot.empty) {
|
|
307
|
+
data = {};
|
|
308
|
+
for (const shardDoc of shardsSnapshot.docs) {
|
|
309
|
+
const shardData = shardDoc.data();
|
|
310
|
+
Object.assign(data, shardData);
|
|
330
311
|
}
|
|
331
|
-
}
|
|
332
|
-
|
|
312
|
+
} else {
|
|
313
|
+
data = null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// [FIX] Handle meta computations (global results) vs standard computations (user-specific)
|
|
318
|
+
// Use metadata from schema collection to determine computation type
|
|
319
|
+
if (data && typeof data === 'object') {
|
|
320
|
+
const metadata = computationMetadata[compName];
|
|
321
|
+
const isMetaComputation = metadata && metadata.type === 'meta';
|
|
322
|
+
|
|
323
|
+
if (isMetaComputation) {
|
|
324
|
+
// Meta computation: return entire data object (global results)
|
|
325
|
+
userResult = data;
|
|
326
|
+
} else {
|
|
327
|
+
// Standard computation: extract user-specific result
|
|
328
|
+
const userCidKey = String(effectiveCid);
|
|
329
|
+
userResult = data.hasOwnProperty(userCidKey) ? data[userCidKey] : null;
|
|
330
|
+
}
|
|
333
331
|
}
|
|
334
332
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (isDevOverrideActive && (compName === 'SignedInUserProfileMetrics' || compName === 'SignedInUserCopiedPIs')) {
|
|
336
|
+
if (compName === 'SignedInUserCopiedPIs') {
|
|
337
|
+
userResult = {
|
|
338
|
+
current: devOverride.fakeCopiedPIs,
|
|
339
|
+
past: [],
|
|
340
|
+
all: devOverride.fakeCopiedPIs
|
|
341
|
+
};
|
|
342
|
+
logger.log('INFO', `[getUserComputations] Applied DEV OVERRIDE to SignedInUserCopiedPIs for user ${userCid}`);
|
|
343
|
+
} else if (compName === 'SignedInUserProfileMetrics' && userResult && userResult.copiedPIs) {
|
|
344
|
+
const fakeMirrors = devOverride.fakeCopiedPIs.map(cid => ({
|
|
345
|
+
cid: Number(cid),
|
|
346
|
+
username: `PI-${cid}`,
|
|
347
|
+
invested: 0,
|
|
348
|
+
netProfit: 0,
|
|
349
|
+
value: 0,
|
|
350
|
+
pendingClosure: false,
|
|
351
|
+
isRanked: false
|
|
352
|
+
}));
|
|
353
|
+
|
|
354
|
+
userResult = {
|
|
355
|
+
...userResult,
|
|
356
|
+
copiedPIs: {
|
|
357
|
+
chartType: 'cards',
|
|
358
|
+
data: fakeMirrors
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
logger.log('INFO', `[getUserComputations] Applied DEV OVERRIDE to SignedInUserProfileMetrics.copiedPIs for user ${userCid}`);
|
|
338
362
|
}
|
|
339
363
|
}
|
|
364
|
+
|
|
365
|
+
if (userResult) {
|
|
366
|
+
results[date][compName] = userResult;
|
|
367
|
+
}
|
|
340
368
|
} catch (err) {
|
|
341
369
|
logger.log('WARN', `[getUserComputations] Error fetching ${compName} for ${date}`, err);
|
|
342
370
|
}
|
|
@@ -138,33 +138,14 @@ async function getPiProfile(req, res, dependencies, config) {
|
|
|
138
138
|
if (!foundDate || !profileData) {
|
|
139
139
|
logger.log('WARN', `[getPiProfile] CID ${cid} not found in any checked dates: ${checkedDates.join(', ')}`);
|
|
140
140
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
db,
|
|
144
|
-
insightsCollection,
|
|
145
|
-
resultsSub,
|
|
146
|
-
compsSub,
|
|
147
|
-
category,
|
|
148
|
-
computationName,
|
|
149
|
-
latestDate,
|
|
150
|
-
cidStr,
|
|
151
|
-
logger
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
const allAvailableCids = latestResult.computationData && typeof latestResult.computationData === 'object' && !Array.isArray(latestResult.computationData)
|
|
155
|
-
? Object.keys(latestResult.computationData)
|
|
156
|
-
.filter(key => !key.startsWith('_'))
|
|
157
|
-
.sort()
|
|
158
|
-
: [];
|
|
159
|
-
|
|
141
|
+
// Note: With the new pages subcollection structure, we can't enumerate all available CIDs
|
|
142
|
+
// from a single document, so we skip that debug information
|
|
160
143
|
return res.status(404).json({
|
|
161
144
|
error: "Profile data not found",
|
|
162
145
|
message: `Popular Investor ${cid} does not exist in computation results for the last ${maxDaysBackForPi + 1} days. This PI may not have been processed recently.`,
|
|
163
146
|
debug: {
|
|
164
147
|
searchedCid: cidStr,
|
|
165
148
|
checkedDates: checkedDates,
|
|
166
|
-
totalCidsInLatestDocument: allAvailableCids.length,
|
|
167
|
-
sampleAvailableCids: allAvailableCids.slice(0, 20),
|
|
168
149
|
latestDate: latestDate
|
|
169
150
|
}
|
|
170
151
|
});
|