bulltrackers-module 1.0.732 → 1.0.733

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.
Files changed (52) hide show
  1. package/functions/orchestrator/index.js +19 -17
  2. package/index.js +8 -29
  3. package/package.json +1 -1
  4. package/functions/computation-system/WorkflowOrchestrator.js +0 -213
  5. package/functions/computation-system/config/monitoring_config.js +0 -31
  6. package/functions/computation-system/config/validation_overrides.js +0 -10
  7. package/functions/computation-system/context/ContextFactory.js +0 -143
  8. package/functions/computation-system/context/ManifestBuilder.js +0 -379
  9. package/functions/computation-system/data/AvailabilityChecker.js +0 -236
  10. package/functions/computation-system/data/CachedDataLoader.js +0 -325
  11. package/functions/computation-system/data/DependencyFetcher.js +0 -455
  12. package/functions/computation-system/executors/MetaExecutor.js +0 -279
  13. package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
  14. package/functions/computation-system/executors/StandardExecutor.js +0 -465
  15. package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
  16. package/functions/computation-system/helpers/computation_worker.js +0 -375
  17. package/functions/computation-system/helpers/monitor.js +0 -64
  18. package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
  19. package/functions/computation-system/layers/extractors.js +0 -1097
  20. package/functions/computation-system/layers/index.js +0 -40
  21. package/functions/computation-system/layers/mathematics.js +0 -522
  22. package/functions/computation-system/layers/profiling.js +0 -537
  23. package/functions/computation-system/layers/validators.js +0 -170
  24. package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
  25. package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
  26. package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
  27. package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
  28. package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
  29. package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
  30. package/functions/computation-system/logger/logger.js +0 -297
  31. package/functions/computation-system/persistence/ContractValidator.js +0 -81
  32. package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
  33. package/functions/computation-system/persistence/ResultCommitter.js +0 -283
  34. package/functions/computation-system/persistence/ResultsValidator.js +0 -130
  35. package/functions/computation-system/persistence/RunRecorder.js +0 -142
  36. package/functions/computation-system/persistence/StatusRepository.js +0 -52
  37. package/functions/computation-system/reporter_epoch.js +0 -6
  38. package/functions/computation-system/scripts/UpdateContracts.js +0 -128
  39. package/functions/computation-system/services/SnapshotService.js +0 -148
  40. package/functions/computation-system/simulation/Fabricator.js +0 -285
  41. package/functions/computation-system/simulation/SeededRandom.js +0 -41
  42. package/functions/computation-system/simulation/SimRunner.js +0 -51
  43. package/functions/computation-system/system_epoch.js +0 -2
  44. package/functions/computation-system/tools/BuildReporter.js +0 -531
  45. package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
  46. package/functions/computation-system/tools/DeploymentValidator.js +0 -536
  47. package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
  48. package/functions/computation-system/topology/HashManager.js +0 -55
  49. package/functions/computation-system/topology/ManifestLoader.js +0 -47
  50. package/functions/computation-system/utils/data_loader.js +0 -675
  51. package/functions/computation-system/utils/schema_capture.js +0 -121
  52. package/functions/computation-system/utils/utils.js +0 -188
