bulltrackers-module 1.0.221 → 1.0.222

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.
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Main Orchestrator. Coordinates the topological execution of calculations.
3
3
  */
4
4
  const { normalizeName, getExpectedDateStrings } = require('./utils/utils');
5
- const { checkRootDependencies, checkRootDataAvailability } = require('./data/AvailabilityChecker');
5
+ const { checkRootDataAvailability, getViableCalculations } = require('./data/AvailabilityChecker');
6
6
  const { fetchExistingResults } = require('./data/DependencyFetcher');
7
7
  const { fetchComputationStatus, updateComputationStatus } = require('./persistence/StatusRepository');
8
8
  const { runBatchPriceComputation } = require('./executors/PriceBatchExecutor');
@@ -68,7 +68,7 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
68
68
  const { logger } = dependencies;
69
69
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
70
70
 
71
- // 1. Version Check
71
+ // 1. Version Check: Determine which calculations are *stale*
72
72
  const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
73
73
  const calcsToAttempt = [];
74
74
 
@@ -77,22 +77,17 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
77
77
  const storedStatus = dailyStatus[cName];
78
78
  const currentHash = calc.hash;
79
79
 
80
- if (calc.dependencies && calc.dependencies.length > 0) {
81
- const missing = calc.dependencies.filter(depName => !dailyStatus[normalizeName(depName)]);
82
- if (missing.length > 0) {
83
- logger.log('TRACE', `[Skip] ${cName} missing deps: ${missing.join(', ')}`);
84
- continue;
85
- }
86
- }
87
80
  if (!storedStatus) {
88
- logger.log('INFO', `[Versioning] ${cName}: New run needed.`);
81
+ // New calculation
89
82
  calcsToAttempt.push(calc); continue;
90
83
  }
91
84
  if (typeof storedStatus === 'string' && currentHash && storedStatus !== currentHash) {
85
+ // Code changed, must re-run
92
86
  logger.log('INFO', `[Versioning] ${cName}: Code Changed.`);
93
87
  calcsToAttempt.push(calc); continue;
94
88
  }
95
89
  if (storedStatus === true && currentHash) {
90
+ // Migrating legacy status
96
91
  logger.log('INFO', `[Versioning] ${cName}: Upgrading legacy status.`);
97
92
  calcsToAttempt.push(calc); continue;
98
93
  }
@@ -100,7 +95,7 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
100
95
 
101
96
  if (!calcsToAttempt.length) return null;
102
97
 
