bulltrackers-module 1.0.214 → 1.0.215
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/computation-system/controllers/computation_controller.js +25 -82
- package/functions/computation-system/helpers/computation_dispatcher.js +11 -24
- package/functions/computation-system/helpers/computation_manifest_builder.js +19 -40
- package/functions/computation-system/helpers/computation_pass_runner.js +23 -63
- package/functions/computation-system/helpers/computation_worker.js +11 -53
- package/functions/computation-system/helpers/orchestration_helpers.js +89 -208
- package/functions/task-engine/helpers/update_helpers.js +34 -70
- package/package.json +1 -1
- package/functions/computation-system/layers/math_primitives.js +0 -744
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* FILENAME: bulltrackers-module/functions/computation-system/helpers/orchestration_helpers.js
|
|
3
3
|
* FIXED: 'commitResults' now records the CODE HASH in the status document
|
|
4
4
|
* instead of a boolean, enabling auto-invalidation on code changes.
|
|
5
|
+
* UPDATED: Dynamic math context loading from 'layers/index.js' to support modular layers.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const { ComputationController } = require('../controllers/computation_controller');
|
|
@@ -13,14 +14,32 @@ const {
|
|
|
13
14
|
getRelevantShardRefs, loadDataByRefs
|
|
14
15
|
} = require('../utils/data_loader');
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
TimeSeries, priceExtractor
|
|
20
|
-
} = require('../layers/math_primitives.js');
|
|
17
|
+
// --- DYNAMIC LAYER LOADING ---
|
|
18
|
+
// Replaces the old static import from 'math_primitives.js'
|
|
19
|
+
const mathLayer = require('../layers/index.js');
|
|
21
20
|
|
|
22
21
|
const pLimit = require('p-limit');
|
|
23
22
|
|
|
23
|
+
// Mappings to ensure backward compatibility with existing calculation code
|
|
24
|
+
// (e.g. allowing 'math.compute' to resolve to 'MathPrimitives')
|
|
25
|
+
const LEGACY_MAPPING = {
|
|
26
|
+
DataExtractor: 'extract',
|
|
27
|
+
HistoryExtractor: 'history',
|
|
28
|
+
MathPrimitives: 'compute',
|
|
29
|
+
Aggregators: 'aggregate',
|
|
30
|
+
Validators: 'validate',
|
|
31
|
+
SignalPrimitives: 'signals',
|
|
32
|
+
SCHEMAS: 'schemas',
|
|
33
|
+
DistributionAnalytics: 'distribution',
|
|
34
|
+
TimeSeries: 'TimeSeries',
|
|
35
|
+
priceExtractor: 'priceExtractor',
|
|
36
|
+
InsightsExtractor: 'insights',
|
|
37
|
+
UserClassifier: 'classifier',
|
|
38
|
+
CognitiveBiases: 'bias',
|
|
39
|
+
SkillAttribution: 'skill',
|
|
40
|
+
Psychometrics: 'psychometrics'
|
|
41
|
+
};
|
|
42
|
+
|
|
24
43
|
function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[calc.pass] = acc[calc.pass] || []).push(calc); return acc; }, {}); }
|
|
25
44
|
|
|
26
45
|
/**
|
|
@@ -28,39 +47,26 @@ function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[ca
|
|
|
28
47
|
*/
|
|
29
48
|
function validateResultPatterns(logger, calcName, results, category) {
|
|
30
49
|
if (category === 'speculator' || category === 'speculators') return;
|
|
31
|
-
|
|
32
50
|
const tickers = Object.keys(results);
|
|
33
51
|
const totalItems = tickers.length;
|
|
34
|
-
|
|
35
52
|
if (totalItems < 5) return;
|
|
36
|
-
|
|
37
53
|
const sampleTicker = tickers.find(t => results[t] && typeof results[t] === 'object');
|
|
38
54
|
if (!sampleTicker) return;
|
|
39
|
-
|
|
40
55
|
const keys = Object.keys(results[sampleTicker]);
|
|
41
|
-
|
|
42
56
|
keys.forEach(key => {
|
|
43
57
|
if (key.startsWith('_')) return;
|
|
44
|
-
|
|
45
58
|
let nullCount = 0;
|
|
46
59
|
let nanCount = 0;
|
|
47
60
|
let undefinedCount = 0;
|
|
48
|
-
|
|
49
61
|
for (const t of tickers) {
|
|
50
62
|
const val = results[t][key];
|
|
51
63
|
if (val === null) nullCount++;
|
|
52
64
|
if (val === undefined) undefinedCount++;
|
|
53
65
|
if (typeof val === 'number' && isNaN(val)) nanCount++;
|
|
54
66
|
}
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
} else if (undefinedCount === totalItems) {
|
|
59
|
-
logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is UNDEFINED for 100% of ${totalItems} items.`);
|
|
60
|
-
}
|
|
61
|
-
else if (nullCount > (totalItems * 0.9)) {
|
|
62
|
-
logger.log('WARN', `[DataQuality] Calc '${calcName}' field '${key}' is NULL for ${nullCount}/${totalItems} items.`);
|
|
63
|
-
}
|
|
67
|
+
if (nanCount === totalItems) { logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is NaN for 100% of ${totalItems} items.`);
|
|
68
|
+
} else if (undefinedCount === totalItems) { logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is UNDEFINED for 100% of ${totalItems} items.`); }
|
|
69
|
+
else if (nullCount > (totalItems * 0.9)) { logger.log('WARN', `[DataQuality] Calc '${calcName}' field '${key}' is NULL for ${nullCount}/${totalItems} items.`); }
|
|
64
70
|
});
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -81,8 +87,7 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
81
87
|
const { logger } = dependencies;
|
|
82
88
|
const dateToProcess = new Date(dateStr + 'T00:00:00Z');
|
|
83
89
|
let portfolioRefs = [], historyRefs = [];
|
|
84
|
-
let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false, hasPrices = false;
|
|
85
|
-
let insightsData = null, socialData = null;
|
|
90
|
+
let hasPortfolio = false, hasInsights = false, hasSocial = false, hasHistory = false, hasPrices = false, insightsData = null, socialData = null;
|
|
86
91
|
|
|
87
92
|
try {
|
|
88
93
|
const tasks = [];
|
|
@@ -91,12 +96,8 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
91
96
|
if (dateToProcess >= earliestDates.social) tasks.push(loadDailySocialPostInsights (config, dependencies, dateStr).then(r => { socialData = r; hasSocial = !!r; }));
|
|
92
97
|
if (dateToProcess >= earliestDates.history) tasks.push(getHistoryPartRefs (config, dependencies, dateStr).then(r => { historyRefs = r; hasHistory = !!r.length; }));
|
|
93
98
|
|
|
94
|
-
if (dateToProcess >= earliestDates.price) {
|
|
95
|
-
tasks.push(checkPriceDataAvailability(config, dependencies).then(r => { hasPrices = r; }));
|
|
96
|
-
}
|
|
97
|
-
|
|
99
|
+
if (dateToProcess >= earliestDates.price) { tasks.push(checkPriceDataAvailability(config, dependencies).then(r => { hasPrices = r; })); }
|
|
98
100
|
await Promise.all(tasks);
|
|
99
|
-
|
|
100
101
|
if (!(hasPortfolio || hasInsights || hasSocial || hasHistory || hasPrices)) return null;
|
|
101
102
|
|
|
102
103
|
return {
|
|
@@ -105,97 +106,55 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
105
106
|
todayInsights: insightsData,
|
|
106
107
|
todaySocialPostInsights: socialData,
|
|
107
108
|
status: { hasPortfolio, hasInsights, hasSocial, hasHistory, hasPrices },
|
|
108
|
-
yesterdayPortfolioRefs: null
|
|
109
|
+
yesterdayPortfolioRefs: null
|
|
109
110
|
};
|
|
110
111
|
|
|
111
|
-
} catch (err) {
|
|
112
|
-
logger.log('ERROR', `Error checking data: ${err.message}`);
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
112
|
+
} catch (err) { logger.log('ERROR', `Error checking data: ${err.message}`); return null; }
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
async function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
async function firestoreHelper(action, { key, updates, config, db }) {
|
|
116
|
+
const collections = { price: config.priceCollection || 'asset_prices', status: config.computationStatusCollection || 'computation_status', };
|
|
117
|
+
|
|
118
|
+
switch (action) {
|
|
119
|
+
case 'checkAvailability': {
|
|
120
|
+
try { const snapshot = await db.collection(collections.price).limit(1).get(); return !snapshot.empty; } catch (e) { return false; } }
|
|
121
|
+
|
|
122
|
+
case 'fetchStatus': {
|
|
123
|
+
if (!key) throw new Error('fetchStatus requires a key');
|
|
124
|
+
const docRef = db.collection(collections.status).doc(key);
|
|
125
|
+
const snap = await docRef.get();
|
|
126
|
+
return snap.exists ? snap.data() : {};
|
|
126
127
|
}
|
|
127
|
-
}
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
case 'updateStatus': {
|
|
130
|
+
if (!key) throw new Error('updateStatus requires a key');
|
|
131
|
+
if (!updates || Object.keys(updates).length === 0) return;
|
|
132
|
+
const docRef = db.collection(collections.status).doc(key);
|
|
133
|
+
await docRef.set(updates, { merge: true });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
const collection = config.computationStatusCollection || 'computation_status';
|
|
138
|
-
const docRef = db.collection(collection).doc('global_status');
|
|
139
|
-
const snap = await docRef.get();
|
|
140
|
-
return snap.exists ? snap.data() : {};
|
|
137
|
+
default: throw new Error(`Unknown action: ${action}`); }
|
|
141
138
|
}
|
|
142
139
|
|
|
143
|
-
async function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await docRef.set(updates, { merge: true });
|
|
148
|
-
}
|
|
140
|
+
async function checkPriceDataAvailability (config, dependencies) { return firestoreHelper('checkAvailability', { config, db: dependencies.db }); }
|
|
141
|
+
async function fetchComputationStatus (dateStr, config, { db }) { return firestoreHelper('fetchStatus', { key: dateStr, config, db }); }
|
|
142
|
+
async function fetchGlobalComputationStatus (config, { db }) { return firestoreHelper('fetchStatus', { key: 'global_status', config, db }); }
|
|
143
|
+
async function updateComputationStatus (dateStr, updates, config, { db }) { return firestoreHelper('updateStatus', { key: dateStr, updates, config, db }); }
|
|
149
144
|
|
|
150
|
-
async function updateGlobalComputationStatus(updatesByDate, config, { db }) {
|
|
151
|
-
if (!updatesByDate || Object.keys(updatesByDate).length === 0) return;
|
|
152
|
-
const collection = config.computationStatusCollection || 'computation_status';
|
|
153
|
-
const docRef = db.collection(collection).doc('global_status');
|
|
154
|
-
const flattenUpdates = {};
|
|
155
|
-
for (const [date, statuses] of Object.entries(updatesByDate)) {
|
|
156
|
-
for (const [calc, status] of Object.entries(statuses)) {
|
|
157
|
-
flattenUpdates[`${date}.${calc}`] = status;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
await docRef.update(flattenUpdates);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
if (err.code === 5) {
|
|
164
|
-
const deepObj = {};
|
|
165
|
-
for (const [date, statuses] of Object.entries(updatesByDate)) {
|
|
166
|
-
deepObj[date] = statuses;
|
|
167
|
-
}
|
|
168
|
-
await docRef.set(deepObj, { merge: true });
|
|
169
|
-
} else {
|
|
170
|
-
throw err;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
145
|
|
|
175
146
|
async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config, { db }, includeSelf = false) {
|
|
176
147
|
const manifestMap = new Map(fullManifest.map(c => [normalizeName(c.name), c]));
|
|
177
148
|
const calcsToFetch = new Set();
|
|
178
|
-
for (const calc of calcsInPass) {
|
|
179
|
-
if (calc.dependencies) { calc.dependencies.forEach(d => calcsToFetch.add(normalizeName(d))); }
|
|
180
|
-
if (includeSelf && calc.isHistorical) { calcsToFetch.add(normalizeName(calc.name)); }
|
|
181
|
-
}
|
|
149
|
+
for (const calc of calcsInPass) { if (calc.dependencies) { calc.dependencies.forEach(d => calcsToFetch.add(normalizeName(d))); } if (includeSelf && calc.isHistorical) { calcsToFetch.add(normalizeName(calc.name)); } }
|
|
182
150
|
if (!calcsToFetch.size) return {};
|
|
183
151
|
const fetched = {};
|
|
184
152
|
const docRefs = [];
|
|
185
153
|
const names = [];
|
|
186
154
|
for (const name of calcsToFetch) {
|
|
187
155
|
const m = manifestMap.get(name);
|
|
188
|
-
if (m) {
|
|
189
|
-
|
|
190
|
-
.collection(config.resultsSubcollection).doc(m.category || 'unknown')
|
|
191
|
-
.collection(config.computationsSubcollection).doc(name));
|
|
192
|
-
names.push(name);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (docRefs.length) {
|
|
196
|
-
const snaps = await db.getAll(...docRefs);
|
|
197
|
-
snaps.forEach((doc, i) => { if (doc.exists && doc.data()._completed) { fetched[names[i]] = doc.data(); } });
|
|
198
|
-
}
|
|
156
|
+
if (m) { docRefs.push(db.collection(config.resultsCollection).doc(dateStr) .collection(config.resultsSubcollection).doc(m.category || 'unknown') .collection(config.computationsSubcollection).doc(name)); names.push(name); } }
|
|
157
|
+
if (docRefs.length) { const snaps = await db.getAll(...docRefs); snaps.forEach((doc, i) => { if (doc.exists && doc.data()._completed) { fetched[names[i]] = doc.data(); } }); }
|
|
199
158
|
return fetched;
|
|
200
159
|
}
|
|
201
160
|
|
|
@@ -203,10 +162,7 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
|
|
|
203
162
|
const { logger } = deps;
|
|
204
163
|
const controller = new ComputationController(config, deps);
|
|
205
164
|
const calcs = Object.values(state).filter(c => c && c.manifest);
|
|
206
|
-
const streamingCalcs = calcs.filter(c =>
|
|
207
|
-
c.manifest.rootDataDependencies.includes('portfolio') ||
|
|
208
|
-
c.manifest.rootDataDependencies.includes('history')
|
|
209
|
-
);
|
|
165
|
+
const streamingCalcs = calcs.filter(c => c.manifest.rootDataDependencies.includes('portfolio') || c.manifest.rootDataDependencies.includes('history') );
|
|
210
166
|
|
|
211
167
|
if (streamingCalcs.length === 0) return;
|
|
212
168
|
|
|
@@ -258,12 +214,7 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
|
|
|
258
214
|
|
|
259
215
|
const state = {};
|
|
260
216
|
for (const c of calcs) {
|
|
261
|
-
try {
|
|
262
|
-
const inst = new c.class();
|
|
263
|
-
inst.manifest = c;
|
|
264
|
-
state[normalizeName(c.name)] = inst;
|
|
265
|
-
logger.log('INFO', `${c.name} calculation running for ${dStr}`);
|
|
266
|
-
}
|
|
217
|
+
try { const inst = new c.class(); inst.manifest = c; state[normalizeName(c.name)] = inst; logger.log('INFO', `${c.name} calculation running for ${dStr}`); }
|
|
267
218
|
catch (e) { logger.log('WARN', `Failed to init ${c.name}`); }
|
|
268
219
|
}
|
|
269
220
|
|
|
@@ -305,10 +256,7 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
305
256
|
|
|
306
257
|
try {
|
|
307
258
|
const result = await calc.getResult();
|
|
308
|
-
if (!result) {
|
|
309
|
-
deps.logger.log('INFO', `${name} for ${dStr}: Skipped (Empty Result)`);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
259
|
+
if (!result) { deps.logger.log('INFO', `${name} for ${dStr}: Skipped (Empty Result)`); continue; }
|
|
312
260
|
|
|
313
261
|
const standardRes = {};
|
|
314
262
|
const shardedWrites = [];
|
|
@@ -320,65 +268,33 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
320
268
|
const sData = result[key];
|
|
321
269
|
for (const colName in sData) {
|
|
322
270
|
const docsMap = sData[colName];
|
|
323
|
-
for (const docId in docsMap) {
|
|
324
|
-
const ref = docId.includes('/') ? deps.db.doc(docId) : deps.db.collection(colName).doc(docId);
|
|
325
|
-
shardedWrites.push({
|
|
326
|
-
ref,
|
|
327
|
-
data: { ...docsMap[docId], _completed: true }
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
271
|
+
for (const docId in docsMap) { const ref = docId.includes('/') ? deps.db.doc(docId) : deps.db.collection(colName).doc(docId); shardedWrites.push({ ref, data: { ...docsMap[docId], _completed: true } }); } }
|
|
331
272
|
if (Object.keys(sData).length > 0) hasData = true;
|
|
332
|
-
} else {
|
|
333
|
-
standardRes[key] = result[key];
|
|
334
|
-
}
|
|
273
|
+
} else { standardRes[key] = result[key]; }
|
|
335
274
|
}
|
|
336
275
|
|
|
337
276
|
// 2. Prepare Standard Result Write
|
|
338
277
|
if (Object.keys(standardRes).length) {
|
|
339
278
|
validateResultPatterns(deps.logger, name, standardRes, calc.manifest.category);
|
|
340
279
|
standardRes._completed = true;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
.collection(config.resultsSubcollection).doc(calc.manifest.category)
|
|
344
|
-
.collection(config.computationsSubcollection).doc(name);
|
|
345
|
-
|
|
346
|
-
calcWrites.push({
|
|
347
|
-
ref: docRef,
|
|
348
|
-
data: standardRes
|
|
349
|
-
});
|
|
280
|
+
const docRef = deps.db.collection(config.resultsCollection).doc(dStr) .collection(config.resultsSubcollection).doc(calc.manifest.category) .collection(config.computationsSubcollection).doc(name);
|
|
281
|
+
calcWrites.push({ ref: docRef, data: standardRes });
|
|
350
282
|
hasData = true;
|
|
351
283
|
}
|
|
352
284
|
|
|
353
285
|
// 3. Queue Schema (Safe to accumulate)
|
|
354
|
-
if (calc.manifest.class.getSchema) {
|
|
355
|
-
const { class: _cls, ...safeMetadata } = calc.manifest;
|
|
356
|
-
schemas.push({
|
|
357
|
-
name, category: calc.manifest.category, schema: calc.manifest.class.getSchema(), metadata: safeMetadata
|
|
358
|
-
});
|
|
359
|
-
}
|
|
286
|
+
if (calc.manifest.class.getSchema) { const { class: _cls, ...safeMetadata } = calc.manifest; schemas.push({ name, category: calc.manifest.category, schema: calc.manifest.class.getSchema(), metadata: safeMetadata }); }
|
|
360
287
|
|
|
361
288
|
// 4. ATTEMPT COMMIT FOR THIS CALCULATION ONLY
|
|
362
289
|
if (hasData) {
|
|
363
290
|
const allWritesForCalc = [...calcWrites, ...shardedWrites];
|
|
364
|
-
|
|
365
291
|
if (allWritesForCalc.length > 0) {
|
|
366
292
|
await commitBatchInChunks(config, deps, allWritesForCalc, `${name} Results`);
|
|
367
|
-
|
|
368
|
-
// --- CRITICAL UPDATE: Store the Smart Hash ---
|
|
369
293
|
successUpdates[name] = calc.manifest.hash || true;
|
|
370
|
-
|
|
371
294
|
deps.logger.log('INFO', `${name} for ${dStr}: \u2714 Success (Written)`);
|
|
372
|
-
} else {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
} else {
|
|
376
|
-
deps.logger.log('INFO', `${name} for ${dStr}: - Empty`);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
} catch (e) {
|
|
380
|
-
deps.logger.log('ERROR', `${name} for ${dStr}: \u2716 FAILED Commit: ${e.message}`);
|
|
381
|
-
}
|
|
295
|
+
} else { deps.logger.log('INFO', `${name} for ${dStr}: - No Data to Write`); }
|
|
296
|
+
} else { deps.logger.log('INFO', `${name} for ${dStr}: - Empty`); }
|
|
297
|
+
} catch (e) { deps.logger.log('ERROR', `${name} for ${dStr}: \u2716 FAILED Commit: ${e.message}`); }
|
|
382
298
|
}
|
|
383
299
|
|
|
384
300
|
// Save Schemas (Best effort, isolated)
|
|
@@ -405,29 +321,20 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
|
|
|
405
321
|
if (targetTickers && targetTickers.length > 0) {
|
|
406
322
|
const tickerToInst = mappings.tickerToInstrument || {};
|
|
407
323
|
targetInstrumentIds = targetTickers.map(t => tickerToInst[t]).filter(id => id);
|
|
408
|
-
if (targetInstrumentIds.length === 0) {
|
|
409
|
-
logger.log('WARN', '[BatchPrice] Target tickers provided but no IDs found. Aborting.');
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
324
|
+
if (targetInstrumentIds.length === 0) { logger.log('WARN', '[BatchPrice] Target tickers provided but no IDs found. Aborting.'); return; } }
|
|
413
325
|
|
|
414
326
|
const allShardRefs = await getRelevantShardRefs(config, deps, targetInstrumentIds);
|
|
415
327
|
|
|
416
|
-
if (!allShardRefs.length) {
|
|
417
|
-
logger.log('WARN', '[BatchPrice] No relevant price shards found. Exiting.');
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
328
|
+
if (!allShardRefs.length) { logger.log('WARN', '[BatchPrice] No relevant price shards found. Exiting.'); return; }
|
|
420
329
|
|
|
421
330
|
const OUTER_CONCURRENCY_LIMIT = 2;
|
|
422
|
-
const SHARD_BATCH_SIZE
|
|
423
|
-
const WRITE_BATCH_LIMIT
|
|
331
|
+
const SHARD_BATCH_SIZE = 20;
|
|
332
|
+
const WRITE_BATCH_LIMIT = 50;
|
|
424
333
|
|
|
425
334
|
logger.log('INFO', `[BatchPrice] Execution Plan: ${dateStrings.length} days, ${allShardRefs.length} shards. Concurrency: ${OUTER_CONCURRENCY_LIMIT}.`);
|
|
426
335
|
|
|
427
336
|
const shardChunks = [];
|
|
428
|
-
for (let i = 0; i < allShardRefs.length; i += SHARD_BATCH_SIZE) {
|
|
429
|
-
shardChunks.push(allShardRefs.slice(i, i + SHARD_BATCH_SIZE));
|
|
430
|
-
}
|
|
337
|
+
for (let i = 0; i < allShardRefs.length; i += SHARD_BATCH_SIZE) { shardChunks.push(allShardRefs.slice(i, i + SHARD_BATCH_SIZE)); }
|
|
431
338
|
|
|
432
339
|
const outerLimit = pLimit(OUTER_CONCURRENCY_LIMIT);
|
|
433
340
|
|
|
@@ -442,32 +349,22 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
|
|
|
442
349
|
|
|
443
350
|
if (targetInstrumentIds.length > 0) {
|
|
444
351
|
const requestedSet = new Set(targetInstrumentIds);
|
|
445
|
-
for (const loadedInstrumentId in pricesData) {
|
|
446
|
-
if (!requestedSet.has(loadedInstrumentId)) {
|
|
447
|
-
delete pricesData[loadedInstrumentId];
|
|
448
|
-
}
|
|
449
|
-
}
|
|
352
|
+
for (const loadedInstrumentId in pricesData) { if (!requestedSet.has(loadedInstrumentId)) { delete pricesData[loadedInstrumentId]; } }
|
|
450
353
|
}
|
|
451
354
|
|
|
452
355
|
const writes = [];
|
|
453
356
|
|
|
454
357
|
for (const dateStr of dateStrings) {
|
|
358
|
+
|
|
359
|
+
// --- DYNAMIC MATH CONTEXT CONSTRUCTION ---
|
|
360
|
+
const dynamicMathContext = {};
|
|
361
|
+
for (const [key, value] of Object.entries(mathLayer)) { dynamicMathContext[key] = value; if (LEGACY_MAPPING[key]) { dynamicMathContext[LEGACY_MAPPING[key]] = value;} }
|
|
362
|
+
|
|
455
363
|
const context = {
|
|
456
364
|
mappings,
|
|
457
365
|
prices: { history: pricesData },
|
|
458
366
|
date: { today: dateStr },
|
|
459
|
-
math:
|
|
460
|
-
extract: DataExtractor,
|
|
461
|
-
history: HistoryExtractor,
|
|
462
|
-
compute: MathPrimitives,
|
|
463
|
-
aggregate: Aggregators,
|
|
464
|
-
validate: Validators,
|
|
465
|
-
signals: SignalPrimitives,
|
|
466
|
-
schemas: SCHEMAS,
|
|
467
|
-
distribution: DistributionAnalytics,
|
|
468
|
-
TimeSeries: TimeSeries,
|
|
469
|
-
priceExtractor: priceExtractor
|
|
470
|
-
}
|
|
367
|
+
math: dynamicMathContext // Injected here
|
|
471
368
|
};
|
|
472
369
|
|
|
473
370
|
for (const calcManifest of calcs) {
|
|
@@ -481,28 +378,18 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
|
|
|
481
378
|
if (result.by_instrument) dataToWrite = result.by_instrument;
|
|
482
379
|
|
|
483
380
|
if (Object.keys(dataToWrite).length > 0) {
|
|
484
|
-
const docRef = db.collection(config.resultsCollection).doc(dateStr)
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
writes.push({
|
|
489
|
-
ref: docRef,
|
|
490
|
-
data: { ...dataToWrite, _completed: true },
|
|
491
|
-
options: { merge: true }
|
|
492
|
-
});
|
|
381
|
+
const docRef = db.collection(config.resultsCollection).doc(dateStr) .collection(config.resultsSubcollection).doc(calcManifest.category) .collection(config.computationsSubcollection).doc(normalizeName(calcManifest.name));
|
|
382
|
+
|
|
383
|
+
writes.push({ ref: docRef, data: { ...dataToWrite, _completed: true }, options: { merge: true } });
|
|
493
384
|
}
|
|
494
385
|
}
|
|
495
|
-
} catch (err) {
|
|
496
|
-
logger.log('ERROR', `[BatchPrice] \u2716 Failed ${calcManifest.name} for ${dateStr}: ${err.message}`);
|
|
497
|
-
}
|
|
386
|
+
} catch (err) { logger.log('ERROR', `[BatchPrice] \u2716 Failed ${calcManifest.name} for ${dateStr}: ${err.message}`); }
|
|
498
387
|
}
|
|
499
388
|
}
|
|
500
389
|
|
|
501
390
|
if (writes.length > 0) {
|
|
502
391
|
const commitBatches = [];
|
|
503
|
-
for (let i = 0; i < writes.length; i += WRITE_BATCH_LIMIT) {
|
|
504
|
-
commitBatches.push(writes.slice(i, i + WRITE_BATCH_LIMIT));
|
|
505
|
-
}
|
|
392
|
+
for (let i = 0; i < writes.length; i += WRITE_BATCH_LIMIT) { commitBatches.push(writes.slice(i, i + WRITE_BATCH_LIMIT)); }
|
|
506
393
|
|
|
507
394
|
const commitLimit = pLimit(10);
|
|
508
395
|
|
|
@@ -510,17 +397,12 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
|
|
|
510
397
|
const batch = db.batch();
|
|
511
398
|
batchWrites.forEach(w => batch.set(w.ref, w.data, w.options));
|
|
512
399
|
|
|
513
|
-
try {
|
|
514
|
-
|
|
515
|
-
} catch (commitErr) {
|
|
516
|
-
logger.log('ERROR', `[BatchPrice] Commit failed for Chunk ${index} Batch ${bIndex}.`, { error: commitErr.message });
|
|
517
|
-
}
|
|
400
|
+
try { await calculationUtils.withRetry(() => batch.commit(), `BatchPrice-C${index}-B${bIndex}`);
|
|
401
|
+
} catch (commitErr) { logger.log('ERROR', `[BatchPrice] Commit failed for Chunk ${index} Batch ${bIndex}.`, { error: commitErr.message }); }
|
|
518
402
|
})));
|
|
519
403
|
}
|
|
520
404
|
|
|
521
|
-
} catch (chunkErr) {
|
|
522
|
-
logger.log('ERROR', `[BatchPrice] Fatal error processing Chunk ${index}.`, { error: chunkErr.message });
|
|
523
|
-
}
|
|
405
|
+
} catch (chunkErr) { logger.log('ERROR', `[BatchPrice] Fatal error processing Chunk ${index}.`, { error: chunkErr.message }); }
|
|
524
406
|
}));
|
|
525
407
|
}
|
|
526
408
|
|
|
@@ -536,7 +418,6 @@ module.exports = {
|
|
|
536
418
|
fetchComputationStatus,
|
|
537
419
|
fetchGlobalComputationStatus,
|
|
538
420
|
updateComputationStatus,
|
|
539
|
-
updateGlobalComputationStatus,
|
|
540
421
|
runStandardComputationPass,
|
|
541
422
|
runMetaComputationPass,
|
|
542
423
|
runBatchPriceComputation
|