bulltrackers-module 1.0.448 → 1.0.450

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.
@@ -199,8 +199,8 @@ async function loadDailySocialPostInsights(config, deps, dateString) {
199
199
  signedIn: {} // Map<UserId, Map<PostId, Data>> - For Signed-In Users
200
200
  };
201
201
 
202
- const PI_COL_NAME = config.piSocialCollectionName || 'pi_social_posts';
203
- const SIGNED_IN_COL_NAME = config.signedInUserSocialCollection || 'signed_in_users';
202
+ const PI_COL_NAME = config.piSocialCollectionName || config.piSocialCollection || 'pi_social_posts';
203
+ const SIGNED_IN_COL_NAME = config.signedInUserSocialCollection || 'signed_in_users_social';
204
204
 
205
205
  // 2. Define Time Range (UTC Day)
206
206
  const startDate = new Date(dateString + 'T00:00:00Z');
@@ -232,7 +232,7 @@ async function loadDailySocialPostInsights(config, deps, dateString) {
232
232
  }
233
233
  }
234
234
  else if (path.includes(SIGNED_IN_COL_NAME)) {
235
- // Path format: .../signed_in_users/{userId}/posts/{postId}
235
+ // Path format: .../signed_in_users_social/{userId}/posts/{postId}
236
236
  const parts = path.split('/');
237
237
  const colIndex = parts.indexOf(SIGNED_IN_COL_NAME);
238
238
  if (colIndex !== -1 && parts[colIndex + 1]) {
@@ -70,7 +70,7 @@ exports.runRootDataIndexer = async (config, dependencies) => {
70
70
 
71
71
  // Collection Names (Fail-safe defaults)
72
72
  const PI_SOCIAL_COLL_NAME = collections.piSocial || 'pi_social_posts';
73
- const SIGNED_IN_SOCIAL_COLL_NAME = collections.signedInUserSocialCollection || 'signed_in_users';
73
+ const SIGNED_IN_SOCIAL_COLL_NAME = collections.signedInUserSocialCollection || 'signed_in_users_social';
74
74
 
75
75
  const scanMode = targetDate ? 'SINGLE_DATE' : 'FULL_SCAN';
76
76
  logger.log('INFO', `[RootDataIndexer] Starting Root Data Availability Scan... Mode: ${scanMode}`, { targetDate });
@@ -252,8 +252,14 @@ exports.runRootDataIndexer = async (config, dependencies) => {
252
252
  if (!universalSocialSnap.empty) {
253
253
  universalSocialSnap.docs.forEach(doc => {
254
254
  const path = doc.ref.path;
255
- if (path.startsWith(PI_SOCIAL_COLL_NAME)) foundPISocial = true;
256
- if (path.startsWith(SIGNED_IN_SOCIAL_COLL_NAME)) foundSignedInSocial = true;
255
+ // Use includes() to match collection name anywhere in path (more robust)
256
+ // Path format: {collectionName}/{userId}/posts/{postId}
257
+ if (path.includes(`/${PI_SOCIAL_COLL_NAME}/`) || path.startsWith(`${PI_SOCIAL_COLL_NAME}/`)) {
258
+ foundPISocial = true;
259
+ }
260
+ if (path.includes(`/${SIGNED_IN_SOCIAL_COLL_NAME}/`) || path.startsWith(`${SIGNED_IN_SOCIAL_COLL_NAME}/`)) {
261
+ foundSignedInSocial = true;
262
+ }
257
263
  });
258
264
  }
259
265
 
@@ -133,7 +133,25 @@ async function getAdvancedAnalysisFromGemini(dependencies, snippet) {
133
133
  };
134
134
 
135
135
  } catch (e) {
136
- logger.log("ERROR", "[Gemini AI] JSON handling failed", e);
136
+ logger.log("ERROR", "[Gemini AI] JSON handling failed", {
137
+ error: e.message,
138
+ stack: e.stack,
139
+ snippet: snippet.substring(0, 100),
140
+ part: part ? {
141
+ hasJson: !!part.json,
142
+ hasText: !!part.text,
143
+ textPreview: part.text ? part.text.substring(0, 200) : null
144
+ } : null,
145
+ resultStructure: result?.response ? {
146
+ hasCandidates: !!result.response.candidates,
147
+ candidatesLength: result.response.candidates?.length,
148
+ firstCandidate: result.response.candidates?.[0] ? {
149
+ hasContent: !!result.response.candidates[0].content,
150
+ hasParts: !!result.response.candidates[0].content?.parts,
151
+ partsLength: result.response.candidates[0].content?.parts?.length
152
+ } : null
153
+ } : null
154
+ });
137
155
  return { overallSentiment: "Neutral", topics: [], isSpam: false, qualityScore: 0.5 };
138
156
  }
139
157
  }
@@ -235,21 +253,36 @@ exports.handleSocialTask = async (message, context, config, dependencies) => {
235
253
  existingDocs.forEach(d => existingIds.add(d.id));
236
254
  }
237
255
 
256
+ // Filter and prepare posts: only new posts in English
257
+ const newPostsWithDiscussion = discussions
258
+ .filter(d => {
259
+ const post = d.post;
260
+ if (!post || !post.id || !post.message?.text || existingIds.has(post.id)) return false;
261
+ const lang = (post.message.languageCode || 'unknown').toLowerCase();
262
+ return lang === 'en' || lang === 'en-gb';
263
+ })
264
+ // Sort by text length (longer posts are generally more useful for analysis)
265
+ .sort((a, b) => (b.post.message.text?.length || 0) - (a.post.message.text?.length || 0));
266
+
267
+ // COST OPTIMIZATION: Cap AI processing to top 5 posts per user to reduce costs
268
+ // Remaining posts will be stored with default AI values
269
+ const MAX_POSTS_FOR_AI = 5;
270
+ const postsForAI = newPostsWithDiscussion.slice(0, MAX_POSTS_FOR_AI);
271
+ const postsWithoutAI = newPostsWithDiscussion.slice(MAX_POSTS_FOR_AI);
272
+
273
+ logger.log('INFO', `[SocialTask/${taskId}] Filtered ${newPostsWithDiscussion.length} new posts. Sending ${postsForAI.length} to AI, ${postsWithoutAI.length} without AI.`);
274
+
238
275
  const batch = db.batch();
239
276
  let pageBatchCount = 0;
240
277
 
241
- for (const discussion of discussions) {
278
+ // Process posts that will get AI analysis (top 5 by text length)
279
+ for (const discussion of postsForAI) {
242
280
  if (totalSaved + pageBatchCount >= MAX_POSTS_TO_STORE) {
243
281
  keepFetching = false;
244
282
  break;
245
283
  }
246
284
 
247
285
  const post = discussion.post;
248
- if (!post || !post.id || !post.message?.text || existingIds.has(post.id)) continue;
249
-
250
- const lang = (post.message.languageCode || 'unknown').toLowerCase();
251
- if (lang !== 'en' && lang !== 'en-gb') continue;
252
-
253
286
  const snippet = post.message.text.substring(0, 500);
254
287
  const aiResult = await getAdvancedAnalysisFromGemini(dependencies, snippet);
255
288
 
@@ -260,7 +293,7 @@ exports.handleSocialTask = async (message, context, config, dependencies) => {
260
293
  username: post.owner?.username,
261
294
  createdAt: post.created,
262
295
  fetchedAt: FieldValue.serverTimestamp(),
263
- snippet: snippet, // Added for debugging
296
+ snippet: snippet,
264
297
  stats: {
265
298
  likes: discussion.emotionsData?.like?.paging?.totalCount || 0,
266
299
  comments: discussion.summary?.totalCommentsAndReplies || 0
@@ -274,6 +307,40 @@ exports.handleSocialTask = async (message, context, config, dependencies) => {
274
307
  pageBatchCount++;
275
308
  }
276
309
 
310
+ // Process remaining posts WITHOUT AI analysis (to avoid costs, but still store them)
311
+ for (const discussion of postsWithoutAI) {
312
+ if (totalSaved + pageBatchCount >= MAX_POSTS_TO_STORE) {
313
+ keepFetching = false;
314
+ break;
315
+ }
316
+
317
+ const post = discussion.post;
318
+ const docData = {
319
+ postId: post.id,
320
+ text: post.message.text,
321
+ ownerId: post.owner?.id,
322
+ username: post.owner?.username,
323
+ createdAt: post.created,
324
+ fetchedAt: FieldValue.serverTimestamp(),
325
+ snippet: post.message.text.substring(0, 500),
326
+ stats: {
327
+ likes: discussion.emotionsData?.like?.paging?.totalCount || 0,
328
+ comments: discussion.summary?.totalCommentsAndReplies || 0
329
+ },
330
+ aiAnalysis: {
331
+ overallSentiment: "Neutral",
332
+ topics: [],
333
+ isSpam: false,
334
+ qualityScore: 0.5
335
+ }, // Default values when AI is skipped to reduce costs
336
+ tags: post.tags?.map(t => t.market?.symbolName).filter(Boolean) || []
337
+ };
338
+
339
+ batch.set(db.collection(targetCollectionPath).doc(post.id), docData);
340
+ batch.set(processedRef.doc(post.id), { processedAt: FieldValue.serverTimestamp() });
341
+ pageBatchCount++;
342
+ }
343
+
277
344
  if (pageBatchCount > 0) {
278
345
  await batch.commit();
279
346
  totalSaved += pageBatchCount;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.448",
3
+ "version": "1.0.450",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [