bulltrackers-module 1.0.700 → 1.0.702
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/CachedDataLoader.js +10 -1
- package/functions/computation-system/data/DependencyFetcher.js +17 -0
- package/functions/computation-system/executors/MetaExecutor.js +9 -12
- package/functions/computation-system/executors/StandardExecutor.js +60 -69
- package/functions/computation-system/utils/data_loader.js +14 -1
- package/package.json +1 -1
|
@@ -17,7 +17,8 @@ const {
|
|
|
17
17
|
loadWatchlistMembership: loadWatchlistMembershipData,
|
|
18
18
|
loadPIAlertHistory,
|
|
19
19
|
loadPIWatchlistData,
|
|
20
|
-
loadPopularInvestorMasterList
|
|
20
|
+
loadPopularInvestorMasterList,
|
|
21
|
+
loadDailyPortfolios // <--- IMPORTED
|
|
21
22
|
} = require('../utils/data_loader');
|
|
22
23
|
const { getAvailabilityWindow } = require('./AvailabilityChecker');
|
|
23
24
|
const zlib = require('zlib');
|
|
@@ -81,6 +82,12 @@ const LOADER_DEFINITIONS = {
|
|
|
81
82
|
// No collection key needed for direct implementation, but handled by fn
|
|
82
83
|
fn: loadPIWatchlistData,
|
|
83
84
|
isIdBased: true // Uses ID instead of Date
|
|
85
|
+
},
|
|
86
|
+
// <--- ADDED SUPPORT FOR PORTFOLIO SERIES
|
|
87
|
+
loadPortfolios: {
|
|
88
|
+
cache: 'portfolios',
|
|
89
|
+
fn: loadDailyPortfolios
|
|
90
|
+
// No configKey/flag ensures legacy loop (required for complex multi-collection fetch)
|
|
84
91
|
}
|
|
85
92
|
};
|
|
86
93
|
|
|
@@ -144,6 +151,8 @@ class CachedDataLoader {
|
|
|
144
151
|
async loadWatchlistMembership(dateStr) { return this._loadGeneric('loadWatchlistMembership', dateStr); }
|
|
145
152
|
async loadAlertHistory(dateStr) { return this._loadGeneric('loadAlertHistory' , dateStr); }
|
|
146
153
|
async loadPIWatchlistData(piCid) { return this._loadGeneric('loadPIWatchlistData' , String(piCid)); }
|
|
154
|
+
// <--- ADDED PORTFOLIOS ACCESSOR
|
|
155
|
+
async loadPortfolios(dateStr) { return this._loadGeneric('loadPortfolios' , dateStr); }
|
|
147
156
|
|
|
148
157
|
// =========================================================================
|
|
149
158
|
// SPECIALIZED LOADERS (Non-Standard Patterns)
|
|
@@ -226,6 +226,7 @@ async function fetchDependencies(date, calcs, config, deps, manifestLookup = {},
|
|
|
226
226
|
async function fetchResultSeries(endDateStr, calcNames, manifestLookup, config, deps, lookbackDays) {
|
|
227
227
|
const { db, logger } = deps;
|
|
228
228
|
const results = {}; // normalizedName -> { date -> data }
|
|
229
|
+
const { resultsCollection = 'computation_results', resultsSubcollection = 'results', computationsSubcollection = 'computations' } = config;
|
|
229
230
|
|
|
230
231
|
// Initialize results structure
|
|
231
232
|
calcNames.forEach(n => results[normalizeName(n)] = {});
|
|
@@ -238,6 +239,15 @@ async function fetchResultSeries(endDateStr, calcNames, manifestLookup, config,
|
|
|
238
239
|
dates.push(d.toISOString().slice(0, 10));
|
|
239
240
|
}
|
|
240
241
|
|
|
242
|
+
// [DEBUG] Log the manifest lookup and resolved categories
|
|
243
|
+
logger.log('INFO', `[DependencyFetcher] 🔍 ManifestLookup has ${Object.keys(manifestLookup).length} entries`);
|
|
244
|
+
for (const rawName of calcNames) {
|
|
245
|
+
const norm = normalizeName(rawName);
|
|
246
|
+
const category = manifestLookup[norm] || 'analytics';
|
|
247
|
+
const samplePath = `${resultsCollection}/${dates[0]}/${resultsSubcollection}/${category}/${computationsSubcollection}/${rawName}`;
|
|
248
|
+
logger.log('INFO', `[DependencyFetcher] 📍 '${rawName}' -> category='${category}' -> Path: ${samplePath}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
241
251
|
// Build Fetch Operations
|
|
242
252
|
const ops = [];
|
|
243
253
|
for (const dateStr of dates) {
|
|
@@ -260,6 +270,13 @@ async function fetchResultSeries(endDateStr, calcNames, manifestLookup, config,
|
|
|
260
270
|
for (let i = 0; i < ops.length; i += BATCH_SIZE) {
|
|
261
271
|
await Promise.all(ops.slice(i, i + BATCH_SIZE).map(fn => fn()));
|
|
262
272
|
}
|
|
273
|
+
|
|
274
|
+
// [DEBUG] Log results summary
|
|
275
|
+
for (const rawName of calcNames) {
|
|
276
|
+
const norm = normalizeName(rawName);
|
|
277
|
+
const foundDates = Object.keys(results[norm] || {});
|
|
278
|
+
logger.log('INFO', `[DependencyFetcher] ✅ '${rawName}' found data for ${foundDates.length}/${lookbackDays} days`);
|
|
279
|
+
}
|
|
263
280
|
|
|
264
281
|
return results;
|
|
265
282
|
}
|
|
@@ -9,14 +9,11 @@ const { commitResults } = require('../persistence/ResultCommitter');
|
|
|
9
9
|
const { fetchResultSeries } = require('../data/DependencyFetcher');
|
|
10
10
|
const { getManifest } = require('../topology/ManifestLoader');
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
catch (e) { _calculations = {}; }
|
|
18
|
-
}
|
|
19
|
-
return _calculations;
|
|
12
|
+
// Helper to get calculations - prefer config.calculations, fallback to direct require
|
|
13
|
+
function getCalculations(config) {
|
|
14
|
+
if (config && config.calculations) return config.calculations;
|
|
15
|
+
try { return require('aiden-shared-calculations-unified').calculations; }
|
|
16
|
+
catch (e) { return {}; }
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
class MetaExecutor {
|
|
@@ -29,8 +26,8 @@ class MetaExecutor {
|
|
|
29
26
|
const dStr = date.toISOString().slice(0, 10);
|
|
30
27
|
const loader = new CachedDataLoader(config, deps);
|
|
31
28
|
|
|
32
|
-
// 1. Setup Manifest Lookup
|
|
33
|
-
const allManifests = getManifest(config.
|
|
29
|
+
// 1. Setup Manifest Lookup (use activeProductLines and calculations from config)
|
|
30
|
+
const allManifests = getManifest(config.activeProductLines || [], getCalculations(config), deps);
|
|
34
31
|
const manifestLookup = Object.fromEntries(allManifests.map(m => [normalizeName(m.name), m.category]));
|
|
35
32
|
|
|
36
33
|
// 2. Load Base Data (Always Required)
|
|
@@ -104,8 +101,8 @@ class MetaExecutor {
|
|
|
104
101
|
const { logger } = deps;
|
|
105
102
|
const calcs = [metadata]; // Treat single as list for helpers
|
|
106
103
|
|
|
107
|
-
//
|
|
108
|
-
const allManifests = getManifest([], getCalculations(), deps);
|
|
104
|
+
// Build manifestLookup using calculations from config (set in index.js)
|
|
105
|
+
const allManifests = getManifest(config.activeProductLines || [], getCalculations(config), deps);
|
|
109
106
|
const manifestLookup = Object.fromEntries(allManifests.map(m => [normalizeName(m.name), m.category]));
|
|
110
107
|
|
|
111
108
|
// 1. Load Data using Shared Helpers
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Executor for "Standard" (User-Level) calculations.
|
|
3
3
|
* REFACTORED: Hoisted data loading, centralized Series/Root logic.
|
|
4
|
+
* UPDATED: Added Master List Driver for PI calculations to ensure full coverage.
|
|
4
5
|
*/
|
|
5
6
|
const { normalizeName, getEarliestDataDates } = require('../utils/utils');
|
|
6
|
-
const { streamPortfolioData, streamHistoryData, getPortfolioPartRefs, getHistoryPartRefs } = require('../utils/data_loader');
|
|
7
|
+
const { streamPortfolioData, streamHistoryData, getPortfolioPartRefs, getHistoryPartRefs, loadFullDayMap } = require('../utils/data_loader');
|
|
7
8
|
const { CachedDataLoader } = require('../data/CachedDataLoader');
|
|
8
9
|
const { ContextFactory } = require('../context/ContextFactory');
|
|
9
10
|
const { commitResults } = require('../persistence/ResultCommitter');
|
|
@@ -13,14 +14,11 @@ const mathLayer = require('../layers/index');
|
|
|
13
14
|
const { performance } = require('perf_hooks');
|
|
14
15
|
const v8 = require('v8');
|
|
15
16
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
catch (e) { _calculations = {}; }
|
|
22
|
-
}
|
|
23
|
-
return _calculations;
|
|
17
|
+
// Helper to get calculations - prefer config.calculations, fallback to direct require
|
|
18
|
+
function getCalculations(config) {
|
|
19
|
+
if (config && config.calculations) return config.calculations;
|
|
20
|
+
try { return require('aiden-shared-calculations-unified').calculations; }
|
|
21
|
+
catch (e) { return {}; }
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
class StandardExecutor {
|
|
@@ -83,8 +81,6 @@ class StandardExecutor {
|
|
|
83
81
|
const loader = new CachedDataLoader(config, deps);
|
|
84
82
|
|
|
85
83
|
// --- 1. PRE-LOAD GLOBAL DATA (Hoisted) ---
|
|
86
|
-
// Load all "Singleton" datasets once (Ratings, Rankings, Series, Mappings)
|
|
87
|
-
// instead of checking/loading per-user inside the loop.
|
|
88
84
|
const startSetup = performance.now();
|
|
89
85
|
|
|
90
86
|
const [
|
|
@@ -120,8 +116,37 @@ class StandardExecutor {
|
|
|
120
116
|
shardMap[normalizeName(c.manifest.name)] = 0;
|
|
121
117
|
});
|
|
122
118
|
|
|
123
|
-
// --- 3.
|
|
124
|
-
|
|
119
|
+
// --- 3. DETERMINE ITERATION STRATEGY ---
|
|
120
|
+
let tP_iter;
|
|
121
|
+
const isPiOnly = requiredUserTypes && requiredUserTypes.length === 1 && requiredUserTypes[0] === 'POPULAR_INVESTOR';
|
|
122
|
+
|
|
123
|
+
// STRATEGY A: MASTER LIST DRIVER (For PIs, ensuring we hit every ID in the list, even if missing today)
|
|
124
|
+
if (isPiOnly && globalRoots.piMasterList && !targetCid) {
|
|
125
|
+
logger.log('INFO', `[StandardExecutor] 🚀 Driving execution via Master List (${Object.keys(globalRoots.piMasterList).length} PIs)`);
|
|
126
|
+
|
|
127
|
+
// Load Today's Map Fully (Small enough for PIs)
|
|
128
|
+
const todayMap = await loadFullDayMap(config, deps, effPortRefs, dateStr);
|
|
129
|
+
|
|
130
|
+
// Create a generator that yields chunks from the Master List keys
|
|
131
|
+
tP_iter = (async function* () {
|
|
132
|
+
const allCids = Object.keys(globalRoots.piMasterList);
|
|
133
|
+
const batchSize = config.partRefBatchSize || 50;
|
|
134
|
+
for (let i = 0; i < allCids.length; i += batchSize) {
|
|
135
|
+
const batchCids = allCids.slice(i, i + batchSize);
|
|
136
|
+
const chunk = {};
|
|
137
|
+
batchCids.forEach(cid => {
|
|
138
|
+
// If missing today, provide a stub so the Calc can look back in History
|
|
139
|
+
chunk[cid] = todayMap[cid] || { _userType: 'POPULAR_INVESTOR', _isMissingToday: true };
|
|
140
|
+
});
|
|
141
|
+
yield chunk;
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
}
|
|
145
|
+
// STRATEGY B: STANDARD STREAM (Iterate whatever is in Firestore/GCS for today)
|
|
146
|
+
else {
|
|
147
|
+
tP_iter = streamPortfolioData(config, deps, dateStr, effPortRefs, requiredUserTypes);
|
|
148
|
+
}
|
|
149
|
+
|
|
125
150
|
const yP_iter = (streamingCalcs.some(c => c.manifest.isHistorical) && rootData.yesterdayPortfolioRefs)
|
|
126
151
|
? streamPortfolioData(config, deps, new Date(new Date(dateStr).getTime() - 86400000).toISOString().slice(0, 10), rootData.yesterdayPortfolioRefs)
|
|
127
152
|
: null;
|
|
@@ -150,7 +175,7 @@ class StandardExecutor {
|
|
|
150
175
|
stats[normalizeName(calc.manifest.name)],
|
|
151
176
|
earliestDates,
|
|
152
177
|
seriesData,
|
|
153
|
-
globalRoots
|
|
178
|
+
globalRoots
|
|
154
179
|
)
|
|
155
180
|
));
|
|
156
181
|
|
|
@@ -171,8 +196,8 @@ class StandardExecutor {
|
|
|
171
196
|
}
|
|
172
197
|
}
|
|
173
198
|
} finally {
|
|
174
|
-
if (yP_iter?.return) await yP_iter.return();
|
|
175
|
-
if (tH_iter?.return) await tH_iter.return();
|
|
199
|
+
if (yP_iter?.return && typeof yP_iter.return === 'function') await yP_iter.return();
|
|
200
|
+
if (tH_iter?.return && typeof tH_iter.return === 'function') await tH_iter.return();
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
const finalRes = await StandardExecutor.flushBuffer(state, dateStr, passName, config, deps, shardMap, stats, 'FINAL', skipStatusWrite, !hasFlushed);
|
|
@@ -182,7 +207,7 @@ class StandardExecutor {
|
|
|
182
207
|
}
|
|
183
208
|
|
|
184
209
|
// =========================================================================
|
|
185
|
-
// PER-USER EXECUTION
|
|
210
|
+
// PER-USER EXECUTION
|
|
186
211
|
// =========================================================================
|
|
187
212
|
static async executePerUser(calcInstance, metadata, dateStr, portfolioData, yesterdayPortfolioData, historyData, computedDeps, prevDeps, config, deps, stats, earliestDates, seriesData, globalRoots) {
|
|
188
213
|
const { logger } = deps;
|
|
@@ -192,18 +217,21 @@ class StandardExecutor {
|
|
|
192
217
|
let success = 0, failures = 0;
|
|
193
218
|
|
|
194
219
|
for (const [userId, todayPortfolio] of Object.entries(portfolioData)) {
|
|
195
|
-
// 1. Filter User
|
|
196
220
|
if (metadata.targetCid && String(userId) !== String(metadata.targetCid)) { stats.skippedUsers++; continue; }
|
|
197
221
|
|
|
198
|
-
// 2. Determine Type
|
|
222
|
+
// 2. Determine Type (Handle Stub for Missing Users)
|
|
199
223
|
let actualType = todayPortfolio._userType;
|
|
200
224
|
if (!actualType) {
|
|
225
|
+
// If driving from Master List, we know they are PIs even if missing today
|
|
201
226
|
const isRanked = globalRoots.rankings && globalRoots.rankings.some(r => String(r.CustomerId) === String(userId));
|
|
202
|
-
|
|
227
|
+
if (todayPortfolio._isMissingToday && isRanked) {
|
|
228
|
+
actualType = 'POPULAR_INVESTOR';
|
|
229
|
+
} else {
|
|
230
|
+
actualType = isRanked ? 'POPULAR_INVESTOR' : (todayPortfolio.PublicPositions ? SCHEMAS.USER_TYPES.SPECULATOR : SCHEMAS.USER_TYPES.NORMAL);
|
|
231
|
+
}
|
|
203
232
|
}
|
|
204
233
|
if (targetUserType && targetUserType !== 'all' && targetUserType !== actualType) { stats.skippedUsers++; continue; }
|
|
205
234
|
|
|
206
|
-
// 3. Resolve User Specifics from Global Data
|
|
207
235
|
const userRank = globalRoots.rankings?.find(r => String(r.CustomerId) === String(userId)) || null;
|
|
208
236
|
const userRankYest = globalRoots.rankingsYesterday?.find(r => String(r.CustomerId) === String(userId)) || null;
|
|
209
237
|
const userVerify = globalRoots.verifications?.[userId] || null;
|
|
@@ -214,30 +242,21 @@ class StandardExecutor {
|
|
|
214
242
|
(actualType === 'SIGNED_IN_USER' ? globalRoots.social.signedIn[userId] : globalRoots.social.generic)) || {};
|
|
215
243
|
}
|
|
216
244
|
|
|
217
|
-
// 4. Build Context
|
|
218
245
|
const context = ContextFactory.buildPerUserContext({
|
|
219
|
-
todayPortfolio,
|
|
246
|
+
todayPortfolio: todayPortfolio._isMissingToday ? null : todayPortfolio,
|
|
220
247
|
yesterdayPortfolio: yesterdayPortfolioData?.[userId] || null,
|
|
221
248
|
todayHistory: historyData?.[userId] || null,
|
|
222
249
|
userId, userType: actualType, dateStr, metadata,
|
|
223
|
-
|
|
224
|
-
// Injected Global Data
|
|
225
250
|
mappings: globalRoots.mappings,
|
|
226
251
|
piMasterList: globalRoots.piMasterList,
|
|
227
252
|
insights: globalRoots.insights,
|
|
228
253
|
socialData: social ? { today: social } : null,
|
|
229
|
-
|
|
230
|
-
// Dependency Data
|
|
231
254
|
computedDependencies: computedDeps,
|
|
232
255
|
previousComputedDependencies: prevDeps,
|
|
233
256
|
config, deps,
|
|
234
|
-
|
|
235
|
-
// Specific Lookups
|
|
236
257
|
verification: userVerify,
|
|
237
258
|
rankings: userRank,
|
|
238
259
|
yesterdayRankings: userRankYest,
|
|
239
|
-
|
|
240
|
-
// Full Access (if needed by calc)
|
|
241
260
|
allRankings: globalRoots.rankings,
|
|
242
261
|
allRankingsYesterday: globalRoots.rankingsYesterday,
|
|
243
262
|
allVerifications: globalRoots.verifications,
|
|
@@ -245,7 +264,6 @@ class StandardExecutor {
|
|
|
245
264
|
pageViews: globalRoots.pageViews || {},
|
|
246
265
|
watchlistMembership: globalRoots.watchlistMembership || {},
|
|
247
266
|
alertHistory: globalRoots.alertHistory || {},
|
|
248
|
-
|
|
249
267
|
seriesData
|
|
250
268
|
});
|
|
251
269
|
|
|
@@ -267,13 +285,12 @@ class StandardExecutor {
|
|
|
267
285
|
}
|
|
268
286
|
|
|
269
287
|
// =========================================================================
|
|
270
|
-
//
|
|
288
|
+
// FLUSH & MERGE
|
|
271
289
|
// =========================================================================
|
|
272
290
|
static async flushBuffer(state, dateStr, passName, config, deps, shardMap, stats, mode, skipStatus, isInitial) {
|
|
273
291
|
const transformedState = {};
|
|
274
292
|
for (const [name, inst] of Object.entries(state)) {
|
|
275
293
|
let data = inst.results || {};
|
|
276
|
-
// Pivot user-date structure if needed
|
|
277
294
|
const first = Object.keys(data)[0];
|
|
278
295
|
if (first && data[first] && typeof data[first] === 'object' && /^\d{4}-\d{2}-\d{2}$/.test(Object.keys(data[first])[0])) {
|
|
279
296
|
const pivoted = {};
|
|
@@ -286,7 +303,7 @@ class StandardExecutor {
|
|
|
286
303
|
data = pivoted;
|
|
287
304
|
}
|
|
288
305
|
transformedState[name] = { manifest: inst.manifest, getResult: async () => data, _executionStats: stats[name] };
|
|
289
|
-
inst.results = {};
|
|
306
|
+
inst.results = {};
|
|
290
307
|
}
|
|
291
308
|
const res = await commitResults(transformedState, dateStr, passName, config, deps, skipStatus, { flushMode: mode, shardIndexes: shardMap, isInitialWrite: isInitial });
|
|
292
309
|
if (res.shardIndexes) Object.assign(shardMap, res.shardIndexes);
|
|
@@ -313,31 +330,13 @@ class StandardExecutor {
|
|
|
313
330
|
}
|
|
314
331
|
}
|
|
315
332
|
|
|
316
|
-
// =============================================================================
|
|
317
|
-
// SHARED LOADING HELPERS
|
|
318
|
-
// =============================================================================
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Pre-loads all shared global datasets required by the active calculations.
|
|
322
|
-
* Returns a consolidated object of { ratings, rankings, insights, ... }
|
|
323
|
-
*/
|
|
324
333
|
async function loadGlobalRoots(loader, dateStr, calcs, deps) {
|
|
325
334
|
const { logger } = deps;
|
|
326
335
|
const roots = {};
|
|
327
|
-
|
|
328
|
-
// 1. Identify Requirements
|
|
329
336
|
const reqs = {
|
|
330
|
-
mappings: true,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
rankingsYesterday: false,
|
|
334
|
-
verifications: false,
|
|
335
|
-
insights: false,
|
|
336
|
-
social: false,
|
|
337
|
-
ratings: false,
|
|
338
|
-
pageViews: false,
|
|
339
|
-
watchlist: false,
|
|
340
|
-
alerts: false
|
|
337
|
+
mappings: true, piMasterList: true, rankings: false, rankingsYesterday: false,
|
|
338
|
+
verifications: false, insights: false, social: false, ratings: false,
|
|
339
|
+
pageViews: false, watchlist: false, alerts: false
|
|
341
340
|
};
|
|
342
341
|
|
|
343
342
|
for (const c of calcs) {
|
|
@@ -353,7 +352,6 @@ async function loadGlobalRoots(loader, dateStr, calcs, deps) {
|
|
|
353
352
|
if (deps.includes('alerts')) reqs.alerts = true;
|
|
354
353
|
}
|
|
355
354
|
|
|
356
|
-
// 2. Fetch Helper
|
|
357
355
|
const fetch = async (key, method, dateArg, optional = true) => {
|
|
358
356
|
if (!reqs[key]) return;
|
|
359
357
|
try {
|
|
@@ -365,9 +363,8 @@ async function loadGlobalRoots(loader, dateStr, calcs, deps) {
|
|
|
365
363
|
}
|
|
366
364
|
};
|
|
367
365
|
|
|
368
|
-
// 3. Execute Loads
|
|
369
366
|
await Promise.all([
|
|
370
|
-
fetch('mappings', 'loadMappings', null, false),
|
|
367
|
+
fetch('mappings', 'loadMappings', null, false),
|
|
371
368
|
fetch('piMasterList', 'loadPIMasterList', null, false),
|
|
372
369
|
fetch('rankings', 'loadRankings', dateStr),
|
|
373
370
|
fetch('verifications', 'loadVerifications', dateStr),
|
|
@@ -384,16 +381,14 @@ async function loadGlobalRoots(loader, dateStr, calcs, deps) {
|
|
|
384
381
|
await fetch('rankingsYesterday', 'loadRankings', prev);
|
|
385
382
|
}
|
|
386
383
|
|
|
387
|
-
// Map internal names to match loadGlobalRoots structure if needed
|
|
388
384
|
roots.watchlistMembership = roots.watchlist;
|
|
389
385
|
roots.alertHistory = roots.alerts;
|
|
390
|
-
|
|
391
386
|
return roots;
|
|
392
387
|
}
|
|
393
388
|
|
|
394
389
|
async function loadSeriesData(loader, dateStr, calcs, config, deps) {
|
|
395
390
|
const rootReqs = {};
|
|
396
|
-
const depReqs = {};
|
|
391
|
+
const depReqs = {};
|
|
397
392
|
|
|
398
393
|
calcs.forEach(c => {
|
|
399
394
|
if (c.manifest.rootDataSeries) {
|
|
@@ -413,26 +408,22 @@ async function loadSeriesData(loader, dateStr, calcs, config, deps) {
|
|
|
413
408
|
const series = { root: {}, results: {} };
|
|
414
409
|
const rootMap = {
|
|
415
410
|
alerts: 'loadAlertHistory', insights: 'loadInsights', ratings: 'loadRatings',
|
|
416
|
-
watchlist: 'loadWatchlistMembership', rankings: 'loadRankings'
|
|
411
|
+
watchlist: 'loadWatchlistMembership', rankings: 'loadRankings',
|
|
412
|
+
portfolios: 'loadPortfolios'
|
|
417
413
|
};
|
|
418
414
|
|
|
419
415
|
await Promise.all(Object.entries(rootReqs).map(async ([key, days]) => {
|
|
420
416
|
if (rootMap[key]) series.root[key] = (await loader.loadSeries(rootMap[key], dateStr, days)).data;
|
|
421
417
|
}));
|
|
422
418
|
|
|
423
|
-
// [FIX] Use getCalculations() to load ALL computations for proper category lookup
|
|
424
419
|
const depEntries = Object.values(depReqs);
|
|
425
420
|
if (depEntries.length) {
|
|
426
421
|
const depOriginalNames = depEntries.map(e => e.originalName);
|
|
427
422
|
const maxDays = Math.max(...depEntries.map(e => e.days));
|
|
428
|
-
|
|
429
|
-
// Build lookup from ALL computations (empty productLines = load all)
|
|
430
|
-
const allManifests = getManifest([], getCalculations(), deps);
|
|
423
|
+
const allManifests = getManifest(config.activeProductLines || [], getCalculations(config), deps);
|
|
431
424
|
const lookup = Object.fromEntries(allManifests.map(m => [normalizeName(m.name), m.category]));
|
|
432
|
-
|
|
433
425
|
series.results = await fetchResultSeries(dateStr, depOriginalNames, lookup, config, deps, maxDays);
|
|
434
426
|
}
|
|
435
|
-
|
|
436
427
|
return series;
|
|
437
428
|
}
|
|
438
429
|
|
|
@@ -228,6 +228,18 @@ async function loadFullDayMap(config, deps, partRefs, dateString) {
|
|
|
228
228
|
return fullMap;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/** Stage 3.5: Load Daily Portfolios (Wrapper for Series Loading) */
|
|
232
|
+
async function loadDailyPortfolios(config, deps, dateString) {
|
|
233
|
+
// 1. GCS FAST PATH
|
|
234
|
+
const cached = await tryLoadFromGCS(config, dateString, 'portfolios', deps.logger);
|
|
235
|
+
if (cached) return cached;
|
|
236
|
+
|
|
237
|
+
// 2. FIRESTORE FALLBACK
|
|
238
|
+
const partRefs = await getPortfolioPartRefs(config, deps, dateString);
|
|
239
|
+
if (partRefs.length === 0) return {};
|
|
240
|
+
return loadDataByRefs(config, deps, partRefs);
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
/** Stage 4: Load daily instrument insights */
|
|
232
244
|
async function loadDailyInsights(config, deps, dateString) {
|
|
233
245
|
const { db, logger, calculationUtils } = deps;
|
|
@@ -800,7 +812,8 @@ async function loadPopularInvestorMasterList(config, deps, dateString = null) {
|
|
|
800
812
|
module.exports = {
|
|
801
813
|
getPortfolioPartRefs,
|
|
802
814
|
loadDataByRefs,
|
|
803
|
-
loadFullDayMap,
|
|
815
|
+
loadFullDayMap,
|
|
816
|
+
loadDailyPortfolios, // <--- EXPORTED NEW WRAPPER
|
|
804
817
|
loadDailyInsights,
|
|
805
818
|
loadDailySocialPostInsights,
|
|
806
819
|
getHistoryPartRefs,
|