bulltrackers-module 1.0.144 → 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,32 +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
-
59
- /**
60
- * --- THIS IS THE FIX ---
61
- * Rename keys to match what streamAndProcess (Stage 8) expects.
62
- * 'insightsData' is renamed to 'todayInsights'.
63
- * 'socialData' is renamed to 'todaySocialPostInsights'.
64
- */
65
- return {
66
- portfolioRefs,
67
- todayInsights: insightsData, // <-- FIX
68
- todaySocialPostInsights: socialData, // <-- FIX
69
- historyRefs,
70
- status: { hasPortfolio, hasInsights, hasSocial, hasHistory }
71
- };
72
-
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 } };
73
49
  } catch (err) { logger.log('ERROR', `[PassRunner] Error checking data for ${dateStr}`, { errorMessage: err.message }); return null; }
74
50
  }
75
51
 
@@ -105,76 +81,32 @@ async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config,
105
81
  function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingResults, passToRun, dateStr, earliestDates, logger) {
106
82
  const skipped = new Set();
107
83
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
108
-
109
- /**
110
- * --- CORRECTED LOGIC ---
111
- * Helper to get the true earliest date a calc can run.
112
- */
113
84
  const getTrueEarliestRunDate = (calc) => {
114
- let earliestRunDate = new Date('1970-01-01T00:00:00Z'); // Start at zero
85
+ let earliestRunDate = new Date('1970-01-01T00:00:00Z');
115
86
  const dependencies = calc.rootDataDependencies || [];
116
-
117
- // 1. Find the LATEST "today" dependency
118
- // This is the date where all *today* data is first available
119
87
  for (const dep of dependencies) {
120
88
  if (dep === 'portfolio' && earliestDates.portfolio > earliestRunDate) earliestRunDate = earliestDates.portfolio;
121
89
  if (dep === 'history' && earliestDates.history > earliestRunDate) earliestRunDate = earliestDates.history;
122
90
  if (dep === 'social' && earliestDates.social > earliestRunDate) earliestRunDate = earliestDates.social;
123
91
  if (dep === 'insights' && earliestDates.insights > earliestRunDate) earliestRunDate = earliestDates.insights;
124
92
  }
125
-
126
- // 2. If the calc is historical, shift the *final* date by +1
127
- // This universally applies the "+1" rule if *any* yesterday data is needed,
128
- // (as long as we found a dependency in step 1).
129
- if (calc.isHistorical && earliestRunDate.getTime() > 0) {
130
- earliestRunDate.setUTCDate(earliestRunDate.getUTCDate() + 1);
131
- }
132
-
93
+ if (calc.isHistorical && earliestRunDate.getTime() > 0) { earliestRunDate.setUTCDate(earliestRunDate.getUTCDate() + 1); }
133
94
  return earliestRunDate;
134
95
  };
135
-
136
96
  const filterCalc = (calc) => {
137
- // 1. Skip if result already exists
138
- if (existingResults[calc.name]) {
139
- logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Result already exists.`);
140
- skipped.add(calc.name);
141
- return false;
142
- }
97
+ if (existingResults[calc.name]) {logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Result already exists.`); skipped.add(calc.name); return false;}
143
98
 
144
- // 2. Check *true* earliest run date
145
99
  const earliestRunDate = getTrueEarliestRunDate(calc);
146
- if (dateToProcess < earliestRunDate) {
147
- logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Date is before true earliest run date (${earliestRunDate.toISOString().slice(0, 10)}).`);
148
- skipped.add(calc.name);
149
- return false;
150
- }
151
-
152
- // 3. Check if *today's* root data was *actually* found
153
- // 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
+
154
102
  const { canRun, missing: missingRoot } = checkRootDependencies(calc, rootDataStatus);
155
- if (!canRun) {
156
- logger.log('INFO', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Data missing for this date: [${missingRoot.join(', ')}]`);
157
- skipped.add(calc.name);
158
- return false;
159
- }
160
-
161
- // 4. (For Meta Calcs) Check computed dependencies
162
- if (calc.type === 'meta') {
163
- const missingDeps = (calc.dependencies || []).map(normalizeName).filter(d => !existingResults[d]);
164
- if (missingDeps.length > 0) {
165
- logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${calc.name} for ${dateStr}. Missing computed deps: [${missingDeps.join(', ')}]`);
166
- skipped.add(calc.name);
167
- return false;
168
- }
169
- }
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;}
170
104
 
171
- // 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;} }
172
106
  return true;
173
107
  };
174
-
175
108
  const standardCalcsToRun = standardCalcs.filter(filterCalc);
176
109
  const metaCalcsToRun = metaCalcs.filter(filterCalc);
177
-
178
110
  return { standardCalcsToRun, metaCalcsToRun };
179
111
  }
180
112
 
@@ -183,50 +115,35 @@ function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingRe
183
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; }
184
116
 
185
117
  /** * Stage 7: Load historical data required for calculations
186
- * --- THIS FUNCTION IS NOW FIXED ---
187
118
  */
188
119
  async function loadHistoricalData(date, calcs, config, deps, rootData) {
189
- const { logger } = deps; // <--- THIS WAS THE MISSING LINE
190
- const updated = {...rootData}, dStr=date.toISOString().slice(0,10); const tasks = [];
191
-
120
+ const { logger } = deps;
121
+ const updated = {...rootData}, dStr=date.toISOString().slice(0,10); const tasks = [];
192
122
  const needsYesterdayPortfolio = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('portfolio'));
193
- const needsTodayHistory = calcs.some(c => c.rootDataDependencies.includes('history'));
194
- const needsYesterdayHistory = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('history'));
195
- const needsYesterdayInsights = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('insights'));
196
- const needsYesterdaySocial = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('social'));
197
-
198
- // --- 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'));
199
127
  if(needsYesterdayPortfolio) {
200
128
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
201
- logger.log('INFO', `[PassRunner] Loading YESTERDAY portfolio data for ${prevStr}`);
202
- updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr));
203
- })());
204
- }
129
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY portfolio data for ${prevStr}`);
130
+ updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr)); })());}
205
131
  if(needsTodayHistory) {
206
132
  tasks.push((async()=>{
207
- logger.log('INFO', `[PassRunner] Loading TODAY history data for ${dStr}`);
208
- updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs);
209
- })());
210
- }
133
+ logger.log('INFO', `[PassRunner] Loading TODAY history data for ${dStr}`);
134
+ updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs); })());}
211
135
  if(needsYesterdayHistory) {
212
136
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
213
- logger.log('INFO', `[PassRunner] Loading YESTERDAY history data for ${prevStr}`);
214
- updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr));
215
- })());
216
- }
137
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY history data for ${prevStr}`);
138
+ updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr)); })());}
217
139
  if(needsYesterdayInsights) {
218
140
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
219
- logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
220
- updated.yesterdayInsights=await loadDailyInsights(config,deps,prevStr);
221
- })());
222
- }
141
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
142
+ updated.yesterdayInsights=await loadDailyInsights(config,deps,prevStr); })());}
223
143
  if(needsYesterdaySocial) {
224
144
  tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
225
- logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
226
- updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr);
227
- })());
228
- }
229
-
145
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
146
+ updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr); })());}
230
147
  await Promise.all(tasks); return updated;
231
148
  }
232
149
 
@@ -237,27 +154,17 @@ async function loadHistoricalData(date, calcs, config, deps, rootData) {
237
154
  */
238
155
  async function streamAndProcess(dateStr, state, passName, config, deps, rootData) {
239
156
  const { logger, calculationUtils } = deps;
240
- // --- MODIFIED: yesterdayInsights/Social are now loaded by loadHistoricalData ---
241
- // --- THIS WILL NOW WORK, as rootData contains 'todayInsights' from Stage 3 ---
242
157
  const { todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights, todayHistoryData, yesterdayHistoryData, yesterdayPortfolios } = rootData;
243
-
244
- // --- NEW: Check if streaming is even needed ---
245
158
  const calcsThatStreamPortfolio = Object.values(state).filter(calc => calc && calc.manifest && (calc.manifest.rootDataDependencies.includes('portfolio') || calc.manifest.category === 'speculators'));
246
-
247
159
  const context={instrumentMappings:(await calculationUtils.loadInstrumentMappings()).instrumentToTicker, sectorMapping:(await calculationUtils.loadInstrumentMappings()).instrumentToSector, todayDateStr:dateStr, dependencies:deps, config};
248
-
249
- // --- MODIFIED: Run non-streaming calcs first (social/insights) ---
250
- // This allows them to run even if portfolio data is missing
251
- let firstUser=true; // Used to run them only once
160
+ let firstUser=true;
252
161
  for(const name in state){
253
162
  const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
254
163
  const cat=calc.manifest.category;
255
164
  if(cat==='socialPosts'||cat==='insights') {
256
165
  if (firstUser) {
257
166
  logger.log('INFO', `[${passName}] Running non-streaming calc: ${name}`);
258
- // --- This 'args' array will now receive the correct data ---
259
167
  let args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
260
- // Pass historical data if needed
261
168
  if(calc.manifest.isHistorical) {
262
169
  args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
263
170
  }
@@ -265,15 +172,10 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
265
172
  }
266
173
  }
267
174
  }
268
-
269
-
270
175
  if (calcsThatStreamPortfolio.length === 0) {
271
176
  logger.log('INFO', `[${passName}] No portfolio-streaming calcs to run for ${dateStr}. Skipping stream.`);
272
- return; // Exit stream function
273
- }
274
-
177
+ return; }
275
178
  logger.log('INFO', `[${passName}] Streaming portfolio data for ${calcsThatStreamPortfolio.length} calcs...`);
276
-
277
179
  for await (const chunk of streamPortfolioData(config, deps, dateStr)) {
278
180
  for(const uid in chunk){ const p=chunk[uid]; if(!p) continue;
279
181
  const userType=p.PublicPositions?'speculator':'normal';
@@ -281,19 +183,12 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
281
183
  for(const name in state){
282
184
  const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
283
185
  const cat=calc.manifest.category, isSocialOrInsights=cat==='socialPosts'||cat==='insights', isHistorical=calc.manifest.isHistorical, isSpec=cat==='speculators';
284
-
285
- // --- MODIFIED: Skip social/insights here, they ran above ---
286
186
  if(isSocialOrInsights) continue;
287
-
288
- // --- This 'args' array will now receive the correct data ---
289
187
  let args=[p,null,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
290
-
291
188
  if(isHistorical){
292
- const pY=yesterdayPortfolios ? yesterdayPortfolios[uid] : null; // Check if yesterdayPortfolios exists
293
- // V3 behavioural calcs (like history aggregator) *can* run without pY
189
+ const pY=yesterdayPortfolios ? yesterdayPortfolios[uid] : null;
294
190
  if(!pY && (cat !== 'behavioural' && name !== 'historical-performance-aggregator')) continue;
295
- args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
296
- }
191
+ args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData]; }
297
192
  if((userType==='normal'&&isSpec)||(userType==='speculator'&&!isSpec&&name!=='users-processed')) continue;
298
193
  try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} for ${uid}`,{err:e.message});} }
