bulltrackers-module 1.0.143 → 1.0.145

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.
@@ -12,8 +12,7 @@ function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[ca
12
12
  */
13
13
  function checkRootDependencies(calcManifest, rootDataStatus) {
14
14
  const missing = [];
15
- if (!calcManifest.rootDataDependencies || !calcManifest.rootDataDependencies.length) {
16
- return { canRun: true, missing };}
15
+ if (!calcManifest.rootDataDependencies || !calcManifest.rootDataDependencies.length) { return { canRun: true, missing };}
17
16
  for (const dep of calcManifest.rootDataDependencies) {
18
17
  if (dep === 'portfolio' && !rootDataStatus.hasPortfolio) missing.push('portfolio');
19
18
  else if (dep === 'insights' && !rootDataStatus.hasInsights) missing.push('insights');
@@ -34,8 +33,6 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
34
33
  let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false;
35
34
  try {
36
35
  const tasks = [];
37
- // This logic is correct. It *avoids* calling get...Refs
38
- // if the dateToProcess is before the earliest known data.
39
36
  if (dateToProcess >= earliestDates.portfolio)
40
37
  {tasks.push(getPortfolioPartRefs(config, dependencies, dateStr).then(res => {portfolioRefs = res;hasPortfolio = !!(res?.length);}));}
41
38
  if (dateToProcess >= earliestDates.insights) {
@@ -44,18 +41,11 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
44
41
  tasks.push(loadDailySocialPostInsights(config, dependencies, dateStr).then(res => {socialData = res;hasSocial = !!res;}));}
45
42
  if (dateToProcess >= earliestDates.history) {
46
43
  tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(res => {historyRefs = res;hasHistory = !!(res?.length);}));}
47
-
48
44
  await Promise.all(tasks);
49
-
50
- // --- NEW: Log what was *actually* found ---
51
45
  logger.log('INFO', `[PassRunner] Data availability for ${dateStr}: P:${hasPortfolio}, I:${hasInsights}, S:${hasSocial}, H:${hasHistory}`);
52
46
 
53
- if (!(hasPortfolio || hasInsights || hasSocial || hasHistory)) {
54
- logger.log('WARN', `[PassRunner] No root data at all for ${dateStr}.`);
55
- // We return null to skip the entire day
56
- return null;
57
- }
58
- return {portfolioRefs, insightsData,socialData,historyRefs,status: { hasPortfolio, hasInsights, hasSocial, hasHistory }};
47
+ if (!(hasPortfolio || hasInsights || hasSocial || hasHistory)) { logger.log('WARN', `[PassRunner] No root data at all for ${dateStr}.`); return null; }
48
+ return { portfolioRefs, todayInsights: insightsData, todaySocialPostInsights: socialData, historyRefs, status: { hasPortfolio, hasInsights, hasSocial, hasHistory } };
59
49
  } catch (err) { logger.log('ERROR', `[PassRunner] Error checking data for ${dateStr}`, { errorMessage: err.message }); return null; }
60
50
  }
61
51
 
@@ -91,76 +81,32 @@ async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config,
91
81
  function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingResults, passToRun, dateStr, earliestDates, logger) {
92
82
  const skipped = new Set();
93
83
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
94
-
95
- /**
96
- * --- CORRECTED LOGIC ---
97
- * Helper to get the true earliest date a calc can run.
98
- */
99
84
  const getTrueEarliestRunDate = (calc) => {
100
- let earliestRunDate = new Date('1970-01-01T00:00:00Z'); // Start at zero
85
+ let earliestRunDate = new Date('1970-01-01T00:00:00Z');
101
86
  const dependencies = calc.rootDataDependencies || [];
102
-
103
- // 1. Find the LATEST "today" dependency
104
- // This is the date where all *today* data is first available
105
87
  for (const dep of dependencies) {
106
88
  if (dep === 'portfolio' && earliestDates.portfolio > earliestRunDate) earliestRunDate = earliestDates.portfolio;
107
89
  if (dep === 'history' && earliestDates.history > earliestRunDate) earliestRunDate = earliestDates.history;
108
90
  if (dep === 'social' && earliestDates.social > earliestRunDate) earliestRunDate = earliestDates.social;
109
91
  if (dep === 'insights' && earliestDates.insights > earliestRunDate) earliestRunDate = earliestDates.insights;
110
92
  }
111
-
112
- // 2. If the calc is historical, shift the *final* date by +1
113
- // This universally applies the "+1" rule if *any* yesterday data is needed,
114
- // (as long as we found a dependency in step 1).
115
- if (calc.isHistorical && earliestRunDate.getTime() > 0) {
116
- earliestRunDate.setUTCDate(earliestRunDate.getUTCDate() + 1);
117
- }
118
-
93
+ if (calc.isHistorical && earliestRunDate.getTime() > 0) { earliestRunDate.setUTCDate(earliestRunDate.getUTCDate() + 1); }
119
94
  return earliestRunDate;
120
95
  };
121
-
122
96
  const filterCalc = (calc) => {
123
- // 1. Skip if result already exists
124
- if (existingResults[calc.name]) {
125
- logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Result already exists.`);
126
- skipped.add(calc.name);
127
- return false;
128
- }
97
+ if (existingResults[calc.name]) {logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Result already exists.`); skipped.add(calc.name); return false;}
129
98
 
130
- // 2. Check *true* earliest run date
131
99
  const earliestRunDate = getTrueEarliestRunDate(calc);
132
- if (dateToProcess < earliestRunDate) {
133
- logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Date is before true earliest run date (${earliestRunDate.toISOString().slice(0, 10)}).`);
134
- skipped.add(calc.name);
135
- return false;
136
- }
137
-
138
- // 3. Check if *today's* root data was *actually* found
139
- // This handles gaps *after* the earliest date
100
+ if (dateToProcess < earliestRunDate) {logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Date is before true earliest run date (${earliestRunDate.toISOString().slice(0, 10)}).`); skipped.add(calc.name); return false; }
101
+
140
102
  const { canRun, missing: missingRoot } = checkRootDependencies(calc, rootDataStatus);