@@ -1,476 +0,0 @@
1
- const { normalizeName, getEarliestDataDates } = require('../utils/utils');
2
- const { streamPortfolioData, streamHistoryData, getPortfolioPartRefs, getHistoryPartRefs } = require('../utils/data_loader');
3
- const { CachedDataLoader } = require('../data/CachedDataLoader');
4
- const { ContextFactory } = require('../context/ContextFactory');
5
- const { commitResults } = require('../persistence/ResultCommitter');
6
- const { fetchResultSeries } = require('../data/DependencyFetcher');
7
- const { getManifest } = require('../topology/ManifestLoader');
8
- const mathLayer = require('../layers/index');
9
- const { performance } = require('perf_hooks');
10
- const v8 = require('v8');
11
-
12
- class StandardExecutor {
13
- static async run(date, calcs, passName, config, deps, rootData, fetchedDeps, previousFetchedDeps, skipStatusWrite = false) {
14
- const dStr = date.toISOString().slice(0, 10);
15
- const logger = deps.logger;
16
-
17
- // Determine required user types for this batch of calculations
18
- const requiredUserTypes = new Set();
19
- calcs.forEach(c => {
20
- const type = (c.userType || 'ALL').toUpperCase();
21
- requiredUserTypes.add(type);
22
- });
23
- const userTypeArray = requiredUserTypes.has('ALL') ? null : Array.from(requiredUserTypes);
24
-
25
- // [OPTIMIZATION] Check for Target CID in manifests (On-Demand Optimization)
26
- const targetCid = calcs.find(c => c.targetCid)?.targetCid || calcs.find(c => c.manifest?.targetCid)?.manifest?.targetCid;
27
- if (targetCid) {
28
- logger.log('INFO', `[StandardExecutor] Running in Targeted Mode for CID: ${targetCid}`);
29
- }
30
-
31
- const fullRoot = { ...rootData };
32
- if (calcs.some(c => c.isHistorical)) {
33
- const prev = new Date(date); prev.setUTCDate(prev.getUTCDate() - 1);
34
- const prevStr = prev.toISOString().slice(0, 10);
35
-
36
- // Fetch yesterday's refs
37
- let yRefs = await getPortfolioPartRefs(config, deps, prevStr, userTypeArray);
38
-
39
- // [OPTIMIZATION] Filter Yesterday's Refs if targetCid is set
40
- if (targetCid && yRefs) {
41
- const originalCount = yRefs.length;
42
- yRefs = yRefs.filter(r => !r.cid || String(r.cid) === String(targetCid));
43
- logger.log('INFO', `[StandardExecutor] Filtered Yesterday's Refs: ${originalCount} -> ${yRefs.length}`);
44
- }
45
-
46
- fullRoot.yesterdayPortfolioRefs = yRefs;
47
- }
48
-
49
- const state = {};
50
- for (const c of calcs) {
51
- try {
52
- const inst = new c.class();
53
- inst.manifest = c;
54
- inst.results = {};
55
- state[normalizeName(c.name)] = inst;
56
- logger.log('INFO', `${c.name} calculation running for ${dStr}`);
57
- } catch (e) { logger.log('WARN', `Failed to init ${c.name}`); }
58
- }
59
-
60
- return await StandardExecutor.streamAndProcess(
61
- dStr, state, passName, config, deps, fullRoot,
62
- rootData.portfolioRefs, rootData.historyRefs,
63
- fetchedDeps, previousFetchedDeps, skipStatusWrite,
64
- userTypeArray, targetCid
65
- );
66
- }
67
-
68
- static async streamAndProcess(dateStr, state, passName, config, deps, rootData, portfolioRefs, historyRefs, fetchedDeps, previousFetchedDeps, skipStatusWrite, requiredUserTypes = null, targetCid = null) {
69
- const { logger } = deps;
70
- const calcs = Object.values(state).filter(c => c && c.manifest);
71
- const streamingCalcs = calcs.filter(c => c.manifest.rootDataDependencies.includes('portfolio') || c.manifest.rootDataDependencies.includes('history'));
72
-
73
- if (streamingCalcs.length === 0) return { successUpdates: {}, failureReport: [] };
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
-
80
- // --- 1. Resolve and Filter Portfolio Refs (Today) ---
81
- let effectivePortfolioRefs = portfolioRefs;
82
- if (!effectivePortfolioRefs) {
83
- effectivePortfolioRefs = await getPortfolioPartRefs(config, deps, dateStr, requiredUserTypes);
84
- }
85
- if (targetCid && effectivePortfolioRefs) {
86
- effectivePortfolioRefs = effectivePortfolioRefs.filter(r => !r.cid || String(r.cid) === String(targetCid));
87
- }
88
-
89
- // --- 2. Resolve and Filter History Refs ---
90
- let effectiveHistoryRefs = historyRefs;
91
- const needsTradingHistory = streamingCalcs.some(c => c.manifest.rootDataDependencies.includes('history'));
92
-
93
- if (needsTradingHistory) {
94
- if (!effectiveHistoryRefs) {
95
- effectiveHistoryRefs = await getHistoryPartRefs(config, deps, dateStr, requiredUserTypes);
96
- }
97
- if (targetCid && effectiveHistoryRefs) {
98
- effectiveHistoryRefs = effectiveHistoryRefs.filter(r => !r.cid || String(r.cid) === String(targetCid));
99
- }
100
- }
101
-
102
- let totalReadOps = (effectivePortfolioRefs?.length || 0) + (effectiveHistoryRefs?.length || 0);
103
- if (rootData.yesterdayPortfolioRefs) totalReadOps += rootData.yesterdayPortfolioRefs.length;
104
- totalReadOps += 2;
105
-
106
- const readOpsPerCalc = Math.ceil(totalReadOps / streamingCalcs.length);
107
-
108
- const executionStats = {};
109
- const shardIndexMap = {};
110
- const aggregatedSuccess = {};
111
- const aggregatedFailures = [];
112
- const errorStats = { count: 0, total: 0 };
113
-
114
- Object.keys(state).forEach(name => {
115
- executionStats[name] = {
116
- processedUsers: 0, skippedUsers: 0, timings: { setup: 0, stream: 0, processing: 0 }
117
- };
118
- shardIndexMap[name] = 0;
119
- });
120
-
121
- let hasFlushed = false;
122
- const cachedLoader = new CachedDataLoader(config, deps);
123
- const startSetup = performance.now();
124
-
125
- // [OPTIMIZATION] Hoist Static Data Load out of User Loop
126
- const mappings = await cachedLoader.loadMappings();
127
- // Pre-load Master List to cache it once
128
- const piMasterList = await cachedLoader.loadPIMasterList();
129
-
130
- const setupDuration = performance.now() - startSetup;
131
- Object.keys(executionStats).forEach(name => executionStats[name].timings.setup += setupDuration);
132
-
133
- // --- [NEW] Series / Lookback Loading Logic ---
134
- const rootSeriesRequests = {};
135
- const dependencySeriesRequests = {};
136
-
137
- // 1. Identify requirements from manifests
138
- streamingCalcs.forEach(c => {
139
- // Check for Root Data Series (e.g., { insights: 7, alerts: 30 })
140
- if (c.manifest.rootDataSeries) {
141
- Object.entries(c.manifest.rootDataSeries).forEach(([type, conf]) => {
142
- const days = typeof conf === 'object' ? conf.lookback : conf;
143
- if (!rootSeriesRequests[type] || days > rootSeriesRequests[type]) {
144
- rootSeriesRequests[type] = days;
145
- }
146
- });
147
- }
148
- // Check for Computation Result Series (e.g., { 'RiskScore': 7 })
149
- if (c.manifest.dependencySeries) {
150
- Object.entries(c.manifest.dependencySeries).forEach(([depName, conf]) => {
151
- const days = typeof conf === 'object' ? conf.lookback : conf;
152
- const normalized = normalizeName(depName);
153
- if (!dependencySeriesRequests[normalized] || days > dependencySeriesRequests[normalized]) {
154
- dependencySeriesRequests[normalized] = days;
155
- }
156
- });
157
- }
158
- });
159
-
160
- const seriesData = { root: {}, results: {} };
161
-
162
- // 2. Load Root Series
163
- for (const [type, days] of Object.entries(rootSeriesRequests)) {
164
- let loaderMethod = null;
165
- if (type === 'alerts') loaderMethod = 'loadAlertHistory';
166
- else if (type === 'insights') loaderMethod = 'loadInsights';
167
- else if (type === 'ratings') loaderMethod = 'loadRatings';
168
- else if (type === 'watchlist') loaderMethod = 'loadWatchlistMembership';
169
- // [CRITICAL UPDATE] Add rankings support for AUM lookback
170
- else if (type === 'rankings') loaderMethod = 'loadRankings';
171
-
172
- if (loaderMethod) {
173
- logger.log('INFO', `[StandardExecutor] Loading ${days}-day series for Root Data '${type}'...`);
174
- // Assume CachedDataLoader has loadSeries method (added in previous step)
175
- const series = await cachedLoader.loadSeries(loaderMethod, dateStr, days);
176
- seriesData.root[type] = series.data; // map of date -> data
177
- }
178
- }
179
-
180
- // 3. Load Computation Result Series
181
- const calcNamesToFetch = Object.keys(dependencySeriesRequests);
182
- if (calcNamesToFetch.length > 0) {
183
- const maxDays = Math.max(...Object.values(dependencySeriesRequests));
184
- logger.log('INFO', `[StandardExecutor] Loading up to ${maxDays}-day series for Dependencies: ${calcNamesToFetch.join(', ')}`);
185
-
186
- // Pass manifestLookup to fetcher
187
- const resultsSeries = await fetchResultSeries(dateStr, calcNamesToFetch, manifestLookup, config, deps, maxDays);
188
- seriesData.results = resultsSeries;
189
- }
190
- // ---------------------------------------------
191
-
192
- const prevDate = new Date(dateStr + 'T00:00:00Z'); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
193
- const prevDateStr = prevDate.toISOString().slice(0, 10);
194
-
195
- let earliestDates = null;
196
- if (streamingCalcs.some(c => c.manifest.requiresEarliestDataDate)) {
197
- earliestDates = await getEarliestDataDates(config, deps);
198
- }
199
-
200
- const tP_iter = streamPortfolioData(config, deps, dateStr, effectivePortfolioRefs, requiredUserTypes);
201
-
202
- const needsYesterdayPortfolio = streamingCalcs.some(c => c.manifest.isHistorical);
203
- const yP_iter = (needsYesterdayPortfolio && rootData.yesterdayPortfolioRefs) ? streamPortfolioData(config, deps, prevDateStr, rootData.yesterdayPortfolioRefs) : null;
204
-
205
- const tH_iter = (needsTradingHistory) ? streamHistoryData(config, deps, dateStr, effectiveHistoryRefs, requiredUserTypes) : null;
206
-
207
- let yP_chunk = {}, tH_chunk = {};
208
- let usersSinceLastFlush = 0;
209
-
210
- try {
211
- for await (const tP_chunk of tP_iter) {
212
- const startStream = performance.now();
213
- if (yP_iter) yP_chunk = (await yP_iter.next()).value || {};
214
- if (tH_iter) tH_chunk = (await tH_iter.next()).value || {};
215
- const streamDuration = performance.now() - startStream;
216
- Object.keys(executionStats).forEach(name => executionStats[name].timings.stream += streamDuration);
217
-
218
- const chunkSize = Object.keys(tP_chunk).length;
219
- const startProcessing = performance.now();
220
-
221
- const batchResults = await Promise.all(streamingCalcs.map(calc =>
222
- StandardExecutor.executePerUser(
223
- calc, calc.manifest, dateStr, tP_chunk, yP_chunk, tH_chunk,
224
- fetchedDeps, previousFetchedDeps, config, deps, cachedLoader,
225
- executionStats[normalizeName(calc.manifest.name)],
226
- earliestDates,
227
- seriesData,
228
- // [NEW] Pass Hoisted Data
229
- mappings,
230
- piMasterList
231
- )
232
- ));
233
-
234
- const procDuration = performance.now() - startProcessing;
235
- Object.keys(executionStats).forEach(name => executionStats[name].timings.processing += procDuration);
236
-
237
- batchResults.forEach(r => { errorStats.total += (r.success + r.failures); errorStats.count += r.failures; });
238
- if (errorStats.total > 100 && (errorStats.count / errorStats.total) > 0.10) { throw new Error(`[Circuit Breaker] High failure rate detected.`); }
239
-
240
- usersSinceLastFlush += chunkSize;
241
- const heapStats = v8.getHeapStatistics();
242
- if (usersSinceLastFlush >= 500 || (heapStats.used_heap_size / heapStats.heap_size_limit) > 0.70) {
243
- const flushResult = await StandardExecutor.flushBuffer(state, dateStr, passName, config, deps, shardIndexMap, executionStats, 'INTERMEDIATE', true, !hasFlushed);
244
- hasFlushed = true;
245
- StandardExecutor.mergeReports(aggregatedSuccess, aggregatedFailures, flushResult);
246
- usersSinceLastFlush = 0;
247
- }
248
- }
249
- } finally {
250
- if (yP_iter && yP_iter.return) await yP_iter.return();
251
- if (tH_iter && tH_iter.return) await tH_iter.return();
252
- }
253
-
254
- const finalResult = await StandardExecutor.flushBuffer(state, dateStr, passName, config, deps, shardIndexMap, executionStats, 'FINAL', skipStatusWrite, !hasFlushed);
255
- StandardExecutor.mergeReports(aggregatedSuccess, aggregatedFailures, finalResult);
256
-
257
- Object.values(aggregatedSuccess).forEach(update => {
258
- if (!update.metrics.io) update.metrics.io = { reads: 0, writes: 0, deletes: 0 };
259
- update.metrics.io.reads = readOpsPerCalc;
260
- });
261
-
262
- return { successUpdates: aggregatedSuccess, failureReport: aggregatedFailures };
263
- }
264
-
265
- static async flushBuffer(state, dateStr, passName, config, deps, shardIndexMap, executionStats, mode, skipStatusWrite, isInitialWrite = false) {
266
- const { logger } = deps;
267
- const transformedState = {};
268
- for (const [name, inst] of Object.entries(state)) {
269
- const rawResult = inst.results || {};
270
- const firstUser = Object.keys(rawResult)[0];
271
- let dataToCommit = rawResult;
272
-
273
- if (firstUser && rawResult[firstUser] && typeof rawResult[firstUser] === 'object') {
274
- const innerKeys = Object.keys(rawResult[firstUser]);
275
- if (innerKeys.length > 0 && innerKeys.every(k => /^\d{4}-\d{2}-\d{2}$/.test(k))) {
276
- const transposed = {};
277
- for (const [userId, dateMap] of Object.entries(rawResult)) {
278
- for (const [dateKey, dailyData] of Object.entries(dateMap)) {
279
- if (!transposed[dateKey]) transposed[dateKey] = {};
280
- transposed[dateKey][userId] = dailyData;
281
- }
282
- }
283
- dataToCommit = transposed;
284
- }
285
- }
286
-
287
- transformedState[name] = {
288
- manifest: inst.manifest,
289
- getResult: async () => dataToCommit,
290
- _executionStats: executionStats[name]
291
- };
292
- inst.results = {};
293
- }
294
-
295
- const result = await commitResults(transformedState, dateStr, passName, config, deps, skipStatusWrite, {
296
- flushMode: mode, shardIndexes: shardIndexMap, isInitialWrite: isInitialWrite
297
- });
298
-
299
- if (result.shardIndexes) Object.assign(shardIndexMap, result.shardIndexes);
300
- return result;
301
- }
302
-
303
- static mergeReports(successAcc, failureAcc, newResult) {
304
- if (!newResult) return;
305
- for (const [name, update] of Object.entries(newResult.successUpdates)) {
306
- if (!successAcc[name]) {
307
- successAcc[name] = update;
308
- } else {
309
- successAcc[name].metrics.storage.sizeBytes += (update.metrics.storage.sizeBytes || 0);
310
- successAcc[name].metrics.storage.keys += (update.metrics.storage.keys || 0);
311
- successAcc[name].metrics.storage.shardCount = Math.max(successAcc[name].metrics.storage.shardCount, update.metrics.storage.shardCount || 1);
312
-
313
- if (update.metrics.io) {
314
- if (!successAcc[name].metrics.io) successAcc[name].metrics.io = { writes: 0, deletes: 0, reads: 0 };
315
- successAcc[name].metrics.io.writes += (update.metrics.io.writes || 0);
316
- successAcc[name].metrics.io.deletes += (update.metrics.io.deletes || 0);
317
- }
318
-
319
- if (update.metrics?.execution?.timings) {
320
- if (!successAcc[name].metrics.execution) successAcc[name].metrics.execution = { timings: { setup:0, stream:0, processing:0 }};
321
- const tDest = successAcc[name].metrics.execution.timings;
322
- const tSrc = update.metrics.execution.timings;
323
- tDest.setup += (tSrc.setup || 0);
324
- tDest.stream += (tSrc.stream || 0);
325
- tDest.processing += (tSrc.processing || 0);
326
- }
327
- successAcc[name].hash = update.hash;
328
- }
329
- }
330
- if (newResult.failureReport) failureAcc.push(...newResult.failureReport);
331
- }
332
-
333
- static async executePerUser(calcInstance, metadata, dateStr, portfolioData, yesterdayPortfolioData, historyData, computedDeps, prevDeps, config, deps, loader, stats, earliestDates, seriesData = {}, mappings = null, piMasterList = null) {
334
- const { logger } = deps;
335
- const targetUserType = metadata.userType;
336
-
337
- // [OPTIMIZATION] Use passed mappings/list if available, else load (fallback)
338
- const mappingsToUse = mappings || await loader.loadMappings();
339
- const piMasterListToUse = piMasterList || await loader.loadPIMasterList();
340
-
341
- const SCHEMAS = mathLayer.SCHEMAS;
342
-
343
- // 1. Load Root Data (CachedLoader handles memoization for these)
344
- const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await loader.loadInsights(dateStr) } : null;
345
- const verifications = metadata.rootDataDependencies?.includes('verification') ? await loader.loadVerifications() : null;
346
- const rankings = metadata.rootDataDependencies?.includes('rankings') ? await loader.loadRankings(dateStr) : null;
347
-
348
- let yesterdayRankings = null;
349
- if (metadata.rootDataDependencies?.includes('rankings') && metadata.isHistorical) {
350
- const prevDate = new Date(dateStr); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
351
- const prevStr = prevDate.toISOString().slice(0, 10);
352
- yesterdayRankings = await loader.loadRankings(prevStr);
353
- }
354
-
355
- const socialContainer = metadata.rootDataDependencies?.includes('social') ? await loader.loadSocial(dateStr) : null;
356
-
357
- const allowMissing = metadata.canHaveMissingRoots === true;
358
-
359
- // Helper to safely load roots
360
- const safeLoad = async (method, name) => {
361
- if (!metadata.rootDataDependencies?.includes(name)) return null;
362
- try {
363
- return await loader[method](dateStr);
364
- } catch (e) {
365
- if (!allowMissing) throw new Error(`[StandardExecutor] Required root '${name}' failed: ${e.message}`);
366
- return null;
367
- }
368
- };
369
-
370
- const ratings = await safeLoad('loadRatings', 'ratings');
371
- const pageViews = await safeLoad('loadPageViews', 'pageViews');
372
- const watchlistMembership = await safeLoad('loadWatchlistMembership', 'watchlist');
373
- const alertHistory = await safeLoad('loadAlertHistory', 'alerts');
374
-
375
- if (!allowMissing) {
376
- if (metadata.rootDataDependencies?.includes('ratings') && !ratings) throw new Error("Missing ratings");
377
- if (metadata.rootDataDependencies?.includes('pageViews') && !pageViews) throw new Error("Missing pageViews");
378
- if (metadata.rootDataDependencies?.includes('watchlist') && !watchlistMembership) throw new Error("Missing watchlist");
379
- if (metadata.rootDataDependencies?.includes('alerts') && !alertHistory) throw new Error("Missing alerts");
380
- }
381
-
382
- let chunkSuccess = 0;
383
- let chunkFailures = 0;
384
-
385
- for (const [userId, todayPortfolio] of Object.entries(portfolioData)) {
386
- // --- OPTIMIZATION: TARGET SPECIFIC USER ---
387
- // If the request contains a targetCid, skip all other users immediately
388
- if (metadata.targetCid && String(userId) !== String(metadata.targetCid)) {
389
- if (stats) stats.skippedUsers++;
390
- continue;
391
- }
392
- // ------------------------------------------
393
-
394
- const yesterdayPortfolio = yesterdayPortfolioData ? yesterdayPortfolioData[userId] : null;
395
- const todayHistory = historyData ? historyData[userId] : null;
396
-
397
- let actualUserType = todayPortfolio._userType;
398
- if (!actualUserType) {
399
- if (todayPortfolio.PublicPositions) {
400
- const isRanked = rankings && rankings.some(r => String(r.CustomerId) === String(userId));
401
- actualUserType = isRanked ? 'POPULAR_INVESTOR' : SCHEMAS.USER_TYPES.SPECULATOR;
402
- } else {
403
- actualUserType = SCHEMAS.USER_TYPES.NORMAL;
404
- }
405
- }
406
-
407
- if (targetUserType && targetUserType !== 'all') {
408
- if (targetUserType !== actualUserType) {
409
- if (stats) stats.skippedUsers++;
410
- continue;
411
- }
412
- }
413
-
414
- const userVerification = verifications ? verifications[userId] : null;
415
-
416
- const userRanking = rankings ? (rankings.find(r => String(r.CustomerId) === String(userId)) || null) : null;
417
- const userRankingYesterday = yesterdayRankings ? (yesterdayRankings.find(r => String(r.CustomerId) === String(userId)) || null) : null;
418
-
419
- let effectiveSocialData = null;
420
- if (socialContainer) {
421
- if (actualUserType === 'POPULAR_INVESTOR') {
422
- effectiveSocialData = socialContainer.pi[userId] || {};
423
- } else if (actualUserType === 'SIGNED_IN_USER') {
424
- effectiveSocialData = socialContainer.signedIn[userId] || {};
425
- } else {
426
- effectiveSocialData = socialContainer.generic || {};
427
- }
428
- }
429
-
430
- const context = ContextFactory.buildPerUserContext({
431
- todayPortfolio, yesterdayPortfolio, todayHistory, userId,
432
- userType: actualUserType, dateStr, metadata,
433
- mappings: mappingsToUse,
434
- insights,
435
- socialData: effectiveSocialData ? { today: effectiveSocialData } : null,
436
- computedDependencies: computedDeps, previousComputedDependencies: prevDeps,
437
- config, deps,
438
- verification: userVerification,
439
-
440
- rankings: userRanking,
441
- yesterdayRankings: userRankingYesterday,
442
-
443
- allRankings: rankings,
444
- allRankingsYesterday: yesterdayRankings,
445
-
446
- allVerifications: verifications,
447
-
448
- ratings: ratings || {},
449
- pageViews: pageViews || {},
450
- watchlistMembership: watchlistMembership || {},
451
- alertHistory: alertHistory || {},
452
-
453
- piMasterList: piMasterListToUse,
454
- // [NEW] Pass Series Data
455
- seriesData
456
- });
457
-
458
- if (metadata.requiresEarliestDataDate && earliestDates) {
459
- if (!context.system) context.system = {};
460
- context.system.earliestHistoryDate = earliestDates.history;
461
- }
462
-
463
- try {
464
- await calcInstance.process(context);
465
- if (stats) stats.processedUsers++;
466
- chunkSuccess++;
467
- } catch (e) {
468
- logger.log('WARN', `Calc ${metadata.name} failed for user ${userId}: ${e.message}`);
469
- chunkFailures++;
470
- }
471
- }
472
- return { success: chunkSuccess, failures: chunkFailures };
473
- }
474
- }
475
-
476
- module.exports = { StandardExecutor };