bulltrackers-module 1.0.184 → 1.0.185
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.
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
checkRootDependencies
|
|
14
14
|
} = require('./orchestration_helpers.js');
|
|
15
15
|
|
|
16
|
-
const { getExpectedDateStrings, normalizeName } = require('../utils/utils.js');
|
|
16
|
+
const { getExpectedDateStrings, normalizeName, getEarliestDataDates } = require('../utils/utils.js');
|
|
17
17
|
|
|
18
18
|
const PARALLEL_BATCH_SIZE = 7;
|
|
19
19
|
|
|
@@ -25,8 +25,7 @@ async function runComputationPass(config, dependencies, computationManifest) {
|
|
|
25
25
|
|
|
26
26
|
logger.log('INFO', `🚀 Starting PASS ${passToRun} (Targeting /computation_status/{YYYY-MM-DD})...`);
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
const earliestDates = { portfolio: new Date('2025-09-25T00:00:00Z'), history: new Date('2025-11-05T00:00:00Z'), social: new Date('2025-10-30T00:00:00Z'), insights: new Date('2025-08-26T00:00:00Z') };
|
|
28
|
+
const earliestDates = await getEarliestDataDates(config, dependencies); // Now not hardcoded.
|
|
30
29
|
earliestDates.absoluteEarliest = Object.values(earliestDates).reduce((a,b) => a < b ? a : b);
|
|
31
30
|
|
|
32
31
|
const passes = groupByPass(computationManifest);
|
|
@@ -28,6 +28,7 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
28
28
|
else if (dep === 'insights' && !rootDataStatus.hasInsights) missing.push('insights');
|
|
29
29
|
else if (dep === 'social' && !rootDataStatus.hasSocial) missing.push('social');
|
|
30
30
|
else if (dep === 'history' && !rootDataStatus.hasHistory) missing.push('history');
|
|
31
|
+
else if (dep === 'price' && !rootDataStatus.hasPrices) missing.push('price'); // NEW
|
|
31
32
|
}
|
|
32
33
|
return { canRun: missing.length === 0, missing };
|
|
33
34
|
}
|
|
@@ -36,26 +37,34 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
36
37
|
* Checks for the availability of all required root data for a specific date.
|
|
37
38
|
*/
|
|
38
39
|
async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
|
|
39
|
-
const { logger }
|
|
40
|
+
const { logger, db } = dependencies;
|
|
40
41
|
const dateToProcess = new Date(dateStr + 'T00:00:00Z');
|
|
41
|
-
let portfolioRefs
|
|
42
|
-
let hasPortfolio
|
|
42
|
+
let portfolioRefs = [], historyRefs = [];
|
|
43
|
+
let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false, hasPrices = false;
|
|
44
|
+
let insightsData = null, socialData = null;
|
|
43
45
|
|
|
44
46
|
try {
|
|
45
47
|
const tasks = [];
|
|
46
|
-
if (dateToProcess >= earliestDates.portfolio)
|
|
47
|
-
if (dateToProcess >= earliestDates.insights)
|
|
48
|
-
if (dateToProcess >= earliestDates.social)
|
|
49
|
-
if (dateToProcess >= earliestDates.history)
|
|
48
|
+
if (dateToProcess >= earliestDates.portfolio) tasks.push(getPortfolioPartRefs(config, dependencies, dateStr).then(r => { portfolioRefs = r; hasPortfolio = !!r.length; }));
|
|
49
|
+
if (dateToProcess >= earliestDates.insights) tasks.push(loadDailyInsights(config, dependencies, dateStr).then(r => { insightsData = r; hasInsights = !!r; }));
|
|
50
|
+
if (dateToProcess >= earliestDates.social) tasks.push(loadDailySocialPostInsights(config, dependencies, dateStr).then(r => { socialData = r; hasSocial = !!r; }));
|
|
51
|
+
if (dateToProcess >= earliestDates.history) tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(r => { historyRefs = r; hasHistory = !!r.length; }));
|
|
52
|
+
|
|
53
|
+
// NEW: Check if price data exists (proper validation)
|
|
54
|
+
if (dateToProcess >= (earliestDates.price || earliestDates.absoluteEarliest)) {
|
|
55
|
+
tasks.push(checkPriceDataAvailability(config, dependencies).then(r => { hasPrices = r; }));
|
|
56
|
+
}
|
|
50
57
|
|
|
51
58
|
await Promise.all(tasks);
|
|
52
59
|
|
|
53
|
-
if (!(hasPortfolio || hasInsights || hasSocial || hasHistory)) return null;
|
|
60
|
+
if (!(hasPortfolio || hasInsights || hasSocial || hasHistory || hasPrices)) return null;
|
|
54
61
|
|
|
55
62
|
return {
|
|
56
|
-
portfolioRefs,
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
portfolioRefs,
|
|
64
|
+
historyRefs,
|
|
65
|
+
todayInsights: insightsData,
|
|
66
|
+
todaySocialPostInsights: socialData,
|
|
67
|
+
status: { hasPortfolio, hasInsights, hasSocial, hasHistory, hasPrices }
|
|
59
68
|
};
|
|
60
69
|
|
|
61
70
|
} catch (err) {
|
|
@@ -64,6 +73,40 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* NEW HELPER: Check if price data collection has any data
|
|
78
|
+
*/
|
|
79
|
+
async function checkPriceDataAvailability(config, dependencies) {
|
|
80
|
+
const { db, logger } = dependencies;
|
|
81
|
+
const collection = config.priceCollection || 'asset_prices';
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Check if the collection has at least one shard document
|
|
85
|
+
const snapshot = await db.collection(collection).limit(1).get();
|
|
86
|
+
|
|
87
|
+
if (snapshot.empty) {
|
|
88
|
+
logger.log('WARN', `[checkPriceData] No price shards found in ${collection}`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check if the first shard actually has price data
|
|
93
|
+
const firstDoc = snapshot.docs[0];
|
|
94
|
+
const data = firstDoc.data();
|
|
95
|
+
|
|
96
|
+
if (!data || Object.keys(data).length === 0) {
|
|
97
|
+
logger.log('WARN', `[checkPriceData] Price shard exists but is empty`);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logger.log('TRACE', `[checkPriceData] Price data available in ${collection}`);
|
|
102
|
+
return true;
|
|
103
|
+
|
|
104
|
+
} catch (e) {
|
|
105
|
+
logger.log('ERROR', `[checkPriceData] Failed to check price availability: ${e.message}`);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
67
110
|
async function fetchComputationStatus(dateStr, config, { db }) {
|
|
68
111
|
const collection = config.computationStatusCollection || 'computation_status';
|
|
69
112
|
const docRef = db.collection(collection).doc(dateStr);
|
|
@@ -75,25 +75,125 @@ async function getFirstDateFromCollection(config, deps, collectionName) {
|
|
|
75
75
|
async function getEarliestDataDates(config, deps) {
|
|
76
76
|
const { logger } = deps;
|
|
77
77
|
logger.log('INFO', 'Querying for earliest date from ALL source data collections...');
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
|
|
79
|
+
const [
|
|
80
|
+
investorDate,
|
|
81
|
+
speculatorDate,
|
|
82
|
+
investorHistoryDate,
|
|
83
|
+
speculatorHistoryDate,
|
|
84
|
+
insightsDate,
|
|
85
|
+
socialDate,
|
|
86
|
+
priceDate // NEW
|
|
87
|
+
] = await Promise.all([
|
|
88
|
+
getFirstDateFromCollection(config, deps, config.normalUserPortfolioCollection),
|
|
89
|
+
getFirstDateFromCollection(config, deps, config.speculatorPortfolioCollection),
|
|
90
|
+
getFirstDateFromCollection(config, deps, config.normalUserHistoryCollection),
|
|
91
|
+
getFirstDateFromCollection(config, deps, config.speculatorHistoryCollection),
|
|
83
92
|
getFirstDateFromSimpleCollection(config, deps, config.insightsCollectionName),
|
|
84
|
-
getFirstDateFromSimpleCollection(config, deps, config.socialInsightsCollectionName)
|
|
93
|
+
getFirstDateFromSimpleCollection(config, deps, config.socialInsightsCollectionName),
|
|
94
|
+
getFirstDateFromPriceCollection(config, deps) // NEW
|
|
85
95
|
]);
|
|
86
96
|
|
|
87
|
-
const getMinDate
|
|
97
|
+
const getMinDate = (...dates) => {
|
|
98
|
+
const validDates = dates.filter(Boolean);
|
|
99
|
+
if (validDates.length === 0) return null;
|
|
100
|
+
return new Date(Math.min(...validDates));
|
|
101
|
+
};
|
|
102
|
+
|
|
88
103
|
const earliestPortfolioDate = getMinDate(investorDate, speculatorDate);
|
|
89
|
-
const earliestHistoryDate
|
|
90
|
-
const earliestInsightsDate
|
|
91
|
-
const earliestSocialDate
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
const earliestHistoryDate = getMinDate(investorHistoryDate, speculatorHistoryDate);
|
|
105
|
+
const earliestInsightsDate = getMinDate(insightsDate);
|
|
106
|
+
const earliestSocialDate = getMinDate(socialDate);
|
|
107
|
+
const earliestPriceDate = getMinDate(priceDate); // NEW
|
|
108
|
+
const absoluteEarliest = getMinDate(
|
|
109
|
+
earliestPortfolioDate,
|
|
110
|
+
earliestHistoryDate,
|
|
111
|
+
earliestInsightsDate,
|
|
112
|
+
earliestSocialDate,
|
|
113
|
+
earliestPriceDate // NEW
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const fallbackDate = new Date(config.earliestComputationDate + 'T00:00:00Z' || '2023-01-01T00:00:00Z');
|
|
117
|
+
|
|
118
|
+
const result = {
|
|
119
|
+
portfolio: earliestPortfolioDate || new Date('2999-12-31'),
|
|
120
|
+
history: earliestHistoryDate || new Date('2999-12-31'),
|
|
121
|
+
insights: earliestInsightsDate || new Date('2999-12-31'),
|
|
122
|
+
social: earliestSocialDate || new Date('2999-12-31'),
|
|
123
|
+
price: earliestPriceDate || new Date('2999-12-31'), // NEW
|
|
124
|
+
absoluteEarliest: absoluteEarliest || fallbackDate
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
logger.log('INFO', 'Earliest data availability map built:', {
|
|
128
|
+
portfolio: result.portfolio.toISOString().slice(0, 10),
|
|
129
|
+
history: result.history.toISOString().slice(0, 10),
|
|
130
|
+
insights: result.insights.toISOString().slice(0, 10),
|
|
131
|
+
social: result.social.toISOString().slice(0, 10),
|
|
132
|
+
price: result.price.toISOString().slice(0, 10), // NEW
|
|
133
|
+
absoluteEarliest: result.absoluteEarliest.toISOString().slice(0, 10)
|
|
134
|
+
});
|
|
135
|
+
|
|
96
136
|
return result;
|
|
97
137
|
}
|
|
98
138
|
|
|
139
|
+
/**
|
|
140
|
+
* NEW HELPER: Get the earliest date from price collection
|
|
141
|
+
* Price data is sharded differently - each shard contains instrumentId -> {prices: {date: price}}
|
|
142
|
+
*/
|
|
143
|
+
async function getFirstDateFromPriceCollection(config, deps) {
|
|
144
|
+
const { db, logger, calculationUtils } = deps;
|
|
145
|
+
const { withRetry } = calculationUtils;
|
|
146
|
+
const collection = config.priceCollection || 'asset_prices';
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
logger.log('TRACE', `[getFirstDateFromPriceCollection] Querying ${collection}...`);
|
|
150
|
+
|
|
151
|
+
// Get all shards (limit to first few for performance)
|
|
152
|
+
const snapshot = await withRetry(
|
|
153
|
+
() => db.collection(collection).limit(10).get(),
|
|
154
|
+
`GetPriceShards(${collection})`
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (snapshot.empty) {
|
|
158
|
+
logger.log('WARN', `No price shards found in ${collection}`);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let earliestDate = null;
|
|
163
|
+
|
|
164
|
+
// Iterate through shards to find the earliest date across all instruments
|
|
165
|
+
snapshot.forEach(doc => {
|
|
166
|
+
const shardData = doc.data();
|
|
167
|
+
|
|
168
|
+
// Each shard has structure: { instrumentId: { ticker, prices: { "YYYY-MM-DD": price } } }
|
|
169
|
+
for (const instrumentId in shardData) {
|
|
170
|
+
const instrumentData = shardData[instrumentId];
|
|
171
|
+
if (!instrumentData.prices) continue;
|
|
172
|
+
|
|
173
|
+
// Get all dates for this instrument
|
|
174
|
+
const dates = Object.keys(instrumentData.prices)
|
|
175
|
+
.filter(d => /^\d{4}-\d{2}-\d{2}$/.test(d))
|
|
176
|
+
.sort();
|
|
177
|
+
|
|
178
|
+
if (dates.length > 0) {
|
|
179
|
+
const firstDate = new Date(dates[0] + 'T00:00:00Z');
|
|
180
|
+
if (!earliestDate || firstDate < earliestDate) {
|
|
181
|
+
earliestDate = firstDate;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (earliestDate) {
|
|
188
|
+
logger.log('TRACE', `[getFirstDateFromPriceCollection] Earliest price date: ${earliestDate.toISOString().slice(0, 10)}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return earliestDate;
|
|
192
|
+
|
|
193
|
+
} catch (e) {
|
|
194
|
+
logger.log('ERROR', `Failed to get earliest price date from ${collection}`, { errorMessage: e.message });
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
99
199
|
module.exports = { FieldValue, FieldPath, normalizeName, commitBatchInChunks, getExpectedDateStrings, getEarliestDataDates };
|