103
- // 2. Data Check
98
+ // 2. Data Availability Check
104
99
  const earliestDates = {
105
100
  portfolio: new Date('2025-09-25T00:00:00Z'),
106
101
  history: new Date('2025-11-05T00:00:00Z'),
@@ -112,8 +107,15 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
112
107
  const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
113
108
  if (!rootData) { logger.log('INFO', `[DateRunner] Root data missing for ${dateStr}. Skipping.`); return null; }
114
109
 
115
- const runnableCalcs = calcsToAttempt.filter(c => checkRootDependencies(c, rootData.status).canRun);
116
- if (!runnableCalcs.length) return null;
110
+ // 3. Viability Check (Smart Execution Map)
111
+ // Filter candidates: Remove any calculation that misses Root Data OR Dependencies
112
+ const runnableCalcs = getViableCalculations(calcsToAttempt, rootData.status, dailyStatus);
113
+
114
+ if (!runnableCalcs.length) {
115
+ // If we had candidates but they were pruned, it means they are blocked.
116
+ // logger.log('INFO', `[DateRunner] ${dateStr}: Candidates pruned due to missing deps/data.`);
117
+ return null;
118
+ }
117
119
 
118
120
  const standardToRun = runnableCalcs.filter(c => c.type === 'standard');
119
121
  const metaToRun = runnableCalcs.filter(c => c.type === 'meta');
@@ -123,6 +125,7 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
123
125
 
124
126
  try {
125
127
  const calcsRunning = [...standardToRun, ...metaToRun];
128
+ // Fetch dependencies for the *runnable* calculations
126
129
  const existingResults = await fetchExistingResults(dateStr, calcsRunning, computationManifest, config, dependencies, false);
127
130
  const prevDate = new Date(dateToProcess); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
128
131
  const prevDateStr = prevDate.toISOString().slice(0, 10);
@@ -5,8 +5,9 @@ const {
5
5
  getPortfolioPartRefs,
6
6
  loadDailyInsights,
7
7
  loadDailySocialPostInsights,
8
- getHistoryPartRefs
8
+ getHistoryPartRefs
9
9
  } = require('../utils/data_loader');
10
+ const { normalizeName } = require('../utils/utils');
10
11
 
11
12
  function checkRootDependencies(calcManifest, rootDataStatus) {
12
13
  const missing = [];
@@ -21,15 +22,60 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
21
22
  return { canRun: missing.length === 0, missing };
22
23
  }
23
24
 
25
+ /**
26
+ * Filters candidates to only those that are strictly "viable" to run.
27
+ * A calculation is Viable if:
28
+ * 1. All required Root Data is present.
29
+ * 2. All required Dependencies (from previous passes) are present in dailyStatus.
30
+ * * @param {Array} candidates - Calculations attempting to run in this pass.
31
+ * @param {Object} rootDataStatus - { hasPortfolio: bool, hasPrices: bool... }
32
+ * @param {Object} dailyStatus - Map of { "calc-name": "hash" } for completed items.
33
+ */
34
+ function getViableCalculations(candidates, rootDataStatus, dailyStatus) {
35
+ const viable = [];
36
+
37
+ for (const calc of candidates) {
38
+ // 1. Check Root Data
39
+ const rootCheck = checkRootDependencies(calc, rootDataStatus);
40
+ if (!rootCheck.canRun) {
41
+ // Root data missing -> Impossible to run.
42
+ continue;
43
+ }
44
+
45
+ // 2. Check Dependencies
46
+ let dependenciesMet = true;
47
+ if (calc.dependencies && calc.dependencies.length > 0) {
48
+ for (const depName of calc.dependencies) {
49
+ const normDep = normalizeName(depName);
50
+
51
+ // If a dependency is missing from dailyStatus, it failed in a previous pass.
52
+ // Therefore, the current calculation is impossible.
53
+ if (!dailyStatus[normDep]) {
54
+ dependenciesMet = false;
55
+ break;
56
+ }
57
+ }
58
+ }
59
+
60
+ if (dependenciesMet) {
61
+ viable.push(calc);
62
+ }
63
+ }
64
+
65
+ return viable;
66
+ }
67
+
24
68
  async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
25
- const { logger } = dependencies;
69
+ const { logger, db } = dependencies;
26
70
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
71
+
27
72
  let portfolioRefs = [], historyRefs = [];
28
73
  let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false, hasPrices = false;
29
74
  let insightsData = null, socialData = null;
30
75
 
31
76
  try {
32
77
  const tasks = [];
78
+ // Only check data sources if the date is after the earliest known data point
33
79
  if (dateToProcess >= earliestDates.portfolio) {
34
80
  tasks.push(getPortfolioPartRefs(config, dependencies, dateStr).then(r => { portfolioRefs = r; hasPortfolio = !!r.length; }));
35
81
  }
@@ -43,11 +89,12 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
43
89
  tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(r => { historyRefs = r; hasHistory = !!r.length; }));
44
90
  }
45
91
  if (dateToProcess >= earliestDates.price) {
46
- tasks.push(checkPriceDataAvailability(config, dependencies).then(r => { hasPrices = r; }));
92
+ tasks.push(checkPriceAvailability(config, db).then(r => { hasPrices = r; }));
47
93
  }
48
94
 
49
95
  await Promise.all(tasks);
50
96
 
97
+ // If ABSOLUTELY NO data exists, we can return null early
51
98
  if (!(hasPortfolio || hasInsights || hasSocial || hasHistory || hasPrices)) return null;
52
99
 
53
100
  return {
@@ -56,7 +103,7 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
56
103
  todayInsights: insightsData,
57
104
  todaySocialPostInsights: socialData,
58
105
  status: { hasPortfolio, hasInsights, hasSocial, hasHistory, hasPrices },
59
- yesterdayPortfolioRefs: null // Filled later if needed
106
+ yesterdayPortfolioRefs: null // Filled later by StandardExecutor if needed
60
107
  };
61
108
  } catch (err) {
62
109
  logger.log('ERROR', `Error checking data: ${err.message}`);
@@ -64,7 +111,11 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
64
111
  }
65
112
  }
66
113
 
67
- async function checkPriceDataAvailability(config, { db }) {
114
+ /**
115
+ * Checks if any price data exists in the collection.
116
+ * Note: Uses a lightweight limit(1) query.
117
+ */
118
+ async function checkPriceAvailability(config, db) {
68
119
  try {
69
120
  const collection = config.priceCollection || 'asset_prices';
70
121
  const snapshot = await db.collection(collection).limit(1).get();
@@ -72,4 +123,4 @@ async function checkPriceDataAvailability(config, { db }) {
72
123
  } catch (e) { return false; }
73
124
  }
74
125
 
75
- module.exports = { checkRootDependencies, checkRootDataAvailability };
126
+ module.exports = { checkRootDependencies, checkRootDataAvailability, getViableCalculations };
@@ -30,8 +30,8 @@ class MetaExecutor {
30
30
  static async executeOncePerDay(calcInstance, metadata, dateStr, computedDeps, prevDeps, config, deps, loader) {
31
31
  const mappings = await loader.loadMappings();
32
32
  const { logger } = deps;
33
- const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await loader.loadInsights(dateStr) } : null;
34
- const social = metadata.rootDataDependencies?.includes('social') ? { today: await loader.loadSocial(dateStr) } : null;
33
+ const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await loader.loadInsights(dateStr) } : null;
34
+ const social = metadata.rootDataDependencies?.includes('social') ? { today: await loader.loadSocial(dateStr) } : null;
35
35
 
36
36
  if (metadata.rootDataDependencies?.includes('price')) {
37
37
  logger.log('INFO', `[Executor] Running Batched/Sharded Execution for ${metadata.name}`);
@@ -15,7 +15,7 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
15
15
 
16
16
  let targetInstrumentIds = [];
17
17
  if (targetTickers && targetTickers.length > 0) {
18
- const tickerToInst = mappings.tickerToInstrument || {};
18
+ const tickerToInst = mappings.tickerToInstrument || {};
19
19
  targetInstrumentIds = targetTickers.map(t => tickerToInst[t]).filter(id => id);
20
20
  if (targetInstrumentIds.length === 0) { logger.log('WARN', '[BatchPrice] Target tickers provided but no IDs found. Aborting.'); return; }
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.221",
3
+ "version": "1.0.222",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [