bulltrackers-module 1.0.588 → 1.0.590
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.
|
@@ -26,7 +26,9 @@ const { getEffectiveCid, getDevOverride } = require('../dev/dev_helpers');
|
|
|
26
26
|
async function checkPiInComputationDate(db, insightsCollection, resultsSub, compsSub, category, computationName, dateStr, cidStr, logger) {
|
|
27
27
|
try {
|
|
28
28
|
// Check if this computation uses the pages subcollection structure
|
|
29
|
-
const usesPagesStructure = computationName === 'PopularInvestorProfileMetrics' ||
|
|
29
|
+
const usesPagesStructure = computationName === 'PopularInvestorProfileMetrics' ||
|
|
30
|
+
computationName === 'SignedInUserProfileMetrics' ||
|
|
31
|
+
computationName === 'SignedInUserPIPersonalizedMetrics';
|
|
30
32
|
|
|
31
33
|
if (usesPagesStructure) {
|
|
32
34
|
// New path format: /unified_insights/YYYY-MM-DD/results/popular-investor/computations/{computationName}/pages/{cid}
|
|
@@ -335,8 +337,10 @@ async function getUserComputations(req, res, dependencies, config) {
|
|
|
335
337
|
let userResult = null;
|
|
336
338
|
|
|
337
339
|
// Special handling for computations that use pages subcollection structure
|
|
338
|
-
//
|
|
339
|
-
if (compName === 'PopularInvestorProfileMetrics' ||
|
|
340
|
+
// PopularInvestorProfileMetrics, SignedInUserProfileMetrics, and SignedInUserPIPersonalizedMetrics use pages subcollection
|
|
341
|
+
if (compName === 'PopularInvestorProfileMetrics' ||
|
|
342
|
+
compName === 'SignedInUserProfileMetrics' ||
|
|
343
|
+
compName === 'SignedInUserPIPersonalizedMetrics') {
|
|
340
344
|
const pageRef = db.collection(insightsCollection)
|
|
341
345
|
.doc(date)
|
|
342
346
|
.collection(resultsSub)
|
|
@@ -8,84 +8,10 @@ const { tryDecompress } = require('../core/compression_helpers');
|
|
|
8
8
|
const { checkIfUserIsPI } = require('../core/user_status_helpers');
|
|
9
9
|
const { getEffectiveCid, getDevOverride } = require('../dev/dev_helpers');
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* Generate sample PI personalized metrics data for dev testing
|
|
13
|
-
* Creates realistic sample data that matches the computation structure 1:1
|
|
14
|
-
*/
|
|
15
|
-
function generateSamplePIPersonalizedMetrics(userCid, rankEntry) {
|
|
16
|
-
const today = new Date().toISOString().split('T')[0];
|
|
17
|
-
const yesterday = new Date();
|
|
18
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
19
|
-
const yesterdayStr = yesterday.toISOString().split('T')[0];
|
|
20
|
-
|
|
21
|
-
// Generate sample base metrics (from PopularInvestorProfileMetrics)
|
|
22
|
-
const baseMetrics = {
|
|
23
|
-
socialEngagement: {
|
|
24
|
-
chartType: 'line',
|
|
25
|
-
data: Array.from({ length: 30 }, (_, i) => {
|
|
26
|
-
const date = new Date();
|
|
27
|
-
date.setDate(date.getDate() - (29 - i));
|
|
28
|
-
return {
|
|
29
|
-
date: date.toISOString().split('T')[0],
|
|
30
|
-
likes: Math.floor(Math.random() * 50) + 10,
|
|
31
|
-
comments: Math.floor(Math.random() * 20) + 5
|
|
32
|
-
};
|
|
33
|
-
})
|
|
34
|
-
},
|
|
35
|
-
profitablePositions: {
|
|
36
|
-
chartType: 'bar',
|
|
37
|
-
data: Array.from({ length: 30 }, (_, i) => {
|
|
38
|
-
const date = new Date();
|
|
39
|
-
date.setDate(date.getDate() - (29 - i));
|
|
40
|
-
return {
|
|
41
|
-
date: date.toISOString().split('T')[0],
|
|
42
|
-
profitableCount: Math.floor(Math.random() * 10) + 5,
|
|
43
|
-
totalCount: Math.floor(Math.random() * 15) + 10
|
|
44
|
-
};
|
|
45
|
-
})
|
|
46
|
-
},
|
|
47
|
-
topWinningPositions: {
|
|
48
|
-
chartType: 'table',
|
|
49
|
-
data: Array.from({ length: 5 }, (_, i) => ({
|
|
50
|
-
instrumentId: 1000 + i,
|
|
51
|
-
instrumentName: `Stock ${i + 1}`,
|
|
52
|
-
netProfit: Math.floor(Math.random() * 1000) + 100,
|
|
53
|
-
invested: Math.floor(Math.random() * 500) + 50
|
|
54
|
-
}))
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
baseMetrics,
|
|
60
|
-
reviewMetrics: {
|
|
61
|
-
overTime: [],
|
|
62
|
-
currentStats: {
|
|
63
|
-
averageRating: 4.5,
|
|
64
|
-
totalReviews: 10,
|
|
65
|
-
distribution: { 1: 0, 2: 0, 3: 1, 4: 3, 5: 6 }
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
profileViews: {
|
|
69
|
-
overTime: [],
|
|
70
|
-
totalViews: 150,
|
|
71
|
-
totalUniqueViews: 50,
|
|
72
|
-
averageDailyViews: 5
|
|
73
|
-
},
|
|
74
|
-
engagementScore: {
|
|
75
|
-
current: 75.5,
|
|
76
|
-
components: {
|
|
77
|
-
reviewScore: 90,
|
|
78
|
-
viewScore: 50,
|
|
79
|
-
copierScore: 80,
|
|
80
|
-
socialScore: 60
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
11
|
/**
|
|
87
12
|
* GET /user/me/pi-personalized-metrics
|
|
88
13
|
* Fetches personalized metrics for a signed-in user who is also a Popular Investor
|
|
14
|
+
* Reads from pages subcollection: /unified_insights/YYYY-MM-DD/results/popular-investor/computations/SignedInUserPIPersonalizedMetrics/pages/{cid}
|
|
89
15
|
*/
|
|
90
16
|
async function getSignedInUserPIPersonalizedMetrics(req, res, dependencies, config) {
|
|
91
17
|
const { db, logger } = dependencies;
|
|
@@ -165,36 +91,19 @@ async function getSignedInUserPIPersonalizedMetrics(req, res, dependencies, conf
|
|
|
165
91
|
});
|
|
166
92
|
}
|
|
167
93
|
|
|
168
|
-
const isDevOverride = devOverride && devOverride.enabled && devOverride.pretendToBePI;
|
|
169
|
-
|
|
170
|
-
// If dev override, generate sample data
|
|
171
|
-
if (isDevOverride) {
|
|
172
|
-
logger.log('INFO', `[getSignedInUserPIPersonalizedMetrics] DEV OVERRIDE: Generating sample PI metrics for effective CID ${effectiveCid}`);
|
|
173
|
-
const sampleData = generateSamplePIPersonalizedMetrics(effectiveCid, rankEntry);
|
|
174
|
-
const today = new Date().toISOString().split('T')[0];
|
|
175
|
-
|
|
176
|
-
return res.status(200).json({
|
|
177
|
-
status: 'success',
|
|
178
|
-
userCid: String(effectiveCid),
|
|
179
|
-
data: sampleData,
|
|
180
|
-
dataDate: today,
|
|
181
|
-
requestedDate: today,
|
|
182
|
-
isFallback: false,
|
|
183
|
-
isDevOverride: true,
|
|
184
|
-
isImpersonating: isImpersonating || false,
|
|
185
|
-
actualCid: Number(userCid)
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
94
|
const insightsCollection = config.unifiedInsightsCollection || 'unified_insights';
|
|
190
95
|
const resultsSub = config.resultsSubcollection || 'results';
|
|
191
96
|
const compsSub = config.computationsSubcollection || 'computations';
|
|
192
97
|
const category = 'popular-investor';
|
|
193
98
|
const computationName = 'SignedInUserPIPersonalizedMetrics';
|
|
194
99
|
const today = new Date().toISOString().split('T')[0];
|
|
100
|
+
const cidStr = String(effectiveCid);
|
|
101
|
+
const maxDaysBack = 30;
|
|
102
|
+
|
|
103
|
+
logger.log('INFO', `[getSignedInUserPIPersonalizedMetrics] Starting search for PI CID: ${effectiveCid}`);
|
|
195
104
|
|
|
196
|
-
// Find latest computation date
|
|
197
|
-
const
|
|
105
|
+
// Find latest available computation date
|
|
106
|
+
const latestDate = await findLatestComputationDate(
|
|
198
107
|
db,
|
|
199
108
|
insightsCollection,
|
|
200
109
|
resultsSub,
|
|
@@ -205,189 +114,97 @@ async function getSignedInUserPIPersonalizedMetrics(req, res, dependencies, conf
|
|
|
205
114
|
30
|
|
206
115
|
);
|
|
207
116
|
|
|
208
|
-
|
|
117
|
+
logger.log('INFO', `[getSignedInUserPIPersonalizedMetrics] Latest computation date found: ${latestDate || 'NONE'}`);
|
|
118
|
+
|
|
119
|
+
if (!latestDate) {
|
|
120
|
+
logger.log('WARN', `[getSignedInUserPIPersonalizedMetrics] No computation document found for ${computationName} in last 30 days`);
|
|
209
121
|
return res.status(200).json({
|
|
210
|
-
status: '
|
|
211
|
-
message: '
|
|
122
|
+
status: 'not_found',
|
|
123
|
+
message: 'No computation data available. Please sync your account to generate personalized metrics.',
|
|
212
124
|
data: null,
|
|
213
|
-
|
|
125
|
+
dataDate: null,
|
|
126
|
+
requestedDate: today,
|
|
127
|
+
fallbackWindowExhausted: true,
|
|
214
128
|
effectiveCid: effectiveCid,
|
|
215
|
-
isImpersonating: isImpersonating || false
|
|
129
|
+
isImpersonating: isImpersonating || false,
|
|
130
|
+
actualCid: Number(userCid)
|
|
216
131
|
});
|
|
217
132
|
}
|
|
218
133
|
|
|
219
|
-
//
|
|
134
|
+
// Try to find the user's page starting from the latest date, then going back up to 30 days
|
|
220
135
|
let foundDate = null;
|
|
221
136
|
let metricsData = null;
|
|
222
137
|
let checkedDates = [];
|
|
223
138
|
|
|
224
|
-
const latestDateObj = new Date(
|
|
139
|
+
const latestDateObj = new Date(latestDate + 'T00:00:00Z');
|
|
225
140
|
|
|
226
|
-
for (let daysBack = 0; daysBack <=
|
|
141
|
+
for (let daysBack = 0; daysBack <= maxDaysBack; daysBack++) {
|
|
227
142
|
const checkDate = new Date(latestDateObj);
|
|
228
143
|
checkDate.setUTCDate(latestDateObj.getUTCDate() - daysBack);
|
|
229
144
|
const dateStr = checkDate.toISOString().split('T')[0];
|
|
230
145
|
checkedDates.push(dateStr);
|
|
231
146
|
|
|
232
|
-
|
|
147
|
+
logger.log('INFO', `[getSignedInUserPIPersonalizedMetrics] Checking date ${dateStr} for CID ${effectiveCid} (${daysBack} days back from latest)`);
|
|
148
|
+
|
|
149
|
+
// Read from pages subcollection: /unified_insights/YYYY-MM-DD/results/popular-investor/computations/SignedInUserPIPersonalizedMetrics/pages/{cid}
|
|
150
|
+
const pageRef = db.collection(insightsCollection)
|
|
233
151
|
.doc(dateStr)
|
|
234
152
|
.collection(resultsSub)
|
|
235
153
|
.doc(category)
|
|
236
154
|
.collection(compsSub)
|
|
237
|
-
.doc(computationName)
|
|
155
|
+
.doc(computationName)
|
|
156
|
+
.collection('pages')
|
|
157
|
+
.doc(cidStr);
|
|
238
158
|
|
|
239
|
-
const
|
|
159
|
+
const pageDoc = await pageRef.get();
|
|
240
160
|
|
|
241
|
-
if (
|
|
242
|
-
const rawData =
|
|
243
|
-
let
|
|
161
|
+
if (pageDoc.exists) {
|
|
162
|
+
const rawData = pageDoc.data();
|
|
163
|
+
let data = tryDecompress(rawData);
|
|
244
164
|
|
|
245
|
-
|
|
165
|
+
// Handle string decompression result
|
|
166
|
+
if (typeof data === 'string') {
|
|
246
167
|
try {
|
|
247
|
-
|
|
168
|
+
data = JSON.parse(data);
|
|
248
169
|
} catch (e) {
|
|
170
|
+
logger.log('WARN', `[getSignedInUserPIPersonalizedMetrics] Failed to parse decompressed string for CID ${effectiveCid} on date ${dateStr}:`, e.message);
|
|
249
171
|
continue;
|
|
250
172
|
}
|
|
251
173
|
}
|
|
252
174
|
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
175
|
+
if (data && typeof data === 'object') {
|
|
176
|
+
foundDate = dateStr;
|
|
177
|
+
metricsData = data;
|
|
178
|
+
logger.log('SUCCESS', `[getSignedInUserPIPersonalizedMetrics] Found personalized metrics for CID ${effectiveCid} in date ${dateStr} (${daysBack} days back from latest)`);
|
|
179
|
+
break;
|
|
180
|
+
} else {
|
|
181
|
+
logger.log('WARN', `[getSignedInUserPIPersonalizedMetrics] Page data is not an object for CID ${effectiveCid} on date ${dateStr}`);
|
|
261
182
|
}
|
|
183
|
+
} else {
|
|
184
|
+
logger.log('INFO', `[getSignedInUserPIPersonalizedMetrics] Page document not found for CID ${effectiveCid} in date ${dateStr}`);
|
|
262
185
|
}
|
|
263
186
|
}
|
|
264
187
|
|
|
188
|
+
// If not found in any checked date, return response indicating sync is needed
|
|
265
189
|
if (!foundDate || !metricsData) {
|
|
190
|
+
logger.log('WARN', `[getSignedInUserPIPersonalizedMetrics] CID ${effectiveCid} not found in any checked dates: ${checkedDates.join(', ')}`);
|
|
191
|
+
|
|
266
192
|
return res.status(200).json({
|
|
267
|
-
status: '
|
|
268
|
-
message: '
|
|
193
|
+
status: 'not_found',
|
|
194
|
+
message: 'No personalized metrics found for this user. Please sync your account to generate personalized metrics.',
|
|
269
195
|
data: null,
|
|
270
|
-
|
|
196
|
+
dataDate: null,
|
|
197
|
+
requestedDate: today,
|
|
198
|
+
latestComputationDate: latestDate,
|
|
271
199
|
checkedDates: checkedDates,
|
|
200
|
+
fallbackWindowExhausted: true,
|
|
272
201
|
effectiveCid: effectiveCid,
|
|
273
|
-
isImpersonating: isImpersonating || false
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Enhance with review metrics and profile views
|
|
278
|
-
const reviewsCollection = config.reviewsCollection || 'pi_reviews';
|
|
279
|
-
const reviewsSnapshot = await db.collection(reviewsCollection)
|
|
280
|
-
.where('piCid', '==', Number(effectiveCid))
|
|
281
|
-
.orderBy('createdAt', 'desc')
|
|
282
|
-
.limit(1000)
|
|
283
|
-
.get();
|
|
284
|
-
|
|
285
|
-
const reviewTimeMap = new Map();
|
|
286
|
-
let totalReviews = 0;
|
|
287
|
-
let totalRating = 0;
|
|
288
|
-
const ratingDistribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
289
|
-
|
|
290
|
-
reviewsSnapshot.forEach(doc => {
|
|
291
|
-
const review = doc.data();
|
|
292
|
-
const rating = review.rating || 0;
|
|
293
|
-
totalReviews++;
|
|
294
|
-
totalRating += rating;
|
|
295
|
-
ratingDistribution[rating] = (ratingDistribution[rating] || 0) + 1;
|
|
296
|
-
|
|
297
|
-
if (review.createdAt) {
|
|
298
|
-
const reviewDate = review.createdAt.toDate ? review.createdAt.toDate().toISOString().split('T')[0] : review.createdAt;
|
|
299
|
-
const existing = reviewTimeMap.get(reviewDate) || { date: reviewDate, count: 0, totalRating: 0 };
|
|
300
|
-
existing.count++;
|
|
301
|
-
existing.totalRating += rating;
|
|
302
|
-
reviewTimeMap.set(reviewDate, existing);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const reviewMetricsOverTime = Array.from(reviewTimeMap.values())
|
|
307
|
-
.map(entry => ({
|
|
308
|
-
date: entry.date,
|
|
309
|
-
averageRating: entry.count > 0 ? Number((entry.totalRating / entry.count).toFixed(2)) : 0,
|
|
310
|
-
count: entry.count
|
|
311
|
-
}))
|
|
312
|
-
.sort((a, b) => a.date.localeCompare(b.date));
|
|
313
|
-
|
|
314
|
-
metricsData.reviewMetrics = {
|
|
315
|
-
overTime: reviewMetricsOverTime,
|
|
316
|
-
currentStats: {
|
|
317
|
-
averageRating: totalReviews > 0 ? Number((totalRating / totalReviews).toFixed(2)) : 0,
|
|
318
|
-
totalReviews: totalReviews,
|
|
319
|
-
distribution: ratingDistribution
|
|
320
|
-
},
|
|
321
|
-
sentimentTrend: []
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
// Fetch profile views
|
|
325
|
-
const profileViewsCollection = config.profileViewsCollection || 'profile_views';
|
|
326
|
-
const viewsSnapshot = await db.collection(profileViewsCollection)
|
|
327
|
-
.where('piCid', '==', Number(effectiveCid))
|
|
328
|
-
.limit(90)
|
|
329
|
-
.get();
|
|
330
|
-
|
|
331
|
-
const viewsOverTime = [];
|
|
332
|
-
let totalViews = 0;
|
|
333
|
-
let totalUniqueViews = 0;
|
|
334
|
-
const uniqueViewersSet = new Set();
|
|
335
|
-
const viewsByDate = new Map();
|
|
336
|
-
|
|
337
|
-
viewsSnapshot.forEach(doc => {
|
|
338
|
-
const viewData = doc.data();
|
|
339
|
-
const date = viewData.date;
|
|
340
|
-
if (!date) return;
|
|
341
|
-
|
|
342
|
-
const views = typeof viewData.totalViews === 'number' ? viewData.totalViews : 0;
|
|
343
|
-
const uniqueViewers = Array.isArray(viewData.uniqueViewers) ? viewData.uniqueViewers : [];
|
|
344
|
-
|
|
345
|
-
const existing = viewsByDate.get(date) || { date, views: 0, uniqueViewers: [] };
|
|
346
|
-
existing.views += views;
|
|
347
|
-
existing.uniqueViewers = [...new Set([...existing.uniqueViewers, ...uniqueViewers])];
|
|
348
|
-
viewsByDate.set(date, existing);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
for (const [date, data] of viewsByDate.entries()) {
|
|
352
|
-
totalViews += data.views;
|
|
353
|
-
data.uniqueViewers.forEach(v => uniqueViewersSet.add(v));
|
|
354
|
-
|
|
355
|
-
viewsOverTime.push({
|
|
356
|
-
date: date,
|
|
357
|
-
views: data.views,
|
|
358
|
-
uniqueViews: data.uniqueViewers.length
|
|
202
|
+
isImpersonating: isImpersonating || false,
|
|
203
|
+
actualCid: Number(userCid)
|
|
359
204
|
});
|
|
360
205
|
}
|
|
361
206
|
|
|
362
|
-
|
|
363
|
-
const averageDailyViews = viewsOverTime.length > 0 ? Number((totalViews / viewsOverTime.length).toFixed(2)) : 0;
|
|
364
|
-
|
|
365
|
-
metricsData.profileViews = {
|
|
366
|
-
overTime: viewsOverTime.sort((a, b) => a.date.localeCompare(b.date)),
|
|
367
|
-
totalViews: totalViews,
|
|
368
|
-
totalUniqueViews: totalUniqueViews,
|
|
369
|
-
averageDailyViews: averageDailyViews
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
// Calculate engagement score
|
|
373
|
-
const reviewScore = metricsData.reviewMetrics.currentStats.averageRating * 20;
|
|
374
|
-
const viewScore = Math.min(100, (averageDailyViews / 10) * 100);
|
|
375
|
-
const copierScore = rankEntry.Copiers ? Math.min(100, (rankEntry.Copiers / 1000) * 100) : 0;
|
|
376
|
-
const socialScore = metricsData.baseMetrics?.socialEngagement?.data?.length > 0 ? Math.min(100, (metricsData.baseMetrics.socialEngagement.data.length / 10) * 100) : 0;
|
|
377
|
-
|
|
378
|
-
const engagementScore = (reviewScore * 0.3 + viewScore * 0.2 + copierScore * 0.3 + socialScore * 0.2);
|
|
379
|
-
|
|
380
|
-
metricsData.engagementScore = {
|
|
381
|
-
current: Number(engagementScore.toFixed(2)),
|
|
382
|
-
components: {
|
|
383
|
-
reviewScore: Number(reviewScore.toFixed(2)),
|
|
384
|
-
viewScore: Number(viewScore.toFixed(2)),
|
|
385
|
-
copierScore: Number(copierScore.toFixed(2)),
|
|
386
|
-
socialScore: Number(socialScore.toFixed(2))
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
logger.log('SUCCESS', `[getSignedInUserPIPersonalizedMetrics] Returning personalized metrics for effective CID ${effectiveCid} from date ${foundDate}`);
|
|
207
|
+
logger.log('SUCCESS', `[getSignedInUserPIPersonalizedMetrics] Returning personalized metrics for CID ${effectiveCid} from date ${foundDate} (requested: ${today}, latest available: ${latestDate})`);
|
|
391
208
|
|
|
392
209
|
return res.status(200).json({
|
|
393
210
|
status: 'success',
|
|
@@ -395,7 +212,8 @@ async function getSignedInUserPIPersonalizedMetrics(req, res, dependencies, conf
|
|
|
395
212
|
data: metricsData,
|
|
396
213
|
dataDate: foundDate,
|
|
397
214
|
requestedDate: today,
|
|
398
|
-
|
|
215
|
+
latestComputationDate: latestDate,
|
|
216
|
+
isFallback: foundDate !== latestDate || foundDate !== today,
|
|
399
217
|
daysBackFromLatest: checkedDates.indexOf(foundDate),
|
|
400
218
|
isImpersonating: isImpersonating || false,
|
|
401
219
|
actualCid: Number(userCid),
|
|
@@ -409,7 +227,5 @@ async function getSignedInUserPIPersonalizedMetrics(req, res, dependencies, conf
|
|
|
409
227
|
}
|
|
410
228
|
|
|
411
229
|
module.exports = {
|
|
412
|
-
getSignedInUserPIPersonalizedMetrics
|
|
413
|
-
generateSamplePIPersonalizedMetrics
|
|
230
|
+
getSignedInUserPIPersonalizedMetrics
|
|
414
231
|
};
|
|
415
|
-
|
|
@@ -405,32 +405,52 @@ async function finalizeOnDemandRequest(deps, config, taskData, isPI, success, to
|
|
|
405
405
|
// 2. Trigger Computations (only if root data indexer completed successfully)
|
|
406
406
|
if (indexerCompleted && pubsub && config.computationSystem) {
|
|
407
407
|
const { triggerComputationWithDependencies } = require('../../computation-system/helpers/on_demand_helpers');
|
|
408
|
+
const { checkIfUserIsPI } = require('../../generic-api/user-api/helpers/core/user_status_helpers');
|
|
408
409
|
|
|
409
410
|
// Use userType from metadata if available, otherwise fall back to isPI
|
|
410
411
|
const userType = metadata?.userType || (isPI ? 'POPULAR_INVESTOR' : 'SIGNED_IN_USER');
|
|
411
412
|
|
|
412
413
|
// Determine which computations to run based on userType
|
|
413
|
-
|
|
414
|
+
// Use a Set to avoid duplicate computation names
|
|
415
|
+
const compsSet = new Set();
|
|
414
416
|
if (userType === 'POPULAR_INVESTOR') {
|
|
415
|
-
|
|
417
|
+
compsSet.add('PopularInvestorProfileMetrics');
|
|
416
418
|
// PIs might also be signed-in users
|
|
417
419
|
const userDoc = await db.collection('signed_in_users').doc(String(cid)).get();
|
|
418
420
|
if (userDoc.exists) {
|
|
419
|
-
|
|
421
|
+
compsSet.add('SignedInUserPIPersonalizedMetrics');
|
|
420
422
|
}
|
|
421
423
|
} else if (userType === 'SIGNED_IN_USER') {
|
|
422
|
-
|
|
424
|
+
// Signed-in users get standard signed-in user computations
|
|
425
|
+
compsSet.add('SignedInUserProfileMetrics');
|
|
426
|
+
compsSet.add('SignedInUserCopiedList');
|
|
427
|
+
compsSet.add('SignedInUserCopiedPIs');
|
|
428
|
+
compsSet.add('SignedInUserPastCopies');
|
|
429
|
+
|
|
430
|
+
// IMPORTANT: Check if this signed-in user is also a Popular Investor
|
|
431
|
+
// If they are, we need to run PI computations as well
|
|
432
|
+
const rankEntry = await checkIfUserIsPI(db, cid, config, logger);
|
|
433
|
+
if (rankEntry) {
|
|
434
|
+
logger.log('INFO', `[On-Demand] Signed-in user ${cid} is also a Popular Investor. Adding PI computations.`);
|
|
435
|
+
compsSet.add('PopularInvestorProfileMetrics');
|
|
436
|
+
compsSet.add('SignedInUserPIPersonalizedMetrics');
|
|
437
|
+
}
|
|
423
438
|
} else {
|
|
424
439
|
// Fallback to isPI-based logic for backward compatibility
|
|
425
440
|
if (isPI) {
|
|
426
|
-
|
|
441
|
+
compsSet.add('PopularInvestorProfileMetrics');
|
|
427
442
|
const userDoc = await db.collection('signed_in_users').doc(String(cid)).get();
|
|
428
|
-
if (userDoc.exists)
|
|
443
|
+
if (userDoc.exists) compsSet.add('SignedInUserPIPersonalizedMetrics');
|
|
429
444
|
} else {
|
|
430
|
-
|
|
445
|
+
compsSet.add('SignedInUserProfileMetrics');
|
|
446
|
+
compsSet.add('SignedInUserCopiedList');
|
|
447
|
+
compsSet.add('SignedInUserCopiedPIs');
|
|
448
|
+
compsSet.add('SignedInUserPastCopies');
|
|
431
449
|
}
|
|
432
450
|
}
|
|
433
451
|
|
|
452
|
+
// Convert Set to Array for logging and iteration
|
|
453
|
+
const comps = Array.from(compsSet);
|
|
434
454
|
logger.log('INFO', `[On-Demand] Triggering ${comps.length} computations for ${userType} (${username}): ${comps.join(', ')}`);
|
|
435
455
|
|
|
436
456
|
for (const comp of comps) {
|