141
- if (!canRun) {
142
- logger.log('INFO', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Data missing for this date: [${missingRoot.join(', ')}]`);
143
- skipped.add(calc.name);
144
- return false;
145
- }
146
-
147
- // 4. (For Meta Calcs) Check computed dependencies
148
- if (calc.type === 'meta') {
149
- const missingDeps = (calc.dependencies || []).map(normalizeName).filter(d => !existingResults[d]);
150
- if (missingDeps.length > 0) {
151
- logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${calc.name} for ${dateStr}. Missing computed deps: [${missingDeps.join(', ')}]`);
152
- skipped.add(calc.name);
153
- return false;
154
- }
155
- }
103
+ if (!canRun) {logger.log('INFO', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Data missing for this date: [${missingRoot.join(', ')}]`);skipped.add(calc.name); return false;}
156
104
 
157
- // If it passed all checks, run it.
105
+ if (calc.type === 'meta') { const missingDeps = (calc.dependencies || []).map(normalizeName).filter(d => !existingResults[d]); if (missingDeps.length > 0) { logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${calc.name} for ${dateStr}. Missing computed deps: [${missingDeps.join(', ')}]`); skipped.add(calc.name); return false;} }
158
106
  return true;
159
107
  };
160
-
161
108
  const standardCalcsToRun = standardCalcs.filter(filterCalc);
162
109
  const metaCalcsToRun = metaCalcs.filter(filterCalc);
163
-
164
110
  return { standardCalcsToRun, metaCalcsToRun };
165
111
  }
166
112
 
@@ -169,50 +115,35 @@ function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingRe
169
115
  function initializeCalculators(calcs, logger) { const state = {}; for (const c of calcs) { const name=normalizeName(c.name), Cl=c.class; if(typeof Cl==='function') try { const inst=new Cl(); inst.manifest=c; state[name]=inst; } catch(e){logger.warn(`Init failed ${name}`,{errorMessage:e.message}); state[name]=null;} else {logger.warn(`Class missing ${name}`); state[name]=null;} } return state; }
170
116
 
171
117
  /** * Stage 7: Load historical data required for calculations
172
- * --- THIS FUNCTION IS NOW FIXED ---
173
118
  */
174
119
  async function loadHistoricalData(date, calcs, config, deps, rootData) {
175
- const { logger } = deps; // <--- THIS WAS THE MISSING LINE
176
- const updated = {...rootData}, dStr=date.toISOString().slice(0,10); const tasks = [];
177
-
120
+ const { logger } = deps;
121
+ const updated = {...rootData}, dStr=date.toISOString().slice(0,10); const tasks = [];
178
122
  const needsYesterdayPortfolio = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('portfolio'));
