bulltrackers-module 1.0.658 → 1.0.659
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/computation-system/data/AvailabilityChecker.js +163 -317
- package/functions/computation-system/data/CachedDataLoader.js +158 -222
- package/functions/computation-system/data/DependencyFetcher.js +201 -406
- package/functions/computation-system/executors/MetaExecutor.js +176 -280
- package/functions/computation-system/executors/StandardExecutor.js +325 -383
- package/functions/computation-system/helpers/computation_dispatcher.js +294 -699
- package/functions/computation-system/helpers/computation_worker.js +3 -2
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +382 -0
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +357 -0
- package/functions/computation-system/legacy/DependencyFetcherOld.js +478 -0
- package/functions/computation-system/legacy/MetaExecutorold.js +364 -0
- package/functions/computation-system/legacy/StandardExecutorold.js +476 -0
- package/functions/computation-system/legacy/computation_dispatcherold.js +944 -0
- package/functions/computation-system/persistence/ResultCommitter.js +137 -188
- package/functions/computation-system/services/SnapshotService.js +129 -0
- package/functions/computation-system/tools/BuildReporter.js +12 -7
- package/functions/computation-system/utils/data_loader.js +213 -238
- package/package.json +3 -2
- package/functions/computation-system/workflows/bulltrackers_pipeline.yaml +0 -163
- package/functions/computation-system/workflows/data_feeder_pipeline.yaml +0 -115
- package/functions/computation-system/workflows/datafeederpipelineinstructions.md +0 -30
- package/functions/computation-system/workflows/morning_prep_pipeline.yaml +0 -55
|
@@ -1,239 +1,186 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Checks availability of root data via the Root Data Index.
|
|
3
|
-
* REFACTORED:
|
|
4
|
-
* UPDATED: Enforces 'mandatoryRoots' metadata to override permissive flags.
|
|
5
|
-
* NEW: Added 'getAvailabilityWindow' for efficient batch availability lookups using range queries.
|
|
3
|
+
* REFACTORED: Config-driven dependency checking and shared status normalization.
|
|
6
4
|
*/
|
|
7
5
|
const { normalizeName } = require('../utils/utils');
|
|
8
6
|
const { FieldPath } = require('@google-cloud/firestore');
|
|
9
7
|
|
|
10
8
|
const INDEX_COLLECTION = process.env.ROOT_DATA_AVAILABILITY_COLLECTION || 'system_root_data_index';
|
|
11
9
|
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// CONFIGURATION: Dependency Mappings
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
// Dependencies that map directly to a single status flag
|
|
15
|
+
const SIMPLE_DEP_MAP = {
|
|
16
|
+
rankings: 'piRankings',
|
|
17
|
+
verification: 'signedInUserVerification',
|
|
18
|
+
insights: 'hasInsights',
|
|
19
|
+
price: 'hasPrices',
|
|
20
|
+
ratings: 'piRatings',
|
|
21
|
+
pageViews: 'piPageViews',
|
|
22
|
+
watchlist: 'watchlistMembership',
|
|
23
|
+
alerts: 'piAlertHistory'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Dependencies that vary based on the userType of the calculation
|
|
27
|
+
const COMPLEX_DEP_MAP = {
|
|
28
|
+
portfolio: {
|
|
29
|
+
speculator: 'speculatorPortfolio',
|
|
30
|
+
normal: 'normalPortfolio',
|
|
31
|
+
popular_investor: 'piPortfolios',
|
|
32
|
+
signed_in_user: 'signedInUserPortfolio',
|
|
33
|
+
_default: 'hasPortfolio'
|
|
34
|
+
},
|
|
35
|
+
history: {
|
|
36
|
+
speculator: 'speculatorHistory',
|
|
37
|
+
normal: 'normalHistory',
|
|
38
|
+
popular_investor: 'piHistory',
|
|
39
|
+
signed_in_user: 'signedInUserHistory',
|
|
40
|
+
_default: 'hasHistory'
|
|
41
|
+
},
|
|
42
|
+
social: {
|
|
43
|
+
popular_investor: 'hasPISocial',
|
|
44
|
+
signed_in_user: 'hasSignedInSocial',
|
|
45
|
+
_default: 'hasSocial'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// LOGIC: Dependency Checking
|
|
51
|
+
// =============================================================================
|
|
52
|
+
|
|
12
53
|
/**
|
|
13
54
|
* 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
55
|
*/
|
|
18
56
|
function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
19
57
|
const missing = [];
|
|
20
58
|
const available = [];
|
|
21
|
-
|
|
22
|
-
if (!calcManifest.rootDataDependencies) return { canRun: true, missing, available };
|
|
59
|
+
const deps = calcManifest.rootDataDependencies || [];
|
|
23
60
|
|
|
24
|
-
|
|
25
|
-
const canHaveMissingRoots = calcManifest.canHaveMissingRoots === true;
|
|
61
|
+
if (!deps.length) return { canRun: true, missing, available };
|
|
26
62
|
|
|
27
|
-
// Normalize userType to lowercase for comparison (computations use uppercase)
|
|
28
63
|
const userType = (calcManifest.userType || 'all').toLowerCase();
|
|
64
|
+
const canHaveMissing = calcManifest.canHaveMissingRoots === true;
|
|
29
65
|
|
|
30
|
-
for (const dep of
|
|
66
|
+
for (const dep of deps) {
|
|
31
67
|
let isAvailable = false;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
else
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
}
|
|
68
|
+
let missingKey = dep;
|
|
69
|
+
|
|
70
|
+
// 1. Resolve Status Key
|
|
71
|
+
if (SIMPLE_DEP_MAP[dep]) {
|
|
72
|
+
const key = SIMPLE_DEP_MAP[dep];
|
|
73
|
+
if (rootDataStatus[key]) isAvailable = true;
|
|
74
|
+
else missingKey = key;
|
|
75
|
+
}
|
|
76
|
+
else if (COMPLEX_DEP_MAP[dep]) {
|
|
77
|
+
const map = COMPLEX_DEP_MAP[dep];
|
|
78
|
+
const key = map[userType] || map._default;
|
|
79
|
+
if (rootDataStatus[key]) isAvailable = true;
|
|
80
|
+
else missingKey = key;
|
|
55
81
|
}
|
|
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
82
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
available.push(
|
|
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');
|
|
83
|
+
// 2. Check Availability (with Optimistic Series Fallback)
|
|
84
|
+
if (isAvailable) {
|
|
85
|
+
available.push(dep);
|
|
86
|
+
} else {
|
|
87
|
+
// [OPTIMIZATION] If series data is requested, we can sometimes proceed without the daily snapshot
|
|
88
|
+
if (calcManifest.rootDataSeries?.[dep]) {
|
|
89
|
+
available.push(dep);
|
|
184
90
|
} else {
|
|
185
|
-
|
|
186
|
-
available.push('alerts');
|
|
187
|
-
} else {
|
|
188
|
-
missing.push('piAlertHistory');
|
|
189
|
-
}
|
|
91
|
+
missing.push(missingKey);
|
|
190
92
|
}
|
|
191
93
|
}
|
|
192
94
|
}
|
|
193
95
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const missingMandatory = calcManifest.mandatoryRoots.filter(r => !available.includes(r));
|
|
198
|
-
if (missingMandatory.length > 0) {
|
|
96
|
+
// 3. Enforce Mandatory Roots (Granular Override)
|
|
97
|
+
if (calcManifest.mandatoryRoots?.length) {
|
|
98
|
+
if (calcManifest.mandatoryRoots.some(r => !available.includes(r))) {
|
|
199
99
|
return { canRun: false, missing, available };
|
|
200
100
|
}
|
|
201
101
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Strict mode: all required rootdata must be available
|
|
209
|
-
return { canRun: missing.length === 0, missing, available };
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
canRun: canHaveMissing ? available.length > 0 : missing.length === 0,
|
|
105
|
+
missing,
|
|
106
|
+
available
|
|
107
|
+
};
|
|
210
108
|
}
|
|
211
109
|
|
|
110
|
+
/**
|
|
111
|
+
* filters a list of calculations to those that can run given the current data.
|
|
112
|
+
*/
|
|
212
113
|
function getViableCalculations(candidates, fullManifest, rootDataStatus, dailyStatus) {
|
|
213
|
-
const viable = [];
|
|
214
114
|
const manifestMap = new Map(fullManifest.map(c => [normalizeName(c.name), c]));
|
|
215
115
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
116
|
+
return candidates.filter(calc => {
|
|
117
|
+
// 1. Check Root Data
|
|
118
|
+
const { canRun } = checkRootDependencies(calc, rootDataStatus);
|
|
119
|
+
if (!canRun) return false;
|
|
120
|
+
|
|
121
|
+
// 2. Check Computed Dependencies (Hashes must match)
|
|
122
|
+
if (calc.dependencies?.length) {
|
|
123
|
+
return calc.dependencies.every(depName => {
|
|
124
|
+
const norm = normalizeName(depName);
|
|
125
|
+
const stored = dailyStatus[norm];
|
|
126
|
+
const ref = manifestMap.get(norm);
|
|
127
|
+
// Dependency must exist, have run, and match the current manifest hash
|
|
128
|
+
return ref && stored && stored.hash === ref.hash;
|
|
129
|
+
});
|
|
231
130
|
}
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
232
134
|
|
|
233
|
-
|
|
234
|
-
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// DATA ACCESS: Status Normalization
|
|
137
|
+
// =============================================================================
|
|
235
138
|
|
|
236
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Standardizes the Firestore document into a flat status object.
|
|
141
|
+
* Handles fallbacks and merges 'details' fields.
|
|
142
|
+
*/
|
|
143
|
+
function normalizeStatus(data) {
|
|
144
|
+
// If data is null/undefined, return object with all false
|
|
145
|
+
const d = data || {};
|
|
146
|
+
const det = d.details || {};
|
|
147
|
+
|
|
148
|
+
// Helper to check both root level and details object
|
|
149
|
+
const val = (key, rootFallback) => !!det[key] || (rootFallback ? !!d[rootFallback] : false);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
// Core Flags
|
|
153
|
+
hasPortfolio: !!d.hasPortfolio,
|
|
154
|
+
hasHistory: !!d.hasHistory,
|
|
155
|
+
hasSocial: !!d.hasSocial,
|
|
156
|
+
hasInsights: !!d.hasInsights,
|
|
157
|
+
hasPrices: !!d.hasPrices,
|
|
158
|
+
|
|
159
|
+
// Granular Portfolio/History
|
|
160
|
+
speculatorPortfolio: !!det.speculatorPortfolio,
|
|
161
|
+
normalPortfolio: !!det.normalPortfolio,
|
|
162
|
+
piPortfolios: !!det.piPortfolios,
|
|
163
|
+
piDeepPortfolios: !!det.piDeepPortfolios,
|
|
164
|
+
signedInUserPortfolio: !!det.signedInUserPortfolio,
|
|
165
|
+
|
|
166
|
+
speculatorHistory: !!det.speculatorHistory,
|
|
167
|
+
normalHistory: !!det.normalHistory,
|
|
168
|
+
piHistory: !!det.piHistory,
|
|
169
|
+
signedInUserHistory: !!det.signedInUserHistory,
|
|
170
|
+
|
|
171
|
+
// Meta Types
|
|
172
|
+
piRankings: !!det.piRankings,
|
|
173
|
+
signedInUserVerification: !!det.signedInUserVerification,
|
|
174
|
+
hasPISocial: val('hasPISocial', 'hasPISocial'),
|
|
175
|
+
hasSignedInSocial: val('hasSignedInSocial', 'hasSignedInSocial'),
|
|
176
|
+
|
|
177
|
+
// Extended Root Types
|
|
178
|
+
piRatings: !!det.piRatings,
|
|
179
|
+
piPageViews: !!det.piPageViews,
|
|
180
|
+
watchlistMembership: !!det.watchlistMembership,
|
|
181
|
+
piAlertHistory: !!det.piAlertHistory,
|
|
182
|
+
piMasterList: !!det.piMasterList
|
|
183
|
+
};
|
|
237
184
|
}
|
|
238
185
|
|
|
239
186
|
/**
|
|
@@ -241,137 +188,36 @@ function getViableCalculations(candidates, fullManifest, rootDataStatus, dailySt
|
|
|
241
188
|
*/
|
|
242
189
|
async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
|
|
243
190
|
const { logger, db } = dependencies;
|
|
244
|
-
|
|
245
191
|
try {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const details = data.details || {};
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
status: {
|
|
254
|
-
hasPortfolio: !!data.hasPortfolio,
|
|
255
|
-
hasHistory: !!data.hasHistory,
|
|
256
|
-
hasSocial: !!data.hasSocial,
|
|
257
|
-
hasInsights: !!data.hasInsights,
|
|
258
|
-
hasPrices: !!data.hasPrices,
|
|
259
|
-
speculatorPortfolio: !!details.speculatorPortfolio,
|
|
260
|
-
normalPortfolio: !!details.normalPortfolio,
|
|
261
|
-
speculatorHistory: !!details.speculatorHistory,
|
|
262
|
-
normalHistory: !!details.normalHistory,
|
|
263
|
-
piRankings: !!details.piRankings,
|
|
264
|
-
piPortfolios: !!details.piPortfolios,
|
|
265
|
-
piDeepPortfolios: !!details.piDeepPortfolios,
|
|
266
|
-
piHistory: !!details.piHistory,
|
|
267
|
-
signedInUserPortfolio: !!details.signedInUserPortfolio,
|
|
268
|
-
signedInUserHistory: !!details.signedInUserHistory,
|
|
269
|
-
signedInUserVerification: !!details.signedInUserVerification,
|
|
270
|
-
hasPISocial: !!details.hasPISocial || !!data.hasPISocial,
|
|
271
|
-
hasSignedInSocial: !!details.hasSignedInSocial || !!data.hasSignedInSocial,
|
|
272
|
-
piRatings: !!details.piRatings,
|
|
273
|
-
piPageViews: !!details.piPageViews,
|
|
274
|
-
watchlistMembership: !!details.watchlistMembership,
|
|
275
|
-
piAlertHistory: !!details.piAlertHistory,
|
|
276
|
-
piMasterList: !!details.piMasterList
|
|
277
|
-
},
|
|
278
|
-
portfolioRefs: null,
|
|
279
|
-
historyRefs: null,
|
|
280
|
-
todayInsights: null,
|
|
281
|
-
todaySocialPostInsights: null,
|
|
282
|
-
yesterdayPortfolioRefs: null
|
|
283
|
-
};
|
|
284
|
-
} else {
|
|
285
|
-
logger.log('WARN', `[Availability] Index not found for ${dateStr}. Assuming NO data.`);
|
|
286
|
-
return {
|
|
287
|
-
status: {
|
|
288
|
-
hasPortfolio: false,
|
|
289
|
-
hasHistory: false,
|
|
290
|
-
hasSocial: false,
|
|
291
|
-
hasInsights: false,
|
|
292
|
-
hasPrices: false,
|
|
293
|
-
speculatorPortfolio: false,
|
|
294
|
-
normalPortfolio: false,
|
|
295
|
-
speculatorHistory: false,
|
|
296
|
-
normalHistory: false,
|
|
297
|
-
piRankings: false,
|
|
298
|
-
piPortfolios: false,
|
|
299
|
-
piDeepPortfolios: false,
|
|
300
|
-
piHistory: false,
|
|
301
|
-
signedInUserPortfolio: false,
|
|
302
|
-
signedInUserHistory: false,
|
|
303
|
-
signedInUserVerification: false,
|
|
304
|
-
hasPISocial: false,
|
|
305
|
-
hasSignedInSocial: false,
|
|
306
|
-
piRatings: false,
|
|
307
|
-
piPageViews: false,
|
|
308
|
-
watchlistMembership: false,
|
|
309
|
-
piAlertHistory: false
|
|
310
|
-
}
|
|
311
|
-
};
|
|
192
|
+
const doc = await db.collection(INDEX_COLLECTION).doc(dateStr).get();
|
|
193
|
+
if (!doc.exists) {
|
|
194
|
+
logger.log('WARN', `[Availability] Index not found for ${dateStr}.`);
|
|
195
|
+
return { status: normalizeStatus(null) };
|
|
312
196
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
197
|
+
return {
|
|
198
|
+
status: normalizeStatus(doc.data()),
|
|
199
|
+
// Legacy placeholders preserved for compatibility
|
|
200
|
+
portfolioRefs: null, historyRefs: null, todayInsights: null, todaySocialPostInsights: null, yesterdayPortfolioRefs: null
|
|
201
|
+
};
|
|
202
|
+
} catch (err) {
|
|
203
|
+
logger.log('ERROR', `Error checking availability index: ${err.message}`);
|
|
204
|
+
return null;
|
|
317
205
|
}
|
|
318
206
|
}
|
|
319
207
|
|
|
320
208
|
/**
|
|
321
|
-
*
|
|
322
|
-
* Uses a range query to only retrieve indices that actually exist, preventing wasted reads on empty days.
|
|
323
|
-
* @param {Object} deps - Dependencies (must include db)
|
|
324
|
-
* @param {string} startDateStr - ISO Date string (YYYY-MM-DD) inclusive start
|
|
325
|
-
* @param {string} endDateStr - ISO Date string (YYYY-MM-DD) inclusive end
|
|
326
|
-
* @returns {Promise<Map<string, Object>>} Map of dateStr -> status object
|
|
209
|
+
* Fetches availability status for a range of dates.
|
|
327
210
|
*/
|
|
328
211
|
async function getAvailabilityWindow(deps, startDateStr, endDateStr) {
|
|
329
212
|
const { db } = deps;
|
|
330
|
-
|
|
331
|
-
// Perform Range Query on Document ID (Date String)
|
|
332
213
|
const snapshot = await db.collection(INDEX_COLLECTION)
|
|
333
214
|
.where(FieldPath.documentId(), '>=', startDateStr)
|
|
334
215
|
.where(FieldPath.documentId(), '<=', endDateStr)
|
|
335
216
|
.get();
|
|
336
217
|
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const data = doc.data();
|
|
341
|
-
const details = data.details || {};
|
|
342
|
-
const dateStr = doc.id;
|
|
343
|
-
|
|
344
|
-
// Construct status object matching checkRootDataAvailability structure
|
|
345
|
-
const status = {
|
|
346
|
-
hasPortfolio: !!data.hasPortfolio,
|
|
347
|
-
hasHistory: !!data.hasHistory,
|
|
348
|
-
hasSocial: !!data.hasSocial,
|
|
349
|
-
hasInsights: !!data.hasInsights,
|
|
350
|
-
hasPrices: !!data.hasPrices,
|
|
351
|
-
speculatorPortfolio: !!details.speculatorPortfolio,
|
|
352
|
-
normalPortfolio: !!details.normalPortfolio,
|
|
353
|
-
speculatorHistory: !!details.speculatorHistory,
|
|
354
|
-
normalHistory: !!details.normalHistory,
|
|
355
|
-
piRankings: !!details.piRankings,
|
|
356
|
-
piPortfolios: !!details.piPortfolios,
|
|
357
|
-
piDeepPortfolios: !!details.piDeepPortfolios,
|
|
358
|
-
piHistory: !!details.piHistory,
|
|
359
|
-
signedInUserPortfolio: !!details.signedInUserPortfolio,
|
|
360
|
-
signedInUserHistory: !!details.signedInUserHistory,
|
|
361
|
-
signedInUserVerification: !!details.signedInUserVerification,
|
|
362
|
-
hasPISocial: !!details.hasPISocial || !!data.hasPISocial,
|
|
363
|
-
hasSignedInSocial: !!details.hasSignedInSocial || !!data.hasSignedInSocial,
|
|
364
|
-
piRatings: !!details.piRatings,
|
|
365
|
-
piPageViews: !!details.piPageViews,
|
|
366
|
-
watchlistMembership: !!details.watchlistMembership,
|
|
367
|
-
piAlertHistory: !!details.piAlertHistory,
|
|
368
|
-
piMasterList: !!details.piMasterList
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
availabilityMap.set(dateStr, status);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
return availabilityMap;
|
|
218
|
+
const map = new Map();
|
|
219
|
+
snapshot.forEach(doc => map.set(doc.id, normalizeStatus(doc.data())));
|
|
220
|
+
return map;
|
|
375
221
|
}
|
|
376
222
|
|
|
377
223
|
module.exports = {
|
|
@@ -379,4 +225,4 @@ module.exports = {
|
|
|
379
225
|
checkRootDataAvailability,
|
|
380
226
|
getViableCalculations,
|
|
381
227
|
getAvailabilityWindow
|
|
382
|
-
};
|
|
228
|
+
};
|