bulltrackers-module 1.0.145 → 1.0.146
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,6 @@
|
|
|
1
1
|
const { FieldPath } = require('@google-cloud/firestore');
|
|
2
2
|
// --- MODIFIED: Import streamPortfolioData ---
|
|
3
|
-
const { getPortfolioPartRefs, loadFullDayMap, loadDataByRefs, loadDailyInsights, loadDailySocialPostInsights, getHistoryPartRefs, streamPortfolioData } = require('../utils/data_loader.js');
|
|
3
|
+
const { getPortfolioPartRefs, loadFullDayMap, loadDataByRefs, loadDailyInsights, loadDailySocialPostInsights, getHistoryPartRefs, streamPortfolioData, streamHistoryData } = require('../utils/data_loader.js');
|
|
4
4
|
const { normalizeName, commitBatchInChunks } = require('../utils/utils.js');
|
|
5
5
|
|
|
6
6
|
/** Stage 1: Group manifest by pass number */
|
|
@@ -116,26 +116,20 @@ function initializeCalculators(calcs, logger) { const state = {}; for (const c o
|
|
|
116
116
|
|
|
117
117
|
/** * Stage 7: Load historical data required for calculations
|
|
118
118
|
*/
|
|
119
|
+
// --- MODIFIED: Stage 7: Load ONLY non-streaming historical data ---
|
|
119
120
|
async function loadHistoricalData(date, calcs, config, deps, rootData) {
|
|
120
121
|
const { logger } = deps;
|
|
121
|
-
const updated = {...rootData}
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const updated = {...rootData};
|
|
123
|
+
const tasks = [];
|
|
124
|
+
|
|
125
|
+
// --- REMOVED: needsYesterdayPortfolio ---
|
|
126
|
+
// --- REMOVED: needsTodayHistory ---
|
|
127
|
+
// --- REMOVED: needsYesterdayHistory ---
|
|
125
128
|
const needsYesterdayInsights = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('insights'));
|
|
126
129
|
const needsYesterdaySocial = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('social'));
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr)); })());}
|
|
131
|
-
if(needsTodayHistory) {
|
|
132
|
-
tasks.push((async()=>{
|
|
133
|
-
logger.log('INFO', `[PassRunner] Loading TODAY history data for ${dStr}`);
|
|
134
|
-
updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs); })());}
|
|
135
|
-
if(needsYesterdayHistory) {
|
|
136
|
-
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
137
|
-
logger.log('INFO', `[PassRunner] Loading YESTERDAY history data for ${prevStr}`);
|
|
138
|
-
updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr)); })());}
|
|
130
|
+
|
|
131
|
+
// --- REMOVED: All async tasks for portfolio and history data ---
|
|
132
|
+
|
|
139
133
|
if(needsYesterdayInsights) {
|
|
140
134
|
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
141
135
|
logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
|
|
@@ -144,56 +138,129 @@ async function loadHistoricalData(date, calcs, config, deps, rootData) {
|
|
|
144
138
|
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
145
139
|
logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
|
|
146
140
|
updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr); })());}
|
|
147
|
-
|
|
141
|
+
|
|
142
|
+
await Promise.all(tasks);
|
|
143
|
+
return updated; // This no longer contains the large data maps
|
|
148
144
|
}
|
|
149
145
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
* This function now
|
|
153
|
-
*
|
|
146
|
+
/**
|
|
147
|
+
* --- REFACTORED: Stage 8: Stream and process data for standard calculations ---
|
|
148
|
+
* This function now streams today's portfolios, yesterday's portfolios,
|
|
149
|
+
* and today's history data in parallel to avoid OOM errors.
|
|
150
|
+
* It loads chunks of all three streams, processes UIDs found in the
|
|
151
|
+
* main (today's portfolio) stream, and then deletes processed users
|
|
152
|
+
* from the historical maps to free memory.
|
|
154
153
|
*/
|
|
155
154
|
async function streamAndProcess(dateStr, state, passName, config, deps, rootData) {
|
|
156
155
|
const { logger, calculationUtils } = deps;
|
|
157
|
-
|
|
156
|
+
|
|
157
|
+
// --- MODIFIED: yesterdayPortfolios & todayHistoryData are no longer in rootData ---
|
|
158
|
+
const { todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights } = rootData;
|
|
159
|
+
|
|
158
160
|
const calcsThatStreamPortfolio = Object.values(state).filter(calc => calc && calc.manifest && (calc.manifest.rootDataDependencies.includes('portfolio') || calc.manifest.category === 'speculators'));
|
|
159
161
|
const context={instrumentMappings:(await calculationUtils.loadInstrumentMappings()).instrumentToTicker, sectorMapping:(await calculationUtils.loadInstrumentMappings()).instrumentToSector, todayDateStr:dateStr, dependencies:deps, config};
|
|
160
162
|
let firstUser=true;
|
|
163
|
+
|
|
164
|
+
// --- (Non-streaming (insights/social) calculation logic remains unchanged) ---
|
|
161
165
|
for(const name in state){
|
|
162
166
|
const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
|
|
163
167
|
const cat=calc.manifest.category;
|
|
164
168
|
if(cat==='socialPosts'||cat==='insights') {
|
|
165
169
|
if (firstUser) {
|
|
166
170
|
logger.log('INFO', `[${passName}] Running non-streaming calc: ${name}`);
|
|
167
|
-
|
|
171
|
+
// (Using 'null' for hT and hY as they aren't relevant for these calcs)
|
|
172
|
+
let args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,null,null];
|
|
168
173
|
if(calc.manifest.isHistorical) {
|
|
169
|
-
args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,
|
|
174
|
+
args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,null,null];
|
|
170
175
|
}
|
|
171
176
|
try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} (non-stream)`,{err:e.message});}
|
|
172
177
|
}
|
|
173
178
|
}
|
|
174
179
|
}
|
|
180
|
+
// --- (End of non-streaming calc logic) ---
|
|
181
|
+
|
|
175
182
|
if (calcsThatStreamPortfolio.length === 0) {
|
|
176
183
|
logger.log('INFO', `[${passName}] No portfolio-streaming calcs to run for ${dateStr}. Skipping stream.`);
|
|
177
|
-
return;
|
|
178
|
-
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
logger.log('INFO', `[${passName}] Streaming portfolio & historical data for ${calcsThatStreamPortfolio.length} calcs...`);
|
|
188
|
+
|
|
189
|
+
// --- NEW: Prepare iterators and maps for parallel streaming ---
|
|
190
|
+
const prevDate = new Date(dateStr + 'T00:00:00Z');
|
|
191
|
+
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
192
|
+
const prevDateStr = prevDate.toISOString().slice(0, 10);
|
|
193
|
+
|
|
194
|
+
const needsYesterdayPortfolio = Object.values(state).some(c => c && c.manifest.isHistorical && c.manifest.rootDataDependencies.includes('portfolio'));
|
|
195
|
+
const needsTodayHistory = Object.values(state).some(c => c && c.manifest.rootDataDependencies.includes('history'));
|
|
196
|
+
|
|
197
|
+
// Get the async iterators
|
|
198
|
+
const yP_iterator = needsYesterdayPortfolio ? streamPortfolioData(config, deps, prevDateStr) : null;
|
|
199
|
+
const hT_iterator = needsTodayHistory ? streamHistoryData(config, deps, dateStr) : null;
|
|
200
|
+
|
|
201
|
+
// These maps will accumulate data chunk-by-chunk
|
|
202
|
+
let yesterdayPortfolios = {};
|
|
203
|
+
let todayHistoryData = {};
|
|
204
|
+
|
|
205
|
+
// Load the FIRST chunk of historical data before the loop starts
|
|
206
|
+
if (yP_iterator) {
|
|
207
|
+
Object.assign(yesterdayPortfolios, (await yP_iterator.next()).value || {});
|
|
208
|
+
logger.log('INFO', `[${passName}] Loaded first chunk of yesterday's portfolios.`);
|
|
209
|
+
}
|
|
210
|
+
if (hT_iterator) {
|
|
211
|
+
Object.assign(todayHistoryData, (await hT_iterator.next()).value || {});
|
|
212
|
+
logger.log('INFO', `[${passName}] Loaded first chunk of today's history.`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// --- MODIFIED: Main streaming loop (driven by TODAY's portfolio stream) ---
|
|
179
216
|
for await (const chunk of streamPortfolioData(config, deps, dateStr)) {
|
|
180
|
-
|
|
217
|
+
|
|
218
|
+
// --- NEW: Load the NEXT chunk of historical data ---
|
|
219
|
+
// This keeps the historical maps populated as the main stream progresses
|
|
220
|
+
if (yP_iterator) {
|
|
221
|
+
Object.assign(yesterdayPortfolios, (await yP_iterator.next()).value || {});
|
|
222
|
+
}
|
|
223
|
+
if (hT_iterator) {
|
|
224
|
+
Object.assign(todayHistoryData, (await hT_iterator.next()).value || {});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for(const uid in chunk){ // Iterate through today's portfolio chunk
|
|
228
|
+
const p = chunk[uid]; if(!p) continue;
|
|
181
229
|
const userType=p.PublicPositions?'speculator':'normal';
|
|
182
230
|
context.userType=userType;
|
|
231
|
+
|
|
232
|
+
// --- NEW: Look up corresponding historical data for THIS user ---
|
|
233
|
+
const pY = yesterdayPortfolios[uid] || null; // Yesterday's Portfolio
|
|
234
|
+
const hT = todayHistoryData[uid] || null; // Today's History
|
|
235
|
+
// (Note: yesterdayHistoryData (hY) would require another stream if needed)
|
|
236
|
+
|
|
183
237
|
for(const name in state){
|
|
184
238
|
const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
|
|
185
239
|
const cat=calc.manifest.category, isSocialOrInsights=cat==='socialPosts'||cat==='insights', isHistorical=calc.manifest.isHistorical, isSpec=cat==='speculators';
|
|
186
|
-
|
|
187
|
-
|
|
240
|
+
|
|
241
|
+
if(isSocialOrInsights) continue; // Skip non-streaming calcs
|
|
242
|
+
|
|
243
|
+
// --- MODIFIED: Arguments now use streamed historical data (hT) ---
|
|
244
|
+
let args=[p,null,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,hT,null];
|
|
245
|
+
|
|
188
246
|
if(isHistorical){
|
|
189
|
-
|
|
247
|
+
// pY is now the streamed yesterday's portfolio for this user
|
|
190
248
|
if(!pY && (cat !== 'behavioural' && name !== 'historical-performance-aggregator')) continue;
|
|
191
|
-
args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,
|
|
249
|
+
args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,hT,null];
|
|
250
|
+
}
|
|
251
|
+
|
|
192
252
|
if((userType==='normal'&&isSpec)||(userType==='speculator'&&!isSpec&&name!=='users-processed')) continue;
|
|
193
|
-
|
|
253
|
+
|
|
254
|
+
try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} for ${uid}`,{err:e.message});}
|
|
255
|
+
}
|
|
194
256
|
firstUser=false;
|
|
257
|
+
|
|
258
|
+
// --- NEW: Clear this user from historical maps to free memory ---
|
|
259
|
+
if (pY) { delete yesterdayPortfolios[uid]; }
|
|
260
|
+
if (hT) { delete todayHistoryData[uid]; }
|
|
195
261
|
}
|
|
196
262
|
}
|
|
263
|
+
logger.log('INFO', `[${passName}] Finished streaming data for ${dateStr}.`);
|
|
197
264
|
}
|
|
198
265
|
|
|
199
266
|
/** Stage 9: Run standard computations */
|
|
@@ -198,6 +198,28 @@ async function* streamPortfolioData(config, deps, dateString) {
|
|
|
198
198
|
}
|
|
199
199
|
// --- END: Stage 7 ---
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* --- NEW: Stage 8: Stream history data in chunks ---
|
|
203
|
+
* Streams history data in chunks for a given date.
|
|
204
|
+
*/
|
|
205
|
+
async function* streamHistoryData(config, deps, dateString) {
|
|
206
|
+
const { logger } = deps;
|
|
207
|
+
const refs = await getHistoryPartRefs(config, deps, dateString); // <-- Uses history refs
|
|
208
|
+
if (refs.length === 0) {
|
|
209
|
+
logger.log('WARN', `[streamHistoryData] No history refs found for ${dateString}. Stream is empty.`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const batchSize = config.partRefBatchSize || 50;
|
|
214
|
+
logger.log('INFO', `[streamHistoryData] Streaming ${refs.length} history parts in chunks of ${batchSize}...`);
|
|
215
|
+
|
|
216
|
+
for (let i = 0; i < refs.length; i += batchSize) {
|
|
217
|
+
const batchRefs = refs.slice(i, i + batchSize);
|
|
218
|
+
const data = await loadDataByRefs(config, deps, batchRefs);
|
|
219
|
+
yield data;
|
|
220
|
+
}
|
|
221
|
+
logger.log('INFO', `[streamHistoryData] Finished streaming for ${dateString}.`);
|
|
222
|
+
}
|
|
201
223
|
|
|
202
224
|
module.exports = {
|
|
203
225
|
getPortfolioPartRefs,
|
|
@@ -207,4 +229,5 @@ module.exports = {
|
|
|
207
229
|
loadDailySocialPostInsights,
|
|
208
230
|
getHistoryPartRefs,
|
|
209
231
|
streamPortfolioData, // <-- EXPORT NEW FUNCTION
|
|
232
|
+
streamHistoryData // <-- EXPORT NEW FUNCTION
|
|
210
233
|
};
|