179
- const needsTodayHistory = calcs.some(c => c.rootDataDependencies.includes('history'));
180
- const needsYesterdayHistory = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('history'));
181
- const needsYesterdayInsights = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('insights'));
182
- const needsYesterdaySocial = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('social'));
183
-
184
- // --- MODIFIED: Be smarter about loading data ---
123
+ const needsTodayHistory = calcs.some(c => c.rootDataDependencies.includes('history'));
124
+ const needsYesterdayHistory = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('history'));
125
+ const needsYesterdayInsights = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('insights'));
126
+ const needsYesterdaySocial = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('social'));
185
127
  if(needsYesterdayPortfolio) {
186
128
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
187
- logger.log('INFO', `[PassRunner] Loading YESTERDAY portfolio data for ${prevStr}`);
188
- updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr));
189
- })());
190
- }
129
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY portfolio data for ${prevStr}`);
130
+ updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr)); })());}
191
131
  if(needsTodayHistory) {
192
132
  tasks.push((async()=>{
193
- logger.log('INFO', `[PassRunner] Loading TODAY history data for ${dStr}`);
194
- updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs);
195
- })());
196
- }
133
+ logger.log('INFO', `[PassRunner] Loading TODAY history data for ${dStr}`);
134
+ updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs); })());}
197
135
  if(needsYesterdayHistory) {
198
136
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
199
- logger.log('INFO', `[PassRunner] Loading YESTERDAY history data for ${prevStr}`);
200
- updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr));
201
- })());
202
- }
137
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY history data for ${prevStr}`);
138
+ updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr)); })());}
203
139
  if(needsYesterdayInsights) {
204
140
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
205
- logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
206
- updated.yesterdayInsights=await loadDailyInsights(config,deps,prevStr);
207
- })());
208
- }
141
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
142
+ updated.yesterdayInsights=await loadDailyInsights(config,deps,prevStr); })());}
209
143
  if(needsYesterdaySocial) {
210
144
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
211
- logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
212
- updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr);
213
- })());
214
- }
215
-
145
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
146
+ updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr); })());}
216
147
  await Promise.all(tasks); return updated;
217
148
  }
218
149
 
@@ -223,17 +154,10 @@ async function loadHistoricalData(date, calcs, config, deps, rootData) {
223
154
  */
224
155
  async function streamAndProcess(dateStr, state, passName, config, deps, rootData) {
225
156
  const { logger, calculationUtils } = deps;
226
- // --- MODIFIED: yesterdayInsights/Social are now loaded by loadHistoricalData ---
227
157
  const { todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights, todayHistoryData, yesterdayHistoryData, yesterdayPortfolios } = rootData;
228
-
229
- // --- NEW: Check if streaming is even needed ---
230
158
  const calcsThatStreamPortfolio = Object.values(state).filter(calc => calc && calc.manifest && (calc.manifest.rootDataDependencies.includes('portfolio') || calc.manifest.category === 'speculators'));
231
-
232
159
  const context={instrumentMappings:(await calculationUtils.loadInstrumentMappings()).instrumentToTicker, sectorMapping:(await calculationUtils.loadInstrumentMappings()).instrumentToSector, todayDateStr:dateStr, dependencies:deps, config};
233
-
234
- // --- MODIFIED: Run non-streaming calcs first (social/insights) ---
235
- // This allows them to run even if portfolio data is missing
236
- let firstUser=true; // Used to run them only once
160
+ let firstUser=true;
237
161
  for(const name in state){
238
162
  const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
239
163
  const cat=calc.manifest.category;
@@ -241,7 +165,6 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
241
165
  if (firstUser) {
242
166
  logger.log('INFO', `[${passName}] Running non-streaming calc: ${name}`);
243
167
  let args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
244
- // Pass historical data if needed
245
168
  if(calc.manifest.isHistorical) {
246
169
  args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
247
170
  }
@@ -249,15 +172,10 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
249
172
  }
250
173
  }
251
174
  }
252
-
253
-
254
175
  if (calcsThatStreamPortfolio.length === 0) {
255
176
  logger.log('INFO', `[${passName}] No portfolio-streaming calcs to run for ${dateStr}. Skipping stream.`);
256
- return; // Exit stream function
257
- }
258
-
177
+ return; }
259
178
  logger.log('INFO', `[${passName}] Streaming portfolio data for ${calcsThatStreamPortfolio.length} calcs...`);
