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.
@@ -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
- const {
17
- DataExtractor, HistoryExtractor, MathPrimitives, Aggregators,
18
- Validators, SCHEMAS, SignalPrimitives, DistributionAnalytics,
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 (nanCount === totalItems) {
57
- logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is NaN for 100% of ${totalItems} items.`);
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 // Will be populated if needed
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 checkPriceDataAvailability(config, dependencies) {
118
- const { db } = dependencies;
119
- const collection = config.priceCollection || 'asset_prices';
120
- try {
121
- const snapshot = await db.collection(collection).limit(1).get();
122
- if (snapshot.empty) return false;
123
- return true;
124
- } catch (e) {
125
- return false;
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
- async function fetchComputationStatus(dateStr, config, { db }) {
130
- const collection = config.computationStatusCollection || 'computation_status';
131
- const docRef = db.collection(collection).doc(dateStr);
132
- const snap = await docRef.get();
133
- return snap.exists ? snap.data() : {};
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
- async function fetchGlobalComputationStatus(config, { db }) {
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 updateComputationStatus(dateStr, updates, config, { db }) {
144
- if (!updates || Object.keys(updates).length === 0) return;
145
- const collection = config.computationStatusCollection || 'computation_status';
146
- const docRef = db.collection(collection).doc(dateStr);
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
- docRefs.push(db.collection(config.resultsCollection).doc(dateStr)
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
- const docRef = deps.db.collection(config.resultsCollection).doc(dStr)
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
- deps.logger.log('INFO', `${name} for ${dStr}: - No Data to Write`);
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 = 20;
423
- const WRITE_BATCH_LIMIT = 50;
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
- .collection(config.resultsSubcollection).doc(calcManifest.category)
486
- .collection(config.computationsSubcollection).doc(normalizeName(calcManifest.name));
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
- await calculationUtils.withRetry(() => batch.commit(), `BatchPrice-C${index}-B${bIndex}`);
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