bulltrackers-module 1.0.184 → 1.0.186
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
|
|
|
@@ -26,9 +26,17 @@ async function runComputationPass(config, dependencies, computationManifest) {
|
|
|
26
26
|
logger.log('INFO', `🚀 Starting PASS ${passToRun} (Targeting /computation_status/{YYYY-MM-DD})...`);
|
|
27
27
|
|
|
28
28
|
// Hardcoded earliest dates
|
|
29
|
-
const earliestDates = {
|
|
29
|
+
const earliestDates = {
|
|
30
|
+
portfolio: new Date('2025-09-25T00:00:00Z'),
|
|
31
|
+
history: new Date('2025-11-05T00:00:00Z'),
|
|
32
|
+
social: new Date('2025-10-30T00:00:00Z'),
|
|
33
|
+
insights: new Date('2025-08-26T00:00:00Z'),
|
|
34
|
+
price: new Date('2025-08-01T00:00:00Z') // A few weeks before insights (earliest other data)
|
|
35
|
+
|
|
36
|
+
};
|
|
30
37
|
earliestDates.absoluteEarliest = Object.values(earliestDates).reduce((a,b) => a < b ? a : b);
|
|
31
38
|
|
|
39
|
+
|
|
32
40
|
const passes = groupByPass(computationManifest);
|
|
33
41
|
const calcsInThisPass = passes[passToRun] || [];
|
|
34
42
|
|
|
@@ -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 } = 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 - simple validation
|
|
54
|
+
if (dateToProcess >= earliestDates.price) {
|
|
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,30 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* NEW HELPER: Simple check if price 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
|
+
// Just check if the collection has at least one 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
|
+
return true;
|
|
93
|
+
|
|
94
|
+
} catch (e) {
|
|
95
|
+
logger.log('ERROR', `[checkPriceData] Failed to check price availability: ${e.message}`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
67
100
|
async function fetchComputationStatus(dateStr, config, { db }) {
|
|
68
101
|
const collection = config.computationStatusCollection || 'computation_status';
|
|
69
102
|
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 };
|