bulltrackers-module 1.0.211 → 1.0.213
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/controllers/computation_controller.js +199 -188
- package/functions/computation-system/helpers/computation_dispatcher.js +90 -90
- package/functions/computation-system/helpers/computation_manifest_builder.js +327 -283
- package/functions/computation-system/helpers/computation_pass_runner.js +168 -157
- package/functions/computation-system/helpers/computation_worker.js +85 -85
- package/functions/computation-system/helpers/orchestration_helpers.js +542 -558
- package/functions/computation-system/layers/extractors.js +279 -0
- package/functions/computation-system/layers/index.js +40 -0
- package/functions/computation-system/layers/math_primitives.js +743 -743
- package/functions/computation-system/layers/mathematics.js +397 -0
- package/functions/computation-system/layers/profiling.js +287 -0
- package/functions/computation-system/layers/validators.js +170 -0
- package/functions/computation-system/utils/schema_capture.js +63 -63
- package/functions/computation-system/utils/utils.js +22 -1
- package/functions/task-engine/helpers/update_helpers.js +17 -49
- package/package.json +1 -1
|
@@ -1,284 +1,328 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
* Dynamic Manifest Builder (
|
|
4
|
-
*
|
|
5
|
-
* This script builds the computation manifest
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*
|
|
36
|
-
*/
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
log
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* Dynamic Manifest Builder (v5.1 - Smart Hashing with Safe Mode)
|
|
4
|
+
*
|
|
5
|
+
* This script builds the computation manifest and generates a "Smart Hash"
|
|
6
|
+
* for each calculation.
|
|
7
|
+
*
|
|
8
|
+
* KEY FEATURE:
|
|
9
|
+
* It performs static analysis on the calculation code to detect which
|
|
10
|
+
* specific math layers it utilizes. It then combines the calculation's own
|
|
11
|
+
* code hash with the current hashes of those specific layers.
|
|
12
|
+
* * SAFE MODE:
|
|
13
|
+
* If a calculation uses no detectable layer keywords, it defaults to
|
|
14
|
+
* depending on ALL layers to prevent staleness.
|
|
15
|
+
* * FIXED:
|
|
16
|
+
* - Broadened LAYER_TRIGGERS to detect context usage (e.g. 'math.signals')
|
|
17
|
+
* - Updated isHistorical check to look for 'previousComputed'
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { generateCodeHash } = require('../utils/utils');
|
|
21
|
+
|
|
22
|
+
// 1. Import Layers directly to generate their "State Hashes"
|
|
23
|
+
// We import them individually to hash them as distinct domains.
|
|
24
|
+
const MathematicsLayer = require('../layers/mathematics');
|
|
25
|
+
const ExtractorsLayer = require('../layers/extractors');
|
|
26
|
+
const ProfilingLayer = require('../layers/profiling');
|
|
27
|
+
const ValidatorsLayer = require('../layers/validators');
|
|
28
|
+
|
|
29
|
+
/* --------------------------------------------------
|
|
30
|
+
* 1. Layer Hash Generation
|
|
31
|
+
* -------------------------------------------------- */
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generates a single hash representing the entire state of a layer module.
|
|
35
|
+
* Combines all exported classes/functions/objects into one deterministic string.
|
|
36
|
+
*/
|
|
37
|
+
function generateLayerHash(layerExports, layerName) {
|
|
38
|
+
const keys = Object.keys(layerExports).sort(); // Sort for determinism
|
|
39
|
+
let combinedSource = `LAYER:${layerName}`;
|
|
40
|
+
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
const item = layerExports[key];
|
|
43
|
+
if (typeof item === 'function') {
|
|
44
|
+
combinedSource += item.toString();
|
|
45
|
+
} else if (typeof item === 'object' && item !== null) {
|
|
46
|
+
combinedSource += JSON.stringify(item);
|
|
47
|
+
} else {
|
|
48
|
+
combinedSource += String(item);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return generateCodeHash(combinedSource);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Pre-compute layer hashes at startup
|
|
55
|
+
const LAYER_HASHES = {
|
|
56
|
+
'mathematics': generateLayerHash(MathematicsLayer, 'mathematics'),
|
|
57
|
+
'extractors': generateLayerHash(ExtractorsLayer, 'extractors'),
|
|
58
|
+
'profiling': generateLayerHash(ProfilingLayer, 'profiling'),
|
|
59
|
+
'validators': generateLayerHash(ValidatorsLayer, 'validators')
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Map code patterns to Layer dependencies
|
|
63
|
+
// If a calculation's code contains these strings, it depends on that layer.
|
|
64
|
+
const LAYER_TRIGGERS = {
|
|
65
|
+
'mathematics': [
|
|
66
|
+
'math.compute', 'MathPrimitives',
|
|
67
|
+
'math.signals', 'SignalPrimitives', 'signals.',
|
|
68
|
+
'math.aggregate', 'Aggregators',
|
|
69
|
+
'math.timeseries', 'TimeSeries', 'timeSeries.',
|
|
70
|
+
'math.distribution', 'DistributionAnalytics', 'distribution.',
|
|
71
|
+
'math.financial', 'FinancialEngineering'
|
|
72
|
+
],
|
|
73
|
+
'extractors': [
|
|
74
|
+
'math.extract', 'DataExtractor',
|
|
75
|
+
'math.history', 'HistoryExtractor',
|
|
76
|
+
'math.prices', 'priceExtractor',
|
|
77
|
+
'math.insights', 'InsightsExtractor', 'insights.',
|
|
78
|
+
'math.tradeSeries', 'TradeSeriesBuilder'
|
|
79
|
+
],
|
|
80
|
+
'profiling': [
|
|
81
|
+
'math.profiling', 'SCHEMAS',
|
|
82
|
+
'math.classifier', 'UserClassifier',
|
|
83
|
+
'math.psychometrics', 'Psychometrics',
|
|
84
|
+
'math.bias', 'CognitiveBiases',
|
|
85
|
+
'math.skill', 'SkillAttribution'
|
|
86
|
+
],
|
|
87
|
+
'validators': [
|
|
88
|
+
'math.validate', 'Validators'
|
|
89
|
+
]
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/* --------------------------------------------------
|
|
93
|
+
* Pretty Console Helpers
|
|
94
|
+
* -------------------------------------------------- */
|
|
95
|
+
const log = {
|
|
96
|
+
info: (msg) => console.log('ℹ︎ ' + msg),
|
|
97
|
+
step: (msg) => console.log('› ' + msg),
|
|
98
|
+
warn: (msg) => console.warn('⚠︎ ' + msg),
|
|
99
|
+
success: (msg) => console.log('✔︎ ' + msg),
|
|
100
|
+
error: (msg) => console.error('✖ ' + msg),
|
|
101
|
+
fatal: (msg) => { console.error('✖ FATAL ✖ ' + msg); console.error('✖ FATAL ✖ Manifest build FAILED.'); },
|
|
102
|
+
divider: (label) => { const line = ''.padEnd(60, '─'); console.log(`\n${line}\n${label}\n${line}\n`); },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/* --------------------------------------------------
|
|
106
|
+
* Helper Utilities
|
|
107
|
+
* -------------------------------------------------- */
|
|
108
|
+
|
|
109
|
+
const normalizeName = (name) => { if (typeof name !== 'string') return name; return name.trim().replace(/,$/, '').replace(/_/g, '-').toLowerCase(); };
|
|
110
|
+
|
|
111
|
+
function suggestClosest(name, candidates, n = 3) {
|
|
112
|
+
const levenshtein = (a = '', b = '') => {
|
|
113
|
+
const m = a.length, n = b.length;
|
|
114
|
+
if (!m) return n; if (!n) return m;
|
|
115
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => Array(n + 1).fill(i));
|
|
116
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
117
|
+
for (let i = 1; i <= m; i++)
|
|
118
|
+
for (let j = 1; j <= n; j++)
|
|
119
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : Math.min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1;
|
|
120
|
+
return dp[m][n];
|
|
121
|
+
};
|
|
122
|
+
const scores = candidates.map(c => [c, levenshtein(name, c)]);
|
|
123
|
+
scores.sort((a, b) => a[1] - b[1]);
|
|
124
|
+
return scores.slice(0, n).map(s => s[0]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findCycles(manifestMap, adjacencyList) {
|
|
128
|
+
const visited = new Set(), stack = new Set(), cycles = [];
|
|
129
|
+
const dfs = (node, path) => {
|
|
130
|
+
if (stack.has(node)) { const idx = path.indexOf(node); cycles.push([...path.slice(idx), node]); return; }
|
|
131
|
+
if (visited.has(node)) return;
|
|
132
|
+
visited.add(node); stack.add(node);
|
|
133
|
+
for (const nb of adjacencyList.get(node) || []) dfs(nb, [...path, nb]);
|
|
134
|
+
stack.delete(node); };
|
|
135
|
+
for (const name of manifestMap.keys()) dfs(name, [name]);
|
|
136
|
+
return cycles;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getDependencySet(endpoints, adjacencyList) {
|
|
140
|
+
const required = new Set(endpoints);
|
|
141
|
+
const queue = [...endpoints];
|
|
142
|
+
while (queue.length > 0) { const calcName = queue.shift(); const dependencies = adjacencyList.get(calcName);
|
|
143
|
+
if (dependencies) { for (const dep of dependencies) { if (!required.has(dep)) { required.add(dep); queue.push(dep); } } } }
|
|
144
|
+
return required;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* --------------------------------------------------
|
|
148
|
+
* Core Manifest Builder
|
|
149
|
+
* -------------------------------------------------- */
|
|
150
|
+
|
|
151
|
+
function buildManifest(productLinesToRun = [], calculations) {
|
|
152
|
+
log.divider('Building Dynamic Manifest (Smart Hashing)');
|
|
153
|
+
log.info(`Target Product Lines: [${productLinesToRun.join(', ')}]`);
|
|
154
|
+
log.info(`Layer Hashes Initialized: ${Object.keys(LAYER_HASHES).join(', ')}`);
|
|
155
|
+
|
|
156
|
+
const manifestMap = new Map();
|
|
157
|
+
const adjacency = new Map();
|
|
158
|
+
const reverseAdjacency = new Map();
|
|
159
|
+
const inDegree = new Map();
|
|
160
|
+
let hasFatalError = false;
|
|
161
|
+
|
|
162
|
+
/* ---------------- 1. Load All Calculations ---------------- */
|
|
163
|
+
log.step('Loading and validating all calculation classes…');
|
|
164
|
+
const allCalculationClasses = new Map();
|
|
165
|
+
|
|
166
|
+
function processCalc(Class, name, folderName) {
|
|
167
|
+
if (!Class || typeof Class !== 'function') return;
|
|
168
|
+
const normalizedName = normalizeName(name);
|
|
169
|
+
allCalculationClasses.set(normalizedName, Class);
|
|
170
|
+
|
|
171
|
+
if (typeof Class.getMetadata !== 'function') { log.fatal(`Calculation "${normalizedName}" is missing static getMetadata().`); hasFatalError = true; return; }
|
|
172
|
+
if (typeof Class.getDependencies !== 'function') { log.fatal(`Calculation "${normalizedName}" is missing static getDependencies().`); hasFatalError = true;return; }
|
|
173
|
+
|
|
174
|
+
const metadata = Class.getMetadata();
|
|
175
|
+
const dependencies = Class.getDependencies().map(normalizeName);
|
|
176
|
+
|
|
177
|
+
// FIX: Updated check to include 'previousComputed'
|
|
178
|
+
const codeStr = Class.toString();
|
|
179
|
+
if (metadata.isHistorical === true && !codeStr.includes('yesterday') && !codeStr.includes('previousComputed')) {
|
|
180
|
+
log.warn(`Calculation "${normalizedName}" marked 'isHistorical' but no 'previousComputed' or 'yesterday' reference found.`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let finalCategory = folderName;
|
|
184
|
+
if (folderName === 'core') {
|
|
185
|
+
if (metadata.category) finalCategory = metadata.category;
|
|
186
|
+
} else {
|
|
187
|
+
finalCategory = folderName;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- SMART HASH GENERATION ---
|
|
191
|
+
let compositeHashString = generateCodeHash(codeStr); // Start with own code
|
|
192
|
+
const usedLayers = [];
|
|
193
|
+
|
|
194
|
+
// 1. Check for specific layer usage
|
|
195
|
+
for (const [layerName, triggers] of Object.entries(LAYER_TRIGGERS)) {
|
|
196
|
+
// If code contains any trigger for this layer
|
|
197
|
+
if (triggers.some(trigger => codeStr.includes(trigger))) {
|
|
198
|
+
compositeHashString += LAYER_HASHES[layerName]; // Append Layer Hash
|
|
199
|
+
usedLayers.push(layerName);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 2. SAFE MODE FALLBACK
|
|
204
|
+
// If we found NO specific triggers (e.g. legacy code or unique structure),
|
|
205
|
+
// assume it might use anything. Depend on ALL layers to be safe.
|
|
206
|
+
let isSafeMode = false;
|
|
207
|
+
if (usedLayers.length === 0) {
|
|
208
|
+
isSafeMode = true;
|
|
209
|
+
Object.values(LAYER_HASHES).forEach(h => compositeHashString += h);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Generate final composite hash
|
|
213
|
+
const finalHash = generateCodeHash(compositeHashString);
|
|
214
|
+
|
|
215
|
+
if (isSafeMode) {
|
|
216
|
+
log.warn(`[Hash] ${normalizedName}: No specific dependencies detected. Enforcing SAFE MODE (Linked to ALL Layers).`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const manifestEntry = {
|
|
220
|
+
name: normalizedName,
|
|
221
|
+
class: Class,
|
|
222
|
+
category: finalCategory,
|
|
223
|
+
sourcePackage: folderName,
|
|
224
|
+
type: metadata.type,
|
|
225
|
+
isHistorical: metadata.isHistorical,
|
|
226
|
+
rootDataDependencies: metadata.rootDataDependencies || [],
|
|
227
|
+
userType: metadata.userType,
|
|
228
|
+
dependencies: dependencies,
|
|
229
|
+
pass: 0,
|
|
230
|
+
hash: finalHash, // The Smart Hash
|
|
231
|
+
debugUsedLayers: isSafeMode ? ['ALL (Safe Mode)'] : usedLayers
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
manifestMap.set(normalizedName, manifestEntry);
|
|
235
|
+
adjacency.set(normalizedName, dependencies);
|
|
236
|
+
inDegree.set(normalizedName, dependencies.length);
|
|
237
|
+
dependencies.forEach(dep => { if (!reverseAdjacency.has(dep)) reverseAdjacency.set(dep, []); reverseAdjacency.get(dep).push(normalizedName); });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!calculations || typeof calculations !== 'object') {
|
|
241
|
+
throw new Error('Manifest build failed: Invalid calculations object.');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Iterate over folders (folderName becomes the default category)
|
|
245
|
+
for (const folderName in calculations) {
|
|
246
|
+
if (folderName === 'legacy') continue;
|
|
247
|
+
const group = calculations[folderName];
|
|
248
|
+
for (const key in group) {
|
|
249
|
+
const entry = group[key];
|
|
250
|
+
if (typeof entry === 'function') { processCalc(entry, key, folderName); }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (hasFatalError) { throw new Error('Manifest build failed due to missing static methods.'); }
|
|
255
|
+
log.success(`Loaded ${manifestMap.size} calculations.`);
|
|
256
|
+
|
|
257
|
+
/* ---------------- 2. Validate Dependency Links ---------------- */
|
|
258
|
+
const allNames = new Set(manifestMap.keys());
|
|
259
|
+
let invalidLinks = false;
|
|
260
|
+
for (const [name, entry] of manifestMap) {
|
|
261
|
+
for (const dep of entry.dependencies) {
|
|
262
|
+
if (!allNames.has(dep)) {
|
|
263
|
+
invalidLinks = true;
|
|
264
|
+
const guesses = suggestClosest(dep, Array.from(allNames));
|
|
265
|
+
log.error(`${name} depends on unknown calculation "${dep}". Did you mean: ${guesses.join(', ')}?`);
|
|
266
|
+
}
|
|
267
|
+
if (dep === name) {
|
|
268
|
+
invalidLinks = true;
|
|
269
|
+
log.error(`${name} has a circular dependency on *itself*!`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (invalidLinks) { throw new Error('Manifest validation failed.'); }
|
|
274
|
+
|
|
275
|
+
/* ---------------- 3. Filter for Product Lines ---------------- */
|
|
276
|
+
const productLineEndpoints = [];
|
|
277
|
+
for (const [name, entry] of manifestMap.entries()) {
|
|
278
|
+
if (productLinesToRun.includes(entry.category)) { productLineEndpoints.push(name); }
|
|
279
|
+
}
|
|
280
|
+
// Always include core
|
|
281
|
+
for (const [name, entry] of manifestMap.entries()) {
|
|
282
|
+
if (entry.sourcePackage === 'core') { productLineEndpoints.push(name); }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const requiredCalcs = getDependencySet(productLineEndpoints, adjacency);
|
|
286
|
+
log.info(`Filtered down to ${requiredCalcs.size} active calculations.`);
|
|
287
|
+
|
|
288
|
+
const filteredManifestMap = new Map();
|
|
289
|
+
const filteredInDegree = new Map();
|
|
290
|
+
const filteredReverseAdjacency = new Map();
|
|
291
|
+
for (const name of requiredCalcs) { filteredManifestMap.set(name, manifestMap.get(name)); filteredInDegree.set(name, inDegree.get(name));
|
|
292
|
+
const consumers = (reverseAdjacency.get(name) || []).filter(consumer => requiredCalcs.has(consumer)); filteredReverseAdjacency.set(name, consumers); }
|
|
293
|
+
|
|
294
|
+
/* ---------------- 4. Topological Sort ---------------- */
|
|
295
|
+
const sortedManifest = [];
|
|
296
|
+
const queue = [];
|
|
297
|
+
let maxPass = 0;
|
|
298
|
+
|
|
299
|
+
for (const [name, degree] of filteredInDegree) { if (degree === 0) { queue.push(name); filteredManifestMap.get(name).pass = 1; maxPass = 1; } }
|
|
300
|
+
queue.sort();
|
|
301
|
+
while (queue.length) {
|
|
302
|
+
const currentName = queue.shift();
|
|
303
|
+
const currentEntry = filteredManifestMap.get(currentName);
|
|
304
|
+
sortedManifest.push(currentEntry);
|
|
305
|
+
|
|
306
|
+
for (const neighborName of (filteredReverseAdjacency.get(currentName) || [])) { const newDegree = filteredInDegree.get(neighborName) - 1; filteredInDegree.set(neighborName, newDegree);
|
|
307
|
+
const neighborEntry = filteredManifestMap.get(neighborName);
|
|
308
|
+
if (neighborEntry.pass <= currentEntry.pass) { neighborEntry.pass = currentEntry.pass + 1; if (neighborEntry.pass > maxPass) maxPass = neighborEntry.pass; }
|
|
309
|
+
if (newDegree === 0) { queue.push(neighborName); } }
|
|
310
|
+
queue.sort(); }
|
|
311
|
+
|
|
312
|
+
if (sortedManifest.length !== filteredManifestMap.size) {
|
|
313
|
+
throw new Error('Circular dependency detected. Manifest build failed.'); }
|
|
314
|
+
|
|
315
|
+
log.success(`Total passes required: ${maxPass}`);
|
|
316
|
+
return sortedManifest;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function build(productLinesToRun, calculations) {
|
|
320
|
+
try {
|
|
321
|
+
return buildManifest(productLinesToRun, calculations);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
log.error(error.message);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
284
328
|
module.exports = { build };
|