bulltrackers-module 1.0.403 → 1.0.405
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Factory for creating the Computation Context.
|
|
3
3
|
* UPDATED: Injects verification and rankings data into context globally and locally.
|
|
4
|
+
* UPDATED: Added support for historical ranking data in both Standard and Meta contexts.
|
|
4
5
|
*/
|
|
5
6
|
const mathLayer = require('../layers/index');
|
|
6
7
|
const { LEGACY_MAPPING } = require('../topology/HashManager');
|
|
@@ -21,8 +22,10 @@ class ContextFactory {
|
|
|
21
22
|
todayPortfolio, yesterdayPortfolio, todayHistory, yesterdayHistory,
|
|
22
23
|
userId, userType, dateStr, metadata, mappings, insights, socialData,
|
|
23
24
|
computedDependencies, previousComputedDependencies, config, deps,
|
|
24
|
-
verification,
|
|
25
|
-
|
|
25
|
+
verification,
|
|
26
|
+
rankings, yesterdayRankings, // User-specific rank entries
|
|
27
|
+
allRankings, allRankingsYesterday, // Global rank lists
|
|
28
|
+
allVerifications
|
|
26
29
|
} = options;
|
|
27
30
|
|
|
28
31
|
return {
|
|
@@ -32,7 +35,8 @@ class ContextFactory {
|
|
|
32
35
|
portfolio: { today: todayPortfolio, yesterday: yesterdayPortfolio },
|
|
33
36
|
history: { today: todayHistory, yesterday: yesterdayHistory },
|
|
34
37
|
verification: verification || null,
|
|
35
|
-
rankEntry: rankings || null
|
|
38
|
+
rankEntry: rankings || null,
|
|
39
|
+
rankEntryYesterday: yesterdayRankings || null
|
|
36
40
|
},
|
|
37
41
|
date: { today: dateStr },
|
|
38
42
|
insights: { today: insights?.today, yesterday: insights?.yesterday },
|
|
@@ -42,9 +46,9 @@ class ContextFactory {
|
|
|
42
46
|
computed: computedDependencies || {},
|
|
43
47
|
previousComputed: previousComputedDependencies || {},
|
|
44
48
|
meta: metadata, config, deps,
|
|
45
|
-
// [NEW] Global Data Injection for Comparative Logic (Similarity, Ranking position, etc.)
|
|
46
49
|
globalData: {
|
|
47
50
|
rankings: allRankings || [],
|
|
51
|
+
rankingsYesterday: allRankingsYesterday || [],
|
|
48
52
|
verifications: allVerifications || {}
|
|
49
53
|
}
|
|
50
54
|
};
|
|
@@ -54,7 +58,8 @@ class ContextFactory {
|
|
|
54
58
|
const {
|
|
55
59
|
dateStr, metadata, mappings, insights, socialData, prices,
|
|
56
60
|
computedDependencies, previousComputedDependencies, config, deps,
|
|
57
|
-
allRankings,
|
|
61
|
+
allRankings, allRankingsYesterday, // [UPDATED] Accepted here
|
|
62
|
+
allVerifications
|
|
58
63
|
} = options;
|
|
59
64
|
|
|
60
65
|
return {
|
|
@@ -69,6 +74,7 @@ class ContextFactory {
|
|
|
69
74
|
meta: metadata, config, deps,
|
|
70
75
|
globalData: {
|
|
71
76
|
rankings: allRankings || [],
|
|
77
|
+
rankingsYesterday: allRankingsYesterday || [], // [UPDATED] Injected here
|
|
72
78
|
verifications: allVerifications || {}
|
|
73
79
|
}
|
|
74
80
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* UPDATED: Uses CachedDataLoader for all data access.
|
|
4
4
|
* UPDATED: Tracks processed shard/item counts.
|
|
5
5
|
* UPDATED: Sends 'isInitialWrite: true' for robust cleanup.
|
|
6
|
+
* UPDATED: Support for historical rankings in Meta Context.
|
|
6
7
|
*/
|
|
7
8
|
const { normalizeName } = require('../utils/utils');
|
|
8
9
|
const { CachedDataLoader } = require('../data/CachedDataLoader');
|
|
@@ -13,9 +14,20 @@ class MetaExecutor {
|
|
|
13
14
|
static async run(date, calcs, passName, config, deps, rootData, fetchedDeps, previousFetchedDeps) {
|
|
14
15
|
const dStr = date.toISOString().slice(0, 10);
|
|
15
16
|
const { logger, db } = deps;
|
|
16
|
-
const { CachedDataLoader } = require('../data/CachedDataLoader');
|
|
17
17
|
const loader = new CachedDataLoader(config, deps);
|
|
18
18
|
|
|
19
|
+
// [FIX] Check if any meta calculation needs history
|
|
20
|
+
const needsHistory = calcs.some(c => c.isHistorical);
|
|
21
|
+
let rankingsYesterday = null;
|
|
22
|
+
|
|
23
|
+
if (needsHistory) {
|
|
24
|
+
const prevDate = new Date(date);
|
|
25
|
+
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
26
|
+
const prevStr = prevDate.toISOString().slice(0, 10);
|
|
27
|
+
logger.log('INFO', `[MetaExecutor] Loading historical rankings for ${prevStr}`);
|
|
28
|
+
rankingsYesterday = await loader.loadRankings(prevStr);
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
// 1. Load Global Dependencies
|
|
20
32
|
const [mappings, rankings, verifications] = await Promise.all([
|
|
21
33
|
loader.loadMappings(),
|
|
@@ -38,11 +50,14 @@ class MetaExecutor {
|
|
|
38
50
|
previousComputedDependencies: previousFetchedDeps,
|
|
39
51
|
config, deps,
|
|
40
52
|
allRankings: rankings,
|
|
53
|
+
allRankingsYesterday: rankingsYesterday, // [FIX] Injected
|
|
41
54
|
allVerifications: verifications
|
|
42
55
|
});
|
|
43
56
|
|
|
44
57
|
try {
|
|
45
58
|
const result = await inst.process(context);
|
|
59
|
+
// Meta results are usually wrapped in a global key or just the result object
|
|
60
|
+
// The structure below implies we store it under the date key
|
|
46
61
|
inst.results = { [dStr]: { global: result } };
|
|
47
62
|
state[c.name] = inst;
|
|
48
63
|
} catch (e) {
|
|
@@ -61,6 +76,18 @@ class MetaExecutor {
|
|
|
61
76
|
const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await loader.loadInsights(dateStr) } : null;
|
|
62
77
|
const social = metadata.rootDataDependencies?.includes('social') ? { today: await loader.loadSocial(dateStr) } : null;
|
|
63
78
|
|
|
79
|
+
// [FIX] Historical support for Batch/OncePerDay execution
|
|
80
|
+
let rankingsYesterday = null;
|
|
81
|
+
if (metadata.isHistorical) {
|
|
82
|
+
const prevDate = new Date(dateStr);
|
|
83
|
+
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
84
|
+
const prevStr = prevDate.toISOString().slice(0, 10);
|
|
85
|
+
rankingsYesterday = await loader.loadRankings(prevStr);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Load current rankings (often needed for ContextFactory.buildMetaContext)
|
|
89
|
+
const rankings = await loader.loadRankings(dateStr);
|
|
90
|
+
|
|
64
91
|
if (metadata.rootDataDependencies?.includes('price')) {
|
|
65
92
|
logger.log('INFO', `[Executor] Running Batched/Sharded Execution for ${metadata.name}`);
|
|
66
93
|
const shardRefs = await loader.getPriceShardReferences();
|
|
@@ -72,7 +99,9 @@ class MetaExecutor {
|
|
|
72
99
|
const partialContext = ContextFactory.buildMetaContext({
|
|
73
100
|
dateStr, metadata, mappings, insights, socialData: social,
|
|
74
101
|
prices: { history: shardData }, computedDependencies: computedDeps,
|
|
75
|
-
previousComputedDependencies: prevDeps, config, deps
|
|
102
|
+
previousComputedDependencies: prevDeps, config, deps,
|
|
103
|
+
allRankings: rankings,
|
|
104
|
+
allRankingsYesterday: rankingsYesterday
|
|
76
105
|
});
|
|
77
106
|
|
|
78
107
|
await calcInstance.process(partialContext);
|
|
@@ -90,7 +119,9 @@ class MetaExecutor {
|
|
|
90
119
|
const context = ContextFactory.buildMetaContext({
|
|
91
120
|
dateStr, metadata, mappings, insights, socialData: social,
|
|
92
121
|
prices: {}, computedDependencies: computedDeps,
|
|
93
|
-
previousComputedDependencies: prevDeps, config, deps
|
|
122
|
+
previousComputedDependencies: prevDeps, config, deps,
|
|
123
|
+
allRankings: rankings,
|
|
124
|
+
allRankingsYesterday: rankingsYesterday
|
|
94
125
|
});
|
|
95
126
|
const res = await calcInstance.process(context);
|
|
96
127
|
|
|
@@ -102,4 +133,4 @@ class MetaExecutor {
|
|
|
102
133
|
}
|
|
103
134
|
}
|
|
104
135
|
|
|
105
|
-
module.exports = { MetaExecutor };
|
|
136
|
+
module.exports = { MetaExecutor };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* {
|
|
3
3
|
* type: uploaded file
|
|
4
|
-
* fileName:
|
|
4
|
+
* fileName: computation-system/executors/StandardExecutor.js
|
|
5
5
|
* }
|
|
6
6
|
*/
|
|
7
7
|
const { normalizeName, getEarliestDataDates } = require('../utils/utils');
|
|
@@ -141,6 +141,7 @@ class StandardExecutor {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
static async flushBuffer(state, dateStr, passName, config, deps, shardIndexMap, executionStats, mode, skipStatusWrite, isInitialWrite = false) {
|
|
144
|
+
// ... (No changes to flushBuffer)
|
|
144
145
|
const transformedState = {};
|
|
145
146
|
for (const [name, inst] of Object.entries(state)) {
|
|
146
147
|
const rawResult = inst.results || {};
|
|
@@ -178,6 +179,7 @@ class StandardExecutor {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
static mergeReports(successAcc, failureAcc, newResult) {
|
|
182
|
+
// ... (No changes to mergeReports)
|
|
181
183
|
if (!newResult) return;
|
|
182
184
|
for (const [name, update] of Object.entries(newResult.successUpdates)) {
|
|
183
185
|
if (!successAcc[name]) {
|
|
@@ -218,8 +220,15 @@ class StandardExecutor {
|
|
|
218
220
|
const verifications = metadata.rootDataDependencies?.includes('verification') ? await loader.loadVerifications() : null;
|
|
219
221
|
const rankings = metadata.rootDataDependencies?.includes('rankings') ? await loader.loadRankings(dateStr) : null;
|
|
220
222
|
|
|
221
|
-
// [
|
|
222
|
-
|
|
223
|
+
// [FIX] Load Yesterday's Rankings if isHistorical is true
|
|
224
|
+
let yesterdayRankings = null;
|
|
225
|
+
if (metadata.rootDataDependencies?.includes('rankings') && metadata.isHistorical) {
|
|
226
|
+
const prevDate = new Date(dateStr); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
227
|
+
const prevStr = prevDate.toISOString().slice(0, 10);
|
|
228
|
+
// Assuming CachedDataLoader handles caching for efficiency
|
|
229
|
+
yesterdayRankings = await loader.loadRankings(prevStr);
|
|
230
|
+
}
|
|
231
|
+
|
|
223
232
|
const socialContainer = metadata.rootDataDependencies?.includes('social') ? await loader.loadSocial(dateStr) : null;
|
|
224
233
|
|
|
225
234
|
let chunkSuccess = 0;
|
|
@@ -229,7 +238,6 @@ class StandardExecutor {
|
|
|
229
238
|
const yesterdayPortfolio = yesterdayPortfolioData ? yesterdayPortfolioData[userId] : null;
|
|
230
239
|
const todayHistory = historyData ? historyData[userId] : null;
|
|
231
240
|
|
|
232
|
-
// 2. Identify User Type
|
|
233
241
|
let actualUserType = todayPortfolio._userType;
|
|
234
242
|
if (!actualUserType) {
|
|
235
243
|
if (todayPortfolio.PublicPositions) {
|
|
@@ -240,7 +248,6 @@ class StandardExecutor {
|
|
|
240
248
|
}
|
|
241
249
|
}
|
|
242
250
|
|
|
243
|
-
// 3. Strict User Type Filtering
|
|
244
251
|
if (targetUserType && targetUserType !== 'all') {
|
|
245
252
|
if (targetUserType !== actualUserType) {
|
|
246
253
|
if (stats) stats.skippedUsers++;
|
|
@@ -248,22 +255,19 @@ class StandardExecutor {
|
|
|
248
255
|
}
|
|
249
256
|
}
|
|
250
257
|
|
|
251
|
-
// 4. Resolve Contextual Data
|
|
252
258
|
const userVerification = verifications ? verifications[userId] : null;
|
|
259
|
+
|
|
260
|
+
// [FIX] Extract current AND yesterday's rank entry for this user
|
|
253
261
|
const userRanking = rankings ? (rankings.find(r => String(r.CustomerId) === String(userId)) || null) : null;
|
|
262
|
+
const userRankingYesterday = yesterdayRankings ? (yesterdayRankings.find(r => String(r.CustomerId) === String(userId)) || null) : null;
|
|
254
263
|
|
|
255
|
-
// [CRITICAL FIX] Select Specific Social Data to Prevent Skew
|
|
256
264
|
let effectiveSocialData = null;
|
|
257
265
|
if (socialContainer) {
|
|
258
266
|
if (actualUserType === 'POPULAR_INVESTOR') {
|
|
259
|
-
// PI Route: Only gets PI-specific posts for this user
|
|
260
|
-
// (PIs do not need generic noise usually, or you can merge it if required)
|
|
261
267
|
effectiveSocialData = socialContainer.pi[userId] || {};
|
|
262
268
|
} else if (actualUserType === 'SIGNED_IN_USER') {
|
|
263
|
-
// Signed-In Route: Only gets Signed-In specific posts for this user
|
|
264
269
|
effectiveSocialData = socialContainer.signedIn[userId] || {};
|
|
265
270
|
} else {
|
|
266
|
-
// Normal/Speculator/All Route: Gets Generic Market Posts
|
|
267
271
|
effectiveSocialData = socialContainer.generic || {};
|
|
268
272
|
}
|
|
269
273
|
}
|
|
@@ -271,15 +275,19 @@ class StandardExecutor {
|
|
|
271
275
|
const context = ContextFactory.buildPerUserContext({
|
|
272
276
|
todayPortfolio, yesterdayPortfolio, todayHistory, userId,
|
|
273
277
|
userType: actualUserType, dateStr, metadata, mappings, insights,
|
|
274
|
-
|
|
275
|
-
// Inject the filtered subset
|
|
276
278
|
socialData: effectiveSocialData ? { today: effectiveSocialData } : null,
|
|
277
|
-
|
|
278
279
|
computedDependencies: computedDeps, previousComputedDependencies: prevDeps,
|
|
279
280
|
config, deps,
|
|
280
281
|
verification: userVerification,
|
|
282
|
+
|
|
283
|
+
// [FIX] Pass both ranking entries
|
|
281
284
|
rankings: userRanking,
|
|
285
|
+
yesterdayRankings: userRankingYesterday,
|
|
286
|
+
|
|
287
|
+
// [FIX] Pass both global lists
|
|
282
288
|
allRankings: rankings,
|
|
289
|
+
allRankingsYesterday: yesterdayRankings,
|
|
290
|
+
|
|
283
291
|
allVerifications: verifications
|
|
284
292
|
});
|
|
285
293
|
|
|
@@ -10,8 +10,53 @@ const { FieldValue } = require('@google-cloud/firestore');
|
|
|
10
10
|
const pLimit = require('p-limit');
|
|
11
11
|
|
|
12
12
|
const CANARY_BLOCK_ID = '19M';
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const PRICE_SHARD_ID = 'shard_0';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Helper function to check if any part document exists in a parts collection
|
|
17
|
+
* @param {Firestore.CollectionReference} partsCollectionRef - Reference to the parts collection
|
|
18
|
+
* @returns {Promise<boolean>} - True if any part_* document exists
|
|
19
|
+
*/
|
|
20
|
+
async function checkAnyPartExists(partsCollectionRef) {
|
|
21
|
+
try {
|
|
22
|
+
// List all documents in the parts collection
|
|
23
|
+
const snapshot = await partsCollectionRef.limit(10).get();
|
|
24
|
+
if (snapshot.empty) return false;
|
|
25
|
+
|
|
26
|
+
// Check if any document ID starts with 'part_'
|
|
27
|
+
for (const doc of snapshot.docs) {
|
|
28
|
+
if (doc.id.startsWith('part_')) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Helper function to check if any shard document exists in a collection
|
|
40
|
+
* @param {Firestore.CollectionReference} collectionRef - Reference to the collection
|
|
41
|
+
* @returns {Promise<boolean>} - True if any shard_* document exists
|
|
42
|
+
*/
|
|
43
|
+
async function checkAnyShardExists(collectionRef) {
|
|
44
|
+
try {
|
|
45
|
+
// List all documents in the collection
|
|
46
|
+
const snapshot = await collectionRef.limit(10).get();
|
|
47
|
+
if (snapshot.empty) return false;
|
|
48
|
+
|
|
49
|
+
// Check if any document ID starts with 'shard_'
|
|
50
|
+
for (const doc of snapshot.docs) {
|
|
51
|
+
if (doc.id.startsWith('shard_')) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
15
60
|
|
|
16
61
|
exports.runRootDataIndexer = async (config, dependencies) => {
|
|
17
62
|
const { db, logger } = dependencies;
|
|
@@ -30,23 +75,33 @@ exports.runRootDataIndexer = async (config, dependencies) => {
|
|
|
30
75
|
logger.log('INFO', '[RootDataIndexer] Starting Root Data Availability Scan...');
|
|
31
76
|
|
|
32
77
|
// 1. Price Availability
|
|
78
|
+
// Sample up to 10 shards to extract date keys (efficient - doesn't read all shards)
|
|
33
79
|
const priceAvailabilitySet = new Set();
|
|
34
80
|
try {
|
|
35
|
-
// Path: asset_prices/
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
81
|
+
// Path: asset_prices/shard_*
|
|
82
|
+
const priceCollectionRef = db.collection(PRICE_COLLECTION_NAME);
|
|
83
|
+
const priceShardsSnapshot = await priceCollectionRef.limit(10).get();
|
|
84
|
+
|
|
85
|
+
if (!priceShardsSnapshot.empty) {
|
|
86
|
+
// Sample up to 10 shards and extract date keys from them
|
|
87
|
+
// This gives us a representative sample without reading hundreds of shards
|
|
88
|
+
for (const shardDoc of priceShardsSnapshot.docs) {
|
|
89
|
+
if (shardDoc.id.startsWith('shard_')) {
|
|
90
|
+
const data = shardDoc.data();
|
|
91
|
+
Object.values(data).forEach(instrument => {
|
|
92
|
+
if (instrument && instrument.prices) {
|
|
93
|
+
Object.keys(instrument.prices).forEach(dateKey => {
|
|
94
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) {
|
|
95
|
+
priceAvailabilitySet.add(dateKey);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
47
102
|
}
|
|
48
103
|
} catch (e) {
|
|
49
|
-
logger.log('ERROR', '[RootDataIndexer] Failed to
|
|
104
|
+
logger.log('ERROR', '[RootDataIndexer] Failed to sample price shards.', { error: e.message });
|
|
50
105
|
}
|
|
51
106
|
|
|
52
107
|
// 2. Determine Date Range
|
|
@@ -96,17 +151,17 @@ exports.runRootDataIndexer = async (config, dependencies) => {
|
|
|
96
151
|
// --- Define Refs & Check Paths ---
|
|
97
152
|
|
|
98
153
|
// 1. Standard Retail
|
|
99
|
-
// Path: {normalPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
100
|
-
const
|
|
154
|
+
// Path: {normalPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
155
|
+
const normPortPartsRef = db.collection(collections.normalPortfolios).doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
101
156
|
|
|
102
|
-
// Path: {speculatorPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
103
|
-
const
|
|
157
|
+
// Path: {speculatorPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
158
|
+
const specPortPartsRef = db.collection(collections.speculatorPortfolios).doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
104
159
|
|
|
105
|
-
// Path: {normalHistory}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
106
|
-
const
|
|
160
|
+
// Path: {normalHistory}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
161
|
+
const normHistPartsRef = db.collection(collections.normalHistory).doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
107
162
|
|
|
108
|
-
// Path: {speculatorHistory}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
109
|
-
const
|
|
163
|
+
// Path: {speculatorHistory}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
164
|
+
const specHistPartsRef = db.collection(collections.speculatorHistory).doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
110
165
|
|
|
111
166
|
// Path: {insights}/{YYYY-MM-DD}
|
|
112
167
|
const insightsRef = db.collection(collections.insights).doc(dateStr);
|
|
@@ -119,26 +174,26 @@ exports.runRootDataIndexer = async (config, dependencies) => {
|
|
|
119
174
|
// Path: {piRankings}/{YYYY-MM-DD}
|
|
120
175
|
const piRankingsRef = db.collection(collections.piRankings || 'popular_investor_rankings').doc(dateStr);
|
|
121
176
|
|
|
122
|
-
// Path: {piPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
123
|
-
const
|
|
124
|
-
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts')
|
|
177
|
+
// Path: {piPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
178
|
+
const piPortfoliosPartsRef = db.collection(collections.piPortfolios || 'pi_portfolios_overall')
|
|
179
|
+
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
125
180
|
|
|
126
|
-
// Path: {piDeepPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
127
|
-
const
|
|
128
|
-
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts')
|
|
181
|
+
// Path: {piDeepPortfolios}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
182
|
+
const piDeepPartsRef = db.collection(collections.piDeepPortfolios || 'pi_portfolios_deep')
|
|
183
|
+
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
129
184
|
|
|
130
|
-
// Path: {piHistory}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
131
|
-
const
|
|
132
|
-
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts')
|
|
185
|
+
// Path: {piHistory}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
186
|
+
const piHistoryPartsRef = db.collection(collections.piHistory || 'pi_trade_history')
|
|
187
|
+
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
133
188
|
|
|
134
189
|
// 3. Signed-In Users
|
|
135
|
-
// Path: {signedInUsers}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
136
|
-
const
|
|
137
|
-
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts')
|
|
190
|
+
// Path: {signedInUsers}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
191
|
+
const signedInPortPartsRef = db.collection(collections.signedInUsers || 'signed_in_users')
|
|
192
|
+
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
138
193
|
|
|
139
|
-
// Path: {signedInHistory}/19M/snapshots/{YYYY-MM-DD}/parts/
|
|
140
|
-
const
|
|
141
|
-
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts')
|
|
194
|
+
// Path: {signedInHistory}/19M/snapshots/{YYYY-MM-DD}/parts/part_*
|
|
195
|
+
const signedInHistPartsRef = db.collection(collections.signedInHistory || 'signed_in_user_history')
|
|
196
|
+
.doc(CANARY_BLOCK_ID).collection('snapshots').doc(dateStr).collection('parts');
|
|
142
197
|
|
|
143
198
|
// Path: {verifications} (Limit 1) - Checks if collection is non-empty generally
|
|
144
199
|
const verificationsRef = db.collection(collections.verifications || 'user_verifications');
|
|
@@ -152,26 +207,29 @@ exports.runRootDataIndexer = async (config, dependencies) => {
|
|
|
152
207
|
|
|
153
208
|
// --- Execute Checks ---
|
|
154
209
|
const [
|
|
155
|
-
|
|
156
|
-
|
|
210
|
+
normPortExists, specPortExists,
|
|
211
|
+
normHistExists, specHistExists,
|
|
157
212
|
insightsSnap, socialQuerySnap,
|
|
158
213
|
piRankingsSnap,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
214
|
+
piPortExists, // UPDATED: Check for any part_* document
|
|
215
|
+
piDeepExists, // UPDATED: Check for any part_* document
|
|
216
|
+
piHistExists, // UPDATED: Check for any part_* document
|
|
217
|
+
signedInPortExists, signedInHistExists,
|
|
163
218
|
verificationsQuery,
|
|
164
219
|
universalSocialSnap
|
|
165
220
|
] = await Promise.all([
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
221
|
+
checkAnyPartExists(normPortPartsRef),
|
|
222
|
+
checkAnyPartExists(specPortPartsRef),
|
|
223
|
+
checkAnyPartExists(normHistPartsRef),
|
|
224
|
+
checkAnyPartExists(specHistPartsRef),
|
|
225
|
+
insightsRef.get(),
|
|
226
|
+
socialPostsRef.limit(1).get(),
|
|
169
227
|
piRankingsRef.get(),
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
228
|
+
checkAnyPartExists(piPortfoliosPartsRef),
|
|
229
|
+
checkAnyPartExists(piDeepPartsRef),
|
|
230
|
+
checkAnyPartExists(piHistoryPartsRef),
|
|
231
|
+
checkAnyPartExists(signedInPortPartsRef),
|
|
232
|
+
checkAnyPartExists(signedInHistPartsRef),
|
|
175
233
|
verificationsRef.limit(1).get(),
|
|
176
234
|
universalSocialQuery.get()
|
|
177
235
|
]);
|
|
@@ -189,16 +247,16 @@ exports.runRootDataIndexer = async (config, dependencies) => {
|
|
|
189
247
|
}
|
|
190
248
|
|
|
191
249
|
// --- Assign to Availability ---
|
|
192
|
-
availability.details.normalPortfolio =
|
|
193
|
-
availability.details.speculatorPortfolio =
|
|
194
|
-
availability.details.normalHistory =
|
|
195
|
-
availability.details.speculatorHistory =
|
|
250
|
+
availability.details.normalPortfolio = normPortExists;
|
|
251
|
+
availability.details.speculatorPortfolio = specPortExists;
|
|
252
|
+
availability.details.normalHistory = normHistExists;
|
|
253
|
+
availability.details.speculatorHistory = specHistExists;
|
|
196
254
|
availability.details.piRankings = piRankingsSnap.exists;
|
|
197
255
|
|
|
198
|
-
// UPDATED: Now checking
|
|
199
|
-
availability.details.piPortfolios =
|
|
200
|
-
availability.details.piDeepPortfolios =
|
|
201
|
-
availability.details.piHistory =
|
|
256
|
+
// UPDATED: Now checking for any part_* document existence
|
|
257
|
+
availability.details.piPortfolios = piPortExists;
|
|
258
|
+
availability.details.piDeepPortfolios = piDeepExists;
|
|
259
|
+
availability.details.piHistory = piHistExists;
|
|
202
260
|
|
|
203
261
|
// PI & Signed-In Social Flags (Strict)
|
|
204
262
|
availability.details.piSocial = foundPISocial;
|
|
@@ -207,14 +265,14 @@ exports.runRootDataIndexer = async (config, dependencies) => {
|
|
|
207
265
|
availability.details.hasSignedInSocial = foundSignedInSocial;
|
|
208
266
|
|
|
209
267
|
// Signed-In Flags
|
|
210
|
-
availability.details.signedInUserPortfolio =
|
|
211
|
-
availability.details.signedInUserHistory =
|
|
268
|
+
availability.details.signedInUserPortfolio = signedInPortExists;
|
|
269
|
+
availability.details.signedInUserHistory = signedInHistExists;
|
|
212
270
|
availability.details.signedInUserVerification = !verificationsQuery.empty;
|
|
213
271
|
|
|
214
272
|
// Aggregates
|
|
215
|
-
// UPDATED:
|
|
216
|
-
availability.hasPortfolio =
|
|
217
|
-
availability.hasHistory =
|
|
273
|
+
// UPDATED: Using part existence checks
|
|
274
|
+
availability.hasPortfolio = normPortExists || specPortExists || piPortExists || signedInPortExists;
|
|
275
|
+
availability.hasHistory = normHistExists || specHistExists || piHistExists || signedInHistExists;
|
|
218
276
|
availability.hasInsights = insightsSnap.exists;
|
|
219
277
|
|
|
220
278
|
// [CRITICAL FIX]
|