bulltrackers-module 1.0.140 → 1.0.142

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.
@@ -7,7 +7,8 @@ const { normalizeName, commitBatchInChunks } = require('../utils/utils.js');
7
7
  function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[calc.pass] = acc[calc.pass] || []).push(calc); return acc; }, {}); }
8
8
 
9
9
  /** * --- MODIFIED: Returns detailed missing dependencies for logging ---
10
- * Stage 2: Check root data dependencies for a calc
10
+ * Stage 2: Check root data dependencies for a calc
11
+ * --- THIS FUNCTION IS NOW MORE GRANULAR ---
11
12
  */
12
13
  function checkRootDependencies(calcManifest, rootDataStatus) {
13
14
  const missing = [];
@@ -23,7 +24,7 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
23
24
  }
24
25
 
25
26
  /** * --- MODIFIED: Uses earliestDates map to avoid unnecessary queries ---
26
- * Stage 3: Check root data availability for a date
27
+ * Stage 3: Check root data availability for a date
27
28
  */
28
29
  async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
29
30
  const { logger } = dependencies;
@@ -33,7 +34,9 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
33
34
  let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false;
34
35
  try {
35
36
  const tasks = [];
36
- if (dateToProcess >= earliestDates.portfolio)
37
+ // This logic is correct. It *avoids* calling get...Refs
38
+ // if the dateToProcess is before the earliest known data.
39
+ if (dateToProcess >= earliestDates.portfolio)
37
40
  {tasks.push(getPortfolioPartRefs(config, dependencies, dateStr).then(res => {portfolioRefs = res;hasPortfolio = !!(res?.length);}));}
38
41
  if (dateToProcess >= earliestDates.insights) {
39
42
  tasks.push(loadDailyInsights(config, dependencies, dateStr).then(res => {insightsData = res;hasInsights = !!res;}));}
@@ -41,8 +44,17 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
41
44
  tasks.push(loadDailySocialPostInsights(config, dependencies, dateStr).then(res => {socialData = res;hasSocial = !!res;}));}
42
45
  if (dateToProcess >= earliestDates.history) {
43
46
  tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(res => {historyRefs = res;hasHistory = !!(res?.length);}));}
47
+
44
48
  await Promise.all(tasks);
45
- if (!(hasPortfolio || hasInsights || hasSocial || hasHistory)) {logger.log('WARN', `[PassRunner] No root data for ${dateStr}.`); return null;}
49
+
50
+ // --- NEW: Log what was *actually* found ---
51
+ logger.log('INFO', `[PassRunner] Data availability for ${dateStr}: P:${hasPortfolio}, I:${hasInsights}, S:${hasSocial}, H:${hasHistory}`);
52
+
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
+ }
46
58
  return {portfolioRefs, insightsData,socialData,historyRefs,status: { hasPortfolio, hasInsights, hasSocial, hasHistory }};
47
59
  } catch (err) { logger.log('ERROR', `[PassRunner] Error checking data for ${dateStr}`, { errorMessage: err.message }); return null; }
48
60
  }
@@ -70,34 +82,85 @@ async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config,
70
82
  return fetched;
71
83
  }
72
84
 
73
- /** --- MODIFIED: Stage 5: Filter calculations to skip completed work ---
85
+ /**
86
+ * --- ENTIRELY REWRITTEN: Stage 5: Filter calculations ---
87
+ * This function now implements your "even better design".
88
+ * It calculates the *true earliest run date* for every calculation
89
+ * and filters them out *before* the "Running..." log ever appears.
74
90
  */
