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
- // Hardcoded earliest dates
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 } = dependencies;
40
+ const { logger, db } = dependencies;
40
41
  const dateToProcess = new Date(dateStr + 'T00:00:00Z');
41
- let portfolioRefs = [], historyRefs = [];
42
- let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false, insightsData = null , socialData = null;
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) tasks.push(getPortfolioPartRefs(config, dependencies, dateStr).then(r => { portfolioRefs = r; hasPortfolio = !!r.length; }));
47
- if (dateToProcess >= earliestDates.insights) tasks.push(loadDailyInsights(config, dependencies, dateStr).then(r => { insightsData = r; hasInsights = !!r; }));
48
- if (dateToProcess >= earliestDates.social) tasks.push(loadDailySocialPostInsights(config, dependencies, dateStr).then(r => { socialData = r; hasSocial = !!r; }));
49
- if (dateToProcess >= earliestDates.history) tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(r => { historyRefs = r; hasHistory = !!r.length; }));
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, historyRefs,
57
- todayInsights: insightsData, todaySocialPostInsights: socialData,
58
- status: { hasPortfolio, hasInsights, hasSocial, hasHistory }
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
- const [ investorDate, speculatorDate, investorHistoryDate, speculatorHistoryDate, insightsDate, socialDate ] = await Promise.all([
79
- getFirstDateFromCollection (config, deps, config.normalUserPortfolioCollection),
80
- getFirstDateFromCollection (config, deps, config.speculatorPortfolioCollection),
81
- getFirstDateFromCollection (config, deps, config.normalUserHistoryCollection),
82
- getFirstDateFromCollection (config, deps, config.speculatorHistoryCollection),
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 = (...dates) => { const validDates = dates.filter(Boolean); if (validDates.length === 0) return null; return new Date(Math.min(...validDates)); };
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 = getMinDate(investorHistoryDate, speculatorHistoryDate);
90
- const earliestInsightsDate = getMinDate(insightsDate);
91
- const earliestSocialDate = getMinDate(socialDate);
92
- const absoluteEarliest = getMinDate(earliestPortfolioDate, earliestHistoryDate, earliestInsightsDate, earliestSocialDate );
93
- const fallbackDate = new Date(config.earliestComputationDate + 'T00:00:00Z' || '2023-01-01T00:00:00Z');
94
- const result = { portfolio: earliestPortfolioDate || new Date('2999-12-31'), history: earliestHistoryDate || new Date('2999-12-31'), insights: earliestInsightsDate || new Date('2999-12-31'), social: earliestSocialDate || new Date('2999-12-31'), absoluteEarliest: absoluteEarliest || fallbackDate };
95
- logger.log('INFO', 'Earliest data availability map built:', { portfolio: result.portfolio.toISOString().slice(0, 10), history: result.history.toISOString().slice(0, 10), insights: result.insights.toISOString().slice(0, 10), social: result.social.toISOString().slice(0, 10), absoluteEarliest: result.absoluteEarliest.toISOString().slice(0, 10) });
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.184",
3
+ "version": "1.0.185",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [