bulltrackers-module 1.0.732 → 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 +1 -1
- 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 -143
- 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 -675
- package/functions/computation-system/utils/schema_capture.js +0 -121
- 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
|
-
};
|