bulltrackers-module 1.0.731 → 1.0.733
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/orchestrator/index.js +19 -17
- package/index.js +8 -29
- package/package.json +6 -5
- package/functions/computation-system/WorkflowOrchestrator.js +0 -213
- package/functions/computation-system/config/monitoring_config.js +0 -31
- package/functions/computation-system/config/validation_overrides.js +0 -10
- package/functions/computation-system/context/ContextFactory.js +0 -132
- package/functions/computation-system/context/ManifestBuilder.js +0 -379
- package/functions/computation-system/data/AvailabilityChecker.js +0 -236
- package/functions/computation-system/data/CachedDataLoader.js +0 -325
- package/functions/computation-system/data/DependencyFetcher.js +0 -455
- package/functions/computation-system/executors/MetaExecutor.js +0 -279
- package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
- package/functions/computation-system/executors/StandardExecutor.js +0 -465
- package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
- package/functions/computation-system/helpers/computation_worker.js +0 -375
- package/functions/computation-system/helpers/monitor.js +0 -64
- package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
- package/functions/computation-system/layers/extractors.js +0 -1097
- package/functions/computation-system/layers/index.js +0 -40
- package/functions/computation-system/layers/mathematics.js +0 -522
- package/functions/computation-system/layers/profiling.js +0 -537
- package/functions/computation-system/layers/validators.js +0 -170
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
- package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
- package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
- package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
- package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
- package/functions/computation-system/logger/logger.js +0 -297
- package/functions/computation-system/persistence/ContractValidator.js +0 -81
- package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
- package/functions/computation-system/persistence/ResultCommitter.js +0 -283
- package/functions/computation-system/persistence/ResultsValidator.js +0 -130
- package/functions/computation-system/persistence/RunRecorder.js +0 -142
- package/functions/computation-system/persistence/StatusRepository.js +0 -52
- package/functions/computation-system/reporter_epoch.js +0 -6
- package/functions/computation-system/scripts/UpdateContracts.js +0 -128
- package/functions/computation-system/services/SnapshotService.js +0 -148
- package/functions/computation-system/simulation/Fabricator.js +0 -285
- package/functions/computation-system/simulation/SeededRandom.js +0 -41
- package/functions/computation-system/simulation/SimRunner.js +0 -51
- package/functions/computation-system/system_epoch.js +0 -2
- package/functions/computation-system/tools/BuildReporter.js +0 -531
- package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
- package/functions/computation-system/tools/DeploymentValidator.js +0 -536
- package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
- package/functions/computation-system/topology/HashManager.js +0 -55
- package/functions/computation-system/topology/ManifestLoader.js +0 -47
- package/functions/computation-system/utils/data_loader.js +0 -597
- package/functions/computation-system/utils/schema_capture.js +0 -121
- package/functions/computation-system/utils/utils.js +0 -188
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Dynamic Manifest Builder - Handles Topological Sort and Auto-Discovery.
|
|
3
|
-
* UPDATED: Removed Automatic Infra Hashing. Now relies strictly on SYSTEM_EPOCH.
|
|
4
|
-
* UPDATED: Whitelisted 'rootDataSeries', 'dependencySeries', 'mandatoryRoots', and 'isTest'.
|
|
5
|
-
* UPDATED: Whitelisted 'schedule' to allow calculations to define their own execution cadence (Daily/Weekly/Monthly).
|
|
6
|
-
* UPDATED: Whitelisted 'ttlDays' to allow calculations to define custom retention policies.
|
|
7
|
-
*/
|
|
8
|
-
const { generateCodeHash, LEGACY_MAPPING } = require('../topology/HashManager.js');
|
|
9
|
-
const { normalizeName } = require('../utils/utils');
|
|
10
|
-
|
|
11
|
-
const SYSTEM_EPOCH = require('../system_epoch');
|
|
12
|
-
|
|
13
|
-
// Import Layers
|
|
14
|
-
const MathematicsLayer = require('../layers/mathematics');
|
|
15
|
-
const ExtractorsLayer = require('../layers/extractors');
|
|
16
|
-
const ProfilingLayer = require('../layers/profiling');
|
|
17
|
-
const ValidatorsLayer = require('../layers/validators');
|
|
18
|
-
|
|
19
|
-
const LAYER_GROUPS = {
|
|
20
|
-
'mathematics': MathematicsLayer,
|
|
21
|
-
'extractors': ExtractorsLayer,
|
|
22
|
-
'profiling': ProfilingLayer,
|
|
23
|
-
'validators': ValidatorsLayer
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Heuristic to estimate the "weight" of a calculation based on its output structure.
|
|
28
|
-
*/
|
|
29
|
-
function estimateComplexity(Class, metadata) {
|
|
30
|
-
let weight = 1.0;
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const schema = typeof Class.getSchema === 'function' ? Class.getSchema() : {};
|
|
34
|
-
if (schema.patternProperties || (schema.type === 'object' && !schema.properties)) {
|
|
35
|
-
weight *= 3.0;
|
|
36
|
-
}
|
|
37
|
-
const name = Class.name.toLowerCase();
|
|
38
|
-
if (name.includes('perstock') || name.includes('perticker')) weight *= 2.0;
|
|
39
|
-
if (name.includes('peruser')) weight *= 10.0;
|
|
40
|
-
|
|
41
|
-
if (metadata.rootDataDependencies && metadata.rootDataDependencies.includes('portfolio')) {
|
|
42
|
-
weight *= 1.5;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
} catch (e) { }
|
|
46
|
-
|
|
47
|
-
return weight;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function generateLayerHashes(layerExports, layerName) {
|
|
51
|
-
const hashes = {};
|
|
52
|
-
const keys = Object.keys(layerExports).sort();
|
|
53
|
-
for (const key of keys) {
|
|
54
|
-
const item = layerExports[key];
|
|
55
|
-
let source = `LAYER:${layerName}:EXPORT:${key}`;
|
|
56
|
-
if (typeof item === 'function') source += item.toString();
|
|
57
|
-
else if (typeof item === 'object' && item !== null) source += JSON.stringify(item);
|
|
58
|
-
else source += String(item);
|
|
59
|
-
hashes[key] = generateCodeHash(source);
|
|
60
|
-
}
|
|
61
|
-
return hashes;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function buildDynamicTriggers() {
|
|
65
|
-
const triggers = {};
|
|
66
|
-
for (const [layerName, layerExports] of Object.entries(LAYER_GROUPS)) {
|
|
67
|
-
triggers[layerName] = {};
|
|
68
|
-
for (const exportName of Object.keys(layerExports)) {
|
|
69
|
-
const patterns = [];
|
|
70
|
-
patterns.push(exportName);
|
|
71
|
-
const alias = LEGACY_MAPPING[exportName];
|
|
72
|
-
if (alias) {
|
|
73
|
-
patterns.push(`math.${alias}`);
|
|
74
|
-
patterns.push(`${alias}.`);
|
|
75
|
-
}
|
|
76
|
-
triggers[layerName][exportName] = patterns;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return triggers;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const LAYER_HASHES = {};
|
|
83
|
-
for (const [name, exports] of Object.entries(LAYER_GROUPS)) { LAYER_HASHES[name] = generateLayerHashes(exports, name); }
|
|
84
|
-
|
|
85
|
-
const LAYER_TRIGGERS = buildDynamicTriggers();
|
|
86
|
-
|
|
87
|
-
const log = {
|
|
88
|
-
info: (msg) => console.log('ℹ︎ ' + msg),
|
|
89
|
-
step: (msg) => console.log('› ' + msg),
|
|
90
|
-
warn: (msg) => console.warn('⚠︎ ' + msg),
|
|
91
|
-
success: (msg) => console.log('✔︎ ' + msg),
|
|
92
|
-
error: (msg) => console.error('✖ ' + msg),
|
|
93
|
-
fatal: (msg) => { console.error('✖ FATAL ✖ ' + msg); console.error('✖ FATAL ✖ Manifest build FAILED.'); },
|
|
94
|
-
divider: (label) => { const line = ''.padEnd(60, '─'); console.log(`\n${line}\n${label}\n${line}\n`); },
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
function getDependencySet(endpoints, adjacencyList) {
|
|
98
|
-
const required = new Set(endpoints);
|
|
99
|
-
const queue = [...endpoints];
|
|
100
|
-
while (queue.length > 0) {
|
|
101
|
-
const calcName = queue.shift();
|
|
102
|
-
const dependencies = adjacencyList.get(calcName);
|
|
103
|
-
if (dependencies) {
|
|
104
|
-
for (const dep of dependencies) {
|
|
105
|
-
if (!required.has(dep)) { required.add(dep); queue.push(dep); }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return required;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function detectCircularDependencies(manifestMap) {
|
|
113
|
-
let index = 0;
|
|
114
|
-
const stack = [];
|
|
115
|
-
const indices = new Map();
|
|
116
|
-
const lowLinks = new Map();
|
|
117
|
-
const onStack = new Set();
|
|
118
|
-
const cycles = [];
|
|
119
|
-
|
|
120
|
-
function strongconnect(v) {
|
|
121
|
-
indices.set(v, index);
|
|
122
|
-
lowLinks.set(v, index);
|
|
123
|
-
index++;
|
|
124
|
-
stack.push(v);
|
|
125
|
-
onStack.add(v);
|
|
126
|
-
|
|
127
|
-
const entry = manifestMap.get(v);
|
|
128
|
-
if (entry && entry.dependencies) {
|
|
129
|
-
for (const w of entry.dependencies) {
|
|
130
|
-
if (!manifestMap.has(w)) continue;
|
|
131
|
-
if (!indices.has(w)) {
|
|
132
|
-
strongconnect(w);
|
|
133
|
-
lowLinks.set(v, Math.min(lowLinks.get(v), lowLinks.get(w)));
|
|
134
|
-
} else if (onStack.has(w)) {
|
|
135
|
-
lowLinks.set(v, Math.min(lowLinks.get(v), indices.get(w)));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (lowLinks.get(v) === indices.get(v)) {
|
|
141
|
-
const scc = [];
|
|
142
|
-
let w;
|
|
143
|
-
do {
|
|
144
|
-
w = stack.pop();
|
|
145
|
-
onStack.delete(w);
|
|
146
|
-
scc.push(w);
|
|
147
|
-
} while (w !== v);
|
|
148
|
-
|
|
149
|
-
if (scc.length > 1) {
|
|
150
|
-
cycles.push(scc);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
for (const name of manifestMap.keys()) {
|
|
156
|
-
if (!indices.has(name)) {
|
|
157
|
-
strongconnect(name);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (cycles.length > 0) {
|
|
162
|
-
const cycle = cycles[0];
|
|
163
|
-
return cycle.join(' -> ') + ' -> ' + cycle[0];
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function buildManifest(productLinesToRun = [], calculations) {
|
|
169
|
-
log.divider('Building Dynamic Manifest');
|
|
170
|
-
log.info(`[ManifestBuilder] Global System Epoch: ${SYSTEM_EPOCH}`);
|
|
171
|
-
|
|
172
|
-
const requestedLog = (!productLinesToRun || productLinesToRun.length === 0)
|
|
173
|
-
? "ALL (Wildcard/Empty)"
|
|
174
|
-
: productLinesToRun.join(', ');
|
|
175
|
-
log.info(`[ManifestBuilder] 📥 Request received for: [${requestedLog}]`);
|
|
176
|
-
|
|
177
|
-
const manifestMap = new Map();
|
|
178
|
-
const adjacency = new Map();
|
|
179
|
-
const reverseAdjacency = new Map();
|
|
180
|
-
const inDegree = new Map();
|
|
181
|
-
let hasFatalError = false;
|
|
182
|
-
|
|
183
|
-
function processCalc(Class, name, folderName) {
|
|
184
|
-
if (!Class || typeof Class !== 'function') return;
|
|
185
|
-
const normalizedName = normalizeName(name);
|
|
186
|
-
|
|
187
|
-
if (typeof Class.getMetadata !== 'function') { log.fatal(`Calculation "${normalizedName}" missing static getMetadata().`); hasFatalError = true; return; }
|
|
188
|
-
if (typeof Class.getDependencies !== 'function') { log.fatal(`Calculation "${normalizedName}" missing static getDependencies().`); hasFatalError = true; return; }
|
|
189
|
-
|
|
190
|
-
const metadata = Class.getMetadata();
|
|
191
|
-
const weight = estimateComplexity(Class, metadata)
|
|
192
|
-
const dependencies = Class.getDependencies().map(normalizeName);
|
|
193
|
-
const codeStr = Class.toString();
|
|
194
|
-
const selfCodeHash = generateCodeHash(codeStr);
|
|
195
|
-
|
|
196
|
-
let compositeHashString = selfCodeHash + `|EPOCH:${SYSTEM_EPOCH}`;
|
|
197
|
-
|
|
198
|
-
const usedDeps = [];
|
|
199
|
-
const usedLayerHashes = {};
|
|
200
|
-
|
|
201
|
-
for (const [layerName, exportsMap] of Object.entries(LAYER_TRIGGERS)) {
|
|
202
|
-
const layerHashes = LAYER_HASHES[layerName];
|
|
203
|
-
for (const [exportName, triggers] of Object.entries(exportsMap)) {
|
|
204
|
-
if (triggers.some(trigger => codeStr.includes(trigger))) {
|
|
205
|
-
const exportHash = layerHashes[exportName];
|
|
206
|
-
if (exportHash) {
|
|
207
|
-
compositeHashString += exportHash;
|
|
208
|
-
usedDeps.push(`${layerName}.${exportName}`);
|
|
209
|
-
|
|
210
|
-
if (!usedLayerHashes[layerName]) usedLayerHashes[layerName] = '';
|
|
211
|
-
usedLayerHashes[layerName] += exportHash;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const layerComposition = {};
|
|
218
|
-
for(const [lName, lStr] of Object.entries(usedLayerHashes)) {
|
|
219
|
-
layerComposition[lName] = generateCodeHash(lStr);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let isSafeMode = false;
|
|
223
|
-
if (usedDeps.length === 0) {
|
|
224
|
-
isSafeMode = true;
|
|
225
|
-
Object.values(LAYER_HASHES).forEach(layerObj => { Object.values(layerObj).forEach(h => compositeHashString += h); });
|
|
226
|
-
layerComposition['ALL_SAFE_MODE'] = 'ALL';
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const intrinsicHash = generateCodeHash(compositeHashString);
|
|
230
|
-
|
|
231
|
-
const manifestEntry = {
|
|
232
|
-
name: normalizedName,
|
|
233
|
-
class: Class,
|
|
234
|
-
category: folderName,
|
|
235
|
-
sourcePackage: folderName,
|
|
236
|
-
type: metadata.type,
|
|
237
|
-
isPage: metadata.isPage === true,
|
|
238
|
-
isHistorical: metadata.isHistorical !== undefined ? metadata.isHistorical : false,
|
|
239
|
-
isTest: metadata.isTest === true,
|
|
240
|
-
rootDataDependencies: metadata.rootDataDependencies || [],
|
|
241
|
-
rootDataSeries: metadata.rootDataSeries || null,
|
|
242
|
-
dependencySeries: metadata.dependencySeries || null,
|
|
243
|
-
mandatoryRoots: metadata.mandatoryRoots || [],
|
|
244
|
-
canHaveMissingRoots: metadata.canHaveMissingRoots || false,
|
|
245
|
-
canHaveMissingSeries: metadata.canHaveMissingSeries || false,
|
|
246
|
-
userType: metadata.userType,
|
|
247
|
-
dependencies: dependencies,
|
|
248
|
-
schedule: metadata.schedule || null,
|
|
249
|
-
ttlDays: metadata.ttlDays,
|
|
250
|
-
pass: 0,
|
|
251
|
-
hash: intrinsicHash,
|
|
252
|
-
weight: weight,
|
|
253
|
-
composition: {
|
|
254
|
-
epoch: SYSTEM_EPOCH,
|
|
255
|
-
code: selfCodeHash,
|
|
256
|
-
layers: layerComposition,
|
|
257
|
-
deps: {}
|
|
258
|
-
},
|
|
259
|
-
debugUsedLayers: isSafeMode ? ['ALL (Safe Mode)'] : usedDeps
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
manifestMap.set(normalizedName, manifestEntry);
|
|
263
|
-
adjacency.set(normalizedName, dependencies);
|
|
264
|
-
inDegree.set(normalizedName, dependencies.length);
|
|
265
|
-
dependencies.forEach(dep => { if (!reverseAdjacency.has(dep)) reverseAdjacency.set(dep, []); reverseAdjacency.get(dep).push(normalizedName); });
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
for (const folderName in calculations) {
|
|
269
|
-
if (folderName === 'legacy') continue;
|
|
270
|
-
const calculationGroup = calculations[folderName];
|
|
271
|
-
for (const key in calculationGroup) {
|
|
272
|
-
const entry = calculationGroup[key];
|
|
273
|
-
if (typeof entry === 'function') { processCalc(entry, key, folderName); }
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (hasFatalError) throw new Error('Manifest build failed due to missing static methods.');
|
|
278
|
-
log.success(`Loaded ${manifestMap.size} calculations.`);
|
|
279
|
-
|
|
280
|
-
const allNames = new Set(manifestMap.keys());
|
|
281
|
-
for (const [name, entry] of manifestMap) {
|
|
282
|
-
for (const dep of entry.dependencies) {
|
|
283
|
-
if (!allNames.has(dep)) log.error(`${name} depends on unknown calculation "${dep}"`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const productLineEndpoints = [];
|
|
288
|
-
const runAll = !productLinesToRun || productLinesToRun.length === 0 || productLinesToRun.includes('*');
|
|
289
|
-
for (const [name, entry] of manifestMap.entries()) {
|
|
290
|
-
if (runAll || productLinesToRun.includes(entry.category)) {
|
|
291
|
-
productLineEndpoints.push(name);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const requiredCalcs = getDependencySet(productLineEndpoints, adjacency);
|
|
296
|
-
log.info(`Filtered down to ${requiredCalcs.size} active calculations.`);
|
|
297
|
-
|
|
298
|
-
const activePackages = new Set();
|
|
299
|
-
requiredCalcs.forEach(name => {
|
|
300
|
-
const entry = manifestMap.get(name);
|
|
301
|
-
if (entry) activePackages.add(entry.sourcePackage);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
const activeList = Array.from(activePackages).sort().join(', ');
|
|
305
|
-
log.info(`[ManifestBuilder] ✅ FINAL ACTIVE PRODUCT LINES: [${activeList}]`);
|
|
306
|
-
|
|
307
|
-
const filteredManifestMap = new Map();
|
|
308
|
-
const filteredInDegree = new Map();
|
|
309
|
-
const filteredReverseAdjacency = new Map();
|
|
310
|
-
|
|
311
|
-
for (const name of requiredCalcs) {
|
|
312
|
-
filteredManifestMap.set(name, manifestMap.get(name));
|
|
313
|
-
filteredInDegree.set(name, inDegree.get(name));
|
|
314
|
-
const consumers = (reverseAdjacency.get(name) || []).filter(consumer => requiredCalcs.has(consumer));
|
|
315
|
-
filteredReverseAdjacency.set(name, consumers);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const sortedManifest = [];
|
|
319
|
-
const queue = [];
|
|
320
|
-
let maxPass = 0;
|
|
321
|
-
|
|
322
|
-
for (const [name, degree] of filteredInDegree) {
|
|
323
|
-
if (degree === 0) { queue.push(name); filteredManifestMap.get(name).pass = 1; maxPass = 1; }
|
|
324
|
-
}
|
|
325
|
-
queue.sort();
|
|
326
|
-
|
|
327
|
-
while (queue.length) {
|
|
328
|
-
const currentName = queue.shift();
|
|
329
|
-
const currentEntry = filteredManifestMap.get(currentName);
|
|
330
|
-
sortedManifest.push(currentEntry);
|
|
331
|
-
|
|
332
|
-
for (const neighborName of (filteredReverseAdjacency.get(currentName) || [])) {
|
|
333
|
-
const newDegree = filteredInDegree.get(neighborName) - 1;
|
|
334
|
-
filteredInDegree.set(neighborName, newDegree);
|
|
335
|
-
const neighborEntry = filteredManifestMap.get(neighborName);
|
|
336
|
-
if (neighborEntry.pass <= currentEntry.pass) {
|
|
337
|
-
neighborEntry.pass = currentEntry.pass + 1;
|
|
338
|
-
if (neighborEntry.pass > maxPass) maxPass = neighborEntry.pass;
|
|
339
|
-
}
|
|
340
|
-
if (newDegree === 0) queue.push(neighborName);
|
|
341
|
-
}
|
|
342
|
-
queue.sort();
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (sortedManifest.length !== filteredManifestMap.size) {
|
|
346
|
-
const cycle = detectCircularDependencies(filteredManifestMap);
|
|
347
|
-
if (cycle) {
|
|
348
|
-
throw new Error(`Circular dependency detected: ${cycle}`);
|
|
349
|
-
} else {
|
|
350
|
-
throw new Error('Circular dependency detected (Unknown topology error).');
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
for (const entry of sortedManifest) {
|
|
355
|
-
let dependencySignature = entry.hash;
|
|
356
|
-
|
|
357
|
-
if (entry.dependencies && entry.dependencies.length > 0) {
|
|
358
|
-
const depHashes = entry.dependencies.map(depName => {
|
|
359
|
-
const depEntry = filteredManifestMap.get(depName);
|
|
360
|
-
if (depEntry) {
|
|
361
|
-
entry.composition.deps[depName] = depEntry.hash;
|
|
362
|
-
return depEntry.hash;
|
|
363
|
-
}
|
|
364
|
-
return '';
|
|
365
|
-
}).join('|');
|
|
366
|
-
dependencySignature += `|DEPS:${depHashes}`;
|
|
367
|
-
}
|
|
368
|
-
entry.hash = generateCodeHash(dependencySignature);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return sortedManifest;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function build(productLinesToRun, calculations) {
|
|
375
|
-
try { return buildManifest(productLinesToRun, calculations); }
|
|
376
|
-
catch (error) { log.error(error.message); return null; }
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
module.exports = { build };
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Checks availability of root data via the Root Data Index.
|
|
3
|
-
* REFACTORED: Config-driven dependency checking and shared status normalization.
|
|
4
|
-
*/
|
|
5
|
-
const { normalizeName } = require('../utils/utils');
|
|
6
|
-
const { FieldPath } = require('@google-cloud/firestore');
|
|
7
|
-
|
|
8
|
-
const INDEX_COLLECTION = process.env.ROOT_DATA_AVAILABILITY_COLLECTION || 'system_root_data_index';
|
|
9
|
-
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// CONFIGURATION: Dependency Mappings
|
|
12
|
-
// =============================================================================
|
|
13
|
-
|
|
14
|
-
// Global dependencies that are NOT date-specific and are always considered available.
|
|
15
|
-
// These are stored at fixed paths and don't rely on daily availability index.
|
|
16
|
-
const GLOBAL_DEPS = new Set([
|
|
17
|
-
'piMasterList' // Stored at /system_state/popular_investor_master_list (global CID->username mapping)
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
// Dependencies that map directly to a single status flag (date-specific)
|
|
21
|
-
const SIMPLE_DEP_MAP = {
|
|
22
|
-
rankings: 'piRankings',
|
|
23
|
-
verification: 'signedInUserVerification',
|
|
24
|
-
insights: 'hasInsights',
|
|
25
|
-
price: 'hasPrices',
|
|
26
|
-
ratings: 'piRatings',
|
|
27
|
-
pageViews: 'piPageViews',
|
|
28
|
-
watchlist: 'watchlistMembership',
|
|
29
|
-
alerts: 'piAlertHistory',
|
|
30
|
-
piMasterList: 'piMasterList' // Kept for normalizeStatus compatibility, but checked via GLOBAL_DEPS
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Dependencies that vary based on the userType of the calculation
|
|
34
|
-
const COMPLEX_DEP_MAP = {
|
|
35
|
-
portfolio: {
|
|
36
|
-
speculator: 'speculatorPortfolio',
|
|
37
|
-
normal: 'normalPortfolio',
|
|
38
|
-
popular_investor: 'piPortfolios',
|
|
39
|
-
signed_in_user: 'signedInUserPortfolio',
|
|
40
|
-
_default: 'hasPortfolio'
|
|
41
|
-
},
|
|
42
|
-
history: {
|
|
43
|
-
speculator: 'speculatorHistory',
|
|
44
|
-
normal: 'normalHistory',
|
|
45
|
-
popular_investor: 'piHistory',
|
|
46
|
-
signed_in_user: 'signedInUserHistory',
|
|
47
|
-
_default: 'hasHistory'
|
|
48
|
-
},
|
|
49
|
-
social: {
|
|
50
|
-
popular_investor: 'hasPISocial',
|
|
51
|
-
signed_in_user: 'hasSignedInSocial',
|
|
52
|
-
_default: 'hasSocial'
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// =============================================================================
|
|
57
|
-
// LOGIC: Dependency Checking
|
|
58
|
-
// =============================================================================
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Checks if a specific calculation can run based on its dependencies and the current data status.
|
|
62
|
-
*/
|
|
63
|
-
function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
64
|
-
const missing = [];
|
|
65
|
-
const available = [];
|
|
66
|
-
const deps = calcManifest.rootDataDependencies || [];
|
|
67
|
-
|
|
68
|
-
if (!deps.length) return { canRun: true, missing, available };
|
|
69
|
-
|
|
70
|
-
const userType = (calcManifest.userType || 'all').toLowerCase();
|
|
71
|
-
const canHaveMissing = calcManifest.canHaveMissingRoots === true;
|
|
72
|
-
|
|
73
|
-
for (const dep of deps) {
|
|
74
|
-
let isAvailable = false;
|
|
75
|
-
let missingKey = dep;
|
|
76
|
-
|
|
77
|
-
// 0. Check for Global Dependencies (always available, not date-specific)
|
|
78
|
-
if (GLOBAL_DEPS.has(dep)) {
|
|
79
|
-
isAvailable = true;
|
|
80
|
-
}
|
|
81
|
-
// 1. Resolve Status Key for date-specific dependencies
|
|
82
|
-
else if (SIMPLE_DEP_MAP[dep]) {
|
|
83
|
-
const key = SIMPLE_DEP_MAP[dep];
|
|
84
|
-
if (rootDataStatus[key]) isAvailable = true;
|
|
85
|
-
else missingKey = key;
|
|
86
|
-
}
|
|
87
|
-
else if (COMPLEX_DEP_MAP[dep]) {
|
|
88
|
-
const map = COMPLEX_DEP_MAP[dep];
|
|
89
|
-
const key = map[userType] || map._default;
|
|
90
|
-
if (rootDataStatus[key]) isAvailable = true;
|
|
91
|
-
else missingKey = key;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 2. Check Availability
|
|
95
|
-
// [FIX] Removed Optimistic Series Fallback.
|
|
96
|
-
// Just because we want history (series) doesn't mean the data type exists for this date.
|
|
97
|
-
if (isAvailable) {
|
|
98
|
-
available.push(dep);
|
|
99
|
-
} else {
|
|
100
|
-
missing.push(missingKey);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 3. Enforce Mandatory Roots (Granular Override)
|
|
105
|
-
if (calcManifest.mandatoryRoots?.length) {
|
|
106
|
-
if (calcManifest.mandatoryRoots.some(r => !available.includes(r))) {
|
|
107
|
-
return { canRun: false, missing, available };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
canRun: canHaveMissing ? available.length > 0 : missing.length === 0,
|
|
113
|
-
missing,
|
|
114
|
-
available
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* filters a list of calculations to those that can run given the current data.
|
|
120
|
-
*/
|
|
121
|
-
function getViableCalculations(candidates, fullManifest, rootDataStatus, dailyStatus) {
|
|
122
|
-
const manifestMap = new Map(fullManifest.map(c => [normalizeName(c.name), c]));
|
|
123
|
-
|
|
124
|
-
return candidates.filter(calc => {
|
|
125
|
-
// 1. Check Root Data
|
|
126
|
-
const { canRun } = checkRootDependencies(calc, rootDataStatus);
|
|
127
|
-
if (!canRun) return false;
|
|
128
|
-
|
|
129
|
-
// 2. Check Computed Dependencies (Hashes must match)
|
|
130
|
-
if (calc.dependencies?.length) {
|
|
131
|
-
return calc.dependencies.every(depName => {
|
|
132
|
-
const norm = normalizeName(depName);
|
|
133
|
-
const stored = dailyStatus[norm];
|
|
134
|
-
const ref = manifestMap.get(norm);
|
|
135
|
-
// Dependency must exist, have run, and match the current manifest hash
|
|
136
|
-
return ref && stored && stored.hash === ref.hash;
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return true;
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// =============================================================================
|
|
144
|
-
// DATA ACCESS: Status Normalization
|
|
145
|
-
// =============================================================================
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Standardizes the Firestore document into a flat status object.
|
|
149
|
-
* Handles fallbacks and merges 'details' fields.
|
|
150
|
-
*/
|
|
151
|
-
function normalizeStatus(data) {
|
|
152
|
-
// If data is null/undefined, return object with all false
|
|
153
|
-
const d = data || {};
|
|
154
|
-
const det = d.details || {};
|
|
155
|
-
|
|
156
|
-
// Helper to check both root level and details object
|
|
157
|
-
const val = (key, rootFallback) => !!det[key] || (rootFallback ? !!d[rootFallback] : false);
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
// Core Flags
|
|
161
|
-
hasPortfolio: !!d.hasPortfolio,
|
|
162
|
-
hasHistory: !!d.hasHistory,
|
|
163
|
-
hasSocial: !!d.hasSocial,
|
|
164
|
-
hasInsights: !!d.hasInsights,
|
|
165
|
-
hasPrices: !!d.hasPrices,
|
|
166
|
-
|
|
167
|
-
// Granular Portfolio/History
|
|
168
|
-
speculatorPortfolio: !!det.speculatorPortfolio,
|
|
169
|
-
normalPortfolio: !!det.normalPortfolio,
|
|
170
|
-
piPortfolios: !!det.piPortfolios,
|
|
171
|
-
piDeepPortfolios: !!det.piDeepPortfolios,
|
|
172
|
-
signedInUserPortfolio: !!det.signedInUserPortfolio,
|
|
173
|
-
|
|
174
|
-
speculatorHistory: !!det.speculatorHistory,
|
|
175
|
-
normalHistory: !!det.normalHistory,
|
|
176
|
-
piHistory: !!det.piHistory,
|
|
177
|
-
signedInUserHistory: !!det.signedInUserHistory,
|
|
178
|
-
|
|
179
|
-
// Meta Types
|
|
180
|
-
piRankings: !!det.piRankings,
|
|
181
|
-
signedInUserVerification: !!det.signedInUserVerification,
|
|
182
|
-
hasPISocial: val('hasPISocial', 'hasPISocial'),
|
|
183
|
-
hasSignedInSocial: val('hasSignedInSocial', 'hasSignedInSocial'),
|
|
184
|
-
|
|
185
|
-
// Extended Root Types
|
|
186
|
-
piRatings: !!det.piRatings,
|
|
187
|
-
piPageViews: !!det.piPageViews,
|
|
188
|
-
watchlistMembership: !!det.watchlistMembership,
|
|
189
|
-
piAlertHistory: !!det.piAlertHistory,
|
|
190
|
-
piMasterList: !!det.piMasterList
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Checks root data availability for a single date.
|
|
196
|
-
*/
|
|
197
|
-
async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
|
|
198
|
-
const { logger, db } = dependencies;
|
|
199
|
-
try {
|
|
200
|
-
const doc = await db.collection(INDEX_COLLECTION).doc(dateStr).get();
|
|
201
|
-
if (!doc.exists) {
|
|
202
|
-
logger.log('WARN', `[Availability] Index not found for ${dateStr}.`);
|
|
203
|
-
return { status: normalizeStatus(null) };
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
status: normalizeStatus(doc.data()),
|
|
207
|
-
// Legacy placeholders preserved for compatibility
|
|
208
|
-
portfolioRefs: null, historyRefs: null, todayInsights: null, todaySocialPostInsights: null, yesterdayPortfolioRefs: null
|
|
209
|
-
};
|
|
210
|
-
} catch (err) {
|
|
211
|
-
logger.log('ERROR', `Error checking availability index: ${err.message}`);
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Fetches availability status for a range of dates.
|
|
218
|
-
*/
|
|
219
|
-
async function getAvailabilityWindow(deps, startDateStr, endDateStr) {
|
|
220
|
-
const { db } = deps;
|
|
221
|
-
const snapshot = await db.collection(INDEX_COLLECTION)
|
|
222
|
-
.where(FieldPath.documentId(), '>=', startDateStr)
|
|
223
|
-
.where(FieldPath.documentId(), '<=', endDateStr)
|
|
224
|
-
.get();
|
|
225
|
-
|
|
226
|
-
const map = new Map();
|
|
227
|
-
snapshot.forEach(doc => map.set(doc.id, normalizeStatus(doc.data())));
|
|
228
|
-
return map;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
module.exports = {
|
|
232
|
-
checkRootDependencies,
|
|
233
|
-
checkRootDataAvailability,
|
|
234
|
-
getViableCalculations,
|
|
235
|
-
getAvailabilityWindow
|
|
236
|
-
};
|