bulltrackers-module 1.0.580 → 1.0.582

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * FILENAME: computation-system/WorkflowOrchestrator.js
3
3
  * UPDATED: Fixed 'Version Mismatch' deadlock for historical chains.
4
+ * UPDATED: Added Force-Run logic for 'isTest' computations on current day.
4
5
  */
5
6
 
6
7
  const { normalizeName, DEFINITIVE_EARLIEST_DATES } = require('./utils/utils');
@@ -46,12 +47,17 @@ function isDependencyReady(depName, isHistoricalSelf, currentStatusMap, prevStat
46
47
  function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus = null) {
47
48
  const report = { runnable: [], blocked: [], impossible: [], failedDependency: [], reRuns: [], skipped: [] };
48
49
  const simulationStatus = { ...dailyStatus };
50
+ const isToday = dateStr === new Date().toISOString().slice(0, 10);
49
51
 
50
52
  for (const calc of calcsInPass) {
51
53
  const cName = normalizeName(calc.name);
52
54
  const stored = simulationStatus[cName];
53
55
  const currentHash = calc.hash;
54
56
 
57
+ // [NEW] Rule: 'isTest' computations always re-run on the current day.
58
+ // This ensures debug/test probes fire immediately to test system changes.
59
+ const shouldForceRun = isToday && (calc.isTest === true);
60
+
55
61
  // 1. Root Data Check
56
62
  const rootCheck = checkRootDependencies(calc, rootDataStatus);
57
63
  if (!rootCheck.canRun) {
@@ -65,7 +71,8 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
65
71
  }
66
72
 
67
73
  // --- OPTIMIZATION: Early skip if code matches AND data is stable ---
68
- if (stored?.hash === currentHash) {
74
+ // [UPDATED] We bypass this optimization if shouldForceRun is true
75
+ if (stored?.hash === currentHash && !shouldForceRun) {
69
76
  let hasDataDrift = false;
70
77
  let isBlocked = false;
71
78
  let missingDeps = [];
@@ -161,6 +168,9 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
161
168
  report.runnable.push({ ...taskPayload, reason: "New Calculation" });
162
169
  } else if (stored.hash !== currentHash) {
163
170
  report.reRuns.push({ ...taskPayload, oldHash: stored.hash, newHash: currentHash, reason: "Hash Mismatch" });
171
+ } else if (shouldForceRun) {
172
+ // [NEW] Logic to handle the forced run for Test probes
173
+ report.reRuns.push({ ...taskPayload, oldHash: stored.hash, newHash: currentHash, reason: "Test Computation (Always Run Today)" });
164
174
  } else if (hasDataDrift) {
165
175
  report.runnable.push({ ...taskPayload, reason: "Input Data Changed" });
166
176
  }
@@ -1,13 +1,7 @@
1
- /**
2
- * {
3
- * type: uploaded file
4
- * fileName: computation-system/context/ManifestBuilder.js
5
- * }
6
- */
7
1
  /**
8
2
  * @fileoverview Dynamic Manifest Builder - Handles Topological Sort and Auto-Discovery.
9
3
  * UPDATED: Removed Automatic Infra Hashing. Now relies strictly on SYSTEM_EPOCH.
10
- * UPDATED: Whitelisted 'rootDataSeries' and 'dependencySeries' metadata fields.
4
+ * UPDATED: Whitelisted 'rootDataSeries', 'dependencySeries', 'mandatoryRoots', and 'isTest'.
11
5
  */
12
6
  const { generateCodeHash, LEGACY_MAPPING } = require('../topology/HashManager.js');
13
7
  const { normalizeName } = require('../utils/utils');
@@ -31,31 +25,22 @@ const LAYER_GROUPS = {
31
25
  * Heuristic to estimate the "weight" of a calculation based on its output structure.
32
26
  */
33
27
  function estimateComplexity(Class, metadata) {
34
- let weight = 1.0; // Base weight (for single aggregate values)
28
+ let weight = 1.0;
35
29
 
36
30
  try {
37
31
  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
32
  if (schema.patternProperties || (schema.type === 'object' && !schema.properties)) {
42
- weight *= 3.0; // Higher weight for dynamic maps (e.g., Per Sector/Ticker)
33
+ weight *= 3.0;
43
34
  }
44
-
45
- // 2. Metadata hints
46
35
  const name = Class.name.toLowerCase();
47
36
  if (name.includes('perstock') || name.includes('perticker')) weight *= 2.0;
48
- if (name.includes('peruser')) weight *= 10.0; // Very high cost
37
+ if (name.includes('peruser')) weight *= 10.0;
49
38
 
50
- // 3. Dependency hints
51
39
  if (metadata.rootDataDependencies && metadata.rootDataDependencies.includes('portfolio')) {
52
- // Portfolio-based calcs usually iterate over all users in the StandardExecutor
53
40
  weight *= 1.5;
54
41
  }
55
42
 
56
- } catch (e) {
57
- // Fallback to base weight if schema is missing/broken
58
- }
43
+ } catch (e) { }
59
44
 
60
45
  return weight;
61
46
  }
@@ -122,9 +107,6 @@ function getDependencySet(endpoints, adjacencyList) {
122
107
  return required;
123
108
  }
124
109
 
125
- /**
126
- * Helper: Detects cycles using Tarjan's SCC Algorithm.
127
- */
128
110
  function detectCircularDependencies(manifestMap) {
129
111
  let index = 0;
130
112
  const stack = [];
@@ -209,7 +191,6 @@ function buildManifest(productLinesToRun = [], calculations) {
209
191
  const codeStr = Class.toString();
210
192
  const selfCodeHash = generateCodeHash(codeStr);
211
193
 
212
- // [UPDATED] Composite Hash now depends ONLY on Code + Epoch + Layers
213
194
  let compositeHashString = selfCodeHash + `|EPOCH:${SYSTEM_EPOCH}`;
214
195
 
215
196
  const usedDeps = [];
@@ -253,10 +234,12 @@ function buildManifest(productLinesToRun = [], calculations) {
253
234
  type: metadata.type,
254
235
  isPage: metadata.isPage === true,
255
236
  isHistorical: metadata.isHistorical !== undefined ? metadata.isHistorical : false,
237
+ isTest: metadata.isTest === true, // [NEW] Whitelisted for WorkflowOrchestrator
256
238
  rootDataDependencies: metadata.rootDataDependencies || [],
257
- // [NEW] Pass Series Configuration
239
+ // [NEW] Pass Series & Mandatory Config
258
240
  rootDataSeries: metadata.rootDataSeries || null,
259
241
  dependencySeries: metadata.dependencySeries || null,
242
+ mandatoryRoots: metadata.mandatoryRoots || [],
260
243
 
261
244
  canHaveMissingRoots: metadata.canHaveMissingRoots || false,
262
245
  userType: metadata.userType,
@@ -300,9 +283,7 @@ function buildManifest(productLinesToRun = [], calculations) {
300
283
 
301
284
  const productLineEndpoints = [];
302
285
  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
286
  for (const [name, entry] of manifestMap.entries()) {
305
- // Removed "|| entry.sourcePackage === 'core'"
306
287
  if (runAll || productLinesToRun.includes(entry.category)) {
307
288
  productLineEndpoints.push(name);
308
289
  }
@@ -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
- * FIXED: Removed permissive fallbacks to enforce strict userType availability rules.
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 === 'speculator' && rootDataStatus.speculatorPortfolio) isAvailable = true;
29
- else if (userType === 'normal' && rootDataStatus.normalPortfolio) isAvailable = true;
30
- else if (userType === 'popular_investor' && rootDataStatus.piPortfolios) isAvailable = true;
31
- else if (userType === 'signed_in_user' && rootDataStatus.signedInUserPortfolio) isAvailable = true;
32
- else if (userType === 'all' && rootDataStatus.hasPortfolio) isAvailable = true;
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 === 'speculator') missing.push('speculatorPortfolio');
44
- else if (userType === 'normal') missing.push('normalPortfolio');
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') missing.push('signedInUserPortfolio');
47
- else missing.push('portfolio');
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 === 'history') {
53
- if (userType === 'speculator' && rootDataStatus.speculatorHistory) isAvailable = true;
54
- else if (userType === 'normal' && rootDataStatus.normalHistory) isAvailable = true;
55
- else if (userType === 'popular_investor' && rootDataStatus.piHistory) isAvailable = true;
56
- else if (userType === 'signed_in_user' && rootDataStatus.signedInUserHistory) isAvailable = true;
57
- else if (userType === 'all' && rootDataStatus.hasHistory) isAvailable = true;
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 === 'speculator') missing.push('speculatorHistory');
67
- else if (userType === 'normal') missing.push('normalHistory');
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') missing.push('signedInUserHistory');
70
- else missing.push('history');
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) isAvailable = true;
104
+ if (rootDataStatus.hasPISocial) isAvailable = true;
112
105
  } else if (userType === 'signed_in_user') {
113
- if (rootDataStatus.hasSignedInSocial) isAvailable = true;
106
+ if (rootDataStatus.hasSignedInSocial) isAvailable = true;
114
107
  } else {
115
- if (rootDataStatus.hasSocial) isAvailable = true;
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') missing.push('piSocial');
119
+ if (userType === 'popular_investor') missing.push('piSocial');
128
120
  else if (userType === 'signed_in_user') missing.push('signedInSocial');
129
- else missing.push('social');
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, // Represents generic/asset posts
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
- hasHistory: false,
302
- hasSocial: false,
303
- hasInsights: false,
304
- hasPrices: false,
305
- speculatorPortfolio: false,
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.580",
3
+ "version": "1.0.582",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [