bulltrackers-module 1.0.631 → 1.0.633

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.
@@ -5,12 +5,14 @@
5
5
  * UPDATED: Sends 'isInitialWrite: true' for robust cleanup.
6
6
  * UPDATED: Support for historical rankings in Meta Context.
7
7
  * UPDATED: Added support for loading Series Data (Root & Results) for lookbacks.
8
+ * UPDATED: Builds Manifest Lookup for DependencyFetcher.
8
9
  */
9
10
  const { normalizeName } = require('../utils/utils');
10
11
  const { CachedDataLoader } = require('../data/CachedDataLoader');
11
12
  const { ContextFactory } = require('../context/ContextFactory');
12
13
  const { commitResults } = require('../persistence/ResultCommitter');
13
- const { fetchResultSeries } = require('../data/DependencyFetcher'); // [NEW] Import series fetcher
14
+ const { fetchResultSeries, fetchDependencies } = require('../data/DependencyFetcher');
15
+ const { getManifest } = require('../topology/ManifestLoader');
14
16
 
15
17
  class MetaExecutor {
16
18
  static async run(date, calcs, passName, config, deps, rootData, fetchedDeps, previousFetchedDeps) {
@@ -18,6 +20,12 @@ class MetaExecutor {
18
20
  const { logger, db } = deps;
19
21
  const loader = new CachedDataLoader(config, deps);
20
22
 
23
+ // --- [FIXED] Build Global Manifest Lookup using getManifest ---
24
+ // getManifest is typically cached, so this is cheap.
25
+ const allManifests = getManifest(config.productLines, config.calculationsDirectory, deps);
26
+ const manifestLookup = {};
27
+ allManifests.forEach(m => { manifestLookup[normalizeName(m.name)] = m.category; });
28
+
21
29
  // [FIX] Check if any meta calculation needs history
22
30
  const needsHistory = calcs.some(c => c.isHistorical);
23
31
  let rankingsYesterday = null;
@@ -47,82 +55,64 @@ class MetaExecutor {
47
55
  let ratings = null, pageViews = null, watchlistMembership = null, alertHistory = null;
48
56
  if (needsNewRootData) {
49
57
  const loadPromises = [];
58
+
59
+ // Ratings
50
60
  if (calcs.some(c => c.rootDataDependencies?.includes('ratings'))) {
51
61
  loadPromises.push(loader.loadRatings(dStr).then(r => { ratings = r; }).catch(e => {
52
- // Only catch if ALL calcs allow missing roots
53
62
  const allAllowMissing = calcs.every(c => {
54
63
  const needsRatings = c.rootDataDependencies?.includes('ratings');
55
64
  return !needsRatings || c.canHaveMissingRoots === true;
56
65
  });
57
- if (allAllowMissing) {
58
- ratings = null;
59
- } else {
60
- throw e; // Re-throw if any calc requires this data
61
- }
66
+ if (allAllowMissing) { ratings = null; } else { throw e; }
62
67
  }));
63
68
  }
69
+
70
+ // PageViews
64
71
  if (calcs.some(c => c.rootDataDependencies?.includes('pageViews'))) {
65
72
  loadPromises.push(loader.loadPageViews(dStr).then(pv => { pageViews = pv; }).catch(e => {
66
73
  const allAllowMissing = calcs.every(c => {
67
74
  const needsPageViews = c.rootDataDependencies?.includes('pageViews');
68
75
  return !needsPageViews || c.canHaveMissingRoots === true;
69
76
  });
70
- if (allAllowMissing) {
71
- pageViews = null;
72
- } else {
73
- throw e;
74
- }
77
+ if (allAllowMissing) { pageViews = null; } else { throw e; }
75
78
  }));
76
79
  }
80
+
81
+ // Watchlist
77
82
  if (calcs.some(c => c.rootDataDependencies?.includes('watchlist'))) {
78
83
  loadPromises.push(loader.loadWatchlistMembership(dStr).then(w => { watchlistMembership = w; }).catch(e => {
79
84
  const allAllowMissing = calcs.every(c => {
80
85
  const needsWatchlist = c.rootDataDependencies?.includes('watchlist');
81
86
  return !needsWatchlist || c.canHaveMissingRoots === true;
82
87
  });
83
- if (allAllowMissing) {
84
- watchlistMembership = null;
85
- } else {
86
- throw e;
87
- }
88
+ if (allAllowMissing) { watchlistMembership = null; } else { throw e; }
88
89
  }));
89
90
  }
91
+
92
+ // Alerts
90
93
  if (calcs.some(c => c.rootDataDependencies?.includes('alerts'))) {
91
94
  loadPromises.push(loader.loadAlertHistory(dStr).then(a => { alertHistory = a; }).catch(e => {
92
95
  const allAllowMissing = calcs.every(c => {
93
96
  const needsAlerts = c.rootDataDependencies?.includes('alerts');
94
97
  return !needsAlerts || c.canHaveMissingRoots === true;
95
98
  });
96
- if (allAllowMissing) {
97
- alertHistory = null;
98
- } else {
99
- throw e;
100
- }
99
+ if (allAllowMissing) { alertHistory = null; } else { throw e; }
101
100
  }));
102
101
  }
102
+
103
103
  await Promise.all(loadPromises);
104
104
 
105
105
  // [FIX] Enforce canHaveMissingRoots - validate after loading
106
106
  for (const c of calcs) {
107
107
  const deps = c.rootDataDependencies || [];
108
108
  const canSkip = c.canHaveMissingRoots === true;
109
-
110
- // Helper to check if a specific root is missing
111
109
  const isMissing = (key, val) => deps.includes(key) && (val === null || val === undefined);
112
110
 
113
111
  if (!canSkip) {
114
- if (isMissing('ratings', ratings)) {
115
- throw new Error(`[MetaExecutor] Missing required root 'ratings' for ${c.name}`);
116
- }
117
- if (isMissing('pageViews', pageViews)) {
118
- throw new Error(`[MetaExecutor] Missing required root 'pageViews' for ${c.name}`);
119
- }
120
- if (isMissing('watchlist', watchlistMembership)) {
121
- throw new Error(`[MetaExecutor] Missing required root 'watchlist' for ${c.name}`);
122
- }
123
- if (isMissing('alerts', alertHistory)) {
124
- throw new Error(`[MetaExecutor] Missing required root 'alerts' for ${c.name}`);
125
- }
112
+ if (isMissing('ratings', ratings)) throw new Error(`[MetaExecutor] Missing required root 'ratings' for ${c.name}`);
113
+ if (isMissing('pageViews', pageViews)) throw new Error(`[MetaExecutor] Missing required root 'pageViews' for ${c.name}`);
114
+ if (isMissing('watchlist', watchlistMembership)) throw new Error(`[MetaExecutor] Missing required root 'watchlist' for ${c.name}`);
115
+ if (isMissing('alerts', alertHistory)) throw new Error(`[MetaExecutor] Missing required root 'alerts' for ${c.name}`);
126
116
  }
127
117
  }
128
118
  }
@@ -133,7 +123,6 @@ class MetaExecutor {
133
123
 
134
124
  // 1. Identify requirements from manifests
135
125
  calcs.forEach(c => {
136
- // Check for Root Data Series
137
126
  if (c.rootDataSeries) {
138
127
  Object.entries(c.rootDataSeries).forEach(([type, conf]) => {
139
128
  const days = typeof conf === 'object' ? conf.lookback : conf;
@@ -142,7 +131,6 @@ class MetaExecutor {
142
131
  }
143
132
  });
144
133
  }
145
- // Check for Computation Result Series
146
134
  if (c.dependencySeries) {
147
135
  Object.entries(c.dependencySeries).forEach(([depName, conf]) => {
148
136
  const days = typeof conf === 'object' ? conf.lookback : conf;
@@ -163,13 +151,13 @@ class MetaExecutor {
163
151
  else if (type === 'insights') loaderMethod = 'loadInsights';
164
152
  else if (type === 'ratings') loaderMethod = 'loadRatings';
165
153
  else if (type === 'watchlist') loaderMethod = 'loadWatchlistMembership';
166
- // Add other root types if needed
154
+ // [CRITICAL UPDATE] Add rankings support for Meta lookbacks
155
+ else if (type === 'rankings') loaderMethod = 'loadRankings';
167
156
 
168
157
  if (loaderMethod) {
169
158
  logger.log('INFO', `[MetaExecutor] Loading ${days}-day series for Root Data '${type}'...`);
170
- // Assume CachedDataLoader has loadSeries method (added in previous step)
171
159
  const series = await loader.loadSeries(loaderMethod, dStr, days);
172
- seriesData.root[type] = series.data; // map of date -> data
160
+ seriesData.root[type] = series.data;
173
161
  }
174
162
  }
175
163
 
@@ -179,11 +167,10 @@ class MetaExecutor {
179
167
  const maxDays = Math.max(...Object.values(dependencySeriesRequests));
180
168
  logger.log('INFO', `[MetaExecutor] Loading up to ${maxDays}-day series for Dependencies: ${calcNamesToFetch.join(', ')}`);
181
169
 
182
- // We pass the list of manifests (calcs) so the fetcher knows details if needed
183
- const resultsSeries = await fetchResultSeries(dStr, calcNamesToFetch, calcs, config, deps, maxDays);
184
- seriesData.results = resultsSeries; // map of date -> { calcName: data }
170
+ // Pass manifestLookup to fetcher
171
+ const resultsSeries = await fetchResultSeries(dStr, calcNamesToFetch, manifestLookup, config, deps, maxDays);
172
+ seriesData.results = resultsSeries;
185
173
  }
186
- // ---------------------------------------------
187
174
 
188
175
  const state = {};
189
176
  for (const c of calcs) {
@@ -200,21 +187,17 @@ class MetaExecutor {
200
187
  previousComputedDependencies: previousFetchedDeps,
201
188
  config, deps,
202
189
  allRankings: rankings,
203
- allRankingsYesterday: rankingsYesterday, // [FIX] Injected
190
+ allRankingsYesterday: rankingsYesterday,
204
191
  allVerifications: verifications,
205
- // [NEW] Pass New Root Data Types for Profile Metrics
206
192
  ratings: ratings || {},
207
193
  pageViews: pageViews || {},
208
194
  watchlistMembership: watchlistMembership || {},
209
195
  alertHistory: alertHistory || {},
210
- // [NEW] Pass Series Data
211
196
  seriesData
212
197
  });
213
198
 
214
199
  try {
215
200
  const result = await inst.process(context);
216
- // Meta results are usually wrapped in a global key or just the result object
217
- // The structure below implies we store it under the date key
218
201
  inst.results = { [dStr]: { global: result } };
219
202
  state[c.name] = inst;
220
203
  } catch (e) {
@@ -222,7 +205,8 @@ class MetaExecutor {
222
205
  }
223
206
  }
224
207
 
225
- return await commitResults(state, dStr, passName, config, deps);
208
+ // CRITICAL FIX: Pass 'isInitialWrite: true' to ensure proper cleanup of old meta data
209
+ return await commitResults(state, dStr, passName, config, deps, false, { isInitialWrite: true });
226
210
  }
227
211
 
228
212
  static async executeOncePerDay(calcInstance, metadata, dateStr, computedDeps, prevDeps, config, deps, loader) {
@@ -233,7 +217,6 @@ class MetaExecutor {
233
217
  const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await loader.loadInsights(dateStr) } : null;
234
218
  const social = metadata.rootDataDependencies?.includes('social') ? { today: await loader.loadSocial(dateStr) } : null;
235
219
 
236
- // [FIX] Historical support for Batch/OncePerDay execution
237
220
  let rankingsYesterday = null;
238
221
  if (metadata.isHistorical) {
239
222
  const prevDate = new Date(dateStr);
@@ -242,67 +225,37 @@ class MetaExecutor {
242
225
  rankingsYesterday = await loader.loadRankings(prevStr);
243
226
  }
244
227
 
245
- // Load current rankings (often needed for ContextFactory.buildMetaContext)
246
228
  const rankings = await loader.loadRankings(dateStr);
247
-
248
- // [NEW] Load New Root Data Types for Profile Metrics
249
- // [FIX] Enforce canHaveMissingRoots
250
229
  const allowMissing = metadata.canHaveMissingRoots === true;
251
230
 
252
231
  let ratings = null;
253
232
  if (metadata.rootDataDependencies?.includes('ratings')) {
254
- try {
255
- ratings = await loader.loadRatings(dateStr);
256
- } catch (e) {
257
- if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'ratings' failed to load for ${metadata.name}: ${e.message}`);
258
- ratings = null;
259
- }
260
- if (!ratings && !allowMissing) {
261
- throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'ratings' is missing for ${metadata.name}`);
262
- }
233
+ try { ratings = await loader.loadRatings(dateStr); }
234
+ catch (e) { if (!allowMissing) throw e; ratings = null; }
235
+ if (!ratings && !allowMissing) throw new Error(`Missing ratings`);
263
236
  }
264
237
 
265
238
  let pageViews = null;
266
239
  if (metadata.rootDataDependencies?.includes('pageViews')) {
267
- try {
268
- pageViews = await loader.loadPageViews(dateStr);
269
- } catch (e) {
270
- if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'pageViews' failed to load for ${metadata.name}: ${e.message}`);
271
- pageViews = null;
272
- }
273
- if (!pageViews && !allowMissing) {
274
- throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'pageViews' is missing for ${metadata.name}`);
275
- }
240
+ try { pageViews = await loader.loadPageViews(dateStr); }
241
+ catch (e) { if (!allowMissing) throw e; pageViews = null; }
242
+ if (!pageViews && !allowMissing) throw new Error(`Missing pageViews`);
276
243
  }
277
244
 
278
245
  let watchlistMembership = null;
279
246
  if (metadata.rootDataDependencies?.includes('watchlist')) {
280
- try {
281
- watchlistMembership = await loader.loadWatchlistMembership(dateStr);
282
- } catch (e) {
283
- if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'watchlist' failed to load for ${metadata.name}: ${e.message}`);
284
- watchlistMembership = null;
285
- }
286
- if (!watchlistMembership && !allowMissing) {
287
- throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'watchlist' is missing for ${metadata.name}`);
288
- }
247
+ try { watchlistMembership = await loader.loadWatchlistMembership(dateStr); }
248
+ catch (e) { if (!allowMissing) throw e; watchlistMembership = null; }
249
+ if (!watchlistMembership && !allowMissing) throw new Error(`Missing watchlist`);
289
250
  }
290
251
 
291
252
  let alertHistory = null;
292
253
  if (metadata.rootDataDependencies?.includes('alerts')) {
293
- try {
294
- alertHistory = await loader.loadAlertHistory(dateStr);
295
- } catch (e) {
296
- if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'alerts' failed to load for ${metadata.name}: ${e.message}`);
297
- alertHistory = null;
298
- }
299
- if (!alertHistory && !allowMissing) {
300
- throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'alerts' is missing for ${metadata.name}`);
301
- }
254
+ try { alertHistory = await loader.loadAlertHistory(dateStr); }
255
+ catch (e) { if (!allowMissing) throw e; alertHistory = null; }
256
+ if (!alertHistory && !allowMissing) throw new Error(`Missing alerts`);
302
257
  }
303
258
 
304
- // [NOTE] "executeOncePerDay" is typically for sharded price/batch jobs.
305
- // We initialize empty series data to maintain compatibility.
306
259
  const seriesData = { root: {}, results: {} };
307
260
 
308
261
  if (metadata.rootDataDependencies?.includes('price')) {
@@ -319,12 +272,10 @@ class MetaExecutor {
319
272
  previousComputedDependencies: prevDeps, config, deps,
320
273
  allRankings: rankings,
321
274
  allRankingsYesterday: rankingsYesterday,
322
- // [NEW] Pass New Root Data Types for Profile Metrics
323
275
  ratings: ratings || {},
324
276
  pageViews: pageViews || {},
325
277
  watchlistMembership: watchlistMembership || {},
326
278
  alertHistory: alertHistory || {},
327
- // [NEW] Pass Series Data
328
279
  seriesData
329
280
  });
330
281
 
@@ -346,12 +297,10 @@ class MetaExecutor {
346
297
  previousComputedDependencies: prevDeps, config, deps,
347
298
  allRankings: rankings,
348
299
  allRankingsYesterday: rankingsYesterday,
349
- // [NEW] Pass New Root Data Types for Profile Metrics
350
300
  ratings: ratings || {},
351
301
  pageViews: pageViews || {},
352
302
  watchlistMembership: watchlistMembership || {},
353
303
  alertHistory: alertHistory || {},
354
- // [NEW] Pass Series Data
355
304
  seriesData
356
305
  });
357
306
  const res = await calcInstance.process(context);
@@ -1,16 +1,10 @@
1
- /**
2
- * {
3
- * type: uploaded file
4
- * fileName: computation-system/executors/StandardExecutor.js
5
- * }
6
- */
7
1
  const { normalizeName, getEarliestDataDates } = require('../utils/utils');
8
2
  const { streamPortfolioData, streamHistoryData, getPortfolioPartRefs, getHistoryPartRefs } = require('../utils/data_loader');
9
3
  const { CachedDataLoader } = require('../data/CachedDataLoader');
10
4
  const { ContextFactory } = require('../context/ContextFactory');
11
5
  const { commitResults } = require('../persistence/ResultCommitter');
12
- // [NEW] Import series fetcher for computation results
13
6
  const { fetchResultSeries } = require('../data/DependencyFetcher');
7
+ const { getManifest } = require('../topology/ManifestLoader');
14
8
  const mathLayer = require('../layers/index');
15
9
  const { performance } = require('perf_hooks');
16
10
  const v8 = require('v8');
@@ -78,6 +72,11 @@ class StandardExecutor {
78
72
 
79
73
  if (streamingCalcs.length === 0) return { successUpdates: {}, failureReport: [] };
80
74
 
75
+ // --- 0. [NEW] Build Global Manifest Lookup ---
76
+ const allManifests = getManifest(config.productLines, config.calculationsDirectory, deps);
77
+ const manifestLookup = {};
78
+ allManifests.forEach(m => { manifestLookup[normalizeName(m.name)] = m.category; });
79
+
81
80
  // --- 1. Resolve and Filter Portfolio Refs (Today) ---
82
81
  let effectivePortfolioRefs = portfolioRefs;
83
82
  if (!effectivePortfolioRefs) {
@@ -167,7 +166,8 @@ class StandardExecutor {
167
166
  else if (type === 'insights') loaderMethod = 'loadInsights';
168
167
  else if (type === 'ratings') loaderMethod = 'loadRatings';
169
168
  else if (type === 'watchlist') loaderMethod = 'loadWatchlistMembership';
170
- // Add other root types as needed...
169
+ // [CRITICAL UPDATE] Add rankings support for AUM lookback
170
+ else if (type === 'rankings') loaderMethod = 'loadRankings';
171
171
 
172
172
  if (loaderMethod) {
173
173
  logger.log('INFO', `[StandardExecutor] Loading ${days}-day series for Root Data '${type}'...`);
@@ -183,22 +183,8 @@ class StandardExecutor {
183
183
  const maxDays = Math.max(...Object.values(dependencySeriesRequests));
184
184
  logger.log('INFO', `[StandardExecutor] Loading up to ${maxDays}-day series for Dependencies: ${calcNamesToFetch.join(', ')}`);
185
185
 
186
- const allManifests = streamingCalcs.map(c => c.manifest);
187
- const resultsSeries = await fetchResultSeries(dateStr, calcNamesToFetch, allManifests, config, deps, maxDays);
188
-
189
- // [NEW] Validate Series Availability
190
- streamingCalcs.forEach(c => {
191
- if (c.manifest.dependencySeries && !c.manifest.canHaveMissingSeries) {
192
- Object.keys(c.manifest.dependencySeries).forEach(depName => {
193
- const normDep = normalizeName(depName);
194
- // If series data is completely missing for a required dependency, verify logic
195
- if (!resultsSeries[normDep] || Object.keys(resultsSeries[normDep]).length === 0) {
196
- logger.log('WARN', `[StandardExecutor] Missing dependency series '${depName}' for '${c.name}' (and canHaveMissingSeries=false). This may cause calculation errors.`);
197
- }
198
- });
199
- }
200
- });
201
-
186
+ // Pass manifestLookup to fetcher
187
+ const resultsSeries = await fetchResultSeries(dateStr, calcNamesToFetch, manifestLookup, config, deps, maxDays);
202
188
  seriesData.results = resultsSeries;
203
189
  }
204
190
  // ---------------------------------------------
@@ -487,4 +473,4 @@ class StandardExecutor {
487
473
  }
488
474
  }
489
475
 
490
- module.exports = { StandardExecutor };
476
+ module.exports = { StandardExecutor };
@@ -4,6 +4,7 @@
4
4
  * UPDATED: Added support for 'isPage' mode to store per-user data in subcollections.
5
5
  * UPDATED: Implemented TTL retention policy. Defaults to 90 days from the computation date.
6
6
  * UPDATED: Fixed issue where switching to 'isPage' mode didn't clean up old sharded/raw data.
7
+ * CRITICAL FIX: Fixed sharding logic to prevent wiping existing shards during INTERMEDIATE flushes.
7
8
  */
8
9
  const { commitBatchInChunks, generateDataHash, FieldValue } = require('../utils/utils')
9
10
  const { updateComputationStatus } = require('./StatusRepository');
@@ -138,8 +139,6 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
138
139
  continue;
139
140
  }
140
141
 
141
- // [NEW] Page Computation Logic (Fan-Out) with TTL
142
- // Bypasses standard compression/sharding to write per-user documents
143
142
  // [NEW] Page Computation Logic (Fan-Out) with TTL
144
143
  // Bypasses standard compression/sharding to write per-user documents
145
144
  if (isPageComputation && !isEmpty) {
@@ -429,7 +428,9 @@ async function writeSingleResult(result, docRef, name, dateContext, logger, conf
429
428
  let finalStats = { totalSize: 0, isSharded: false, shardCount: 1, nextShardIndex: startShardIndex };
430
429
  let rootMergeOption = !isInitialWrite;
431
430
 
432
- let shouldWipeShards = wasSharded;
431
+ // CRITICAL FIX: Only wipe existing shards if this is the INITIAL write for this batch run.
432
+ // If we are flushing intermediate chunks, we should NOT wipe the shards created by previous chunks!
433
+ let shouldWipeShards = wasSharded && isInitialWrite;
433
434
 
434
435
  for (let attempt = 0; attempt < strategies.length; attempt++) {
435
436
  if (committed) break;