75
- function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingResults, passToRun, dateStr, logger, isFirstDayOfBackfill = false) {
91
+ function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingResults, passToRun, dateStr, earliestDates, logger) {
76
92
  const skipped = new Set();
77
- const standardCalcsToRun = standardCalcs.filter(c => {
78
- if (existingResults[c.name]) {logger.log('TRACE', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Result already exists.`);return false;}
79
- if (isFirstDayOfBackfill && c.isHistorical) {
80
- logger.log('INFO', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Historical calc on the first day of backfill (no yesterday).`);
81
- skipped.add(c.name);
93
+ 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
+ const getTrueEarliestRunDate = (calc) => {
100
+ let earliestRunDate = new Date('1970-01-01T00:00:00Z'); // Start at zero
101
+ 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
+ for (const dep of dependencies) {
106
+ if (dep === 'portfolio' && earliestDates.portfolio > earliestRunDate) earliestRunDate = earliestDates.portfolio;
107
+ if (dep === 'history' && earliestDates.history > earliestRunDate) earliestRunDate = earliestDates.history;
108
+ if (dep === 'social' && earliestDates.social > earliestRunDate) earliestRunDate = earliestDates.social;
109
+ if (dep === 'insights' && earliestDates.insights > earliestRunDate) earliestRunDate = earliestDates.insights;
110
+ }
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
+
119
+ return earliestRunDate;
120
+ };
121
+
122
+ 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);
82
127
  return false;
83
128
  }
84
- });
85
- // Filter Meta Calcs
86
- const metaCalcsToRun = metaCalcs.filter(c => {
87
- if (existingResults[c.name]) {logger.log('TRACE', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Result already exists.`);skipped.add(c.name);return false;}
88
- // --- START: RECOMMENDED ADDITION ---
89
- if (isFirstDayOfBackfill && c.isHistorical) {
90
- logger.log('INFO', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Historical calc on the first day of backfill (no yesterday).`);
91
- skipped.add(c.name);
129
+
130
+ // 2. Check *true* earliest run date
131
+ 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
140
+ 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);
92
144
  return false;
93
145
  }
94
- // --- END: RECOMMENDED ADDITION ---
95
- // 1. Check root data
96
- const { canRun, missing: missingRoot } = checkRootDependencies(c, rootDataStatus);
97
- if (!canRun) {logger.log('INFO', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Missing root data: [${missingRoot.join(', ')}]`);skipped.add(c.name);return false;}
98
- // 2. Check computed dependencies
99
- const missingDeps = (c.dependencies || []).map(normalizeName).filter(d => !existingResults[d]);
100
- if (missingDeps.length > 0) {logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Missing computed deps: [${missingDeps.join(', ')}]`);skipped.add(c.name);return false;} return true;});
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
+ }
156
+
157
+ // If it passed all checks, run it.
158
+ return true;
159
+ };
160
+
161
+ const standardCalcsToRun = standardCalcs.filter(filterCalc);
162
+ const metaCalcsToRun = metaCalcs.filter(filterCalc);
163
+
101
164
  return { standardCalcsToRun, metaCalcsToRun };
102
165
  }
103
166
 
@@ -107,21 +170,91 @@ function initializeCalculators(calcs, logger) { const state = {}; for (const c o
107
170
 
108
171
  /** Stage 7: Load historical data required for calculations */
109
172
  async function loadHistoricalData(date, calcs, config, deps, rootData) { const updated = {...rootData}, dStr=date.toISOString().slice(0,10); const tasks = [];
110
- if(calcs.some(c=>c.isHistorical)) tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10); updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr)); })());
111
- if(calcs.some(c=>c.rootDataDependencies.includes('history'))) tasks.push((async()=>{ updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs); })());
112
- if(calcs.some(c=>c.isHistorical)) tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10); updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr)); })());
173
+ const needsYesterdayPortfolio = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('portfolio'));
174
+ const needsTodayHistory = calcs.some(c => c.rootDataDependencies.includes('history'));
175
+ const needsYesterdayHistory = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('history'));
176
+ // --- NEW: Add checks for historical insights and social ---
177
+ const needsYesterdayInsights = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('insights'));
178
+ const needsYesterdaySocial = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('social'));
179
+
180
+ // --- MODIFIED: Be smarter about loading data ---
181
+ if(needsYesterdayPortfolio) {
182
+ tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
183
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY portfolio data for ${prevStr}`);
184
+ updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr));
185
+ })());
186
+ }
187
+ if(needsTodayHistory) {
188
+ tasks.push((async()=>{
189
+ logger.log('INFO', `[PassRunner] Loading TODAY history data for ${dStr}`);
190
+ updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs);
191
+ })());
192
+ }
193
+ if(needsYesterdayHistory) {
194
+ tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
195
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY history data for ${prevStr}`);
196
+ updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr));
197
+ })());
198
+ }
199
+ // --- NEW: Load historical insights/social ---
200
+ if(needsYesterdayInsights) {
201
+ tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
202
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
203
+ updated.yesterdayInsights=await loadDailyInsights(config,deps,prevStr);
204
+ })());
205
+ }
206
+ if(needsYesterdaySocial) {
207
+ tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
208
+ logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
209
+ updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr);
210
+ })());
211
+ }
212
+
113
213
  await Promise.all(tasks); return updated;
