bulltrackers-module 1.0.221 → 1.0.223
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/WorkflowOrchestrator.js +11 -12
- package/functions/computation-system/data/AvailabilityChecker.js +66 -6
- package/functions/computation-system/executors/MetaExecutor.js +2 -2
- package/functions/computation-system/executors/PriceBatchExecutor.js +1 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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');
|
|
@@ -77,15 +77,7 @@ 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.`);
|
|
89
81
|
calcsToAttempt.push(calc); continue;
|
|
90
82
|
}
|
|
91
83
|
if (typeof storedStatus === 'string' && currentHash && storedStatus !== currentHash) {
|
|
@@ -100,7 +92,7 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
|
|
|
100
92
|
|
|
101
93
|
if (!calcsToAttempt.length) return null;
|
|
102
94
|
|
|
103
|
-
// 2. Data Check
|
|
95
|
+
// 2. Data Availability Check
|
|
104
96
|
const earliestDates = {
|
|
105
97
|
portfolio: new Date('2025-09-25T00:00:00Z'),
|
|
106
98
|
history: new Date('2025-11-05T00:00:00Z'),
|
|
@@ -112,8 +104,15 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
|
|
|
112
104
|
const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
|
|
113
105
|
if (!rootData) { logger.log('INFO', `[DateRunner] Root data missing for ${dateStr}. Skipping.`); return null; }
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
// 3. Viability Check (Smart Execution Map)
|
|
108
|
+
// Filter candidates: Remove any calculation that misses Root Data OR Matches Stale Dependencies
|
|
109
|
+
// PASSED: computationManifest (needed for hash lookup)
|
|
110
|
+
const runnableCalcs = getViableCalculations(calcsToAttempt, computationManifest, rootData.status, dailyStatus);
|
|
111
|
+
|
|
112
|
+
if (!runnableCalcs.length) {
|
|
113
|
+
// logger.log('INFO', `[DateRunner] ${dateStr}: Candidates pruned due to missing deps/data/stale hashes.`);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
117
116
|
|
|
118
117
|
const standardToRun = runnableCalcs.filter(c => c.type === 'standard');
|
|
119
118
|
const metaToRun = runnableCalcs.filter(c => c.type === 'meta');
|
|
@@ -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,9 +22,68 @@ 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 are present AND their stored hash matches their current code hash.
|
|
30
|
+
* * @param {Array} candidates - Calculations attempting to run in this pass.
|
|
31
|
+
* @param {Array} fullManifest - The complete manifest (to lookup dependency current hashes).
|
|
32
|
+
* @param {Object} rootDataStatus - { hasPortfolio: bool, hasPrices: bool... }
|
|
33
|
+
* @param {Object} dailyStatus - Map of { "calc-name": "hash" } for completed items.
|
|
34
|
+
*/
|
|
35
|
+
function getViableCalculations(candidates, fullManifest, rootDataStatus, dailyStatus) {
|
|
36
|
+
const viable = [];
|
|
37
|
+
const manifestMap = new Map(fullManifest.map(c => [normalizeName(c.name), c]));
|
|
38
|
+
|
|
39
|
+
for (const calc of candidates) {
|
|
40
|
+
// 1. Check Root Data
|
|
41
|
+
const rootCheck = checkRootDependencies(calc, rootDataStatus);
|
|
42
|
+
if (!rootCheck.canRun) {
|
|
43
|
+
continue; // Root data missing -> Impossible.
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Check Dependencies (Strict Hash Verification)
|
|
47
|
+
let dependenciesMet = true;
|
|
48
|
+
if (calc.dependencies && calc.dependencies.length > 0) {
|
|
49
|
+
for (const depName of calc.dependencies) {
|
|
50
|
+
const normDep = normalizeName(depName);
|
|
51
|
+
const storedHash = dailyStatus[normDep];
|
|
52
|
+
const depManifest = manifestMap.get(normDep);
|
|
53
|
+
|
|
54
|
+
// If dependency is missing from manifest, we can't verify it (shouldn't happen)
|
|
55
|
+
if (!depManifest) {
|
|
56
|
+
dependenciesMet = false;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// CHECK: Does the dependency exist in DB?
|
|
61
|
+
if (!storedHash) {
|
|
62
|
+
dependenciesMet = false;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// CHECK: Does the stored hash match the current code hash?
|
|
67
|
+
// This prevents running on stale data if a dependency failed to update.
|
|
68
|
+
if (storedHash !== depManifest.hash) {
|
|
69
|
+
dependenciesMet = false;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (dependenciesMet) {
|
|
76
|
+
viable.push(calc);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return viable;
|
|
81
|
+
}
|
|
82
|
+
|
|
24
83
|
async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
|
|
25
|
-
const { logger } = dependencies;
|
|
84
|
+
const { logger, db } = dependencies;
|
|
26
85
|
const dateToProcess = new Date(dateStr + 'T00:00:00Z');
|
|
86
|
+
|
|
27
87
|
let portfolioRefs = [], historyRefs = [];
|
|
28
88
|
let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false, hasPrices = false;
|
|
29
89
|
let insightsData = null, socialData = null;
|
|
@@ -43,7 +103,7 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
43
103
|
tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(r => { historyRefs = r; hasHistory = !!r.length; }));
|
|
44
104
|
}
|
|
45
105
|
if (dateToProcess >= earliestDates.price) {
|
|
46
|
-
tasks.push(
|
|
106
|
+
tasks.push(checkPriceAvailability(config, db).then(r => { hasPrices = r; }));
|
|
47
107
|
}
|
|
48
108
|
|
|
49
109
|
await Promise.all(tasks);
|
|
@@ -56,7 +116,7 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
56
116
|
todayInsights: insightsData,
|
|
57
117
|
todaySocialPostInsights: socialData,
|
|
58
118
|
status: { hasPortfolio, hasInsights, hasSocial, hasHistory, hasPrices },
|
|
59
|
-
yesterdayPortfolioRefs: null
|
|
119
|
+
yesterdayPortfolioRefs: null
|
|
60
120
|
};
|
|
61
121
|
} catch (err) {
|
|
62
122
|
logger.log('ERROR', `Error checking data: ${err.message}`);
|
|
@@ -64,7 +124,7 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
64
124
|
}
|
|
65
125
|
}
|
|
66
126
|
|
|
67
|
-
async function
|
|
127
|
+
async function checkPriceAvailability(config, db) {
|
|
68
128
|
try {
|
|
69
129
|
const collection = config.priceCollection || 'asset_prices';
|
|
70
130
|
const snapshot = await db.collection(collection).limit(1).get();
|
|
@@ -72,4 +132,4 @@ async function checkPriceDataAvailability(config, { db }) {
|
|
|
72
132
|
} catch (e) { return false; }
|
|
73
133
|
}
|
|
74
134
|
|
|
75
|
-
module.exports = { checkRootDependencies, checkRootDataAvailability };
|
|
135
|
+
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
|
|
34
|
-
const social
|
|
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
|
|
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
|
}
|