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.
- package/functions/orchestrator/index.js +19 -17
- package/index.js +8 -29
- package/package.json +1 -1
- package/functions/computation-system/WorkflowOrchestrator.js +0 -213
- package/functions/computation-system/config/monitoring_config.js +0 -31
- package/functions/computation-system/config/validation_overrides.js +0 -10
- package/functions/computation-system/context/ContextFactory.js +0 -143
- package/functions/computation-system/context/ManifestBuilder.js +0 -379
- package/functions/computation-system/data/AvailabilityChecker.js +0 -236
- package/functions/computation-system/data/CachedDataLoader.js +0 -325
- package/functions/computation-system/data/DependencyFetcher.js +0 -455
- package/functions/computation-system/executors/MetaExecutor.js +0 -279
- package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
- package/functions/computation-system/executors/StandardExecutor.js +0 -465
- package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
- package/functions/computation-system/helpers/computation_worker.js +0 -375
- package/functions/computation-system/helpers/monitor.js +0 -64
- package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
- package/functions/computation-system/layers/extractors.js +0 -1097
- package/functions/computation-system/layers/index.js +0 -40
- package/functions/computation-system/layers/mathematics.js +0 -522
- package/functions/computation-system/layers/profiling.js +0 -537
- package/functions/computation-system/layers/validators.js +0 -170
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
- package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
- package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
- package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
- package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
- package/functions/computation-system/logger/logger.js +0 -297
- package/functions/computation-system/persistence/ContractValidator.js +0 -81
- package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
- package/functions/computation-system/persistence/ResultCommitter.js +0 -283
- package/functions/computation-system/persistence/ResultsValidator.js +0 -130
- package/functions/computation-system/persistence/RunRecorder.js +0 -142
- package/functions/computation-system/persistence/StatusRepository.js +0 -52
- package/functions/computation-system/reporter_epoch.js +0 -6
- package/functions/computation-system/scripts/UpdateContracts.js +0 -128
- package/functions/computation-system/services/SnapshotService.js +0 -148
- package/functions/computation-system/simulation/Fabricator.js +0 -285
- package/functions/computation-system/simulation/SeededRandom.js +0 -41
- package/functions/computation-system/simulation/SimRunner.js +0 -51
- package/functions/computation-system/system_epoch.js +0 -2
- package/functions/computation-system/tools/BuildReporter.js +0 -531
- package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
- package/functions/computation-system/tools/DeploymentValidator.js +0 -536
- package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
- package/functions/computation-system/topology/HashManager.js +0 -55
- package/functions/computation-system/topology/ManifestLoader.js +0 -47
- package/functions/computation-system/utils/data_loader.js +0 -675
- package/functions/computation-system/utils/schema_capture.js +0 -121
- 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 };
|