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.
Files changed (52) hide show
  1. package/functions/orchestrator/index.js +19 -17
  2. package/index.js +8 -29
  3. package/package.json +6 -5
  4. package/functions/computation-system/WorkflowOrchestrator.js +0 -213
  5. package/functions/computation-system/config/monitoring_config.js +0 -31
  6. package/functions/computation-system/config/validation_overrides.js +0 -10
  7. package/functions/computation-system/context/ContextFactory.js +0 -132
  8. package/functions/computation-system/context/ManifestBuilder.js +0 -379
  9. package/functions/computation-system/data/AvailabilityChecker.js +0 -236
  10. package/functions/computation-system/data/CachedDataLoader.js +0 -325
  11. package/functions/computation-system/data/DependencyFetcher.js +0 -455
  12. package/functions/computation-system/executors/MetaExecutor.js +0 -279
  13. package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
  14. package/functions/computation-system/executors/StandardExecutor.js +0 -465
  15. package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
  16. package/functions/computation-system/helpers/computation_worker.js +0 -375
  17. package/functions/computation-system/helpers/monitor.js +0 -64
  18. package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
  19. package/functions/computation-system/layers/extractors.js +0 -1097
  20. package/functions/computation-system/layers/index.js +0 -40
  21. package/functions/computation-system/layers/mathematics.js +0 -522
  22. package/functions/computation-system/layers/profiling.js +0 -537
  23. package/functions/computation-system/layers/validators.js +0 -170
  24. package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
  25. package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
  26. package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
  27. package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
  28. package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
  29. package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
  30. package/functions/computation-system/logger/logger.js +0 -297
  31. package/functions/computation-system/persistence/ContractValidator.js +0 -81
  32. package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
  33. package/functions/computation-system/persistence/ResultCommitter.js +0 -283
  34. package/functions/computation-system/persistence/ResultsValidator.js +0 -130
  35. package/functions/computation-system/persistence/RunRecorder.js +0 -142
  36. package/functions/computation-system/persistence/StatusRepository.js +0 -52
  37. package/functions/computation-system/reporter_epoch.js +0 -6
  38. package/functions/computation-system/scripts/UpdateContracts.js +0 -128
  39. package/functions/computation-system/services/SnapshotService.js +0 -148
  40. package/functions/computation-system/simulation/Fabricator.js +0 -285
  41. package/functions/computation-system/simulation/SeededRandom.js +0 -41
  42. package/functions/computation-system/simulation/SimRunner.js +0 -51
  43. package/functions/computation-system/system_epoch.js +0 -2
  44. package/functions/computation-system/tools/BuildReporter.js +0 -531
  45. package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
  46. package/functions/computation-system/tools/DeploymentValidator.js +0 -536
  47. package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
  48. package/functions/computation-system/topology/HashManager.js +0 -55
  49. package/functions/computation-system/topology/ManifestLoader.js +0 -47
  50. package/functions/computation-system/utils/data_loader.js +0 -597
  51. package/functions/computation-system/utils/schema_capture.js +0 -121
  52. 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
- };