114
214
  }
115
215
 
216
+
116
217
  /** * --- MODIFIED: Stage 8: Stream and process data for standard calculations ---
117
218
  * This function now uses an async generator to stream portfolio data
118
219
  * instead of loading it all into memory.
119
220
  */
120
221
  async function streamAndProcess(dateStr, state, passName, config, deps, rootData) {
121
222
  const { logger, calculationUtils } = deps;
223
+ // --- MODIFIED: yesterdayInsights/Social are now loaded by loadHistoricalData ---
122
224
  const { todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights, todayHistoryData, yesterdayHistoryData, yesterdayPortfolios } = rootData;
123
- let firstUser=true;
225
+
226
+ // --- NEW: Check if streaming is even needed ---
227
+ const calcsThatStreamPortfolio = Object.values(state).filter(calc => calc && calc.manifest && (calc.manifest.rootDataDependencies.includes('portfolio') || calc.manifest.category === 'speculators'));
228
+
124
229
  const context={instrumentMappings:(await calculationUtils.loadInstrumentMappings()).instrumentToTicker, sectorMapping:(await calculationUtils.loadInstrumentMappings()).instrumentToSector, todayDateStr:dateStr, dependencies:deps, config};
230
+
231
+ // --- MODIFIED: Run non-streaming calcs first (social/insights) ---
232
+ // This allows them to run even if portfolio data is missing
233
+ let firstUser=true; // Used to run them only once
234
+ for(const name in state){
235
+ const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
236
+ const cat=calc.manifest.category;
237
+ if(cat==='socialPosts'||cat==='insights') {
238
+ if (firstUser) {
239
+ logger.log('INFO', `[${passName}] Running non-streaming calc: ${name}`);
240
+ let args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
241
+ // Pass historical data if needed
242
+ if(calc.manifest.isHistorical) {
243
+ args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
244
+ }
245
+ try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} (non-stream)`,{err:e.message});}
246
+ }
247
+ }
248
+ }
249
+
250
+
251
+ if (calcsThatStreamPortfolio.length === 0) {
252
+ logger.log('INFO', `[${passName}] No portfolio-streaming calcs to run for ${dateStr}. Skipping stream.`);
253
+ return; // Exit stream function
254
+ }
255
+
256
+ logger.log('INFO', `[${passName}] Streaming portfolio data for ${calcsThatStreamPortfolio.length} calcs...`);
257
+
125
258
  for await (const chunk of streamPortfolioData(config, deps, dateStr)) {
126
259
  for(const uid in chunk){ const p=chunk[uid]; if(!p) continue;
127
260
  const userType=p.PublicPositions?'speculator':'normal';
@@ -129,9 +262,18 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
129
262
  for(const name in state){
130
263
  const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
131
264
  const cat=calc.manifest.category, isSocialOrInsights=cat==='socialPosts'||cat==='insights', isHistorical=calc.manifest.isHistorical, isSpec=cat==='speculators';
265
+
266
+ // --- MODIFIED: Skip social/insights here, they ran above ---
267
+ if(isSocialOrInsights) continue;
268
+
132
269
  let args=[p,null,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
133
- if(isSocialOrInsights&&!firstUser) continue;
134
- if(isHistorical){ const pY=yesterdayPortfolios[uid]; if(!pY) continue; args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData]; }
270
+
271
+ if(isHistorical){
272
+ const pY=yesterdayPortfolios ? yesterdayPortfolios[uid] : null; // Check if yesterdayPortfolios exists
273
+ // V3 behavioural calcs (like history aggregator) *can* run without pY
274
+ if(!pY && (cat !== 'behavioural' && name !== 'historical-performance-aggregator')) continue;
275
+ args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
276
+ }
135
277
  if((userType==='normal'&&isSpec)||(userType==='speculator'&&!isSpec&&name!=='users-processed')) continue;
136
278
  try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} for ${uid}`,{err:e.message});} }
137
279
  firstUser=false;
@@ -142,11 +284,20 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
142
284
  /** Stage 9: Run standard computations */
