bulltrackers-module 1.0.578 → 1.0.580
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.
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* REFACTORED: Fully supports granular flags for PI, Signed-In Users, Rankings, and Verification.
|
|
4
4
|
* FIXED: Removed permissive fallbacks to enforce strict userType availability rules.
|
|
5
5
|
* UPDATED: Added strict social data checking for Popular Investors and Signed-In Users.
|
|
6
|
+
* UPDATED: Added Optimistic Series Permission. Allows execution if "Today's" data is missing but a Lookback Series is requested.
|
|
6
7
|
*/
|
|
7
8
|
const { normalizeName } = require('../utils/utils');
|
|
8
9
|
|
|
@@ -24,37 +25,49 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
24
25
|
let isAvailable = false;
|
|
25
26
|
|
|
26
27
|
if (dep === 'portfolio') {
|
|
27
|
-
if (userType
|
|
28
|
-
else if (userType === 'normal'
|
|
29
|
-
else if (userType === 'popular_investor' && rootDataStatus.piPortfolios)
|
|
30
|
-
else if (userType === 'signed_in_user'
|
|
31
|
-
else if (userType === 'all'
|
|
32
|
-
// REMOVED: Unconditional fallback that bypassed strict userType checks
|
|
28
|
+
if (userType === 'speculator' && rootDataStatus.speculatorPortfolio) isAvailable = true;
|
|
29
|
+
else if (userType === 'normal' && rootDataStatus.normalPortfolio) isAvailable = true;
|
|
30
|
+
else if (userType === 'popular_investor' && rootDataStatus.piPortfolios) isAvailable = true;
|
|
31
|
+
else if (userType === 'signed_in_user' && rootDataStatus.signedInUserPortfolio) isAvailable = true;
|
|
32
|
+
else if (userType === 'all' && rootDataStatus.hasPortfolio) isAvailable = true;
|
|
33
33
|
|
|
34
34
|
if (!isAvailable) {
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
36
|
+
// If Today's Portfolio is missing, but the computation explicitly asks for a Series (History),
|
|
37
|
+
// we allow it to proceed optimistically, assuming historical data might exist.
|
|
38
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
39
|
+
available.push('portfolio');
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (userType === 'speculator') missing.push('speculatorPortfolio');
|
|
44
|
+
else if (userType === 'normal') missing.push('normalPortfolio');
|
|
37
45
|
else if (userType === 'popular_investor') missing.push('piPortfolios');
|
|
38
|
-
else if (userType === 'signed_in_user')
|
|
39
|
-
else
|
|
46
|
+
else if (userType === 'signed_in_user') missing.push('signedInUserPortfolio');
|
|
47
|
+
else missing.push('portfolio');
|
|
40
48
|
} else {
|
|
41
49
|
available.push('portfolio');
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
|
-
else if (dep
|
|
45
|
-
if (userType
|
|
46
|
-
else if (userType === 'normal'
|
|
47
|
-
else if (userType === 'popular_investor' && rootDataStatus.piHistory)
|
|
48
|
-
else if (userType === 'signed_in_user'
|
|
49
|
-
else if (userType === 'all'
|
|
50
|
-
// REMOVED: Unconditional fallback that bypassed strict userType checks
|
|
52
|
+
else if (dep === 'history') {
|
|
53
|
+
if (userType === 'speculator' && rootDataStatus.speculatorHistory) isAvailable = true;
|
|
54
|
+
else if (userType === 'normal' && rootDataStatus.normalHistory) isAvailable = true;
|
|
55
|
+
else if (userType === 'popular_investor' && rootDataStatus.piHistory) isAvailable = true;
|
|
56
|
+
else if (userType === 'signed_in_user' && rootDataStatus.signedInUserHistory) isAvailable = true;
|
|
57
|
+
else if (userType === 'all' && rootDataStatus.hasHistory) isAvailable = true;
|
|
51
58
|
|
|
52
59
|
if (!isAvailable) {
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
61
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
62
|
+
available.push('history');
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (userType === 'speculator') missing.push('speculatorHistory');
|
|
67
|
+
else if (userType === 'normal') missing.push('normalHistory');
|
|
55
68
|
else if (userType === 'popular_investor') missing.push('piHistory');
|
|
56
|
-
else if (userType === 'signed_in_user')
|
|
57
|
-
else
|
|
69
|
+
else if (userType === 'signed_in_user') missing.push('signedInUserHistory');
|
|
70
|
+
else missing.push('history');
|
|
58
71
|
} else {
|
|
59
72
|
available.push('history');
|
|
60
73
|
}
|
|
@@ -64,7 +77,12 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
64
77
|
isAvailable = true;
|
|
65
78
|
available.push('rankings');
|
|
66
79
|
} else {
|
|
67
|
-
|
|
80
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
81
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
82
|
+
available.push('rankings');
|
|
83
|
+
} else {
|
|
84
|
+
missing.push('piRankings');
|
|
85
|
+
}
|
|
68
86
|
}
|
|
69
87
|
}
|
|
70
88
|
else if (dep === 'verification') {
|
|
@@ -80,27 +98,35 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
80
98
|
isAvailable = true;
|
|
81
99
|
available.push('insights');
|
|
82
100
|
} else {
|
|
83
|
-
|
|
101
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
102
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
103
|
+
available.push('insights');
|
|
104
|
+
} else {
|
|
105
|
+
missing.push('insights');
|
|
106
|
+
}
|
|
84
107
|
}
|
|
85
108
|
}
|
|
86
|
-
// [UPDATED] Strict Social Data Checking
|
|
87
109
|
else if (dep === 'social') {
|
|
88
|
-
// Strictly check based on userType to prevent fallback to generic social data
|
|
89
110
|
if (userType === 'popular_investor') {
|
|
90
|
-
if (rootDataStatus.hasPISocial)
|
|
111
|
+
if (rootDataStatus.hasPISocial) isAvailable = true;
|
|
91
112
|
} else if (userType === 'signed_in_user') {
|
|
92
|
-
if (rootDataStatus.hasSignedInSocial)
|
|
113
|
+
if (rootDataStatus.hasSignedInSocial) isAvailable = true;
|
|
93
114
|
} else {
|
|
94
|
-
|
|
95
|
-
if (rootDataStatus.hasSocial) isAvailable = true;
|
|
115
|
+
if (rootDataStatus.hasSocial) isAvailable = true;
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
if (isAvailable) {
|
|
99
119
|
available.push('social');
|
|
100
120
|
} else {
|
|
101
|
-
|
|
121
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
122
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
123
|
+
available.push('social');
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (userType === 'popular_investor') missing.push('piSocial');
|
|
102
128
|
else if (userType === 'signed_in_user') missing.push('signedInSocial');
|
|
103
|
-
else
|
|
129
|
+
else missing.push('social');
|
|
104
130
|
}
|
|
105
131
|
}
|
|
106
132
|
else if (dep === 'price') {
|
|
@@ -108,7 +134,12 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
108
134
|
isAvailable = true;
|
|
109
135
|
available.push('price');
|
|
110
136
|
} else {
|
|
111
|
-
|
|
137
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
138
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
139
|
+
available.push('price');
|
|
140
|
+
} else {
|
|
141
|
+
missing.push('price');
|
|
142
|
+
}
|
|
112
143
|
}
|
|
113
144
|
}
|
|
114
145
|
// [NEW] New Root Data Types for Profile Metrics
|
|
@@ -117,7 +148,12 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
117
148
|
isAvailable = true;
|
|
118
149
|
available.push('ratings');
|
|
119
150
|
} else {
|
|
120
|
-
|
|
151
|
+
// [OPTIMIZATION] Optimistic Series Check (Vital for sparse data like ratings)
|
|
152
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
153
|
+
available.push('ratings');
|
|
154
|
+
} else {
|
|
155
|
+
missing.push('piRatings');
|
|
156
|
+
}
|
|
121
157
|
}
|
|
122
158
|
}
|
|
123
159
|
else if (dep === 'pageViews') {
|
|
@@ -125,7 +161,12 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
125
161
|
isAvailable = true;
|
|
126
162
|
available.push('pageViews');
|
|
127
163
|
} else {
|
|
128
|
-
|
|
164
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
165
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
166
|
+
available.push('pageViews');
|
|
167
|
+
} else {
|
|
168
|
+
missing.push('piPageViews');
|
|
169
|
+
}
|
|
129
170
|
}
|
|
130
171
|
}
|
|
131
172
|
else if (dep === 'watchlist') {
|
|
@@ -133,7 +174,12 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
133
174
|
isAvailable = true;
|
|
134
175
|
available.push('watchlist');
|
|
135
176
|
} else {
|
|
136
|
-
|
|
177
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
178
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
179
|
+
available.push('watchlist');
|
|
180
|
+
} else {
|
|
181
|
+
missing.push('watchlistMembership');
|
|
182
|
+
}
|
|
137
183
|
}
|
|
138
184
|
}
|
|
139
185
|
else if (dep === 'alerts') {
|
|
@@ -141,7 +187,14 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
141
187
|
isAvailable = true;
|
|
142
188
|
available.push('alerts');
|
|
143
189
|
} else {
|
|
144
|
-
|
|
190
|
+
// [OPTIMIZATION] Optimistic Series Check
|
|
191
|
+
// This explicitly solves the Alert History edge case where today's snapshot
|
|
192
|
+
// might be missing but the lookback period contains data.
|
|
193
|
+
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
194
|
+
available.push('alerts');
|
|
195
|
+
} else {
|
|
196
|
+
missing.push('piAlertHistory');
|
|
197
|
+
}
|
|
145
198
|
}
|
|
146
199
|
}
|
|
147
200
|
}
|
|
@@ -244,12 +297,28 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
244
297
|
logger.log('WARN', `[Availability] Index not found for ${dateStr}. Assuming NO data.`);
|
|
245
298
|
return {
|
|
246
299
|
status: {
|
|
247
|
-
hasPortfolio: false,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
300
|
+
hasPortfolio: false,
|
|
301
|
+
hasHistory: false,
|
|
302
|
+
hasSocial: false,
|
|
303
|
+
hasInsights: false,
|
|
304
|
+
hasPrices: false,
|
|
305
|
+
speculatorPortfolio: false,
|
|
306
|
+
normalPortfolio: false,
|
|
307
|
+
speculatorHistory: false,
|
|
308
|
+
normalHistory: false,
|
|
309
|
+
piRankings: false,
|
|
310
|
+
piPortfolios: false,
|
|
311
|
+
piDeepPortfolios: false,
|
|
312
|
+
piHistory: false,
|
|
313
|
+
signedInUserPortfolio: false,
|
|
314
|
+
signedInUserHistory: false,
|
|
315
|
+
signedInUserVerification: false,
|
|
316
|
+
hasPISocial: false,
|
|
317
|
+
hasSignedInSocial: false,
|
|
318
|
+
piRatings: false,
|
|
319
|
+
piPageViews: false,
|
|
320
|
+
watchlistMembership: false,
|
|
321
|
+
piAlertHistory: false
|
|
253
322
|
}
|
|
254
323
|
};
|
|
255
324
|
}
|
|
@@ -209,56 +209,99 @@ async function getUserComputations(req, res, dependencies, config) {
|
|
|
209
209
|
|
|
210
210
|
const isDevOverrideActive = devOverride && devOverride.enabled && devOverride.fakeCopiedPIs.length > 0;
|
|
211
211
|
|
|
212
|
+
// Check if any of the requested computations are meta computations
|
|
213
|
+
const firstCompName = computationNames[0];
|
|
214
|
+
const firstCompMetadata = computationMetadata[firstCompName];
|
|
215
|
+
const isMetaComputation = firstCompMetadata && firstCompMetadata.type === 'meta';
|
|
216
|
+
|
|
212
217
|
let datesToCheck = [today];
|
|
213
218
|
|
|
214
219
|
if (mode === 'latest') {
|
|
215
|
-
// Use same logic as data-status: search backwards and verify user exists
|
|
216
|
-
// This ensures we find the same date that data-status found
|
|
217
|
-
const firstCompName = computationNames[0];
|
|
218
|
-
const maxDaysBack = 30; // Match data-status search window
|
|
219
220
|
let foundDate = null;
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const dateStr = checkDate.toISOString().split('T')[0];
|
|
222
|
+
if (isMetaComputation) {
|
|
223
|
+
// For meta computations: check today first, then look back 7 days
|
|
224
|
+
const maxDaysBack = 7;
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
226
|
+
for (let daysBack = 0; daysBack < maxDaysBack; daysBack++) {
|
|
227
|
+
const checkDate = new Date();
|
|
228
|
+
checkDate.setDate(checkDate.getDate() - daysBack);
|
|
229
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
// For meta computations, just check if the document exists
|
|
233
|
+
const computationRef = db.collection(insightsCollection)
|
|
234
|
+
.doc(dateStr)
|
|
235
|
+
.collection(resultsSub)
|
|
236
|
+
.doc(category)
|
|
237
|
+
.collection(compsSub)
|
|
238
|
+
.doc(firstCompName);
|
|
239
|
+
|
|
240
|
+
const computationDoc = await computationRef.get();
|
|
241
|
+
|
|
242
|
+
if (computationDoc.exists) {
|
|
243
|
+
foundDate = dateStr;
|
|
244
|
+
if (dateStr !== today) {
|
|
245
|
+
logger.log('INFO', `[getUserComputations] Meta computation ${firstCompName} found on fallback date ${foundDate} (today: ${today})`);
|
|
246
|
+
} else {
|
|
247
|
+
logger.log('INFO', `[getUserComputations] Meta computation ${firstCompName} found on today's date`);
|
|
248
|
+
}
|
|
249
|
+
break; // Found document, stop searching
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
// Continue to next date if error
|
|
253
|
+
logger.log('DEBUG', `[getUserComputations] Error checking date ${dateStr} for meta computation:`, error.message);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
// For user-specific computations: use same logic as data-status
|
|
259
|
+
// Search backwards and verify user exists
|
|
260
|
+
const maxDaysBack = 30; // Match data-status search window
|
|
261
|
+
|
|
262
|
+
for (let daysBack = 0; daysBack < maxDaysBack; daysBack++) {
|
|
263
|
+
const checkDate = new Date();
|
|
264
|
+
checkDate.setDate(checkDate.getDate() - daysBack);
|
|
265
|
+
const dateStr = checkDate.toISOString().split('T')[0];
|
|
241
266
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
267
|
+
try {
|
|
268
|
+
// Check if page/computation document exists for this user and date
|
|
269
|
+
// For computations using pages subcollection, checkPiInComputationDate handles it
|
|
270
|
+
// For other computations, check the main document first
|
|
271
|
+
const { found } = await checkPiInComputationDate(
|
|
272
|
+
db,
|
|
273
|
+
insightsCollection,
|
|
274
|
+
resultsSub,
|
|
275
|
+
compsSub,
|
|
276
|
+
category,
|
|
277
|
+
firstCompName,
|
|
278
|
+
dateStr,
|
|
279
|
+
String(effectiveCid),
|
|
280
|
+
logger
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (found) {
|
|
284
|
+
foundDate = dateStr;
|
|
285
|
+
if (dateStr !== today) {
|
|
286
|
+
logger.log('INFO', `[getUserComputations] Using fallback date ${foundDate} for effective CID ${effectiveCid} (today: ${today})`);
|
|
287
|
+
} else {
|
|
288
|
+
logger.log('INFO', `[getUserComputations] Found computation for effective CID ${effectiveCid} on today's date`);
|
|
289
|
+
}
|
|
290
|
+
break; // Found user, stop searching
|
|
248
291
|
}
|
|
249
|
-
|
|
292
|
+
} catch (error) {
|
|
293
|
+
// Continue to next date if error
|
|
294
|
+
logger.log('DEBUG', `[getUserComputations] Error checking date ${dateStr}:`, error.message);
|
|
295
|
+
continue;
|
|
250
296
|
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
// Continue to next date if error
|
|
253
|
-
logger.log('DEBUG', `[getUserComputations] Error checking date ${dateStr}:`, error.message);
|
|
254
|
-
continue;
|
|
255
297
|
}
|
|
256
298
|
}
|
|
257
299
|
|
|
258
300
|
if (foundDate) {
|
|
259
301
|
datesToCheck = [foundDate];
|
|
260
302
|
} else {
|
|
261
|
-
|
|
303
|
+
const maxDaysBack = isMetaComputation ? 7 : 30;
|
|
304
|
+
logger.log('WARN', `[getUserComputations] No computation data found for ${isMetaComputation ? 'meta computation' : `CID ${effectiveCid}`} in last ${maxDaysBack} days. Frontend will use fallback.`);
|
|
262
305
|
return res.status(200).json({
|
|
263
306
|
status: 'success',
|
|
264
307
|
userCid: String(effectiveCid),
|