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,170 +0,0 @@
1
- /**
2
- * @fileoverview Validators Layer
3
- * Schema validation logic adhering to schema.md production definitions.
4
- */
5
-
6
- const { SCHEMAS } = require('./profiling');
7
-
8
- class Validators {
9
-
10
- /**
11
- * Validates a User Portfolio based on User Type.
12
- * @param {Object} portfolio - The portfolio object.
13
- * @param {string} userType - 'normal' or 'speculator'.
14
- * @returns {Object} { valid: boolean, errors: string[] }
15
- */
16
- static validatePortfolio(portfolio, userType) {
17
- const errors = [];
18
- if (!portfolio || typeof portfolio !== 'object') {
19
- return { valid: false, errors: ['Portfolio is null or invalid object'] };
20
- }
21
-
22
- // 1. SPECULATOR PORTFOLIO VALIDATION
23
- if (userType === SCHEMAS.USER_TYPES.SPECULATOR) {
24
- // Must have PublicPositions array
25
- if (!Array.isArray(portfolio.PublicPositions)) {
26
- errors.push('Speculator portfolio missing "PublicPositions" array');
27
- } else {
28
- // Validate a sample position structure (checking the first one for performance)
29
- if (portfolio.PublicPositions.length > 0) {
30
- const pos = portfolio.PublicPositions[0];
31
- if (typeof pos.InstrumentID !== 'number') errors.push('Speculator Position missing numeric "InstrumentID"');
32
- if (typeof pos.PositionID !== 'number') errors.push('Speculator Position missing numeric "PositionID"');
33
- if (typeof pos.NetProfit !== 'number') errors.push('Speculator Position missing numeric "NetProfit"');
34
- if (typeof pos.Leverage !== 'number') errors.push('Speculator Position missing numeric "Leverage"');
35
- if (typeof pos.OpenRate !== 'number') errors.push('Speculator Position missing numeric "OpenRate"');
36
- if (typeof pos.IsBuy !== 'boolean') errors.push('Speculator Position missing boolean "IsBuy"');
37
- }
38
- }
39
-
40
- // Root level metrics (Speculator specific)
41
- if (typeof portfolio.NetProfit !== 'number') errors.push('Speculator portfolio missing root "NetProfit"');
42
- if (typeof portfolio.Invested !== 'number') errors.push('Speculator portfolio missing root "Invested"');
43
- }
44
-
45
- // 2. NORMAL USER PORTFOLIO VALIDATION
46
- else {
47
- // Must have AggregatedPositions array
48
- if (!Array.isArray(portfolio.AggregatedPositions)) {
49
- errors.push('Normal portfolio missing "AggregatedPositions" array');
50
- } else {
51
- // Validate a sample position structure
52
- if (portfolio.AggregatedPositions.length > 0) {
53
- const pos = portfolio.AggregatedPositions[0];
54
- if (typeof pos.InstrumentID !== 'number') errors.push('Normal Position missing numeric "InstrumentID"');
55
- if (typeof pos.NetProfit !== 'number') errors.push('Normal Position missing numeric "NetProfit"');
56
- if (typeof pos.Invested !== 'number') errors.push('Normal Position missing numeric "Invested"');
57
- if (typeof pos.Value !== 'number') errors.push('Normal Position missing numeric "Value"');
58
- if (!pos.Direction) errors.push('Normal Position missing "Direction" string');
59
- }
60
- }
61
- }
62
-
63
- return { valid: errors.length === 0, errors };
64
- }
65
-
66
- /**
67
- * Validates Trade History Data.
68
- * @param {Object} historyDoc - The history document object.
69
- * @returns {Object} { valid: boolean, errors: string[] }
70
- */
71
- static validateTradeHistory(historyDoc) {
72
- const errors = [];
73
- if (!historyDoc || typeof historyDoc !== 'object') {
74
- return { valid: false, errors: ['History document is null'] };
75
- }
76
-
77
- if (!Array.isArray(historyDoc.PublicHistoryPositions)) {
78
- errors.push('History missing "PublicHistoryPositions" array');
79
- } else if (historyDoc.PublicHistoryPositions.length > 0) {
80
- // Validate first item schema
81
- const trade = historyDoc.PublicHistoryPositions[0];
82
- if (typeof trade.PositionID !== 'number') errors.push('History trade missing numeric "PositionID"');
83
- if (typeof trade.CID !== 'number') errors.push('History trade missing numeric "CID"');
84
- if (typeof trade.InstrumentID !== 'number') errors.push('History trade missing numeric "InstrumentID"');
85
- if (typeof trade.CloseRate !== 'number') errors.push('History trade missing numeric "CloseRate"');
86
- if (typeof trade.CloseReason !== 'number') errors.push('History trade missing numeric "CloseReason"');
87
- if (typeof trade.NetProfit !== 'number') errors.push('History trade missing numeric "NetProfit"');
88
- if (!trade.OpenDateTime) errors.push('History trade missing "OpenDateTime"');
89
- if (!trade.CloseDateTime) errors.push('History trade missing "CloseDateTime"');
90
- }
91
-
92
- return { valid: errors.length === 0, errors };
93
- }
94
-
95
- /**
96
- * Validates Social Post Data.
97
- * @param {Object} post - The social post object.
98
- * @returns {Object} { valid: boolean, errors: string[] }
99
- */
100
- static validateSocialPost(post) {
101
- const errors = [];
102
- if (!post) return { valid: false, errors: ['Post is null'] };
103
-
104
- if (!post.postOwnerId) errors.push('Post missing "postOwnerId"');
105
- if (!post.fullText) errors.push('Post missing "fullText"');
106
- if (!post.createdAt) errors.push('Post missing "createdAt"');
107
- if (typeof post.likeCount !== 'number') errors.push('Post missing numeric "likeCount"');
108
-
109
- // Sentiment Map
110
- if (!post.sentiment || typeof post.sentiment.overallSentiment !== 'string') {
111
- errors.push('Post missing valid "sentiment.overallSentiment"');
112
- }
113
-
114
- return { valid: errors.length === 0, errors };
115
- }
116
-
117
- /**
118
- * Validates Insights Data (Platform Ownership).
119
- * @param {Object} insight - A single insight item from the array.
120
- * @returns {Object} { valid: boolean, errors: string[] }
121
- */
122
- static validateInsight(insight) {
123
- const errors = [];
124
- if (!insight) return { valid: false, errors: ['Insight object is null'] };
125
-
126
- if (typeof insight.instrumentId !== 'number') errors.push('Insight missing numeric "instrumentId"');
127
- if (typeof insight.total !== 'number') errors.push('Insight missing numeric "total" (Total Owners)');
128
- if (typeof insight.percentage !== 'number') errors.push('Insight missing numeric "percentage" (Global Ownership)');
129
- if (typeof insight.growth !== 'number') errors.push('Insight missing numeric "growth"');
130
-
131
- // Ownership split check
132
- if (typeof insight.buy !== 'number') errors.push('Insight missing numeric "buy" %');
133
- if (typeof insight.sell !== 'number') errors.push('Insight missing numeric "sell" %');
134
-
135
- return { valid: errors.length === 0, errors };
136
- }
137
-
138
- /**
139
- * Validates Asset Price Shard Data.
140
- * @param {Object} instrumentData - The price data map for a specific instrument ID.
141
- * @returns {Object} { valid: boolean, errors: string[] }
142
- */
143
- static validatePriceData(instrumentData) {
144
- const errors = [];
145
- if (!instrumentData) return { valid: false, errors: ['Price data is null'] };
146
-
147
- // Must have 'prices' map
148
- if (!instrumentData.prices || typeof instrumentData.prices !== 'object') {
149
- errors.push('Instrument price data missing "prices" map');
150
- } else {
151
- // Check at least one price key matches date format YYYY-MM-DD
152
- const keys = Object.keys(instrumentData.prices);
153
- if (keys.length > 0) {
154
- const sampleKey = keys[0];
155
- const sampleVal = instrumentData.prices[sampleKey];
156
-
157
- if (!/^\d{4}-\d{2}-\d{2}$/.test(sampleKey)) {
158
- errors.push(`Price map key "${sampleKey}" does not match YYYY-MM-DD format`);
159
- }
160
- if (typeof sampleVal !== 'number') {
161
- errors.push('Price value is not a number');
162
- }
163
- }
164
- }
165
-
166
- return { valid: errors.length === 0, errors };
167
- }
168
- }
169
-
170
- module.exports = { Validators };
@@ -1,388 +0,0 @@
1
- /**
2
- * @fileoverview Checks availability of root data via the Root Data Index.
3
- * REFACTORED: Fully supports granular flags for PI, Signed-In Users, Rankings, and Verification.
4
- * UPDATED: Enforces 'mandatoryRoots' metadata to override permissive flags.
5
- * NEW: Added 'getAvailabilityWindow' for efficient batch availability lookups using range queries.
6
- */
7
- const { normalizeName } = require('../utils/utils');
8
- const { FieldPath } = require('@google-cloud/firestore');
9
-
10
- const INDEX_COLLECTION = process.env.ROOT_DATA_AVAILABILITY_COLLECTION || 'system_root_data_index';
11
-
12
- /**
13
- * Checks if a specific calculation can run based on its dependencies and the current data status.
14
- * @param {Object} calcManifest - The calculation manifest.
15
- * @param {Object} rootDataStatus - The availability status object.
16
- * @returns {Object} { canRun: boolean, missing: Array, available: Array }
17
- */
18
- function checkRootDependencies(calcManifest, rootDataStatus) {
19
- const missing = [];
20
- const available = [];
21
-
22
- if (!calcManifest.rootDataDependencies) return { canRun: true, missing, available };
23
-
24
- // Check if computation can run with missing rootdata
25
- const canHaveMissingRoots = calcManifest.canHaveMissingRoots === true;
26
-
27
- // Normalize userType to lowercase for comparison (computations use uppercase)
28
- const userType = (calcManifest.userType || 'all').toLowerCase();
29
-
30
- for (const dep of calcManifest.rootDataDependencies) {
31
- let isAvailable = false;
32
-
33
- if (dep === 'portfolio') {
34
- if (userType === 'speculator' && rootDataStatus.speculatorPortfolio) isAvailable = true;
35
- else if (userType === 'normal' && rootDataStatus.normalPortfolio) isAvailable = true;
36
- else if (userType === 'popular_investor' && rootDataStatus.piPortfolios) isAvailable = true;
37
- else if (userType === 'signed_in_user' && rootDataStatus.signedInUserPortfolio) isAvailable = true;
38
- else if (userType === 'all' && rootDataStatus.hasPortfolio) isAvailable = true;
39
-
40
- if (!isAvailable) {
41
- // [OPTIMIZATION] Optimistic Series Check
42
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
43
- available.push('portfolio');
44
- continue;
45
- }
46
-
47
- if (userType === 'speculator') missing.push('speculatorPortfolio');
48
- else if (userType === 'normal') missing.push('normalPortfolio');
49
- else if (userType === 'popular_investor') missing.push('piPortfolios');
50
- else if (userType === 'signed_in_user') missing.push('signedInUserPortfolio');
51
- else missing.push('portfolio');
52
- } else {
53
- available.push('portfolio');
54
- }
55
- }
56
- else if (dep === 'history') {
57
- if (userType === 'speculator' && rootDataStatus.speculatorHistory) isAvailable = true;
58
- else if (userType === 'normal' && rootDataStatus.normalHistory) isAvailable = true;
59
- else if (userType === 'popular_investor' && rootDataStatus.piHistory) isAvailable = true;
60
- else if (userType === 'signed_in_user' && rootDataStatus.signedInUserHistory) isAvailable = true;
61
- else if (userType === 'all' && rootDataStatus.hasHistory) isAvailable = true;
62
-
63
- if (!isAvailable) {
64
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
65
- available.push('history');
66
- continue;
67
- }
68
-
69
- if (userType === 'speculator') missing.push('speculatorHistory');
70
- else if (userType === 'normal') missing.push('normalHistory');
71
- else if (userType === 'popular_investor') missing.push('piHistory');
72
- else if (userType === 'signed_in_user') missing.push('signedInUserHistory');
73
- else missing.push('history');
74
- } else {
75
- available.push('history');
76
- }
77
- }
78
- else if (dep === 'rankings') {
79
- if (rootDataStatus.piRankings) {
80
- isAvailable = true;
81
- available.push('rankings');
82
- } else {
83
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
84
- available.push('rankings');
85
- } else {
86
- missing.push('piRankings');
87
- }
88
- }
89
- }
90
- else if (dep === 'verification') {
91
- if (rootDataStatus.signedInUserVerification) {
92
- isAvailable = true;
93
- available.push('verification');
94
- } else {
95
- missing.push('signedInUserVerification');
96
- }
97
- }
98
- else if (dep === 'insights') {
99
- if (rootDataStatus.hasInsights) {
100
- isAvailable = true;
101
- available.push('insights');
102
- } else {
103
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
104
- available.push('insights');
105
- } else {
106
- missing.push('insights');
107
- }
108
- }
109
- }
110
- else if (dep === 'social') {
111
- if (userType === 'popular_investor') {
112
- if (rootDataStatus.hasPISocial) isAvailable = true;
113
- } else if (userType === 'signed_in_user') {
114
- if (rootDataStatus.hasSignedInSocial) isAvailable = true;
115
- } else {
116
- if (rootDataStatus.hasSocial) isAvailable = true;
117
- }
118
-
119
- if (isAvailable) {
120
- available.push('social');
121
- } else {
122
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
123
- available.push('social');
124
- continue;
125
- }
126
-
127
- if (userType === 'popular_investor') missing.push('piSocial');
128
- else if (userType === 'signed_in_user') missing.push('signedInSocial');
129
- else missing.push('social');
130
- }
131
- }
132
- else if (dep === 'price') {
133
- if (rootDataStatus.hasPrices) {
134
- isAvailable = true;
135
- available.push('price');
136
- } else {
137
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
138
- available.push('price');
139
- } else {
140
- missing.push('price');
141
- }
142
- }
143
- }
144
- else if (dep === 'ratings') {
145
- if (rootDataStatus.piRatings) {
146
- isAvailable = true;
147
- available.push('ratings');
148
- } else {
149
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
150
- available.push('ratings');
151
- } else {
152
- missing.push('piRatings');
153
- }
154
- }
155
- }
156
- else if (dep === 'pageViews') {
157
- if (rootDataStatus.piPageViews) {
158
- isAvailable = true;
159
- available.push('pageViews');
160
- } else {
161
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
162
- available.push('pageViews');
163
- } else {
164
- missing.push('piPageViews');
165
- }
166
- }
167
- }
168
- else if (dep === 'watchlist') {
169
- if (rootDataStatus.watchlistMembership) {
170
- isAvailable = true;
171
- available.push('watchlist');
172
- } else {
173
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
174
- available.push('watchlist');
175
- } else {
176
- missing.push('watchlistMembership');
177
- }
178
- }
179
- }
180
- else if (dep === 'alerts') {
181
- if (rootDataStatus.piAlertHistory) {
182
- isAvailable = true;
183
- available.push('alerts');
184
- } else {
185
- if (calcManifest.rootDataSeries && calcManifest.rootDataSeries[dep]) {
186
- available.push('alerts');
187
- } else {
188
- missing.push('piAlertHistory');
189
- }
190
- }
191
- }
192
- // [GLOBAL] piMasterList is always available (not date-specific)
193
- // Stored at /system_state/popular_investor_master_list
194
- else if (dep === 'piMasterList') {
195
- isAvailable = true;
196
- available.push('piMasterList');
197
- }
198
- }
199
-
200
- // [NEW] Enforce Mandatory Roots (defined by computation)
201
- // This allows granular control: "I need portfolio, but ratings are optional"
202
- if (calcManifest.mandatoryRoots && Array.isArray(calcManifest.mandatoryRoots) && calcManifest.mandatoryRoots.length > 0) {
203
- const missingMandatory = calcManifest.mandatoryRoots.filter(r => !available.includes(r));
204
- if (missingMandatory.length > 0) {
205
- return { canRun: false, missing, available };
206
- }
207
- }
208
-
209
- if (canHaveMissingRoots) {
210
- const canRun = available.length > 0;
211
- return { canRun, missing, available };
212
- }
213
-
214
- // Strict mode: all required rootdata must be available
215
- return { canRun: missing.length === 0, missing, available };
216
- }
217
-
218
- function getViableCalculations(candidates, fullManifest, rootDataStatus, dailyStatus) {
219
- const viable = [];
220
- const manifestMap = new Map(fullManifest.map(c => [normalizeName(c.name), c]));
221
-
222
- for (const calc of candidates) {
223
- const rootCheck = checkRootDependencies(calc, rootDataStatus);
224
- if (!rootCheck.canRun) continue;
225
-
226
- let dependenciesMet = true;
227
- if (calc.dependencies && calc.dependencies.length > 0) {
228
- for (const depName of calc.dependencies) {
229
- const normDep = normalizeName(depName);
230
- const storedHash = dailyStatus[normDep];
231
- const depManifest = manifestMap.get(normDep);
232
-
233
- if (!depManifest) { dependenciesMet = false; break; }
234
- if (!storedHash) { dependenciesMet = false; break; }
235
- if (storedHash.hash !== depManifest.hash) { dependenciesMet = false; break; }
236
- }
237
- }
238
-
239
- if (dependenciesMet) { viable.push(calc); }
240
- }
241
-
242
- return viable;
243
- }
244
-
245
- /**
246
- * Checks root data availability for a single date.
247
- */
248
- async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
249
- const { logger, db } = dependencies;
250
-
251
- try {
252
- const indexDoc = await db.collection(INDEX_COLLECTION).doc(dateStr).get();
253
-
254
- if (indexDoc.exists) {
255
- const data = indexDoc.data();
256
- const details = data.details || {};
257
-
258
- return {
259
- status: {
260
- hasPortfolio: !!data.hasPortfolio,
261
- hasHistory: !!data.hasHistory,
262
- hasSocial: !!data.hasSocial,
263
- hasInsights: !!data.hasInsights,
264
- hasPrices: !!data.hasPrices,
265
- speculatorPortfolio: !!details.speculatorPortfolio,
266
- normalPortfolio: !!details.normalPortfolio,
267
- speculatorHistory: !!details.speculatorHistory,
268
- normalHistory: !!details.normalHistory,
269
- piRankings: !!details.piRankings,
270
- piPortfolios: !!details.piPortfolios,
271
- piDeepPortfolios: !!details.piDeepPortfolios,
272
- piHistory: !!details.piHistory,
273
- signedInUserPortfolio: !!details.signedInUserPortfolio,
274
- signedInUserHistory: !!details.signedInUserHistory,
275
- signedInUserVerification: !!details.signedInUserVerification,
276
- hasPISocial: !!details.hasPISocial || !!data.hasPISocial,
277
- hasSignedInSocial: !!details.hasSignedInSocial || !!data.hasSignedInSocial,
278
- piRatings: !!details.piRatings,
279
- piPageViews: !!details.piPageViews,
280
- watchlistMembership: !!details.watchlistMembership,
281
- piAlertHistory: !!details.piAlertHistory,
282
- piMasterList: !!details.piMasterList
283
- },
284
- portfolioRefs: null,
285
- historyRefs: null,
286
- todayInsights: null,
287
- todaySocialPostInsights: null,
288
- yesterdayPortfolioRefs: null
289
- };
290
- } else {
291
- logger.log('WARN', `[Availability] Index not found for ${dateStr}. Assuming NO data.`);
292
- return {
293
- status: {
294
- hasPortfolio: false,
295
- hasHistory: false,
296
- hasSocial: false,
297
- hasInsights: false,
298
- hasPrices: false,
299
- speculatorPortfolio: false,
300
- normalPortfolio: false,
301
- speculatorHistory: false,
302
- normalHistory: false,
303
- piRankings: false,
304
- piPortfolios: false,
305
- piDeepPortfolios: false,
306
- piHistory: false,
307
- signedInUserPortfolio: false,
308
- signedInUserHistory: false,
309
- signedInUserVerification: false,
310
- hasPISocial: false,
311
- hasSignedInSocial: false,
312
- piRatings: false,
313
- piPageViews: false,
314
- watchlistMembership: false,
315
- piAlertHistory: false
316
- }
317
- };
318
- }
319
-
320
- } catch (err) {
321
- logger.log('ERROR', `Error checking availability index: ${err.message}`);
322
- return null;
323
- }
324
- }
325
-
326
- /**
327
- * [NEW] Fetches availability status for a range of dates.
328
- * Uses a range query to only retrieve indices that actually exist, preventing wasted reads on empty days.
329
- * @param {Object} deps - Dependencies (must include db)
330
- * @param {string} startDateStr - ISO Date string (YYYY-MM-DD) inclusive start
331
- * @param {string} endDateStr - ISO Date string (YYYY-MM-DD) inclusive end
332
- * @returns {Promise<Map<string, Object>>} Map of dateStr -> status object
333
- */
334
- async function getAvailabilityWindow(deps, startDateStr, endDateStr) {
335
- const { db } = deps;
336
-
337
- // Perform Range Query on Document ID (Date String)
338
- const snapshot = await db.collection(INDEX_COLLECTION)
339
- .where(FieldPath.documentId(), '>=', startDateStr)
340
- .where(FieldPath.documentId(), '<=', endDateStr)
341
- .get();
342
-
343
- const availabilityMap = new Map();
344
-
345
- snapshot.forEach(doc => {
346
- const data = doc.data();
347
- const details = data.details || {};
348
- const dateStr = doc.id;
349
-
350
- // Construct status object matching checkRootDataAvailability structure
351
- const status = {
352
- hasPortfolio: !!data.hasPortfolio,
353
- hasHistory: !!data.hasHistory,
354
- hasSocial: !!data.hasSocial,
355
- hasInsights: !!data.hasInsights,
356
- hasPrices: !!data.hasPrices,
357
- speculatorPortfolio: !!details.speculatorPortfolio,
358
- normalPortfolio: !!details.normalPortfolio,
359
- speculatorHistory: !!details.speculatorHistory,
360
- normalHistory: !!details.normalHistory,
361
- piRankings: !!details.piRankings,
362
- piPortfolios: !!details.piPortfolios,
363
- piDeepPortfolios: !!details.piDeepPortfolios,
364
- piHistory: !!details.piHistory,
365
- signedInUserPortfolio: !!details.signedInUserPortfolio,
366
- signedInUserHistory: !!details.signedInUserHistory,
367
- signedInUserVerification: !!details.signedInUserVerification,
368
- hasPISocial: !!details.hasPISocial || !!data.hasPISocial,
369
- hasSignedInSocial: !!details.hasSignedInSocial || !!data.hasSignedInSocial,
370
- piRatings: !!details.piRatings,
371
- piPageViews: !!details.piPageViews,
372
- watchlistMembership: !!details.watchlistMembership,
373
- piAlertHistory: !!details.piAlertHistory,
374
- piMasterList: !!details.piMasterList
375
- };
376
-
377
- availabilityMap.set(dateStr, status);
378
- });
379
-
380
- return availabilityMap;
381
- }
382
-
383
- module.exports = {
384
- checkRootDependencies,
385
- checkRootDataAvailability,
386
- getViableCalculations,
387
- getAvailabilityWindow
388
- };