260
-
261
179
  for await (const chunk of streamPortfolioData(config, deps, dateStr)) {
262
180
  for(const uid in chunk){ const p=chunk[uid]; if(!p) continue;
263
181
  const userType=p.PublicPositions?'speculator':'normal';
@@ -265,18 +183,12 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
265
183
  for(const name in state){
266
184
  const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
267
185
  const cat=calc.manifest.category, isSocialOrInsights=cat==='socialPosts'||cat==='insights', isHistorical=calc.manifest.isHistorical, isSpec=cat==='speculators';
268
-
269
- // --- MODIFIED: Skip social/insights here, they ran above ---
270
186
  if(isSocialOrInsights) continue;
271
-
272
187
  let args=[p,null,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
273
-
274
188
  if(isHistorical){
275
- const pY=yesterdayPortfolios ? yesterdayPortfolios[uid] : null; // Check if yesterdayPortfolios exists
276
- // V3 behavioural calcs (like history aggregator) *can* run without pY
189
+ const pY=yesterdayPortfolios ? yesterdayPortfolios[uid] : null;
277
190
  if(!pY && (cat !== 'behavioural' && name !== 'historical-performance-aggregator')) continue;
278
- args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
279
- }
191
+ args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData]; }
280
192
  if((userType==='normal'&&isSpec)||(userType==='speculator'&&!isSpec&&name!=='users-processed')) continue;
281
193
  try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} for ${uid}`,{err:e.message});} }
282
194
  firstUser=false;
@@ -287,20 +199,13 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
287
199
  /** Stage 9: Run standard computations */
288
200
  async function runStandardComputationPass(date, calcs, passName, config, deps, rootData) {
289
201
  const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
290
- // --- THIS IS THE CRITICAL CHANGE ---
291
- // If 'calcs' is empty *because of the new filter*, this log won't even appear.
292
202
  if (calcs.length === 0) {
293
203
  logger.log('INFO', `[${passName}] No standard calcs to run for ${dStr} after filtering.`);
294
- return;
295
- }
296
- // This log now only appears if there is *actually* work to do.
204
+ return; }
297
205
  logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
298
-
299
206
  const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
300
207
  const state = initializeCalculators(calcs, logger);
301
-
302
208
  await streamAndProcess(dStr, state, passName, config, deps, fullRoot);
303
-
304
209
  let success = 0;
305
210
  const standardWrites = [];
306
211
  const shardedWrites = {};
@@ -337,8 +242,8 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
337
242
  if (standardWrites.length > 0) {
338
243
  await commitBatchInChunks(config, deps, standardWrites, `${passName} Standard ${dStr}`);
339
244
  }
340
- for (const docPath in shardedWrites) { // 'docPath' is the key, e.g., 'user_profile_history_shard_0' or 'social_.../history'
341
- const docData = shardedWrites[docPath]; // 'docData' is the object to write, e.g., { profiles: ... }
245
+ for (const docPath in shardedWrites) {
246
+ const docData = shardedWrites[docPath];
342
247
  const shardedDocWrites = [];
343
248
  let docRef;
344
249
  if (docPath.includes('/')) {
@@ -364,17 +269,12 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
364
269
  /** Stage 10: Run meta computations */
365
270
  async function runMetaComputationPass(date, calcs, passName, config, deps, fetchedDeps, rootData) {
366
271
  const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
367
- // --- THIS IS THE CRITICAL CHANGE ---
368
272
  if (calcs.length === 0) {
369
273
  logger.log('INFO', `[${passName}] No meta calcs to run for ${dStr} after filtering.`);
370
274
  return;
371
275
  }
372
276
  logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
373
-
374
- // --- NEW: Load historical data for meta calcs if needed ---
375
- // (This might be redundant if standard pass ran, but meta-calcs can run standalone)
376
277
  const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
377
-
378
278
  let success = 0;
379
279
  const standardWrites = [];
380
280
  const shardedWrites = {};
@@ -383,7 +283,6 @@ async function runMetaComputationPass(date, calcs, passName, config, deps, fetch
383
283
  if (typeof Cl !== 'function') {logger.log('ERROR', `Invalid class ${name}`);continue;}
384
284
  const inst = new Cl();
385
285
  try {
386
- // --- MODIFIED: Pass fullRoot to meta calcs ---
387
286
  const result = await Promise.resolve(inst.process(dStr, { ...deps, rootData: fullRoot }, config, fetchedDeps));
388
287
  if (result && Object.keys(result).length > 0) {const standardResult = {}; for (const key in result) {
389
288
  if (key.startsWith('sharded_')) {const shardedData = result[key];for (const collectionName in shardedData)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.143",
3
+ "version": "1.0.145",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [