bulltrackers-module 1.0.575 → 1.0.577

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.
@@ -9,7 +9,8 @@ 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
+ * For PopularInvestorProfileMetrics and SignedInUserProfileMetrics: reads from pages subcollection
13
+ * For other computations: reads from main computation document
13
14
  * Returns { found: boolean, profileData: object | null, computationData: object | null }
14
15
  * @param {object} db - Firestore instance
15
16
  * @param {string} insightsCollection - Insights collection name
@@ -24,46 +25,121 @@ const { getEffectiveCid, getDevOverride } = require('../dev/dev_helpers');
24
25
  */
25
26
  async function checkPiInComputationDate(db, insightsCollection, resultsSub, compsSub, category, computationName, dateStr, cidStr, logger) {
26
27
  try {
27
- // New path format: /unified_insights/YYYY-MM-DD/results/popular-investor/computations/PopularInvestorProfileMetrics/pages/{cid}
28
- const pageRef = db.collection(insightsCollection)
29
- .doc(dateStr)
30
- .collection(resultsSub)
31
- .doc(category)
32
- .collection(compsSub)
33
- .doc(computationName)
34
- .collection('pages')
35
- .doc(cidStr);
28
+ // Check if this computation uses the pages subcollection structure
29
+ const usesPagesStructure = computationName === 'PopularInvestorProfileMetrics' || computationName === 'SignedInUserProfileMetrics';
36
30
 
37
- const pageDoc = await pageRef.get();
38
-
39
- if (!pageDoc.exists) {
40
- logger.log('INFO', `[checkPiInComputationDate] Page document not found for CID ${cidStr} in date ${dateStr}`);
41
- return { found: false, profileData: null, computationData: null };
42
- }
43
-
44
- const rawData = pageDoc.data();
45
- let profileData = null;
46
-
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);
31
+ if (usesPagesStructure) {
32
+ // New path format: /unified_insights/YYYY-MM-DD/results/popular-investor/computations/{computationName}/pages/{cid}
33
+ const pageRef = db.collection(insightsCollection)
34
+ .doc(dateStr)
35
+ .collection(resultsSub)
36
+ .doc(category)
37
+ .collection(compsSub)
38
+ .doc(computationName)
39
+ .collection('pages')
40
+ .doc(cidStr);
41
+
42
+ const pageDoc = await pageRef.get();
43
+
44
+ if (!pageDoc.exists) {
45
+ logger.log('INFO', `[checkPiInComputationDate] Page document not found for CID ${cidStr} in date ${dateStr}`);
46
+ return { found: false, profileData: null, computationData: null };
47
+ }
48
+
49
+ const rawData = pageDoc.data();
50
+ let profileData = null;
51
+
52
+ // Decompress if needed
53
+ profileData = tryDecompress(rawData);
54
+
55
+ // Handle string decompression result
56
+ if (typeof profileData === 'string') {
57
+ try {
58
+ profileData = JSON.parse(profileData);
59
+ } catch (e) {
60
+ logger.log('WARN', `[checkPiInComputationDate] Failed to parse decompressed string for CID ${cidStr} on date ${dateStr}:`, e.message);
61
+ return { found: false, profileData: null, computationData: null };
62
+ }
63
+ }
64
+
65
+ if (profileData && typeof profileData === 'object') {
66
+ logger.log('INFO', `[checkPiInComputationDate] Found profile data for CID ${cidStr} in date ${dateStr}`);
67
+ // Return the profile data - computationData is set to null since we're reading individual pages
68
+ return { found: true, profileData, computationData: null };
69
+ } else {
70
+ logger.log('WARN', `[checkPiInComputationDate] Profile data is not an object for CID ${cidStr} on date ${dateStr}`);
56
71
  return { found: false, profileData: null, computationData: null };
57
72
  }
58
- }
59
-
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 };
64
73
  } else {
65
- logger.log('WARN', `[checkPiInComputationDate] Profile data is not an object for CID ${cidStr} on date ${dateStr}`);
66
- return { found: false, profileData: null, computationData: null };
74
+ // Standard path for other computations: read from main computation document
75
+ const computationRef = db.collection(insightsCollection)
76
+ .doc(dateStr)
77
+ .collection(resultsSub)
78
+ .doc(category)
79
+ .collection(compsSub)
80
+ .doc(computationName);
81
+
82
+ const computationDoc = await computationRef.get();
83
+
84
+ if (!computationDoc.exists) {
85
+ return { found: false, profileData: null, computationData: null };
86
+ }
87
+
88
+ const rawData = computationDoc.data();
89
+ let computationData = null;
90
+
91
+ // Check if data is sharded
92
+ if (rawData._sharded === true && rawData._shardCount) {
93
+ const shardsCol = computationRef.collection('_shards');
94
+ const shardCount = rawData._shardCount;
95
+
96
+ logger.log('INFO', `[checkPiInComputationDate] Reading ${shardCount} shards for date ${dateStr}`);
97
+
98
+ computationData = {};
99
+
100
+ // Read all shards (shard_0, shard_1, ..., shard_N-1)
101
+ for (let i = 0; i < shardCount; i++) {
102
+ const shardDoc = await shardsCol.doc(`shard_${i}`).get();
103
+ if (shardDoc.exists) {
104
+ const shardData = shardDoc.data();
105
+ Object.assign(computationData, shardData);
106
+ } else {
107
+ logger.log('WARN', `[checkPiInComputationDate] Shard shard_${i} missing for date ${dateStr}`);
108
+ }
109
+ }
110
+ } else {
111
+ // Data is in the main document (compressed or raw)
112
+ computationData = tryDecompress(rawData);
113
+
114
+ // Handle string decompression result
115
+ if (typeof computationData === 'string') {
116
+ try {
117
+ computationData = JSON.parse(computationData);
118
+ } catch (e) {
119
+ logger.log('WARN', `[checkPiInComputationDate] Failed to parse decompressed string for date ${dateStr}:`, e.message);
120
+ return { found: false, profileData: null, computationData: null };
121
+ }
122
+ }
123
+ }
124
+
125
+ // Check if CID exists in the computation data
126
+ if (computationData && typeof computationData === 'object' && !Array.isArray(computationData)) {
127
+ // Filter out metadata keys that start with underscore
128
+ const cids = Object.keys(computationData).filter(key => !key.startsWith('_'));
129
+
130
+ // Check if the requested CID exists
131
+ const profileData = computationData[cidStr];
132
+ if (profileData) {
133
+ logger.log('INFO', `[checkPiInComputationDate] Found CID ${cidStr} in date ${dateStr} (total CIDs: ${cids.length})`);
134
+ return { found: true, profileData, computationData };
135
+ } else {
136
+ logger.log('INFO', `[checkPiInComputationDate] CID ${cidStr} not found in date ${dateStr} (total CIDs: ${cids.length})`);
137
+ return { found: false, profileData: null, computationData };
138
+ }
139
+ } else {
140
+ logger.log('WARN', `[checkPiInComputationDate] Computation data is not an object for date ${dateStr}`);
141
+ return { found: false, profileData: null, computationData: null };
142
+ }
67
143
  }
