bulltrackers-module 1.0.580 → 1.0.581
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.
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* @fileoverview Dynamic Manifest Builder - Handles Topological Sort and Auto-Discovery.
|
|
9
9
|
* UPDATED: Removed Automatic Infra Hashing. Now relies strictly on SYSTEM_EPOCH.
|
|
10
|
-
* UPDATED: Whitelisted 'rootDataSeries' and '
|
|
10
|
+
* UPDATED: Whitelisted 'rootDataSeries', 'dependencySeries', and 'mandatoryRoots' metadata fields.
|
|
11
11
|
*/
|
|
12
12
|
const { generateCodeHash, LEGACY_MAPPING } = require('../topology/HashManager.js');
|
|
13
13
|
const { normalizeName } = require('../utils/utils');
|
|
@@ -31,31 +31,22 @@ const LAYER_GROUPS = {
|
|
|
31
31
|
* Heuristic to estimate the "weight" of a calculation based on its output structure.
|
|
32
32
|
*/
|
|
33
33
|
function estimateComplexity(Class, metadata) {
|
|
34
|
-
let weight = 1.0;
|
|
34
|
+
let weight = 1.0;
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
const schema = typeof Class.getSchema === 'function' ? Class.getSchema() : {};
|
|
38
|
-
|
|
39
|
-
// 1. Detect Map-like outputs (per-ticker, per-sector, per-user)
|
|
40
|
-
// If the schema uses patternProperties, it's likely a dynamic map.
|
|
41
38
|
if (schema.patternProperties || (schema.type === 'object' && !schema.properties)) {
|
|
42
|
-
weight *= 3.0;
|
|
39
|
+
weight *= 3.0;
|
|
43
40
|
}
|
|
44
|
-
|
|
45
|
-
// 2. Metadata hints
|
|
46
41
|
const name = Class.name.toLowerCase();
|
|
47
42
|
if (name.includes('perstock') || name.includes('perticker')) weight *= 2.0;
|
|
48
|
-
if (name.includes('peruser')) weight *= 10.0;
|
|
43
|
+
if (name.includes('peruser')) weight *= 10.0;
|
|
49
44
|
|
|
50
|
-
// 3. Dependency hints
|
|
51
45
|
if (metadata.rootDataDependencies && metadata.rootDataDependencies.includes('portfolio')) {
|
|
52
|
-
// Portfolio-based calcs usually iterate over all users in the StandardExecutor
|
|
53
46
|
weight *= 1.5;
|
|
54
47
|
}
|
|
55
48
|
|
|
56
|
-
} catch (e) {
|
|
57
|
-
// Fallback to base weight if schema is missing/broken
|
|
58
|
-
}
|
|
49
|
+
} catch (e) { }
|
|
59
50
|
|
|
60
51
|
return weight;
|
|
61
52
|
}
|
|
@@ -122,9 +113,6 @@ function getDependencySet(endpoints, adjacencyList) {
|
|
|
122
113
|
return required;
|
|
123
114
|
}
|
|
124
115
|
|
|
125
|
-
/**
|
|
126
|
-
* Helper: Detects cycles using Tarjan's SCC Algorithm.
|
|
127
|
-
*/
|
|
128
116
|
function detectCircularDependencies(manifestMap) {
|
|
129
117
|
let index = 0;
|
|
130
118
|
const stack = [];
|
|
@@ -209,7 +197,6 @@ function buildManifest(productLinesToRun = [], calculations) {
|
|
|
209
197
|
const codeStr = Class.toString();
|
|
210
198
|
const selfCodeHash = generateCodeHash(codeStr);
|
|
211
199
|
|
|
212
|
-
// [UPDATED] Composite Hash now depends ONLY on Code + Epoch + Layers
|
|
213
200
|
let compositeHashString = selfCodeHash + `|EPOCH:${SYSTEM_EPOCH}`;
|
|
214
201
|
|
|
215
202
|
const usedDeps = [];
|
|
@@ -254,9 +241,10 @@ function buildManifest(productLinesToRun = [], calculations) {
|
|
|
254
241
|
isPage: metadata.isPage === true,
|
|
255
242
|
isHistorical: metadata.isHistorical !== undefined ? metadata.isHistorical : false,
|
|
256
243
|
rootDataDependencies: metadata.rootDataDependencies || [],
|
|
257
|
-
// [NEW] Pass Series
|
|
244
|
+
// [NEW] Pass Series & Mandatory Config
|
|
258
245
|
rootDataSeries: metadata.rootDataSeries || null,
|
|
259
246
|
dependencySeries: metadata.dependencySeries || null,
|
|
247
|
+
mandatoryRoots: metadata.mandatoryRoots || [], // [NEW]
|
|
260
248
|
|
|
261
249
|
canHaveMissingRoots: metadata.canHaveMissingRoots || false,
|
|
262
250
|
userType: metadata.userType,
|
|
@@ -300,9 +288,7 @@ function buildManifest(productLinesToRun = [], calculations) {
|
|
|
300
288
|
|
|
301
289
|
const productLineEndpoints = [];
|
|
302
290
|
const runAll = !productLinesToRun || productLinesToRun.length === 0 || productLinesToRun.includes('*');
|
|
303
|
-
// Respect the product lines to run, but don't force core calculations to be run if not explicitly requested
|
|
304
291
|
for (const [name, entry] of manifestMap.entries()) {
|
|
305
|
-
// Removed "|| entry.sourcePackage === 'core'"
|
|
306
292
|
if (runAll || productLinesToRun.includes(entry.category)) {
|
|
307
293
|
productLineEndpoints.push(name);
|
|
308
294
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Checks availability of root data via the Root Data Index.
|
|
3
3
|
* REFACTORED: Fully supports granular flags for PI, Signed-In Users, Rankings, and Verification.
|
|
4
|
-
*
|
|
5
|
-
* UPDATED: Added strict social data checking for Popular Investors and Signed-In Users.
|
|
6
|
-
* UPDATED: Added Optimistic Series Permission. Allows execution if "Today's" data is missing but a Lookback Series is requested.
|
|
4
|
+
* UPDATED: Enforces 'mandatoryRoots' metadata to override permissive flags.
|
|
7
5
|
*/
|
|
8
6
|
const { normalizeName } = require('../utils/utils');
|
|
9
7
|
|
|
@@ -25,49 +23,46 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
25
23
|
let isAvailable = false;
|
|
26
24
|
|
|
27
25
|
if (dep === 'portfolio') {
|
|
28
|
-
if (userType
|
|
29
|
-
else if (userType === 'normal'
|
|
30
|
-
else if (userType === 'popular_investor' && rootDataStatus.piPortfolios)
|
|
31
|
-
else if (userType === 'signed_in_user'
|
|
32
|
-
else if (userType === 'all'
|
|
26
|
+
if (userType === 'speculator' && rootDataStatus.speculatorPortfolio) isAvailable = true;
|
|
27
|
+
else if (userType === 'normal' && rootDataStatus.normalPortfolio) isAvailable = true;
|
|
28
|
+
else if (userType === 'popular_investor' && rootDataStatus.piPortfolios) isAvailable = true;
|
|
29
|
+
else if (userType === 'signed_in_user' && rootDataStatus.signedInUserPortfolio) isAvailable = true;
|
|
30
|
+
else if (userType === 'all' && rootDataStatus.hasPortfolio) isAvailable = true;
|
|
33
31
|
|
|
34
32
|
if (!isAvailable) {
|
|
35
33
|
// [OPTIMIZATION] Optimistic Series Check
|
|
36
|
-
// If Today's Portfolio is missing, but the computation explicitly asks for a Series (History),
|
|
37
|
-
// we allow it to proceed optimistically, assuming historical data might exist.
|
|
38
34
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
39
35
|
available.push('portfolio');
|
|
40
36
|
continue;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
if (userType
|
|
44
|
-
else if (userType === 'normal')
|
|
39
|
+
if (userType === 'speculator') missing.push('speculatorPortfolio');
|
|
40
|
+
else if (userType === 'normal') missing.push('normalPortfolio');
|
|
45
41
|
else if (userType === 'popular_investor') missing.push('piPortfolios');
|
|
46
|
-
else if (userType === 'signed_in_user')
|
|
47
|
-
else
|
|
42
|
+
else if (userType === 'signed_in_user') missing.push('signedInUserPortfolio');
|
|
43
|
+
else missing.push('portfolio');
|
|
48
44
|
} else {
|
|
49
45
|
available.push('portfolio');
|
|
50
46
|
}
|
|
51
47
|
}
|
|
52
|
-
else if (dep
|
|
53
|
-
if (userType
|
|
54
|
-
else if (userType === 'normal'
|
|
55
|
-
else if (userType === 'popular_investor' && rootDataStatus.piHistory)
|
|
56
|
-
else if (userType === 'signed_in_user'
|
|
57
|
-
else if (userType === 'all'
|
|
48
|
+
else if (dep === 'history') {
|
|
49
|
+
if (userType === 'speculator' && rootDataStatus.speculatorHistory) isAvailable = true;
|
|
50
|
+
else if (userType === 'normal' && rootDataStatus.normalHistory) isAvailable = true;
|
|
51
|
+
else if (userType === 'popular_investor' && rootDataStatus.piHistory) isAvailable = true;
|
|
52
|
+
else if (userType === 'signed_in_user' && rootDataStatus.signedInUserHistory) isAvailable = true;
|
|
53
|
+
else if (userType === 'all' && rootDataStatus.hasHistory) isAvailable = true;
|
|
58
54
|
|
|
59
55
|
if (!isAvailable) {
|
|
60
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
61
56
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
62
57
|
available.push('history');
|
|
63
58
|
continue;
|
|
64
59
|
}
|
|
65
60
|
|
|
66
|
-
if (userType
|
|
67
|
-
else if (userType === 'normal')
|
|
61
|
+
if (userType === 'speculator') missing.push('speculatorHistory');
|
|
62
|
+
else if (userType === 'normal') missing.push('normalHistory');
|
|
68
63
|
else if (userType === 'popular_investor') missing.push('piHistory');
|
|
69
|
-
else if (userType === 'signed_in_user')
|
|
70
|
-
else
|
|
64
|
+
else if (userType === 'signed_in_user') missing.push('signedInUserHistory');
|
|
65
|
+
else missing.push('history');
|
|
71
66
|
} else {
|
|
72
67
|
available.push('history');
|
|
73
68
|
}
|
|
@@ -77,7 +72,6 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
77
72
|
isAvailable = true;
|
|
78
73
|
available.push('rankings');
|
|
79
74
|
} else {
|
|
80
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
81
75
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
82
76
|
available.push('rankings');
|
|
83
77
|
} else {
|
|
@@ -98,7 +92,6 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
98
92
|
isAvailable = true;
|
|
99
93
|
available.push('insights');
|
|
100
94
|
} else {
|
|
101
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
102
95
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
103
96
|
available.push('insights');
|
|
104
97
|
} else {
|
|
@@ -108,25 +101,24 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
108
101
|
}
|
|
109
102
|
else if (dep === 'social') {
|
|
110
103
|
if (userType === 'popular_investor') {
|
|
111
|
-
if (rootDataStatus.hasPISocial)
|
|
104
|
+
if (rootDataStatus.hasPISocial) isAvailable = true;
|
|
112
105
|
} else if (userType === 'signed_in_user') {
|
|
113
|
-
if (rootDataStatus.hasSignedInSocial)
|
|
106
|
+
if (rootDataStatus.hasSignedInSocial) isAvailable = true;
|
|
114
107
|
} else {
|
|
115
|
-
if (rootDataStatus.hasSocial)
|
|
108
|
+
if (rootDataStatus.hasSocial) isAvailable = true;
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
if (isAvailable) {
|
|
119
112
|
available.push('social');
|
|
120
113
|
} else {
|
|
121
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
122
114
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
123
115
|
available.push('social');
|
|
124
116
|
continue;
|
|
125
117
|
}
|
|
126
118
|
|
|
127
|
-
if (userType === 'popular_investor')
|
|
119
|
+
if (userType === 'popular_investor') missing.push('piSocial');
|
|
128
120
|
else if (userType === 'signed_in_user') missing.push('signedInSocial');
|
|
129
|
-
else
|
|
121
|
+
else missing.push('social');
|
|
130
122
|
}
|
|
131
123
|
}
|
|
132
124
|
else if (dep === 'price') {
|
|
@@ -134,7 +126,6 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
134
126
|
isAvailable = true;
|
|
135
127
|
available.push('price');
|
|
136
128
|
} else {
|
|
137
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
138
129
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
139
130
|
available.push('price');
|
|
140
131
|
} else {
|
|
@@ -142,13 +133,11 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
142
133
|
}
|
|
143
134
|
}
|
|
144
135
|
}
|
|
145
|
-
// [NEW] New Root Data Types for Profile Metrics
|
|
146
136
|
else if (dep === 'ratings') {
|
|
147
137
|
if (rootDataStatus.piRatings) {
|
|
148
138
|
isAvailable = true;
|
|
149
139
|
available.push('ratings');
|
|
150
140
|
} else {
|
|
151
|
-
// [OPTIMIZATION] Optimistic Series Check (Vital for sparse data like ratings)
|
|
152
141
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
153
142
|
available.push('ratings');
|
|
154
143
|
} else {
|
|
@@ -161,7 +150,6 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
161
150
|
isAvailable = true;
|
|
162
151
|
available.push('pageViews');
|
|
163
152
|
} else {
|
|
164
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
165
153
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
166
154
|
available.push('pageViews');
|
|
167
155
|
} else {
|
|
@@ -174,7 +162,6 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
174
162
|
isAvailable = true;
|
|
175
163
|
available.push('watchlist');
|
|
176
164
|
} else {
|
|
177
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
178
165
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
179
166
|
available.push('watchlist');
|
|
180
167
|
} else {
|
|
@@ -187,9 +174,6 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
187
174
|
isAvailable = true;
|
|
188
175
|
available.push('alerts');
|
|
189
176
|
} else {
|
|
190
|
-
// [OPTIMIZATION] Optimistic Series Check
|
|
191
|
-
// This explicitly solves the Alert History edge case where today's snapshot
|
|
192
|
-
// might be missing but the lookback period contains data.
|
|
193
177
|
if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
|
|
194
178
|
available.push('alerts');
|
|
195
179
|
} else {
|
|
@@ -198,8 +182,16 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
198
182
|
}
|
|
199
183
|
}
|
|
200
184
|
}
|
|
185
|
+
|
|
186
|
+
// [NEW] Enforce Mandatory Roots (defined by computation)
|
|
187
|
+
// This allows granular control: "I need portfolio, but ratings are optional"
|
|
188
|
+
if (calcManifest.mandatoryRoots && Array.isArray(calcManifest.mandatoryRoots) && calcManifest.mandatoryRoots.length > 0) {
|
|
189
|
+
const missingMandatory = calcManifest.mandatoryRoots.filter(r => !available.includes(r));
|
|
190
|
+
if (missingMandatory.length > 0) {
|
|
191
|
+
return { canRun: false, missing, available };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
201
194
|
|
|
202
|
-
// [FIX] Enforce canHaveMissingRoots logic:
|
|
203
195
|
if (canHaveMissingRoots) {
|
|
204
196
|
const canRun = available.length > 0;
|
|
205
197
|
return { canRun, missing, available };
|
|
@@ -236,10 +228,6 @@ function getViableCalculations(candidates, fullManifest, rootDataStatus, dailySt
|
|
|
236
228
|
return viable;
|
|
237
229
|
}
|
|
238
230
|
|
|
239
|
-
/**
|
|
240
|
-
* Checks data availability by reading the centralized index.
|
|
241
|
-
* Extracts all granular details to support extended user types.
|
|
242
|
-
*/
|
|
243
231
|
async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
|
|
244
232
|
const { logger, db } = dependencies;
|
|
245
233
|
|
|
@@ -252,20 +240,15 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
252
240
|
|
|
253
241
|
return {
|
|
254
242
|
status: {
|
|
255
|
-
// Global flags
|
|
256
243
|
hasPortfolio: !!data.hasPortfolio,
|
|
257
244
|
hasHistory: !!data.hasHistory,
|
|
258
|
-
hasSocial: !!data.hasSocial,
|
|
245
|
+
hasSocial: !!data.hasSocial,
|
|
259
246
|
hasInsights: !!data.hasInsights,
|
|
260
247
|
hasPrices: !!data.hasPrices,
|
|
261
|
-
|
|
262
|
-
// Granular flags - Existing
|
|
263
248
|
speculatorPortfolio: !!details.speculatorPortfolio,
|
|
264
249
|
normalPortfolio: !!details.normalPortfolio,
|
|
265
250
|
speculatorHistory: !!details.speculatorHistory,
|
|
266
251
|
normalHistory: !!details.normalHistory,
|
|
267
|
-
|
|
268
|
-
// Granular flags - NEW
|
|
269
252
|
piRankings: !!details.piRankings,
|
|
270
253
|
piPortfolios: !!details.piPortfolios,
|
|
271
254
|
piDeepPortfolios: !!details.piDeepPortfolios,
|
|
@@ -273,18 +256,12 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
273
256
|
signedInUserPortfolio: !!details.signedInUserPortfolio,
|
|
274
257
|
signedInUserHistory: !!details.signedInUserHistory,
|
|
275
258
|
signedInUserVerification: !!details.signedInUserVerification,
|
|
276
|
-
|
|
277
|
-
// [UPDATED] Granular Social Flags
|
|
278
259
|
hasPISocial: !!details.hasPISocial || !!data.hasPISocial,
|
|
279
260
|
hasSignedInSocial: !!details.hasSignedInSocial || !!data.hasSignedInSocial,
|
|
280
|
-
|
|
281
|
-
// [NEW] New Root Data Types for Profile Metrics
|
|
282
261
|
piRatings: !!details.piRatings,
|
|
283
262
|
piPageViews: !!details.piPageViews,
|
|
284
263
|
watchlistMembership: !!details.watchlistMembership,
|
|
285
264
|
piAlertHistory: !!details.piAlertHistory,
|
|
286
|
-
|
|
287
|
-
// [NEW] Global Helper Data
|
|
288
265
|
piMasterList: !!details.piMasterList
|
|
289
266
|
},
|
|
290
267
|
portfolioRefs: null,
|
|
@@ -297,28 +274,12 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
297
274
|
logger.log('WARN', `[Availability] Index not found for ${dateStr}. Assuming NO data.`);
|
|
298
275
|
return {
|
|
299
276
|
status: {
|
|
300
|
-
hasPortfolio: false,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
normalPortfolio: false,
|
|
307
|
-
speculatorHistory: false,
|
|
308
|
-
normalHistory: false,
|
|
309
|
-
piRankings: false,
|
|
310
|
-
piPortfolios: false,
|
|
311
|
-
piDeepPortfolios: false,
|
|
312
|
-
piHistory: false,
|
|
313
|
-
signedInUserPortfolio: false,
|
|
314
|
-
signedInUserHistory: false,
|
|
315
|
-
signedInUserVerification: false,
|
|
316
|
-
hasPISocial: false,
|
|
317
|
-
hasSignedInSocial: false,
|
|
318
|
-
piRatings: false,
|
|
319
|
-
piPageViews: false,
|
|
320
|
-
watchlistMembership: false,
|
|
321
|
-
piAlertHistory: false
|
|
277
|
+
hasPortfolio: false, hasHistory: false, hasSocial: false, hasInsights: false, hasPrices: false,
|
|
278
|
+
speculatorPortfolio: false, normalPortfolio: false, speculatorHistory: false, normalHistory: false,
|
|
279
|
+
piRankings: false, piPortfolios: false, piDeepPortfolios: false, piHistory: false,
|
|
280
|
+
signedInUserPortfolio: false, signedInUserHistory: false, signedInUserVerification: false,
|
|
281
|
+
hasPISocial: false, hasSignedInSocial: false,
|
|
282
|
+
piRatings: false, piPageViews: false, watchlistMembership: false, piAlertHistory: false
|
|
322
283
|
}
|
|
323
284
|
};
|
|
324
285
|
}
|