299
194
  firstUser=false;
@@ -304,20 +199,13 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
304
199
  /** Stage 9: Run standard computations */
305
200
  async function runStandardComputationPass(date, calcs, passName, config, deps, rootData) {
306
201
  const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
307
- // --- THIS IS THE CRITICAL CHANGE ---
308
- // If 'calcs' is empty *because of the new filter*, this log won't even appear.
309
202
  if (calcs.length === 0) {
310
203
  logger.log('INFO', `[${passName}] No standard calcs to run for ${dStr} after filtering.`);
311
- return;
312
- }
313
- // This log now only appears if there is *actually* work to do.
204
+ return; }
314
205
  logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
315
-
316
206
  const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
317
207
  const state = initializeCalculators(calcs, logger);
318
-
319
208
  await streamAndProcess(dStr, state, passName, config, deps, fullRoot);
320
-
321
209
  let success = 0;
322
210
  const standardWrites = [];
323
211
  const shardedWrites = {};
@@ -354,8 +242,8 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
354
242
  if (standardWrites.length > 0) {
355
243
  await commitBatchInChunks(config, deps, standardWrites, `${passName} Standard ${dStr}`);
356
244
  }
357
- for (const docPath in shardedWrites) { // 'docPath' is the key, e.g., 'user_profile_history_shard_0' or 'social_.../history'
358
- const docData = shardedWrites[docPath]; // 'docData' is the object to write, e.g., { profiles: ... }
245
+ for (const docPath in shardedWrites) {
246
+ const docData = shardedWrites[docPath];
359
247
  const shardedDocWrites = [];
360
248
  let docRef;
361
249
  if (docPath.includes('/')) {
@@ -381,17 +269,12 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
381
269
  /** Stage 10: Run meta computations */
382
270
  async function runMetaComputationPass(date, calcs, passName, config, deps, fetchedDeps, rootData) {
383
271
  const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
384
- // --- THIS IS THE CRITICAL CHANGE ---
385
272
  if (calcs.length === 0) {
386
273
  logger.log('INFO', `[${passName}] No meta calcs to run for ${dStr} after filtering.`);
387
274
  return;
388
275
  }
389
276
  logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
390
-
391
- // --- NEW: Load historical data for meta calcs if needed ---
392
- // (This might be redundant if standard pass ran, but meta-calcs can run standalone)
393
277
  const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
394
-
395
278
  let success = 0;
396
279
  const standardWrites = [];
397
280
  const shardedWrites = {};
@@ -400,7 +283,6 @@ async function runMetaComputationPass(date, calcs, passName, config, deps, fetch
400
283
  if (typeof Cl !== 'function') {logger.log('ERROR', `Invalid class ${name}`);continue;}
401
284
  const inst = new Cl();
402
285
  try {
403
- // --- MODIFIED: Pass fullRoot to meta calcs ---
404
286
  const result = await Promise.resolve(inst.process(dStr, { ...deps, rootData: fullRoot }, config, fetchedDeps));
405
287
  if (result && Object.keys(result).length > 0) {const standardResult = {}; for (const key in result) {
406
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.144",
3
+ "version": "1.0.145",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [