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 === 'speculator' && rootDataStatus.speculatorPortfolio) isAvailable = true;
28
- else if (userType === 'normal' && rootDataStatus.normalPortfolio) isAvailable = true;
29
- else if (userType === 'popular_investor' && rootDataStatus.piPortfolios) isAvailable = true;
30
- else if (userType === 'signed_in_user' && rootDataStatus.signedInUserPortfolio) isAvailable = true;
31
- else if (userType === 'all' && rootDataStatus.hasPortfolio) isAvailable = true;
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
- if (userType === 'speculator') missing.push('speculatorPortfolio');
36
- else if (userType === 'normal') missing.push('normalPortfolio');
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') missing.push('signedInUserPortfolio');
39
- else missing.push('portfolio');
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 === 'history') {
45
- if (userType === 'speculator' && rootDataStatus.speculatorHistory) isAvailable = true;
46
- else if (userType === 'normal' && rootDataStatus.normalHistory) isAvailable = true;
47
- else if (userType === 'popular_investor' && rootDataStatus.piHistory) isAvailable = true;
48
- else if (userType === 'signed_in_user' && rootDataStatus.signedInUserHistory) isAvailable = true;
49
- else if (userType === 'all' && rootDataStatus.hasHistory) isAvailable = true;
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
- if (userType === 'speculator') missing.push('speculatorHistory');
54
- else if (userType === 'normal') missing.push('normalHistory');
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') missing.push('signedInUserHistory');
57
- else missing.push('history');
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
- missing.push('piRankings');
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
- missing.push('insights');
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) isAvailable = true;
111
+ if (rootDataStatus.hasPISocial) isAvailable = true;
91
112
  } else if (userType === 'signed_in_user') {
92
- if (rootDataStatus.hasSignedInSocial) isAvailable = true;
113
+ if (rootDataStatus.hasSignedInSocial) isAvailable = true;
93
114
  } else {
94
- // For 'all', 'normal', 'speculator' or undefined, accept generic social data
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
- if (userType === 'popular_investor') missing.push('piSocial');
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 missing.push('social');
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
- missing.push('price');
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
- missing.push('piRatings');
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
- missing.push('piPageViews');
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
- missing.push('watchlistMembership');
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
- missing.push('piAlertHistory');
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, hasHistory: false, hasSocial: false, hasInsights: false, hasPrices: false,
248
- speculatorPortfolio: false, normalPortfolio: false, speculatorHistory: false, normalHistory: false,
249
- piRankings: false, piPortfolios: false, piDeepPortfolios: false, piHistory: false,
250
- signedInUserPortfolio: false, signedInUserHistory: false, signedInUserVerification: false,
251
- hasPISocial: false, hasSignedInSocial: false,
252
- piRatings: false, piPageViews: false, watchlistMembership: false, piAlertHistory: false
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
- for (let daysBack = 0; daysBack < maxDaysBack; daysBack++) {
222
- const checkDate = new Date();
223
- checkDate.setDate(checkDate.getDate() - daysBack);
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
- try {
227
- // Check if page/computation document exists for this user and date
228
- // For computations using pages subcollection, checkPiInComputationDate handles it
229
- // For other computations, check the main document first
230
- const { found } = await checkPiInComputationDate(
231
- db,
232
- insightsCollection,
233
- resultsSub,
234
- compsSub,
235
- category,
236
- firstCompName,
237
- dateStr,
238
- String(effectiveCid),
239
- logger
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
- if (found) {
243
- foundDate = dateStr;
244
- if (dateStr !== today) {
245
- logger.log('INFO', `[getUserComputations] Using fallback date ${foundDate} for effective CID ${effectiveCid} (today: ${today})`);
246
- } else {
247
- logger.log('INFO', `[getUserComputations] Found computation for effective CID ${effectiveCid} on today's date`);
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
- break; // Found user, stop searching
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
- logger.log('WARN', `[getUserComputations] No computation data found for CID ${effectiveCid} in last ${maxDaysBack} days. Frontend will use fallback.`);
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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.578",
3
+ "version": "1.0.580",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [