bulltrackers-module 1.0.732 → 1.0.734

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.
Files changed (106) hide show
  1. package/functions/computation-system-v2/README.md +152 -0
  2. package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +720 -0
  3. package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +176 -0
  4. package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +294 -0
  5. package/functions/computation-system-v2/computations/TestComputation.js +46 -0
  6. package/functions/computation-system-v2/computations/UserPortfolioSummary.js +172 -0
  7. package/functions/computation-system-v2/config/bulltrackers.config.js +317 -0
  8. package/functions/computation-system-v2/framework/core/Computation.js +73 -0
  9. package/functions/computation-system-v2/framework/core/Manifest.js +223 -0
  10. package/functions/computation-system-v2/framework/core/RuleInjector.js +53 -0
  11. package/functions/computation-system-v2/framework/core/Rules.js +231 -0
  12. package/functions/computation-system-v2/framework/core/RunAnalyzer.js +163 -0
  13. package/functions/computation-system-v2/framework/cost/CostTracker.js +154 -0
  14. package/functions/computation-system-v2/framework/data/DataFetcher.js +399 -0
  15. package/functions/computation-system-v2/framework/data/QueryBuilder.js +232 -0
  16. package/functions/computation-system-v2/framework/data/SchemaRegistry.js +287 -0
  17. package/functions/computation-system-v2/framework/execution/Orchestrator.js +498 -0
  18. package/functions/computation-system-v2/framework/execution/TaskRunner.js +35 -0
  19. package/functions/computation-system-v2/framework/execution/middleware/CostTrackerMiddleware.js +32 -0
  20. package/functions/computation-system-v2/framework/execution/middleware/LineageMiddleware.js +32 -0
  21. package/functions/computation-system-v2/framework/execution/middleware/Middleware.js +14 -0
  22. package/functions/computation-system-v2/framework/execution/middleware/ProfilerMiddleware.js +47 -0
  23. package/functions/computation-system-v2/framework/index.js +45 -0
  24. package/functions/computation-system-v2/framework/lineage/LineageTracker.js +147 -0
  25. package/functions/computation-system-v2/framework/monitoring/Profiler.js +80 -0
  26. package/functions/computation-system-v2/framework/resilience/Checkpointer.js +66 -0
  27. package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +327 -0
  28. package/functions/computation-system-v2/framework/storage/StateRepository.js +286 -0
  29. package/functions/computation-system-v2/framework/storage/StorageManager.js +469 -0
  30. package/functions/computation-system-v2/framework/storage/index.js +9 -0
  31. package/functions/computation-system-v2/framework/testing/ComputationTester.js +86 -0
  32. package/functions/computation-system-v2/framework/utils/Graph.js +205 -0
  33. package/functions/computation-system-v2/handlers/dispatcher.js +109 -0
  34. package/functions/computation-system-v2/handlers/index.js +23 -0
  35. package/functions/computation-system-v2/handlers/onDemand.js +289 -0
  36. package/functions/computation-system-v2/handlers/scheduler.js +327 -0
  37. package/functions/computation-system-v2/index.js +163 -0
  38. package/functions/computation-system-v2/rules/index.js +49 -0
  39. package/functions/computation-system-v2/rules/instruments.js +465 -0
  40. package/functions/computation-system-v2/rules/metrics.js +304 -0
  41. package/functions/computation-system-v2/rules/portfolio.js +534 -0
  42. package/functions/computation-system-v2/rules/rankings.js +655 -0
  43. package/functions/computation-system-v2/rules/social.js +562 -0
  44. package/functions/computation-system-v2/rules/trades.js +545 -0
  45. package/functions/computation-system-v2/scripts/migrate-sectors.js +73 -0
  46. package/functions/computation-system-v2/test/test-dispatcher.js +317 -0
  47. package/functions/computation-system-v2/test/test-framework.js +500 -0
  48. package/functions/computation-system-v2/test/test-real-execution.js +166 -0
  49. package/functions/computation-system-v2/test/test-real-integration.js +194 -0
  50. package/functions/computation-system-v2/test/test-refactor-e2e.js +131 -0
  51. package/functions/computation-system-v2/test/test-results.json +31 -0
  52. package/functions/computation-system-v2/test/test-risk-metrics-computation.js +329 -0
  53. package/functions/computation-system-v2/test/test-scheduler.js +204 -0
  54. package/functions/computation-system-v2/test/test-storage.js +449 -0
  55. package/functions/orchestrator/index.js +24 -30
  56. package/index.js +8 -29
  57. package/package.json +3 -2
  58. package/functions/computation-system/WorkflowOrchestrator.js +0 -213
  59. package/functions/computation-system/config/monitoring_config.js +0 -31
  60. package/functions/computation-system/config/validation_overrides.js +0 -10
  61. package/functions/computation-system/context/ContextFactory.js +0 -143
  62. package/functions/computation-system/context/ManifestBuilder.js +0 -379
  63. package/functions/computation-system/data/AvailabilityChecker.js +0 -236
  64. package/functions/computation-system/data/CachedDataLoader.js +0 -325
  65. package/functions/computation-system/data/DependencyFetcher.js +0 -455
  66. package/functions/computation-system/executors/MetaExecutor.js +0 -279
  67. package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
  68. package/functions/computation-system/executors/StandardExecutor.js +0 -465
  69. package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
  70. package/functions/computation-system/helpers/computation_worker.js +0 -375
  71. package/functions/computation-system/helpers/monitor.js +0 -64
  72. package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
  73. package/functions/computation-system/layers/extractors.js +0 -1097
  74. package/functions/computation-system/layers/index.js +0 -40
  75. package/functions/computation-system/layers/mathematics.js +0 -522
  76. package/functions/computation-system/layers/profiling.js +0 -537
  77. package/functions/computation-system/layers/validators.js +0 -170
  78. package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
  79. package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
  80. package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
  81. package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
  82. package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
  83. package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
  84. package/functions/computation-system/logger/logger.js +0 -297
  85. package/functions/computation-system/persistence/ContractValidator.js +0 -81
  86. package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
  87. package/functions/computation-system/persistence/ResultCommitter.js +0 -283
  88. package/functions/computation-system/persistence/ResultsValidator.js +0 -130
  89. package/functions/computation-system/persistence/RunRecorder.js +0 -142
  90. package/functions/computation-system/persistence/StatusRepository.js +0 -52
  91. package/functions/computation-system/reporter_epoch.js +0 -6
  92. package/functions/computation-system/scripts/UpdateContracts.js +0 -128
  93. package/functions/computation-system/services/SnapshotService.js +0 -148
  94. package/functions/computation-system/simulation/Fabricator.js +0 -285
  95. package/functions/computation-system/simulation/SeededRandom.js +0 -41
  96. package/functions/computation-system/simulation/SimRunner.js +0 -51
  97. package/functions/computation-system/system_epoch.js +0 -2
  98. package/functions/computation-system/tools/BuildReporter.js +0 -531
  99. package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
  100. package/functions/computation-system/tools/DeploymentValidator.js +0 -536
  101. package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
  102. package/functions/computation-system/topology/HashManager.js +0 -55
  103. package/functions/computation-system/topology/ManifestLoader.js +0 -47
  104. package/functions/computation-system/utils/data_loader.js +0 -675
  105. package/functions/computation-system/utils/schema_capture.js +0 -121
  106. 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
- };