68
144
  } catch (error) {
69
145
  logger.log('ERROR', `[checkPiInComputationDate] Error checking PI in computation:`, error);
@@ -148,59 +224,29 @@ async function getUserComputations(req, res, dependencies, config) {
148
224
  const dateStr = checkDate.toISOString().split('T')[0];
149
225
 
150
226
  try {
151
- // For PopularInvestorProfileMetrics, check pages subcollection directly
152
- // For other computations, check main computation document
153
- let shouldCheck = false;
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
+ );
154
241
 
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
- }
180
-
181
- if (shouldCheck) {
182
- // Computation/page exists, verify user is in it (same as data-status)
183
- const { found } = await checkPiInComputationDate(
184
- db,
185
- insightsCollection,
186
- resultsSub,
187
- compsSub,
188
- category,
189
- firstCompName,
190
- dateStr,
191
- String(effectiveCid),
192
- logger
193
- );
194
-
195
- if (found) {
196
- foundDate = dateStr;
197
- if (dateStr !== today) {
198
- logger.log('INFO', `[getUserComputations] Using fallback date ${foundDate} for effective CID ${effectiveCid} (today: ${today})`);
199
- } else {
200
- logger.log('INFO', `[getUserComputations] Found computation for effective CID ${effectiveCid} on today's date`);
201
- }
202
- break; // Found user, stop searching
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`);
203
248
  }
249
+ break; // Found user, stop searching
204
250
  }
205
251
  } catch (error) {
206
252
  // Continue to next date if error
@@ -245,8 +291,9 @@ async function getUserComputations(req, res, dependencies, config) {
245
291
  try {
246
292
  let userResult = null;
247
293
 
248
- // Special handling for PopularInvestorProfileMetrics - read from pages subcollection
249
- if (compName === 'PopularInvestorProfileMetrics') {
294
+ // Special handling for computations that use pages subcollection structure
295
+ // Both PopularInvestorProfileMetrics and SignedInUserProfileMetrics use pages subcollection
296
+ if (compName === 'PopularInvestorProfileMetrics' || compName === 'SignedInUserProfileMetrics') {
250
297
  const pageRef = db.collection(insightsCollection)
251
298
  .doc(date)
252
299
  .collection(resultsSub)
@@ -163,45 +163,33 @@ async function getUserDataStatus(req, res, dependencies, config) {
163
163
  const dateStr = checkDate.toISOString().split('T')[0];
164
164
 
165
165
  try {
166
- // Check if computation document exists for this date
167
- const computationRef = db.collection(insightsCollection)
168
- .doc(dateStr)
169
- .collection(resultsSub)
170
- .doc(category)
171
- .collection(compsSub)
172
- .doc(computationName);
166
+ // Check if page document exists for this user and date (new pages subcollection structure)
167
+ const { found } = await checkPiInComputationDate(
168
+ db,
169
+ insightsCollection,
170
+ resultsSub,
171
+ compsSub,
172
+ category,
173
+ computationName,
174
+ dateStr,
175
+ String(effectiveCid),
176
+ logger
177
+ );
173
178
 
174
- const computationDoc = await computationRef.get();
175
-
176
- if (computationDoc.exists) {
177
- // Computation exists for this date, check if user is in it
178
- const { found } = await checkPiInComputationDate(
179
- db,
180
- insightsCollection,
181
- resultsSub,
182
- compsSub,
183
- category,
184
- computationName,
185
- dateStr,
186
- String(effectiveCid),
187
- logger
188
- );
179
+ if (found) {
180
+ foundDate = dateStr;
181
+ computationAvailable = true;
182
+ computationDate = dateStr;
183
+ isComputationFallback = daysBack > 0;
189
184
 
190
- if (found) {
191
- foundDate = dateStr;
192
- computationAvailable = true;
193
- computationDate = dateStr;
194
- isComputationFallback = daysBack > 0;
195
-
196
- if (isComputationFallback) {
197
- logger.log('INFO', `[getUserDataStatus] Found computation for user ${userCid} on date ${computationDate} (${daysBack} days back from today: ${today})`);
198
- } else {
199
- logger.log('INFO', `[getUserDataStatus] Found computation for user ${userCid} on today's date`);
200
- }
201
- break; // Found user, stop searching
185
+ if (isComputationFallback) {
186
+ logger.log('INFO', `[getUserDataStatus] Found computation for user ${userCid} on date ${computationDate} (${daysBack} days back from today: ${today})`);
202
187
  } else {
203
- logger.log('DEBUG', `[getUserDataStatus] Computation exists for date ${dateStr} but user ${userCid} not found in it, continuing search...`);
188
+ logger.log('INFO', `[getUserDataStatus] Found computation for user ${userCid} on today's date`);
204
189
  }
190
+ break; // Found user, stop searching
191
+ } else {
192
+ logger.log('DEBUG', `[getUserDataStatus] Page document not found for user ${userCid} on date ${dateStr}, continuing search...`);
205
193
  }
206
194
  } catch (error) {
207
195
  // Continue to next date if error
@@ -210,8 +198,11 @@ async function getUserDataStatus(req, res, dependencies, config) {
210
198
  }
211
199
  }
212
200
 
201
+ // Check if we've exhausted the fallback window
202
+ const fallbackWindowExhausted = !foundDate;
203
+
213
204
  if (!foundDate) {
214
- logger.log('INFO', `[getUserDataStatus] No computation found for user ${userCid} in last ${maxDaysBack} days`);
205
+ logger.log('INFO', `[getUserDataStatus] No computation found for user ${userCid} in last ${maxDaysBack} days - fallback window exhausted`);
215
206
  }
216
207
 
217
208
  // For backward compatibility, keep portfolioAvailable and historyAvailable
@@ -224,6 +215,7 @@ async function getUserDataStatus(req, res, dependencies, config) {
224
215
  computationDate: computationDate, // The actual date where computation was found
225
216
  isComputationFallback: isComputationFallback, // True if using T-1 or older date
226
217
  requestedDate: today, // What date was requested (today)
218
+ fallbackWindowExhausted: fallbackWindowExhausted, // True if checked all dates in fallback window and found nothing
227
219
  userCid: String(userCid),
228
220
  effectiveCid: String(effectiveCid)
229
221
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.575",
3
+ "version": "1.0.577",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [