bulltrackers-module 1.0.658 → 1.0.659
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/computation-system/data/AvailabilityChecker.js +163 -317
- package/functions/computation-system/data/CachedDataLoader.js +158 -222
- package/functions/computation-system/data/DependencyFetcher.js +201 -406
- package/functions/computation-system/executors/MetaExecutor.js +176 -280
- package/functions/computation-system/executors/StandardExecutor.js +325 -383
- package/functions/computation-system/helpers/computation_dispatcher.js +294 -699
- package/functions/computation-system/helpers/computation_worker.js +3 -2
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +382 -0
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +357 -0
- package/functions/computation-system/legacy/DependencyFetcherOld.js +478 -0
- package/functions/computation-system/legacy/MetaExecutorold.js +364 -0
- package/functions/computation-system/legacy/StandardExecutorold.js +476 -0
- package/functions/computation-system/legacy/computation_dispatcherold.js +944 -0
- package/functions/computation-system/persistence/ResultCommitter.js +137 -188
- package/functions/computation-system/services/SnapshotService.js +129 -0
- package/functions/computation-system/tools/BuildReporter.js +12 -7
- package/functions/computation-system/utils/data_loader.js +213 -238
- package/package.json +3 -2
- package/functions/computation-system/workflows/bulltrackers_pipeline.yaml +0 -163
- package/functions/computation-system/workflows/data_feeder_pipeline.yaml +0 -115
- package/functions/computation-system/workflows/datafeederpipelineinstructions.md +0 -30
- package/functions/computation-system/workflows/morning_prep_pipeline.yaml +0 -55
|
@@ -1,457 +1,241 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Executor for "Standard" (User-Level) calculations.
|
|
3
|
+
* REFACTORED: Hoisted data loading, centralized Series/Root logic.
|
|
4
|
+
*/
|
|
5
|
+
const { normalizeName, getEarliestDataDates } = require('../utils/utils');
|
|
2
6
|
const { streamPortfolioData, streamHistoryData, getPortfolioPartRefs, getHistoryPartRefs } = require('../utils/data_loader');
|
|
3
|
-
const { CachedDataLoader }
|
|
4
|
-
const { ContextFactory }
|
|
5
|
-
const { commitResults }
|
|
6
|
-
const { fetchResultSeries }
|
|
7
|
-
const { getManifest }
|
|
8
|
-
const mathLayer
|
|
9
|
-
const { performance }
|
|
10
|
-
const v8
|
|
7
|
+
const { CachedDataLoader } = require('../data/CachedDataLoader');
|
|
8
|
+
const { ContextFactory } = require('../context/ContextFactory');
|
|
9
|
+
const { commitResults } = require('../persistence/ResultCommitter');
|
|
10
|
+
const { fetchResultSeries } = require('../data/DependencyFetcher');
|
|
11
|
+
const { getManifest } = require('../topology/ManifestLoader');
|
|
12
|
+
const mathLayer = require('../layers/index');
|
|
13
|
+
const { performance } = require('perf_hooks');
|
|
14
|
+
const v8 = require('v8');
|
|
11
15
|
|
|
12
16
|
class StandardExecutor {
|
|
17
|
+
|
|
18
|
+
// =========================================================================
|
|
19
|
+
// ENTRY POINT
|
|
20
|
+
// =========================================================================
|
|
13
21
|
static async run(date, calcs, passName, config, deps, rootData, fetchedDeps, previousFetchedDeps, skipStatusWrite = false) {
|
|
14
|
-
const dStr
|
|
15
|
-
const logger = deps
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
const requiredUserTypes = new Set();
|
|
19
|
-
calcs.forEach(c => {
|
|
20
|
-
const type = (c.userType || 'ALL').toUpperCase();
|
|
21
|
-
requiredUserTypes.add(type);
|
|
22
|
-
});
|
|
22
|
+
const dStr = date.toISOString().slice(0, 10);
|
|
23
|
+
const { logger } = deps;
|
|
24
|
+
|
|
25
|
+
// 1. Setup Scope (User Types & Target CID)
|
|
26
|
+
const requiredUserTypes = new Set(calcs.map(c => (c.userType || 'ALL').toUpperCase()));
|
|
23
27
|
const userTypeArray = requiredUserTypes.has('ALL') ? null : Array.from(requiredUserTypes);
|
|
24
28
|
|
|
25
|
-
// [OPTIMIZATION] Check for Target CID in manifests (On-Demand Optimization)
|
|
26
29
|
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
|
+
if (targetCid) logger.log('INFO', `[StandardExecutor] 🎯 Targeted Mode: CID ${targetCid}`);
|
|
30
31
|
|
|
32
|
+
// 2. Prepare History Context (Refs)
|
|
31
33
|
const fullRoot = { ...rootData };
|
|
32
34
|
if (calcs.some(c => c.isHistorical)) {
|
|
33
|
-
const
|
|
34
|
-
const prevStr = prev.toISOString().slice(0, 10);
|
|
35
|
-
|
|
36
|
-
// Fetch yesterday's refs
|
|
35
|
+
const prevStr = new Date(date.getTime() - 86400000).toISOString().slice(0, 10);
|
|
37
36
|
let yRefs = await getPortfolioPartRefs(config, deps, prevStr, userTypeArray);
|
|
38
37
|
|
|
39
|
-
// [OPTIMIZATION] Filter Yesterday's Refs if targetCid is set
|
|
40
38
|
if (targetCid && yRefs) {
|
|
41
|
-
const originalCount = yRefs.length;
|
|
42
39
|
yRefs = yRefs.filter(r => !r.cid || String(r.cid) === String(targetCid));
|
|
43
|
-
logger.log('INFO', `[StandardExecutor] Filtered Yesterday's Refs: ${originalCount} -> ${yRefs.length}`);
|
|
44
40
|
}
|
|
45
|
-
|
|
46
41
|
fullRoot.yesterdayPortfolioRefs = yRefs;
|
|
47
42
|
}
|
|
48
43
|
|
|
44
|
+
// 3. Initialize Instances
|
|
49
45
|
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
|
-
|
|
57
|
-
} catch (e) { logger.log('WARN', `Failed to init ${c.name}`); }
|
|
46
|
+
for (const c of calcs) {
|
|
47
|
+
try {
|
|
48
|
+
const inst = new c.class();
|
|
49
|
+
inst.manifest = c;
|
|
50
|
+
inst.results = {};
|
|
51
|
+
state[normalizeName(c.name)] = inst;
|
|
52
|
+
} catch (e) { logger.log('WARN', `Failed to init ${c.name}`); }
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
return await StandardExecutor.streamAndProcess(
|
|
61
|
-
dStr, state, passName, config, deps, fullRoot,
|
|
62
|
-
rootData.portfolioRefs, rootData.historyRefs,
|
|
63
|
-
fetchedDeps, previousFetchedDeps, skipStatusWrite,
|
|
56
|
+
dStr, state, passName, config, deps, fullRoot,
|
|
57
|
+
rootData.portfolioRefs, rootData.historyRefs,
|
|
58
|
+
fetchedDeps, previousFetchedDeps, skipStatusWrite,
|
|
64
59
|
userTypeArray, targetCid
|
|
65
60
|
);
|
|
66
61
|
}
|
|
67
62
|
|
|
63
|
+
// =========================================================================
|
|
64
|
+
// STREAMING LOOP
|
|
65
|
+
// =========================================================================
|
|
68
66
|
static async streamAndProcess(dateStr, state, passName, config, deps, rootData, portfolioRefs, historyRefs, fetchedDeps, previousFetchedDeps, skipStatusWrite, requiredUserTypes = null, targetCid = null) {
|
|
69
67
|
const { logger } = deps;
|
|
70
|
-
const calcs = Object.values(state).filter(c => c
|
|
68
|
+
const calcs = Object.values(state).filter(c => c?.manifest);
|
|
71
69
|
const streamingCalcs = calcs.filter(c => c.manifest.rootDataDependencies.includes('portfolio') || c.manifest.rootDataDependencies.includes('history'));
|
|
72
70
|
|
|
73
|
-
if (streamingCalcs.length
|
|
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; });
|
|
71
|
+
if (!streamingCalcs.length) return { successUpdates: {}, failureReport: [] };
|
|
79
72
|
|
|
80
|
-
|
|
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
|
-
}
|
|
73
|
+
const loader = new CachedDataLoader(config, deps);
|
|
101
74
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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);
|
|
75
|
+
// --- 1. PRE-LOAD GLOBAL DATA (Hoisted) ---
|
|
76
|
+
// Load all "Singleton" datasets once (Ratings, Rankings, Series, Mappings)
|
|
77
|
+
// instead of checking/loading per-user inside the loop.
|
|
123
78
|
const startSetup = performance.now();
|
|
124
79
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
80
|
+
const [
|
|
81
|
+
globalRoots,
|
|
82
|
+
seriesData,
|
|
83
|
+
earliestDates
|
|
84
|
+
] = await Promise.all([
|
|
85
|
+
loadGlobalRoots(loader, dateStr, streamingCalcs, deps),
|
|
86
|
+
loadSeriesData(loader, dateStr, streamingCalcs, config, deps),
|
|
87
|
+
streamingCalcs.some(c => c.manifest.requiresEarliestDataDate) ? getEarliestDataDates(config, deps) : null
|
|
88
|
+
]);
|
|
129
89
|
|
|
130
90
|
const setupDuration = performance.now() - startSetup;
|
|
131
|
-
Object.keys(executionStats).forEach(name => executionStats[name].timings.setup += setupDuration);
|
|
132
91
|
|
|
133
|
-
// ---
|
|
134
|
-
|
|
135
|
-
|
|
92
|
+
// --- 2. PREPARE REFS & STATS ---
|
|
93
|
+
let effPortRefs = portfolioRefs || await getPortfolioPartRefs(config, deps, dateStr, requiredUserTypes);
|
|
94
|
+
let effHistRefs = historyRefs;
|
|
136
95
|
|
|
137
|
-
|
|
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
|
-
}
|
|
96
|
+
if (targetCid) effPortRefs = effPortRefs?.filter(r => !r.cid || String(r.cid) === String(targetCid));
|
|
179
97
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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;
|
|
98
|
+
const needsHistory = streamingCalcs.some(c => c.manifest.rootDataDependencies.includes('history'));
|
|
99
|
+
if (needsHistory) {
|
|
100
|
+
effHistRefs = effHistRefs || await getHistoryPartRefs(config, deps, dateStr, requiredUserTypes);
|
|
101
|
+
if (targetCid) effHistRefs = effHistRefs?.filter(r => !r.cid || String(r.cid) === String(targetCid));
|
|
189
102
|
}
|
|
190
|
-
// ---------------------------------------------
|
|
191
103
|
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
104
|
+
const stats = {};
|
|
105
|
+
const shardMap = {};
|
|
106
|
+
calcs.forEach(c => {
|
|
107
|
+
stats[normalizeName(c.manifest.name)] = {
|
|
108
|
+
processedUsers: 0, skippedUsers: 0, timings: { setup: setupDuration, stream: 0, processing: 0 }
|
|
109
|
+
};
|
|
110
|
+
shardMap[normalizeName(c.manifest.name)] = 0;
|
|
111
|
+
});
|
|
199
112
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const tH_iter =
|
|
113
|
+
// --- 3. STREAM EXECUTION ---
|
|
114
|
+
const tP_iter = streamPortfolioData(config, deps, dateStr, effPortRefs, requiredUserTypes);
|
|
115
|
+
const yP_iter = (streamingCalcs.some(c => c.manifest.isHistorical) && rootData.yesterdayPortfolioRefs)
|
|
116
|
+
? streamPortfolioData(config, deps, new Date(new Date(dateStr).getTime() - 86400000).toISOString().slice(0, 10), rootData.yesterdayPortfolioRefs)
|
|
117
|
+
: null;
|
|
118
|
+
const tH_iter = needsHistory ? streamHistoryData(config, deps, dateStr, effHistRefs, requiredUserTypes) : null;
|
|
206
119
|
|
|
207
120
|
let yP_chunk = {}, tH_chunk = {};
|
|
208
|
-
let
|
|
121
|
+
let usersSinceFlush = 0;
|
|
122
|
+
let hasFlushed = false;
|
|
123
|
+
const aggregatedSuccess = {};
|
|
124
|
+
const aggregatedFailures = [];
|
|
209
125
|
|
|
210
126
|
try {
|
|
211
127
|
for await (const tP_chunk of tP_iter) {
|
|
212
|
-
const
|
|
128
|
+
const t0 = performance.now();
|
|
213
129
|
if (yP_iter) yP_chunk = (await yP_iter.next()).value || {};
|
|
214
130
|
if (tH_iter) tH_chunk = (await tH_iter.next()).value || {};
|
|
215
|
-
const
|
|
216
|
-
Object.keys(executionStats).forEach(name => executionStats[name].timings.stream += streamDuration);
|
|
131
|
+
const tStream = performance.now() - t0;
|
|
217
132
|
|
|
218
|
-
const
|
|
219
|
-
const startProcessing = performance.now();
|
|
133
|
+
const t1 = performance.now();
|
|
220
134
|
|
|
221
|
-
|
|
135
|
+
// Parallelize Calc Execution for this Chunk
|
|
136
|
+
await Promise.all(streamingCalcs.map(calc =>
|
|
222
137
|
StandardExecutor.executePerUser(
|
|
223
|
-
calc, calc.manifest, dateStr, tP_chunk, yP_chunk, tH_chunk,
|
|
224
|
-
fetchedDeps, previousFetchedDeps, config, deps,
|
|
225
|
-
|
|
138
|
+
calc, calc.manifest, dateStr, tP_chunk, yP_chunk, tH_chunk,
|
|
139
|
+
fetchedDeps, previousFetchedDeps, config, deps,
|
|
140
|
+
stats[normalizeName(calc.manifest.name)],
|
|
226
141
|
earliestDates,
|
|
227
142
|
seriesData,
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
piMasterList
|
|
231
|
-
)
|
|
143
|
+
globalRoots // <--- PASSED DOWN
|
|
144
|
+
)
|
|
232
145
|
));
|
|
233
146
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
147
|
+
const tProc = performance.now() - t1;
|
|
148
|
+
streamingCalcs.forEach(c => {
|
|
149
|
+
const s = stats[normalizeName(c.manifest.name)];
|
|
150
|
+
s.timings.stream += tStream;
|
|
151
|
+
s.timings.processing += tProc;
|
|
152
|
+
});
|
|
239
153
|
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
const
|
|
154
|
+
usersSinceFlush += Object.keys(tP_chunk).length;
|
|
155
|
+
const heap = v8.getHeapStatistics();
|
|
156
|
+
if (usersSinceFlush >= 500 || (heap.used_heap_size / heap.heap_size_limit) > 0.7) {
|
|
157
|
+
const res = await StandardExecutor.flushBuffer(state, dateStr, passName, config, deps, shardMap, stats, 'INTERMEDIATE', true, !hasFlushed);
|
|
158
|
+
StandardExecutor.mergeReports(aggregatedSuccess, aggregatedFailures, res);
|
|
244
159
|
hasFlushed = true;
|
|
245
|
-
|
|
246
|
-
usersSinceLastFlush = 0;
|
|
160
|
+
usersSinceFlush = 0;
|
|
247
161
|
}
|
|
248
162
|
}
|
|
249
163
|
} finally {
|
|
250
|
-
if (yP_iter
|
|
251
|
-
if (tH_iter
|
|
164
|
+
if (yP_iter?.return) await yP_iter.return();
|
|
165
|
+
if (tH_iter?.return) await tH_iter.return();
|
|
252
166
|
}
|
|
253
167
|
|
|
254
|
-
const
|
|
255
|
-
StandardExecutor.mergeReports(aggregatedSuccess, aggregatedFailures,
|
|
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
|
-
});
|
|
168
|
+
const finalRes = await StandardExecutor.flushBuffer(state, dateStr, passName, config, deps, shardMap, stats, 'FINAL', skipStatusWrite, !hasFlushed);
|
|
169
|
+
StandardExecutor.mergeReports(aggregatedSuccess, aggregatedFailures, finalRes);
|
|
261
170
|
|
|
262
171
|
return { successUpdates: aggregatedSuccess, failureReport: aggregatedFailures };
|
|
263
172
|
}
|
|
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
173
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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) {
|
|
174
|
+
// =========================================================================
|
|
175
|
+
// PER-USER EXECUTION (Pure Logic)
|
|
176
|
+
// =========================================================================
|
|
177
|
+
static async executePerUser(calcInstance, metadata, dateStr, portfolioData, yesterdayPortfolioData, historyData, computedDeps, prevDeps, config, deps, stats, earliestDates, seriesData, globalRoots) {
|
|
334
178
|
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
|
-
|
|
179
|
+
const targetUserType = metadata.userType;
|
|
341
180
|
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
181
|
|
|
357
|
-
|
|
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;
|
|
182
|
+
let success = 0, failures = 0;
|
|
384
183
|
|
|
385
184
|
for (const [userId, todayPortfolio] of Object.entries(portfolioData)) {
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
if (metadata.targetCid && String(userId) !== String(metadata.targetCid)) {
|
|
389
|
-
if (stats) stats.skippedUsers++;
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
// ------------------------------------------
|
|
185
|
+
// 1. Filter User
|
|
186
|
+
if (metadata.targetCid && String(userId) !== String(metadata.targetCid)) { stats.skippedUsers++; continue; }
|
|
393
187
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
}
|
|
188
|
+
// 2. Determine Type
|
|
189
|
+
let actualType = todayPortfolio._userType;
|
|
190
|
+
if (!actualType) {
|
|
191
|
+
const isRanked = globalRoots.rankings && globalRoots.rankings.some(r => String(r.CustomerId) === String(userId));
|
|
192
|
+
actualType = isRanked ? 'POPULAR_INVESTOR' : (todayPortfolio.PublicPositions ? SCHEMAS.USER_TYPES.SPECULATOR : SCHEMAS.USER_TYPES.NORMAL);
|
|
412
193
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
effectiveSocialData = socialContainer.signedIn[userId] || {};
|
|
425
|
-
} else {
|
|
426
|
-
effectiveSocialData = socialContainer.generic || {};
|
|
427
|
-
}
|
|
194
|
+
if (targetUserType && targetUserType !== 'all' && targetUserType !== actualType) { stats.skippedUsers++; continue; }
|
|
195
|
+
|
|
196
|
+
// 3. Resolve User Specifics from Global Data
|
|
197
|
+
const userRank = globalRoots.rankings?.find(r => String(r.CustomerId) === String(userId)) || null;
|
|
198
|
+
const userRankYest = globalRoots.rankingsYesterday?.find(r => String(r.CustomerId) === String(userId)) || null;
|
|
199
|
+
const userVerify = globalRoots.verifications?.[userId] || null;
|
|
200
|
+
|
|
201
|
+
let social = null;
|
|
202
|
+
if (globalRoots.social) {
|
|
203
|
+
social = (actualType === 'POPULAR_INVESTOR' ? globalRoots.social.pi[userId] :
|
|
204
|
+
(actualType === 'SIGNED_IN_USER' ? globalRoots.social.signedIn[userId] : globalRoots.social.generic)) || {};
|
|
428
205
|
}
|
|
429
206
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
computedDependencies: computedDeps, previousComputedDependencies: prevDeps,
|
|
437
|
-
config, deps,
|
|
438
|
-
verification: userVerification,
|
|
207
|
+
// 4. Build Context
|
|
208
|
+
const context = ContextFactory.buildPerUserContext({
|
|
209
|
+
todayPortfolio,
|
|
210
|
+
yesterdayPortfolio: yesterdayPortfolioData?.[userId] || null,
|
|
211
|
+
todayHistory: historyData?.[userId] || null,
|
|
212
|
+
userId, userType: actualType, dateStr, metadata,
|
|
439
213
|
|
|
440
|
-
|
|
441
|
-
|
|
214
|
+
// Injected Global Data
|
|
215
|
+
mappings: globalRoots.mappings,
|
|
216
|
+
piMasterList: globalRoots.piMasterList,
|
|
217
|
+
insights: globalRoots.insights,
|
|
218
|
+
socialData: social ? { today: social } : null,
|
|
442
219
|
|
|
443
|
-
|
|
444
|
-
|
|
220
|
+
// Dependency Data
|
|
221
|
+
computedDependencies: computedDeps,
|
|
222
|
+
previousComputedDependencies: prevDeps,
|
|
223
|
+
config, deps,
|
|
445
224
|
|
|
446
|
-
|
|
225
|
+
// Specific Lookups
|
|
226
|
+
verification: userVerify,
|
|
227
|
+
rankings: userRank,
|
|
228
|
+
yesterdayRankings: userRankYest,
|
|
229
|
+
|
|
230
|
+
// Full Access (if needed by calc)
|
|
231
|
+
allRankings: globalRoots.rankings,
|
|
232
|
+
allRankingsYesterday: globalRoots.rankingsYesterday,
|
|
233
|
+
allVerifications: globalRoots.verifications,
|
|
234
|
+
ratings: globalRoots.ratings || {},
|
|
235
|
+
pageViews: globalRoots.pageViews || {},
|
|
236
|
+
watchlistMembership: globalRoots.watchlistMembership || {},
|
|
237
|
+
alertHistory: globalRoots.alertHistory || {},
|
|
447
238
|
|
|
448
|
-
ratings: ratings || {},
|
|
449
|
-
pageViews: pageViews || {},
|
|
450
|
-
watchlistMembership: watchlistMembership || {},
|
|
451
|
-
alertHistory: alertHistory || {},
|
|
452
|
-
|
|
453
|
-
piMasterList: piMasterListToUse,
|
|
454
|
-
// [NEW] Pass Series Data
|
|
455
239
|
seriesData
|
|
456
240
|
});
|
|
457
241
|
|
|
@@ -459,18 +243,176 @@ class StandardExecutor {
|
|
|
459
243
|
if (!context.system) context.system = {};
|
|
460
244
|
context.system.earliestHistoryDate = earliestDates.history;
|
|
461
245
|
}
|
|
462
|
-
|
|
463
|
-
try {
|
|
464
|
-
await calcInstance.process(context);
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
} catch (e) {
|
|
468
|
-
logger.log('WARN', `Calc ${metadata.name} failed for user ${userId}: ${e.message}`);
|
|
469
|
-
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await calcInstance.process(context);
|
|
249
|
+
stats.processedUsers++;
|
|
250
|
+
success++;
|
|
251
|
+
} catch (e) {
|
|
252
|
+
logger.log('WARN', `Calc ${metadata.name} failed for user ${userId}: ${e.message}`);
|
|
253
|
+
failures++;
|
|
470
254
|
}
|
|
471
255
|
}
|
|
472
|
-
return { success
|
|
256
|
+
return { success, failures };
|
|
473
257
|
}
|
|
258
|
+
|
|
259
|
+
// =========================================================================
|
|
260
|
+
// HELPERS (Flush & Merge)
|
|
261
|
+
// =========================================================================
|
|
262
|
+
static async flushBuffer(state, dateStr, passName, config, deps, shardMap, stats, mode, skipStatus, isInitial) {
|
|
263
|
+
const transformedState = {};
|
|
264
|
+
for (const [name, inst] of Object.entries(state)) {
|
|
265
|
+
let data = inst.results || {};
|
|
266
|
+
// Pivot user-date structure if needed
|
|
267
|
+
const first = Object.keys(data)[0];
|
|
268
|
+
if (first && data[first] && typeof data[first] === 'object' && /^\d{4}-\d{2}-\d{2}$/.test(Object.keys(data[first])[0])) {
|
|
269
|
+
const pivoted = {};
|
|
270
|
+
for (const [uid, dMap] of Object.entries(data)) {
|
|
271
|
+
for (const [dKey, val] of Object.entries(dMap)) {
|
|
272
|
+
if (!pivoted[dKey]) pivoted[dKey] = {};
|
|
273
|
+
pivoted[dKey][uid] = val;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
data = pivoted;
|
|
277
|
+
}
|
|
278
|
+
transformedState[name] = { manifest: inst.manifest, getResult: async () => data, _executionStats: stats[name] };
|
|
279
|
+
inst.results = {}; // Clear buffer
|
|
280
|
+
}
|
|
281
|
+
const res = await commitResults(transformedState, dateStr, passName, config, deps, skipStatus, { flushMode: mode, shardIndexes: shardMap, isInitialWrite: isInitial });
|
|
282
|
+
if (res.shardIndexes) Object.assign(shardMap, res.shardIndexes);
|
|
283
|
+
return res;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static mergeReports(success, failure, result) {
|
|
287
|
+
if (!result) return;
|
|
288
|
+
if (result.failureReport) failure.push(...result.failureReport);
|
|
289
|
+
for (const [name, update] of Object.entries(result.successUpdates)) {
|
|
290
|
+
if (!success[name]) success[name] = update;
|
|
291
|
+
else {
|
|
292
|
+
const m = success[name].metrics;
|
|
293
|
+
const u = update.metrics;
|
|
294
|
+
m.storage.sizeBytes += u.storage.sizeBytes;
|
|
295
|
+
m.storage.keys += u.storage.keys;
|
|
296
|
+
m.storage.shardCount = Math.max(m.storage.shardCount, u.storage.shardCount);
|
|
297
|
+
if (u.execution) {
|
|
298
|
+
m.execution.timings.stream += u.execution.timings.stream;
|
|
299
|
+
m.execution.timings.processing += u.execution.timings.processing;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// =============================================================================
|
|
307
|
+
// SHARED LOADING HELPERS
|
|
308
|
+
// =============================================================================
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Pre-loads all shared global datasets required by the active calculations.
|
|
312
|
+
* Returns a consolidated object of { ratings, rankings, insights, ... }
|
|
313
|
+
*/
|
|
314
|
+
async function loadGlobalRoots(loader, dateStr, calcs, deps) {
|
|
315
|
+
const { logger } = deps;
|
|
316
|
+
const roots = {};
|
|
317
|
+
|
|
318
|
+
// 1. Identify Requirements
|
|
319
|
+
const reqs = {
|
|
320
|
+
mappings: true,
|
|
321
|
+
piMasterList: true,
|
|
322
|
+
rankings: false,
|
|
323
|
+
rankingsYesterday: false,
|
|
324
|
+
verifications: false,
|
|
325
|
+
insights: false,
|
|
326
|
+
social: false,
|
|
327
|
+
ratings: false,
|
|
328
|
+
pageViews: false,
|
|
329
|
+
watchlist: false,
|
|
330
|
+
alerts: false
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
for (const c of calcs) {
|
|
334
|
+
const deps = c.manifest.rootDataDependencies || [];
|
|
335
|
+
if (deps.includes('rankings')) reqs.rankings = true;
|
|
336
|
+
if (deps.includes('rankings') && c.manifest.isHistorical) reqs.rankingsYesterday = true;
|
|
337
|
+
if (deps.includes('verification')) reqs.verifications = true;
|
|
338
|
+
if (deps.includes('insights')) reqs.insights = true;
|
|
339
|
+
if (deps.includes('social')) reqs.social = true;
|
|
340
|
+
if (deps.includes('ratings')) reqs.ratings = true;
|
|
341
|
+
if (deps.includes('pageViews')) reqs.pageViews = true;
|
|
342
|
+
if (deps.includes('watchlist')) reqs.watchlist = true;
|
|
343
|
+
if (deps.includes('alerts')) reqs.alerts = true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 2. Fetch Helper
|
|
347
|
+
const fetch = async (key, method, dateArg, optional = true) => {
|
|
348
|
+
if (!reqs[key]) return;
|
|
349
|
+
try {
|
|
350
|
+
roots[key] = await loader[method](dateArg);
|
|
351
|
+
} catch (e) {
|
|
352
|
+
if (!optional) throw e;
|
|
353
|
+
logger.log('WARN', `[StandardExecutor] Optional root '${key}' failed to load: ${e.message}`);
|
|
354
|
+
roots[key] = null;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// 3. Execute Loads
|
|
359
|
+
await Promise.all([
|
|
360
|
+
fetch('mappings', 'loadMappings', null, false), // Always required
|
|
361
|
+
fetch('piMasterList', 'loadPIMasterList', null, false),
|
|
362
|
+
fetch('rankings', 'loadRankings', dateStr),
|
|
363
|
+
fetch('verifications', 'loadVerifications', dateStr),
|
|
364
|
+
fetch('insights', 'loadInsights', dateStr),
|
|
365
|
+
fetch('social', 'loadSocial', dateStr),
|
|
366
|
+
fetch('ratings', 'loadRatings', dateStr),
|
|
367
|
+
fetch('pageViews', 'loadPageViews', dateStr),
|
|
368
|
+
fetch('watchlist', 'loadWatchlistMembership', dateStr),
|
|
369
|
+
fetch('alerts', 'loadAlertHistory', dateStr)
|
|
370
|
+
]);
|
|
371
|
+
|
|
372
|
+
if (reqs.rankingsYesterday) {
|
|
373
|
+
const prev = new Date(new Date(dateStr).getTime() - 86400000).toISOString().slice(0, 10);
|
|
374
|
+
await fetch('rankingsYesterday', 'loadRankings', prev);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Map internal names to match loadGlobalRoots structure if needed
|
|
378
|
+
roots.watchlistMembership = roots.watchlist;
|
|
379
|
+
roots.alertHistory = roots.alerts;
|
|
380
|
+
|
|
381
|
+
return roots;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function loadSeriesData(loader, dateStr, calcs, config, deps) {
|
|
385
|
+
const rootReqs = {};
|
|
386
|
+
const depReqs = {};
|
|
387
|
+
|
|
388
|
+
calcs.forEach(c => {
|
|
389
|
+
if (c.manifest.rootDataSeries) {
|
|
390
|
+
Object.entries(c.manifest.rootDataSeries).forEach(([k, v]) => rootReqs[k] = Math.max(rootReqs[k]||0, v.lookback||v));
|
|
391
|
+
}
|
|
392
|
+
if (c.manifest.dependencySeries) {
|
|
393
|
+
Object.entries(c.manifest.dependencySeries).forEach(([k, v]) => depReqs[normalizeName(k)] = Math.max(depReqs[normalizeName(k)]||0, v.lookback||v));
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const series = { root: {}, results: {} };
|
|
398
|
+
const rootMap = {
|
|
399
|
+
alerts: 'loadAlertHistory', insights: 'loadInsights', ratings: 'loadRatings',
|
|
400
|
+
watchlist: 'loadWatchlistMembership', rankings: 'loadRankings'
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
await Promise.all(Object.entries(rootReqs).map(async ([key, days]) => {
|
|
404
|
+
if (rootMap[key]) series.root[key] = (await loader.loadSeries(rootMap[key], dateStr, days)).data;
|
|
405
|
+
}));
|
|
406
|
+
|
|
407
|
+
const depNames = Object.keys(depReqs);
|
|
408
|
+
if (depNames.length) {
|
|
409
|
+
// Construct manifest lookup on the fly for fetched names
|
|
410
|
+
const allManifests = getManifest(config.productLines, config.calculationsDirectory, deps);
|
|
411
|
+
const lookup = Object.fromEntries(allManifests.map(m => [normalizeName(m.name), m.category]));
|
|
412
|
+
series.results = await fetchResultSeries(dateStr, depNames, lookup, config, deps, Math.max(...Object.values(depReqs)));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return series;
|
|
474
416
|
}
|
|
475
417
|
|
|
476
418
|
module.exports = { StandardExecutor };
|