143
285
  async function runStandardComputationPass(date, calcs, passName, config, deps, rootData) {
144
286
  const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
145
- if (calcs.length === 0) return;
287
+ // --- THIS IS THE CRITICAL CHANGE ---
288
+ // If 'calcs' is empty *because of the new filter*, this log won't even appear.
289
+ if (calcs.length === 0) {
290
+ logger.log('INFO', `[${passName}] No standard calcs to run for ${dateStr} after filtering.`);
291
+ return;
292
+ }
293
+ // This log now only appears if there is *actually* work to do.
146
294
  logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
295
+
147
296
  const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
148
297
  const state = initializeCalculators(calcs, logger);
298
+
149
299
  await streamAndProcess(dStr, state, passName, config, deps, fullRoot);
300
+
150
301
  let success = 0;
151
302
  const standardWrites = [];
152
303
  const shardedWrites = {};
@@ -188,25 +339,19 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
188
339
  const shardedDocWrites = [];
189
340
  let docRef;
190
341
  if (docPath.includes('/')) {
191
- // Path is absolute, e.g., 'social_prediction_regime_state/history'
192
342
  docRef = deps.db.doc(docPath);
193
343
  } else {
194
- // Path is a docId, e.g., 'user_profile_history_shard_0'
195
- // We must infer its collection from config.
196
344
  const collection = (docPath.startsWith('user_profile_history'))
197
- ? config.shardedUserProfileCollection // 'user_profile_history'
198
- : config.shardedProfitabilityCollection; // Fallback
345
+ ? config.shardedUserProfileCollection
346
+ : config.shardedProfitabilityCollection;
199
347
  docRef = deps.db.collection(collection).doc(docPath);
200
348
  }
201
- // Ensure data is a valid object before pushing
202
349
  if (docData && typeof docData === 'object' && !Array.isArray(docData)) {
203
350
  shardedDocWrites.push({ ref: docRef, data: docData });
204
351
  } else {
205
352
  logger.log('ERROR', `[${passName}] Invalid sharded document data for ${docPath}. Not an object.`, { data: docData });
206
353
  }
207
- // Commit this single document write (or small batch if logic is changed later)
208
354
  if (shardedDocWrites.length > 0) {
209
- // Use the docPath in the operation name for clearer logging
210
355
  await commitBatchInChunks(config, deps, shardedDocWrites, `${passName} Sharded ${docPath} ${dStr}`);
211
356
  }
212
357
  }
@@ -216,8 +361,17 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
216
361
  /** Stage 10: Run meta computations */
217
362
  async function runMetaComputationPass(date, calcs, passName, config, deps, fetchedDeps, rootData) {
218
363
  const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
219
- if (calcs.length === 0) return;
364
+ // --- THIS IS THE CRITICAL CHANGE ---
365
+ if (calcs.length === 0) {
366
+ logger.log('INFO', `[${passName}] No meta calcs to run for ${dStr} after filtering.`);
367
+ return;
368
+ }
220
369
  logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
370
+
371
+ // --- NEW: Load historical data for meta calcs if needed ---
372
+ // (This might be redundant if standard pass ran, but meta-calcs can run standalone)
373
+ const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
374
+
221
375
  let success = 0;
222
376
  const standardWrites = [];
223
377
  const shardedWrites = {};
@@ -226,7 +380,8 @@ async function runMetaComputationPass(date, calcs, passName, config, deps, fetch
226
380
  if (typeof Cl !== 'function') {logger.log('ERROR', `Invalid class ${name}`);continue;}
227
381
  const inst = new Cl();
228
382
  try {
229
- const result = await Promise.resolve(inst.process(dStr, { ...deps, rootData }, config, fetchedDeps));
383
+ // --- MODIFIED: Pass fullRoot to meta calcs ---
384
+ const result = await Promise.resolve(inst.process(dStr, { ...deps, rootData: fullRoot }, config, fetchedDeps));
230
385
  if (result && Object.keys(result).length > 0) {const standardResult = {}; for (const key in result) {
231
386
  if (key.startsWith('sharded_')) {const shardedData = result[key];for (const collectionName in shardedData)
232
387
  {if (!shardedWrites[collectionName]) shardedWrites[collectionName] = {};Object.assign(shardedWrites[collectionName], shardedData[collectionName]);}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.140",
3
+ "version": "1.0.142",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [