bulltrackers-module 1.0.144 → 1.0.145
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.
|
@@ -12,8 +12,7 @@ function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[ca
|
|
|
12
12
|
*/
|
|
13
13
|
function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
14
14
|
const missing = [];
|
|
15
|
-
if (!calcManifest.rootDataDependencies || !calcManifest.rootDataDependencies.length) {
|
|
16
|
-
return { canRun: true, missing };}
|
|
15
|
+
if (!calcManifest.rootDataDependencies || !calcManifest.rootDataDependencies.length) { return { canRun: true, missing };}
|
|
17
16
|
for (const dep of calcManifest.rootDataDependencies) {
|
|
18
17
|
if (dep === 'portfolio' && !rootDataStatus.hasPortfolio) missing.push('portfolio');
|
|
19
18
|
else if (dep === 'insights' && !rootDataStatus.hasInsights) missing.push('insights');
|
|
@@ -34,8 +33,6 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
34
33
|
let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false;
|
|
35
34
|
try {
|
|
36
35
|
const tasks = [];
|
|
37
|
-
// This logic is correct. It *avoids* calling get...Refs
|
|
38
|
-
// if the dateToProcess is before the earliest known data.
|
|
39
36
|
if (dateToProcess >= earliestDates.portfolio)
|
|
40
37
|
{tasks.push(getPortfolioPartRefs(config, dependencies, dateStr).then(res => {portfolioRefs = res;hasPortfolio = !!(res?.length);}));}
|
|
41
38
|
if (dateToProcess >= earliestDates.insights) {
|
|
@@ -44,32 +41,11 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
44
41
|
tasks.push(loadDailySocialPostInsights(config, dependencies, dateStr).then(res => {socialData = res;hasSocial = !!res;}));}
|
|
45
42
|
if (dateToProcess >= earliestDates.history) {
|
|
46
43
|
tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(res => {historyRefs = res;hasHistory = !!(res?.length);}));}
|
|
47
|
-
|
|
48
44
|
await Promise.all(tasks);
|
|
49
|
-
|
|
50
|
-
// --- NEW: Log what was *actually* found ---
|
|
51
45
|
logger.log('INFO', `[PassRunner] Data availability for ${dateStr}: P:${hasPortfolio}, I:${hasInsights}, S:${hasSocial}, H:${hasHistory}`);
|
|
52
46
|
|
|
53
|
-
if (!(hasPortfolio || hasInsights || hasSocial || hasHistory)) {
|
|
54
|
-
|
|
55
|
-
// We return null to skip the entire day
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* --- THIS IS THE FIX ---
|
|
61
|
-
* Rename keys to match what streamAndProcess (Stage 8) expects.
|
|
62
|
-
* 'insightsData' is renamed to 'todayInsights'.
|
|
63
|
-
* 'socialData' is renamed to 'todaySocialPostInsights'.
|
|
64
|
-
*/
|
|
65
|
-
return {
|
|
66
|
-
portfolioRefs,
|
|
67
|
-
todayInsights: insightsData, // <-- FIX
|
|
68
|
-
todaySocialPostInsights: socialData, // <-- FIX
|
|
69
|
-
historyRefs,
|
|
70
|
-
status: { hasPortfolio, hasInsights, hasSocial, hasHistory }
|
|
71
|
-
};
|
|
72
|
-
|
|
47
|
+
if (!(hasPortfolio || hasInsights || hasSocial || hasHistory)) { logger.log('WARN', `[PassRunner] No root data at all for ${dateStr}.`); return null; }
|
|
48
|
+
return { portfolioRefs, todayInsights: insightsData, todaySocialPostInsights: socialData, historyRefs, status: { hasPortfolio, hasInsights, hasSocial, hasHistory } };
|
|
73
49
|
} catch (err) { logger.log('ERROR', `[PassRunner] Error checking data for ${dateStr}`, { errorMessage: err.message }); return null; }
|
|
74
50
|
}
|
|
75
51
|
|
|
@@ -105,76 +81,32 @@ async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config,
|
|
|
105
81
|
function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingResults, passToRun, dateStr, earliestDates, logger) {
|
|
106
82
|
const skipped = new Set();
|
|
107
83
|
const dateToProcess = new Date(dateStr + 'T00:00:00Z');
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* --- CORRECTED LOGIC ---
|
|
111
|
-
* Helper to get the true earliest date a calc can run.
|
|
112
|
-
*/
|
|
113
84
|
const getTrueEarliestRunDate = (calc) => {
|
|
114
|
-
let earliestRunDate = new Date('1970-01-01T00:00:00Z');
|
|
85
|
+
let earliestRunDate = new Date('1970-01-01T00:00:00Z');
|
|
115
86
|
const dependencies = calc.rootDataDependencies || [];
|
|
116
|
-
|
|
117
|
-
// 1. Find the LATEST "today" dependency
|
|
118
|
-
// This is the date where all *today* data is first available
|
|
119
87
|
for (const dep of dependencies) {
|
|
120
88
|
if (dep === 'portfolio' && earliestDates.portfolio > earliestRunDate) earliestRunDate = earliestDates.portfolio;
|
|
121
89
|
if (dep === 'history' && earliestDates.history > earliestRunDate) earliestRunDate = earliestDates.history;
|
|
122
90
|
if (dep === 'social' && earliestDates.social > earliestRunDate) earliestRunDate = earliestDates.social;
|
|
123
91
|
if (dep === 'insights' && earliestDates.insights > earliestRunDate) earliestRunDate = earliestDates.insights;
|
|
124
92
|
}
|
|
125
|
-
|
|
126
|
-
// 2. If the calc is historical, shift the *final* date by +1
|
|
127
|
-
// This universally applies the "+1" rule if *any* yesterday data is needed,
|
|
128
|
-
// (as long as we found a dependency in step 1).
|
|
129
|
-
if (calc.isHistorical && earliestRunDate.getTime() > 0) {
|
|
130
|
-
earliestRunDate.setUTCDate(earliestRunDate.getUTCDate() + 1);
|
|
131
|
-
}
|
|
132
|
-
|
|
93
|
+
if (calc.isHistorical && earliestRunDate.getTime() > 0) { earliestRunDate.setUTCDate(earliestRunDate.getUTCDate() + 1); }
|
|
133
94
|
return earliestRunDate;
|
|
134
95
|
};
|
|
135
|
-
|
|
136
96
|
const filterCalc = (calc) => {
|
|
137
|
-
|
|
138
|
-
if (existingResults[calc.name]) {
|
|
139
|
-
logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Result already exists.`);
|
|
140
|
-
skipped.add(calc.name);
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
97
|
+
if (existingResults[calc.name]) {logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Result already exists.`); skipped.add(calc.name); return false;}
|
|
143
98
|
|
|
144
|
-
// 2. Check *true* earliest run date
|
|
145
99
|
const earliestRunDate = getTrueEarliestRunDate(calc);
|
|
146
|
-
if (dateToProcess < earliestRunDate) {
|
|
147
|
-
|
|
148
|
-
skipped.add(calc.name);
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// 3. Check if *today's* root data was *actually* found
|
|
153
|
-
// This handles gaps *after* the earliest date
|
|
100
|
+
if (dateToProcess < earliestRunDate) {logger.log('TRACE', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Date is before true earliest run date (${earliestRunDate.toISOString().slice(0, 10)}).`); skipped.add(calc.name); return false; }
|
|
101
|
+
|
|
154
102
|
const { canRun, missing: missingRoot } = checkRootDependencies(calc, rootDataStatus);
|
|
155
|
-
if (!canRun) {
|
|
156
|
-
logger.log('INFO', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Data missing for this date: [${missingRoot.join(', ')}]`);
|
|
157
|
-
skipped.add(calc.name);
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 4. (For Meta Calcs) Check computed dependencies
|
|
162
|
-
if (calc.type === 'meta') {
|
|
163
|
-
const missingDeps = (calc.dependencies || []).map(normalizeName).filter(d => !existingResults[d]);
|
|
164
|
-
if (missingDeps.length > 0) {
|
|
165
|
-
logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${calc.name} for ${dateStr}. Missing computed deps: [${missingDeps.join(', ')}]`);
|
|
166
|
-
skipped.add(calc.name);
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
103
|
+
if (!canRun) {logger.log('INFO', `[Pass ${passToRun}] Skipping ${calc.name} for ${dateStr}. Data missing for this date: [${missingRoot.join(', ')}]`);skipped.add(calc.name); return false;}
|
|
170
104
|
|
|
171
|
-
|
|
105
|
+
if (calc.type === 'meta') { const missingDeps = (calc.dependencies || []).map(normalizeName).filter(d => !existingResults[d]); if (missingDeps.length > 0) { logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${calc.name} for ${dateStr}. Missing computed deps: [${missingDeps.join(', ')}]`); skipped.add(calc.name); return false;} }
|
|
172
106
|
return true;
|
|
173
107
|
};
|
|
174
|
-
|
|
175
108
|
const standardCalcsToRun = standardCalcs.filter(filterCalc);
|
|
176
109
|
const metaCalcsToRun = metaCalcs.filter(filterCalc);
|
|
177
|
-
|
|
178
110
|
return { standardCalcsToRun, metaCalcsToRun };
|
|
179
111
|
}
|
|
180
112
|
|
|
@@ -183,50 +115,35 @@ function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingRe
|
|
|
183
115
|
function initializeCalculators(calcs, logger) { const state = {}; for (const c of calcs) { const name=normalizeName(c.name), Cl=c.class; if(typeof Cl==='function') try { const inst=new Cl(); inst.manifest=c; state[name]=inst; } catch(e){logger.warn(`Init failed ${name}`,{errorMessage:e.message}); state[name]=null;} else {logger.warn(`Class missing ${name}`); state[name]=null;} } return state; }
|
|
184
116
|
|
|
185
117
|
/** * Stage 7: Load historical data required for calculations
|
|
186
|
-
* --- THIS FUNCTION IS NOW FIXED ---
|
|
187
118
|
*/
|
|
188
119
|
async function loadHistoricalData(date, calcs, config, deps, rootData) {
|
|
189
|
-
const { logger }
|
|
190
|
-
const updated
|
|
191
|
-
|
|
120
|
+
const { logger } = deps;
|
|
121
|
+
const updated = {...rootData}, dStr=date.toISOString().slice(0,10); const tasks = [];
|
|
192
122
|
const needsYesterdayPortfolio = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('portfolio'));
|
|
193
|
-
const needsTodayHistory
|
|
194
|
-
const needsYesterdayHistory
|
|
195
|
-
const needsYesterdayInsights
|
|
196
|
-
const needsYesterdaySocial
|
|
197
|
-
|
|
198
|
-
// --- MODIFIED: Be smarter about loading data ---
|
|
123
|
+
const needsTodayHistory = calcs.some(c => c.rootDataDependencies.includes('history'));
|
|
124
|
+
const needsYesterdayHistory = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('history'));
|
|
125
|
+
const needsYesterdayInsights = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('insights'));
|
|
126
|
+
const needsYesterdaySocial = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('social'));
|
|
199
127
|
if(needsYesterdayPortfolio) {
|
|
200
128
|
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
})());
|
|
204
|
-
}
|
|
129
|
+
logger.log('INFO', `[PassRunner] Loading YESTERDAY portfolio data for ${prevStr}`);
|
|
130
|
+
updated.yesterdayPortfolios=await loadFullDayMap(config,deps,await getPortfolioPartRefs(config,deps,prevStr)); })());}
|
|
205
131
|
if(needsTodayHistory) {
|
|
206
132
|
tasks.push((async()=>{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
})());
|
|
210
|
-
}
|
|
133
|
+
logger.log('INFO', `[PassRunner] Loading TODAY history data for ${dStr}`);
|
|
134
|
+
updated.todayHistoryData=await loadFullDayMap(config,deps,rootData.historyRefs); })());}
|
|
211
135
|
if(needsYesterdayHistory) {
|
|
212
136
|
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
})());
|
|
216
|
-
}
|
|
137
|
+
logger.log('INFO', `[PassRunner] Loading YESTERDAY history data for ${prevStr}`);
|
|
138
|
+
updated.yesterdayHistoryData=await loadFullDayMap(config,deps,await getHistoryPartRefs(config,deps,prevStr)); })());}
|
|
217
139
|
if(needsYesterdayInsights) {
|
|
218
140
|
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
})());
|
|
222
|
-
}
|
|
141
|
+
logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
|
|
142
|
+
updated.yesterdayInsights=await loadDailyInsights(config,deps,prevStr); })());}
|
|
223
143
|
if(needsYesterdaySocial) {
|
|
224
144
|
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
})());
|
|
228
|
-
}
|
|
229
|
-
|
|
145
|
+
logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
|
|
146
|
+
updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr); })());}
|
|
230
147
|
await Promise.all(tasks); return updated;
|
|
231
148
|
}
|
|
232
149
|
|
|
@@ -237,27 +154,17 @@ async function loadHistoricalData(date, calcs, config, deps, rootData) {
|
|
|
237
154
|
*/
|
|
238
155
|
async function streamAndProcess(dateStr, state, passName, config, deps, rootData) {
|
|
239
156
|
const { logger, calculationUtils } = deps;
|
|
240
|
-
// --- MODIFIED: yesterdayInsights/Social are now loaded by loadHistoricalData ---
|
|
241
|
-
// --- THIS WILL NOW WORK, as rootData contains 'todayInsights' from Stage 3 ---
|
|
242
157
|
const { todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights, todayHistoryData, yesterdayHistoryData, yesterdayPortfolios } = rootData;
|
|
243
|
-
|
|
244
|
-
// --- NEW: Check if streaming is even needed ---
|
|
245
158
|
const calcsThatStreamPortfolio = Object.values(state).filter(calc => calc && calc.manifest && (calc.manifest.rootDataDependencies.includes('portfolio') || calc.manifest.category === 'speculators'));
|
|
246
|
-
|
|
247
159
|
const context={instrumentMappings:(await calculationUtils.loadInstrumentMappings()).instrumentToTicker, sectorMapping:(await calculationUtils.loadInstrumentMappings()).instrumentToSector, todayDateStr:dateStr, dependencies:deps, config};
|
|
248
|
-
|
|
249
|
-
// --- MODIFIED: Run non-streaming calcs first (social/insights) ---
|
|
250
|
-
// This allows them to run even if portfolio data is missing
|
|
251
|
-
let firstUser=true; // Used to run them only once
|
|
160
|
+
let firstUser=true;
|
|
252
161
|
for(const name in state){
|
|
253
162
|
const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
|
|
254
163
|
const cat=calc.manifest.category;
|
|
255
164
|
if(cat==='socialPosts'||cat==='insights') {
|
|
256
165
|
if (firstUser) {
|
|
257
166
|
logger.log('INFO', `[${passName}] Running non-streaming calc: ${name}`);
|
|
258
|
-
// --- This 'args' array will now receive the correct data ---
|
|
259
167
|
let args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
|
|
260
|
-
// Pass historical data if needed
|
|
261
168
|
if(calc.manifest.isHistorical) {
|
|
262
169
|
args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
|
|
263
170
|
}
|
|
@@ -265,15 +172,10 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
|
|
|
265
172
|
}
|
|
266
173
|
}
|
|
267
174
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
175
|
if (calcsThatStreamPortfolio.length === 0) {
|
|
271
176
|
logger.log('INFO', `[${passName}] No portfolio-streaming calcs to run for ${dateStr}. Skipping stream.`);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
177
|
+
return; }
|
|
275
178
|
logger.log('INFO', `[${passName}] Streaming portfolio data for ${calcsThatStreamPortfolio.length} calcs...`);
|
|
276
|
-
|
|
277
179
|
for await (const chunk of streamPortfolioData(config, deps, dateStr)) {
|
|
278
180
|
for(const uid in chunk){ const p=chunk[uid]; if(!p) continue;
|
|
279
181
|
const userType=p.PublicPositions?'speculator':'normal';
|
|
@@ -281,19 +183,12 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
|
|
|
281
183
|
for(const name in state){
|
|
282
184
|
const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
|
|
283
185
|
const cat=calc.manifest.category, isSocialOrInsights=cat==='socialPosts'||cat==='insights', isHistorical=calc.manifest.isHistorical, isSpec=cat==='speculators';
|
|
284
|
-
|
|
285
|
-
// --- MODIFIED: Skip social/insights here, they ran above ---
|
|
286
186
|
if(isSocialOrInsights) continue;
|
|
287
|
-
|
|
288
|
-
// --- This 'args' array will now receive the correct data ---
|
|
289
187
|
let args=[p,null,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
|
|
290
|
-
|
|
291
188
|
if(isHistorical){
|
|
292
|
-
const pY=yesterdayPortfolios ? yesterdayPortfolios[uid] : null;
|
|
293
|
-
// V3 behavioural calcs (like history aggregator) *can* run without pY
|
|
189
|
+
const pY=yesterdayPortfolios ? yesterdayPortfolios[uid] : null;
|
|
294
190
|
if(!pY && (cat !== 'behavioural' && name !== 'historical-performance-aggregator')) continue;
|
|
295
|
-
args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData];
|
|
296
|
-
}
|
|
191
|
+
args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,todayHistoryData,yesterdayHistoryData]; }
|
|
297
192
|
if((userType==='normal'&&isSpec)||(userType==='speculator'&&!isSpec&&name!=='users-processed')) continue;
|
|
298
193
|
try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} for ${uid}`,{err:e.message});} }
|
|
299
194
|
firstUser=false;
|
|
@@ -304,20 +199,13 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
|
|
|
304
199
|
/** Stage 9: Run standard computations */
|
|
305
200
|
async function runStandardComputationPass(date, calcs, passName, config, deps, rootData) {
|
|
306
201
|
const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
|
|
307
|
-
// --- THIS IS THE CRITICAL CHANGE ---
|
|
308
|
-
// If 'calcs' is empty *because of the new filter*, this log won't even appear.
|
|
309
202
|
if (calcs.length === 0) {
|
|
310
203
|
logger.log('INFO', `[${passName}] No standard calcs to run for ${dStr} after filtering.`);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
// This log now only appears if there is *actually* work to do.
|
|
204
|
+
return; }
|
|
314
205
|
logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
|
|
315
|
-
|
|
316
206
|
const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
|
|
317
207
|
const state = initializeCalculators(calcs, logger);
|
|
318
|
-
|
|
319
208
|
await streamAndProcess(dStr, state, passName, config, deps, fullRoot);
|
|
320
|
-
|
|
321
209
|
let success = 0;
|
|
322
210
|
const standardWrites = [];
|
|
323
211
|
const shardedWrites = {};
|
|
@@ -354,8 +242,8 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
|
|
|
354
242
|
if (standardWrites.length > 0) {
|
|
355
243
|
await commitBatchInChunks(config, deps, standardWrites, `${passName} Standard ${dStr}`);
|
|
356
244
|
}
|
|
357
|
-
for (const docPath in shardedWrites) {
|
|
358
|
-
const docData = shardedWrites[docPath];
|
|
245
|
+
for (const docPath in shardedWrites) {
|
|
246
|
+
const docData = shardedWrites[docPath];
|
|
359
247
|
const shardedDocWrites = [];
|
|
360
248
|
let docRef;
|
|
361
249
|
if (docPath.includes('/')) {
|
|
@@ -381,17 +269,12 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
|
|
|
381
269
|
/** Stage 10: Run meta computations */
|
|
382
270
|
async function runMetaComputationPass(date, calcs, passName, config, deps, fetchedDeps, rootData) {
|
|
383
271
|
const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
|
|
384
|
-
// --- THIS IS THE CRITICAL CHANGE ---
|
|
385
272
|
if (calcs.length === 0) {
|
|
386
273
|
logger.log('INFO', `[${passName}] No meta calcs to run for ${dStr} after filtering.`);
|
|
387
274
|
return;
|
|
388
275
|
}
|
|
389
276
|
logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
|
|
390
|
-
|
|
391
|
-
// --- NEW: Load historical data for meta calcs if needed ---
|
|
392
|
-
// (This might be redundant if standard pass ran, but meta-calcs can run standalone)
|
|
393
277
|
const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
|
|
394
|
-
|
|
395
278
|
let success = 0;
|
|
396
279
|
const standardWrites = [];
|
|
397
280
|
const shardedWrites = {};
|
|
@@ -400,7 +283,6 @@ async function runMetaComputationPass(date, calcs, passName, config, deps, fetch
|
|
|
400
283
|
if (typeof Cl !== 'function') {logger.log('ERROR', `Invalid class ${name}`);continue;}
|
|
401
284
|
const inst = new Cl();
|
|
402
285
|
try {
|
|
403
|
-
// --- MODIFIED: Pass fullRoot to meta calcs ---
|
|
404
286
|
const result = await Promise.resolve(inst.process(dStr, { ...deps, rootData: fullRoot }, config, fetchedDeps));
|
|
405
287
|
if (result && Object.keys(result).length > 0) {const standardResult = {}; for (const key in result) {
|
|
406
288
|
if (key.startsWith('sharded_')) {const shardedData = result[key];for (const collectionName in shardedData)
|