bulltrackers-module 1.0.152 → 1.0.154
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.
- package/functions/appscript-api/index.js +8 -38
- package/functions/computation-system/helpers/computation_pass_runner.js +38 -183
- package/functions/computation-system/helpers/orchestration_helpers.js +105 -326
- package/functions/computation-system/utils/data_loader.js +38 -133
- package/functions/computation-system/utils/schema_capture.js +7 -41
- package/functions/computation-system/utils/utils.js +37 -124
- package/functions/core/utils/firestore_utils.js +8 -46
- package/functions/core/utils/intelligent_header_manager.js +26 -128
- package/functions/core/utils/intelligent_proxy_manager.js +33 -171
- package/functions/core/utils/pubsub_utils.js +7 -24
- package/functions/dispatcher/helpers/dispatch_helpers.js +9 -30
- package/functions/dispatcher/index.js +7 -30
- package/functions/etoro-price-fetcher/helpers/handler_helpers.js +12 -80
- package/functions/fetch-insights/helpers/handler_helpers.js +18 -70
- package/functions/generic-api/helpers/api_helpers.js +28 -167
- package/functions/generic-api/index.js +49 -188
- package/functions/invalid-speculator-handler/helpers/handler_helpers.js +10 -47
- package/functions/orchestrator/helpers/discovery_helpers.js +1 -5
- package/functions/orchestrator/index.js +1 -6
- package/functions/price-backfill/helpers/handler_helpers.js +13 -69
- package/functions/social-orchestrator/helpers/orchestrator_helpers.js +5 -37
- package/functions/social-task-handler/helpers/handler_helpers.js +29 -186
- package/functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers.js +19 -78
- package/functions/task-engine/handler_creator.js +2 -8
- package/functions/task-engine/helpers/update_helpers.js +74 -100
- package/functions/task-engine/helpers/verify_helpers.js +11 -56
- package/functions/task-engine/utils/firestore_batch_manager.js +29 -65
- package/functions/task-engine/utils/task_engine_utils.js +14 -37
- package/index.js +45 -43
- package/package.json +1 -1
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
const { FieldPath } = require('@google-cloud/firestore');
|
|
2
|
-
// --- MODIFIED: Import streamPortfolioData ---
|
|
3
2
|
const { getPortfolioPartRefs, loadFullDayMap, loadDataByRefs, loadDailyInsights, loadDailySocialPostInsights, getHistoryPartRefs, streamPortfolioData, streamHistoryData } = require('../utils/data_loader.js');
|
|
4
3
|
const { normalizeName, commitBatchInChunks } = require('../utils/utils.js');
|
|
5
|
-
|
|
6
|
-
const { batchStoreSchemas } = require('../utils/schema_capture');
|
|
4
|
+
const { batchStoreSchemas } = require('../utils/schema_capture.js');
|
|
7
5
|
|
|
8
6
|
/** Stage 1: Group manifest by pass number */
|
|
9
7
|
function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[calc.pass] = acc[calc.pass] || []).push(calc); return acc; }, {}); }
|
|
10
8
|
|
|
11
9
|
/** * --- MODIFIED: Returns detailed missing dependencies for logging ---
|
|
12
10
|
* Stage 2: Check root data dependencies for a calc
|
|
13
|
-
* --- THIS FUNCTION IS NOW MORE GRANULAR ---
|
|
14
11
|
*/
|
|
15
12
|
function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
16
13
|
const missing = [];
|
|
17
14
|
if (!calcManifest.rootDataDependencies || !calcManifest.rootDataDependencies.length) { return { canRun: true, missing };}
|
|
18
15
|
for (const dep of calcManifest.rootDataDependencies) {
|
|
19
|
-
if (dep === 'portfolio'
|
|
20
|
-
else if (dep === 'insights' && !rootDataStatus.hasInsights)
|
|
21
|
-
else if (dep === 'social'
|
|
22
|
-
else if (dep === 'history'
|
|
23
|
-
}
|
|
16
|
+
if (dep === 'portfolio' && !rootDataStatus.hasPortfolio) missing.push('portfolio');
|
|
17
|
+
else if (dep === 'insights' && !rootDataStatus.hasInsights) missing.push('insights');
|
|
18
|
+
else if (dep === 'social' && !rootDataStatus.hasSocial) missing.push('social');
|
|
19
|
+
else if (dep === 'history' && !rootDataStatus.hasHistory) missing.push('history'); }
|
|
24
20
|
return { canRun: missing.length === 0, missing };
|
|
25
21
|
}
|
|
26
22
|
|
|
@@ -40,7 +36,7 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
40
36
|
if (dateToProcess >= earliestDates.social) {tasks.push(loadDailySocialPostInsights(config, dependencies, dateStr).then(res => {socialData = res;hasSocial = !!res;}));}
|
|
41
37
|
if (dateToProcess >= earliestDates.history) {tasks.push(getHistoryPartRefs(config, dependencies, dateStr).then(res => {historyRefs = res;hasHistory = !!(res?.length);}));}
|
|
42
38
|
await Promise.all(tasks);
|
|
43
|
-
logger.log('INFO', `[PassRunner] Data availability for ${dateStr}: P:${hasPortfolio}, I:${hasInsights}, S:${hasSocial}, H:${hasHistory}`);
|
|
39
|
+
logger.log('INFO', `[PassRunner] Data availability for ${dateStr}: P:${hasPortfolio}, I:${hasInsights}, S:${hasSocial}, H:${hasHistory}`);
|
|
44
40
|
if (!(hasPortfolio || hasInsights || hasSocial || hasHistory)) { logger.log('WARN', `[PassRunner] No root data at all for ${dateStr}.`); return null; }
|
|
45
41
|
return { portfolioRefs, todayInsights: insightsData, todaySocialPostInsights: socialData, historyRefs, status: { hasPortfolio, hasInsights, hasSocial, hasHistory } };
|
|
46
42
|
} catch (err) { logger.log('ERROR', `[PassRunner] Error checking data for ${dateStr}`, { errorMessage: err.message }); return null; }
|
|
@@ -77,9 +73,9 @@ function filterCalculations(standardCalcs, metaCalcs, rootDataStatus, existingRe
|
|
|
77
73
|
const dependencies = calc.rootDataDependencies || [];
|
|
78
74
|
for (const dep of dependencies) {
|
|
79
75
|
if (dep === 'portfolio' && earliestDates.portfolio > earliestRunDate) earliestRunDate = earliestDates.portfolio;
|
|
80
|
-
if (dep === 'history'
|
|
81
|
-
if (dep === 'social'
|
|
82
|
-
if (dep === 'insights'
|
|
76
|
+
if (dep === 'history' && earliestDates.history > earliestRunDate) earliestRunDate = earliestDates.history;
|
|
77
|
+
if (dep === 'social' && earliestDates.social > earliestRunDate) earliestRunDate = earliestDates.social;
|
|
78
|
+
if (dep === 'insights' && earliestDates.insights > earliestRunDate) earliestRunDate = earliestDates.insights;
|
|
83
79
|
}
|
|
84
80
|
if (calc.isHistorical && earliestRunDate.getTime() > 0) { earliestRunDate.setUTCDate(earliestRunDate.getUTCDate() + 1); }
|
|
85
81
|
return earliestRunDate;
|
|
@@ -103,21 +99,16 @@ function initializeCalculators(calcs, logger) { const state = {}; for (const c o
|
|
|
103
99
|
|
|
104
100
|
/** * Stage 7: Load historical data required for calculations
|
|
105
101
|
*/
|
|
106
|
-
// --- MODIFIED: Stage 7: Load ONLY non-streaming historical data ---
|
|
107
102
|
async function loadHistoricalData(date, calcs, config, deps, rootData) {
|
|
108
103
|
const { logger } = deps;
|
|
109
104
|
const updated = {...rootData};
|
|
110
105
|
const tasks = [];
|
|
111
106
|
const needsYesterdayInsights = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('insights'));
|
|
112
107
|
const needsYesterdaySocial = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('social'));
|
|
113
|
-
|
|
114
|
-
// --- ADD THIS ---
|
|
115
108
|
const needsYesterdayPortfolio = calcs.some(c => c.isHistorical && c.rootDataDependencies.includes('portfolio'));
|
|
116
109
|
const prev = new Date(date);
|
|
117
110
|
prev.setUTCDate(prev.getUTCDate() - 1);
|
|
118
111
|
const prevStr = prev.toISOString().slice(0, 10);
|
|
119
|
-
// --- END ADD ---
|
|
120
|
-
|
|
121
112
|
if(needsYesterdayInsights) {
|
|
122
113
|
tasks.push((async()=>{ const prev=new Date(date); prev.setUTCDate(prev.getUTCDate()-1); const prevStr=prev.toISOString().slice(0,10);
|
|
123
114
|
logger.log('INFO', `[PassRunner] Loading YESTERDAY insights data for ${prevStr}`);
|
|
@@ -127,28 +118,18 @@ async function loadHistoricalData(date, calcs, config, deps, rootData) {
|
|
|
127
118
|
logger.log('INFO', `[PassRunner] Loading YESTERDAY social data for ${prevStr}`);
|
|
128
119
|
updated.yesterdaySocialPostInsights=await loadDailySocialPostInsights(config,deps,prevStr); })());}
|
|
129
120
|
|
|
130
|
-
// --- ADD THIS BLOCK ---
|
|
131
121
|
if(needsYesterdayPortfolio) {
|
|
132
122
|
tasks.push((async()=>{
|
|
133
123
|
logger.log('INFO', `[PassRunner] Getting YESTERDAY portfolio refs for ${prevStr}`);
|
|
134
|
-
// This adds the refs to the 'fullRoot' object for later
|
|
135
124
|
updated.yesterdayPortfolioRefs = await getPortfolioPartRefs(config, deps, prevStr);
|
|
136
125
|
})());
|
|
137
126
|
}
|
|
138
|
-
// --- END ADD ---
|
|
139
|
-
|
|
140
127
|
await Promise.all(tasks);
|
|
141
128
|
return updated;
|
|
142
129
|
}
|
|
143
130
|
|
|
144
131
|
/**
|
|
145
132
|
* --- REFACTORED: Stage 8: Stream and process data for standard calculations ---
|
|
146
|
-
* This function now streams today's portfolios, yesterday's portfolios,
|
|
147
|
-
* and today's history data in parallel to avoid OOM errors.
|
|
148
|
-
* It loads chunks of all three streams, processes UIDs found in the
|
|
149
|
-
* main (today's portfolio) stream, and then deletes processed users
|
|
150
|
-
* from the historical maps to free memory.
|
|
151
|
-
* --- FIX: Added portfolioRefs and historyRefs to signature ---
|
|
152
133
|
*/
|
|
153
134
|
async function streamAndProcess(dateStr, state, passName, config, deps, rootData, portfolioRefs, historyRefs) {
|
|
154
135
|
const { logger, calculationUtils } = deps;
|
|
@@ -156,231 +137,101 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
|
|
|
156
137
|
const calcsThatStreamPortfolio = Object.values(state).filter(calc => calc && calc.manifest && (calc.manifest.rootDataDependencies.includes('portfolio') || calc.manifest.category === 'speculators'));
|
|
157
138
|
const context={instrumentMappings:(await calculationUtils.loadInstrumentMappings()).instrumentToTicker, sectorMapping:(await calculationUtils.loadInstrumentMappings()).instrumentToSector, todayDateStr:dateStr, dependencies:deps, config};
|
|
158
139
|
let firstUser=true;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,null,null];
|
|
169
|
-
}
|
|
170
|
-
try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} (non-stream)`,{err:e.message});}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (calcsThatStreamPortfolio.length === 0) {
|
|
176
|
-
logger.log('INFO', `[${passName}] No portfolio-streaming calcs to run for ${dateStr}. Skipping stream.`);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
140
|
+
for(const name in state){ const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
|
|
141
|
+
const cat=calc.manifest.category;
|
|
142
|
+
if(cat==='socialPosts'||cat==='insights') {
|
|
143
|
+
if (firstUser) {
|
|
144
|
+
logger.log('INFO', `[${passName}] Running non-streaming calc: ${name}`);
|
|
145
|
+
let args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,null,null];
|
|
146
|
+
if(calc.manifest.isHistorical) { args=[null,null,null,{...context, userType: 'n/a'},todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,null,null]; }
|
|
147
|
+
try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} (non-stream)`,{err:e.message});} } } }
|
|
148
|
+
if (calcsThatStreamPortfolio.length === 0) { logger.log('INFO', `[${passName}] No portfolio-streaming calcs to run for ${dateStr}. Skipping stream.`); return; }
|
|
180
149
|
logger.log('INFO', `[${passName}] Streaming portfolio & historical data for ${calcsThatStreamPortfolio.length} calcs...`);
|
|
181
|
-
|
|
182
150
|
const prevDate = new Date(dateStr + 'T00:00:00Z');
|
|
183
151
|
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
184
152
|
const prevDateStr = prevDate.toISOString().slice(0, 10);
|
|
185
|
-
|
|
186
153
|
const needsYesterdayPortfolio = Object.values(state).some(c => c && c.manifest.isHistorical && c.manifest.rootDataDependencies.includes('portfolio'));
|
|
187
154
|
const needsTodayHistory = Object.values(state).some(c => c && c.manifest.rootDataDependencies.includes('history'));
|
|
188
|
-
|
|
189
|
-
// --- FIX: Pass pre-fetched refs to the generators ---
|
|
190
|
-
// Use the refs from fullRoot (which is rootData + yesterdayPortfolioRefs)
|
|
191
155
|
const yP_iterator = needsYesterdayPortfolio ? streamPortfolioData(config, deps, prevDateStr, rootData.yesterdayPortfolioRefs) : null;
|
|
192
|
-
// Use the historyRefs argument
|
|
193
156
|
const hT_iterator = needsTodayHistory ? streamHistoryData(config, deps, dateStr, historyRefs) : null;
|
|
194
|
-
// --- END FIX ---
|
|
195
|
-
|
|
196
157
|
let yesterdayPortfolios = {};
|
|
197
158
|
let todayHistoryData = {};
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
Object.assign(yesterdayPortfolios, (await yP_iterator.next()).value || {});
|
|
201
|
-
logger.log('INFO', `[${passName}] Loaded first chunk of yesterday's portfolios.`);
|
|
202
|
-
}
|
|
203
|
-
if (hT_iterator) {
|
|
204
|
-
Object.assign(todayHistoryData, (await hT_iterator.next()).value || {});
|
|
205
|
-
logger.log('INFO', `[${passName}] Loaded first chunk of today's history.`);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// --- FIX: Pass pre-fetched portfolioRefs to the main loop generator ---
|
|
159
|
+
if (yP_iterator) { Object.assign(yesterdayPortfolios, (await yP_iterator.next()).value || {}); logger.log('INFO', `[${passName}] Loaded first chunk of yesterday's portfolios.`); }
|
|
160
|
+
if (hT_iterator) { Object.assign(todayHistoryData, (await hT_iterator.next()).value || {}); logger.log('INFO', `[${passName}] Loaded first chunk of today's history.`); }
|
|
209
161
|
for await (const chunk of streamPortfolioData(config, deps, dateStr, portfolioRefs)) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
|
|
230
|
-
const cat=calc.manifest.category, isSocialOrInsights=cat==='socialPosts'||cat==='insights', isHistorical=calc.manifest.isHistorical, isSpec=cat==='speculators';
|
|
231
|
-
|
|
232
|
-
if(isSocialOrInsights) continue; // Skip non-streaming calcs
|
|
233
|
-
|
|
234
|
-
let args=[p,null,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,hT,null];
|
|
235
|
-
|
|
236
|
-
if(isHistorical){
|
|
237
|
-
if(!pY && (cat !== 'behavioural' && name !== 'historical-performance-aggregator')) continue;
|
|
238
|
-
args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,hT,null];
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if((userType==='normal'&&isSpec)||(userType==='speculator'&&!isSpec&&name!=='users-processed')) continue;
|
|
242
|
-
|
|
243
|
-
try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} for ${uid}`,{err:e.message});}
|
|
244
|
-
}
|
|
245
|
-
firstUser=false;
|
|
246
|
-
|
|
247
|
-
if (pY) { delete yesterdayPortfolios[uid]; }
|
|
248
|
-
if (hT) { delete todayHistoryData[uid]; }
|
|
249
|
-
}
|
|
250
|
-
}
|
|
162
|
+
if (yP_iterator) { Object.assign(yesterdayPortfolios, (await yP_iterator.next()).value || {}); }
|
|
163
|
+
if (hT_iterator) { Object.assign(todayHistoryData, (await hT_iterator.next()).value || {}); }
|
|
164
|
+
for(const uid in chunk){
|
|
165
|
+
const p = chunk[uid]; if(!p) continue;
|
|
166
|
+
const userType=p.PublicPositions?'speculator':'normal';
|
|
167
|
+
context.userType=userType;
|
|
168
|
+
const pY = yesterdayPortfolios[uid] || null;
|
|
169
|
+
const hT = todayHistoryData[uid] || null;
|
|
170
|
+
for(const name in state){
|
|
171
|
+
const calc=state[name]; if(!calc||typeof calc.process!=='function') continue;
|
|
172
|
+
const cat=calc.manifest.category, isSocialOrInsights=cat==='socialPosts'||cat==='insights', isHistorical=calc.manifest.isHistorical, isSpec=cat==='speculators';
|
|
173
|
+
if(isSocialOrInsights) continue;
|
|
174
|
+
let args=[p,null,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,hT,null];
|
|
175
|
+
if(isHistorical){ if(!pY && (cat !== 'behavioural' && name !== 'historical-performance-aggregator')) continue; args=[p,pY,uid,context,todayInsights,yesterdayInsights,todaySocialPostInsights,yesterdaySocialPostInsights,hT,null]; }
|
|
176
|
+
if((userType==='normal'&&isSpec)||(userType==='speculator'&&!isSpec&&name!=='users-processed')) continue;
|
|
177
|
+
try{ await Promise.resolve(calc.process(...args)); } catch(e){logger.log('WARN',`Process error ${name} for ${uid}`,{err:e.message});} }
|
|
178
|
+
firstUser=false;
|
|
179
|
+
if (pY) { delete yesterdayPortfolios[uid]; }
|
|
180
|
+
if (hT) { delete todayHistoryData[uid]; } } }
|
|
251
181
|
logger.log('INFO', `[${passName}] Finished streaming data for ${dateStr}.`);
|
|
252
182
|
}
|
|
253
183
|
|
|
254
184
|
/** Stage 9: Run standard computations */
|
|
255
185
|
async function runStandardComputationPass(date, calcs, passName, config, deps, rootData) {
|
|
256
186
|
const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
|
|
257
|
-
if (calcs.length === 0) {
|
|
258
|
-
logger.log('INFO', `[${passName}] No standard calcs to run for ${dStr} after filtering.`);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
187
|
+
if (calcs.length === 0) { logger.log('INFO', `[${passName}] No standard calcs to run for ${dStr} after filtering.`); return; }
|
|
261
188
|
logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
|
|
262
189
|
const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
|
|
263
190
|
const state = initializeCalculators(calcs, logger);
|
|
264
|
-
|
|
265
|
-
// --- FIX: Pass portfolioRefs and historyRefs from rootData ---
|
|
266
191
|
await streamAndProcess(dStr, state, passName, config, deps, fullRoot, rootData.portfolioRefs, rootData.historyRefs);
|
|
267
|
-
// --- END FIX ---
|
|
268
|
-
|
|
269
192
|
let success = 0;
|
|
270
193
|
const failedCalcs = [];
|
|
271
194
|
const standardWrites = [];
|
|
272
195
|
const shardedWrites = {};
|
|
273
|
-
|
|
274
|
-
// === NEW: Collect schemas ===
|
|
275
196
|
const schemasToStore = [];
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// === CHANGED: Capture static schema ===
|
|
303
|
-
const calcClass = calc.manifest.class;
|
|
304
|
-
let staticSchema = null;
|
|
305
|
-
if (calcClass && typeof calcClass.getSchema === 'function') {
|
|
306
|
-
try {
|
|
307
|
-
staticSchema = calcClass.getSchema();
|
|
308
|
-
} catch (e) {
|
|
309
|
-
logger.log('WARN', `[SchemaCapture] Failed to get static schema for ${name}`, { err: e.message });
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
logger.log('TRACE', `[SchemaCapture] No static schema found for ${name}. Skipping manifest entry.`);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (staticSchema) {
|
|
316
|
-
schemasToStore.push({
|
|
317
|
-
name,
|
|
318
|
-
category: calc.manifest.category,
|
|
319
|
-
schema: staticSchema, // <-- Use the static schema
|
|
320
|
-
metadata: {
|
|
321
|
-
isHistorical: calc.manifest.isHistorical || false,
|
|
322
|
-
dependencies: calc.manifest.dependencies || [],
|
|
323
|
-
rootDataDependencies: calc.manifest.rootDataDependencies || [],
|
|
324
|
-
pass: calc.manifest.pass,
|
|
325
|
-
type: calc.manifest.type || 'standard'
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
// === END CHANGED SECTION ===
|
|
330
|
-
|
|
331
|
-
success++;
|
|
332
|
-
}
|
|
333
|
-
} catch (e) {
|
|
334
|
-
logger.log('ERROR', `getResult failed ${name} for ${dStr}`, { err: e.message, stack: e.stack });
|
|
335
|
-
failedCalcs.push(name);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// === NEW: Store schemas asynchronously (don't block computation) ===
|
|
340
|
-
if (schemasToStore.length > 0) {
|
|
341
|
-
// This function is now imported from the simplified schema_capture.js
|
|
342
|
-
batchStoreSchemas(deps, config, schemasToStore).catch(err => {
|
|
343
|
-
logger.log('WARN', '[SchemaCapture] Non-blocking schema storage failed', {
|
|
344
|
-
errorMessage: err.message
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (standardWrites.length > 0) {
|
|
350
|
-
await commitBatchInChunks(config, deps, standardWrites, `${passName} Standard ${dStr}`);
|
|
351
|
-
}
|
|
352
|
-
|
|
197
|
+
for (const name in state) { const calc = state[name];
|
|
198
|
+
if (!calc || typeof calc.getResult !== 'function') continue;
|
|
199
|
+
try { const result = await Promise.resolve(calc.getResult());
|
|
200
|
+
if (result && Object.keys(result).length > 0) {
|
|
201
|
+
const standardResult = {};
|
|
202
|
+
for (const key in result) {
|
|
203
|
+
if (key.startsWith('sharded_')) {
|
|
204
|
+
const shardedData = result[key];
|
|
205
|
+
for (const collectionName in shardedData) {
|
|
206
|
+
if (!shardedWrites[collectionName]) shardedWrites[collectionName] = {};
|
|
207
|
+
Object.assign(shardedWrites[collectionName], shardedData[collectionName]); }
|
|
208
|
+
} else { standardResult[key] = result[key]; }}
|
|
209
|
+
if (Object.keys(standardResult).length > 0) {
|
|
210
|
+
const docRef = deps.db.collection(config.resultsCollection).doc(dStr) .collection(config.resultsSubcollection).doc(calc.manifest.category) .collection(config.computationsSubcollection).doc(name);
|
|
211
|
+
standardWrites.push({ ref: docRef, data: standardResult });}
|
|
212
|
+
const calcClass = calc.manifest.class;
|
|
213
|
+
let staticSchema = null;
|
|
214
|
+
if (calcClass && typeof calcClass.getSchema === 'function') {
|
|
215
|
+
try { staticSchema = calcClass.getSchema(); } catch (e) { logger.log('WARN', `[SchemaCapture] Failed to get static schema for ${name}`, { err: e.message }); }
|
|
216
|
+
} else { logger.log('TRACE', `[SchemaCapture] No static schema found for ${name}. Skipping manifest entry.`); }
|
|
217
|
+
if (staticSchema) {
|
|
218
|
+
schemasToStore.push({ name, category: calc.manifest.category, schema: staticSchema, metadata: { isHistorical: calc.manifest.isHistorical || false, dependencies: calc.manifest.dependencies || [], rootDataDependencies: calc.manifest.rootDataDependencies || [], pass: calc.manifest.pass, type: calc.manifest.type || 'standard' } }); }
|
|
219
|
+
success++; } } catch (e) { logger.log('ERROR', `getResult failed ${name} for ${dStr}`, { err: e.message, stack: e.stack }); failedCalcs.push(name); } }
|
|
220
|
+
if (schemasToStore.length > 0) { batchStoreSchemas(deps, config, schemasToStore).catch(err => { logger.log('WARN', '[SchemaCapture] Non-blocking schema storage failed', { errorMessage: err.message }); });}
|
|
221
|
+
if (standardWrites.length > 0) { await commitBatchInChunks(config, deps, standardWrites, `${passName} Standard ${dStr}`); }
|
|
353
222
|
for (const docPath in shardedWrites) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
docRef = deps.db.collection(collection).doc(docPath);
|
|
364
|
-
}
|
|
365
|
-
if (docData && typeof docData === 'object' && !Array.isArray(docData)) {
|
|
366
|
-
shardedDocWrites.push({ ref: docRef, data: docData });
|
|
367
|
-
} else {
|
|
368
|
-
logger.log('ERROR', `[${passName}] Invalid sharded document data for ${docPath}. Not an object.`, { data: docData });
|
|
369
|
-
}
|
|
370
|
-
if (shardedDocWrites.length > 0) {
|
|
371
|
-
await commitBatchInChunks(config, deps, shardedDocWrites, `${passName} Sharded ${docPath} ${dStr}`);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
223
|
+
const docData = shardedWrites[docPath];
|
|
224
|
+
const shardedDocWrites = [];
|
|
225
|
+
let docRef;
|
|
226
|
+
if (docPath.includes('/')) { docRef = deps.db.doc(docPath); } else {
|
|
227
|
+
const collection = (docPath.startsWith('user_profile_history')) ? config.shardedUserProfileCollection : config.shardedProfitabilityCollection;
|
|
228
|
+
docRef = deps.db.collection(collection).doc(docPath); }
|
|
229
|
+
if (docData && typeof docData === 'object' && !Array.isArray(docData)) {shardedDocWrites.push({ ref: docRef, data: docData });
|
|
230
|
+
} else { logger.log('ERROR', `[${passName}] Invalid sharded document data for ${docPath}. Not an object.`, { data: docData }); }
|
|
231
|
+
if (shardedDocWrites.length > 0) { await commitBatchInChunks(config, deps, shardedDocWrites, `${passName} Sharded ${docPath} ${dStr}`); } }
|
|
375
232
|
const logMetadata = {};
|
|
376
|
-
if (failedCalcs.length > 0) {
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
logger.log(
|
|
380
|
-
success === calcs.length ? 'SUCCESS' : 'WARN',
|
|
381
|
-
`[${passName}] Completed ${dStr}. Success: ${success}/${calcs.length}`,
|
|
382
|
-
logMetadata
|
|
383
|
-
);
|
|
233
|
+
if (failedCalcs.length > 0) { logMetadata.failedComputations = failedCalcs; }
|
|
234
|
+
logger.log(success === calcs.length ? 'SUCCESS' : 'WARN', `[${passName}] Completed ${dStr}. Success: ${success}/${calcs.length}`, logMetadata );
|
|
384
235
|
}
|
|
385
236
|
|
|
386
237
|
/**
|
|
@@ -388,116 +239,44 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
|
|
|
388
239
|
*/
|
|
389
240
|
async function runMetaComputationPass(date, calcs, passName, config, deps, fetchedDeps, rootData) {
|
|
390
241
|
const dStr = date.toISOString().slice(0, 10), logger = deps.logger;
|
|
391
|
-
if (calcs.length === 0) {
|
|
392
|
-
logger.log('INFO', `[${passName}] No meta calcs to run for ${dStr} after filtering.`);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
242
|
+
if (calcs.length === 0) { logger.log('INFO', `[${passName}] No meta calcs to run for ${dStr} after filtering.`); return; }
|
|
395
243
|
logger.log('INFO', `[${passName}] Running ${dStr} with ${calcs.length} calcs.`);
|
|
396
244
|
const fullRoot = await loadHistoricalData(date, calcs, config, deps, rootData);
|
|
397
|
-
|
|
398
245
|
let success = 0;
|
|
399
246
|
const failedCalcs = [];
|
|
400
247
|
const standardWrites = [];
|
|
401
248
|
const shardedWrites = {};
|
|
402
|
-
|
|
403
|
-
// === NEW: Collect schemas ===
|
|
404
249
|
const schemasToStore = [];
|
|
405
|
-
|
|
406
250
|
for (const mCalc of calcs) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
.collection(config.resultsSubcollection).doc(mCalc.category)
|
|
432
|
-
.collection(config.computationsSubcollection).doc(name);
|
|
433
|
-
standardWrites.push({ ref: docRef, data: standardResult });
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// === CHANGED: Capture static schema ===
|
|
437
|
-
const calcClass = mCalc.class;
|
|
438
|
-
let staticSchema = null;
|
|
439
|
-
if (calcClass && typeof calcClass.getSchema === 'function') {
|
|
440
|
-
try {
|
|
441
|
-
staticSchema = calcClass.getSchema();
|
|
442
|
-
} catch (e) {
|
|
443
|
-
logger.log('WARN', `[SchemaCapture] Failed to get static schema for ${name}`, { err: e.message });
|
|
444
|
-
}
|
|
445
|
-
} else {
|
|
446
|
-
logger.log('TRACE', `[SchemaCapture] No static schema found for ${name}. Skipping manifest entry.`);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (staticSchema) {
|
|
450
|
-
schemasToStore.push({
|
|
451
|
-
name,
|
|
452
|
-
category: mCalc.category,
|
|
453
|
-
schema: staticSchema, // <-- Use the static schema
|
|
454
|
-
metadata: {
|
|
455
|
-
isHistorical: mCalc.isHistorical || false,
|
|
456
|
-
dependencies: mCalc.dependencies || [],
|
|
457
|
-
rootDataDependencies: mCalc.rootDataDependencies || [],
|
|
458
|
-
pass: mCalc.pass,
|
|
459
|
-
type: 'meta'
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
// === END CHANGED SECTION ===
|
|
464
|
-
|
|
465
|
-
success++;
|
|
466
|
-
}
|
|
467
|
-
} catch (e) {
|
|
468
|
-
logger.log('ERROR', `Meta-calc failed ${name} for ${dStr}`, { err: e.message, stack: e.stack });
|
|
469
|
-
failedCalcs.push(name);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// === NEW: Store schemas asynchronously ===
|
|
474
|
-
if (schemasToStore.length > 0) {
|
|
475
|
-
// This function is now imported from the simplified schema_capture.js
|
|
476
|
-
batchStoreSchemas(deps, config, schemasToStore).catch(err => {
|
|
477
|
-
logger.log('WARN', '[SchemaCapture] Non-blocking schema storage failed', {
|
|
478
|
-
errorMessage: err.message
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (standardWrites.length > 0) {
|
|
484
|
-
await commitBatchInChunks(config, deps, standardWrites, `${passName} Meta ${dStr}`);
|
|
485
|
-
}
|
|
486
|
-
|
|
251
|
+
const name = normalizeName(mCalc.name), Cl = mCalc.class;
|
|
252
|
+
if (typeof Cl !== 'function') { logger.log('ERROR', `Invalid class ${name}`); failedCalcs.push(name); continue; }
|
|
253
|
+
const inst = new Cl();
|
|
254
|
+
try { const result = await Promise.resolve(inst.process(dStr, { ...deps, rootData: fullRoot }, config, fetchedDeps));
|
|
255
|
+
if (result && Object.keys(result).length > 0) {
|
|
256
|
+
const standardResult = {};
|
|
257
|
+
for (const key in result) {
|
|
258
|
+
if (key.startsWith('sharded_')) { const shardedData = result[key]; for (const collectionName in shardedData) {
|
|
259
|
+
if (!shardedWrites[collectionName]) shardedWrites[collectionName] = {}; Object.assign(shardedWrites[collectionName], shardedData[collectionName]); }
|
|
260
|
+
} else { standardResult[key] = result[key]; } }
|
|
261
|
+
if (Object.keys(standardResult).length > 0) {
|
|
262
|
+
const docRef = deps.db.collection(config.resultsCollection).doc(dStr) .collection(config.resultsSubcollection).doc(mCalc.category) .collection(config.computationsSubcollection).doc(name);
|
|
263
|
+
standardWrites.push({ ref: docRef, data: standardResult }); }
|
|
264
|
+
const calcClass = mCalc.class;
|
|
265
|
+
let staticSchema = null;
|
|
266
|
+
if (calcClass && typeof calcClass.getSchema === 'function') {
|
|
267
|
+
try { staticSchema = calcClass.getSchema();
|
|
268
|
+
} catch (e) { logger.log('WARN', `[SchemaCapture] Failed to get static schema for ${name}`, { err: e.message }); }
|
|
269
|
+
} else { logger.log('TRACE', `[SchemaCapture] No static schema found for ${name}. Skipping manifest entry.`); }
|
|
270
|
+
if (staticSchema) { schemasToStore.push({ name, category: mCalc.category, schema: staticSchema, metadata: { isHistorical: mCalc.isHistorical || false, dependencies: mCalc.dependencies || [], rootDataDependencies: mCalc.rootDataDependencies || [], pass: mCalc.pass, type: 'meta' } }); }
|
|
271
|
+
success++; }
|
|
272
|
+
} catch (e) { logger.log('ERROR', `Meta-calc failed ${name} for ${dStr}`, { err: e.message, stack: e.stack }); failedCalcs.push(name); } }
|
|
273
|
+
if (schemasToStore.length > 0) { batchStoreSchemas(deps, config, schemasToStore).catch(err => { logger.log('WARN', '[SchemaCapture] Non-blocking schema storage failed', { errorMessage: err.message }); }); }
|
|
274
|
+
if (standardWrites.length > 0) { await commitBatchInChunks(config, deps, standardWrites, `${passName} Meta ${dStr}`);}
|
|
487
275
|
for (const collectionName in shardedWrites) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
? deps.db.doc(docId)
|
|
493
|
-
: deps.db.collection(collectionName).doc(docId);
|
|
494
|
-
shardedDocWrites.push({ ref: docRef, data: docs[docId] });
|
|
495
|
-
}
|
|
496
|
-
if (shardedDocWrites.length > 0) {
|
|
497
|
-
await commitBatchInChunks(config, deps, shardedDocWrites, `${passName} Sharded ${collectionName} ${dStr}`);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
276
|
+
const docs = shardedWrites[collectionName];
|
|
277
|
+
const shardedDocWrites = [];
|
|
278
|
+
for (const docId in docs) { const docRef = docId.includes('/') ? deps.db.doc(docId) : deps.db.collection(collectionName).doc(docId); shardedDocWrites.push({ ref: docRef, data: docs[docId] }); }
|
|
279
|
+
if (shardedDocWrites.length > 0) { await commitBatchInChunks(config, deps, shardedDocWrites, `${passName} Sharded ${collectionName} ${dStr}`); } }
|
|
501
280
|
const logMetadata = {};
|
|
502
281
|
if (failedCalcs.length > 0) { logMetadata.failedComputations = failedCalcs; }
|
|
503
282
|
logger.log( success === calcs.length ? 'SUCCESS' : 'WARN', `[${passName}] Completed ${dStr}. Success: ${success}/${calcs.length}`, logMetadata );
|