bulltrackers-module 1.0.199 → 1.0.200

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * FILENAME: bulltrackers-module/functions/computation-system/helpers/orchestration_helpers.js
3
- * FIXED: TS Error (controller.loader.mappings)
4
- * ADDED: Smart Shard Lookup for specific tickers
5
- * OPTIMIZED: Added Concurrency (Parallel Commits & Pipelined Shards) for runBatchPriceComputation
3
+ * FIXED: Context math mapping in runBatchPriceComputation (resolves 'undefined' crash).
4
+ * IMPROVED: Explicit logging for every calculation run (Start, Success, Failure).
5
+ * OPTIMIZED: Parallel Commits & Pipelined Shards.
6
6
  */
7
7
 
8
8
  const { ComputationController } = require('../controllers/computation_controller');
@@ -14,7 +14,14 @@ const {
14
14
  getRelevantShardRefs, loadDataByRefs
15
15
  } = require('../utils/data_loader');
16
16
 
17
- const pLimit = require('p-limit'); // TODO, THIS OUGHT TO BE INJECTED.
17
+ // --- FIX 1: Import Math Layer Primitives for Correct Context Mapping ---
18
+ const {
19
+ DataExtractor, HistoryExtractor, MathPrimitives, Aggregators,
20
+ Validators, SCHEMAS, SignalPrimitives, DistributionAnalytics,
21
+ TimeSeries, priceExtractor
22
+ } = require('../layers/math_primitives.js');
23
+
24
+ const pLimit = require('p-limit');
18
25
 
19
26
  /**
20
27
  * Groups calculations from a manifest by their 'pass' property.
@@ -22,29 +29,23 @@ const pLimit = require('p-limit'); // TODO, THIS OUGHT TO BE INJECTED.
22
29
  function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[calc.pass] = acc[calc.pass] || []).push(calc); return acc; }, {}); }
23
30
 
24
31
  /**
25
- * --- NEW HELPER: PASSIVE DATA VALIDATION ---
32
+ * --- PASSIVE DATA VALIDATION ---
26
33
  * Scans a result set for suspicious patterns (e.g., a field is NULL for 100% of tickers).
27
- * Logs warnings but DOES NOT block the commit.
28
34
  */
29
- function validateResultPatterns(logger, calcName, results, category) { // TODO, THIS COULD BE MUCH MORE SOPHISTICATED, WE WILL NEVER FORCE FAIL A COMPUTATION REGARDLESS, BUT IT COULD BE A GREAT WARNING SYSTEM, USE GLOUD LOG SINKS TO DETECT PROBLEMS BASED ON THESE LOGS
30
- // 1. Skip Speculators (Too sparse, nulls are expected)
35
+ function validateResultPatterns(logger, calcName, results, category) {
31
36
  if (category === 'speculator' || category === 'speculators') return;
32
37
 
33
38
  const tickers = Object.keys(results);
34
39
  const totalItems = tickers.length;
35
40
 
36
- // 2. Need a decent sample size to judge patterns
37
41
  if (totalItems < 5) return;
38
42
 
39
- // 3. Get all keys from the first valid object
40
- // We assume schema is roughly consistent across tickers
41
43
  const sampleTicker = tickers.find(t => results[t] && typeof results[t] === 'object');
42
44
  if (!sampleTicker) return;
43
45
 
44
46
  const keys = Object.keys(results[sampleTicker]);
45
47
 
46
48
  keys.forEach(key => {
47
- // Skip internal keys or metadata
48
49
  if (key.startsWith('_')) return;
49
50
 
50
51
  let nullCount = 0;
@@ -58,14 +59,11 @@ function validateResultPatterns(logger, calcName, results, category) { // TODO,
58
59
  if (typeof val === 'number' && isNaN(val)) nanCount++;
59
60
  }
60
61
 
61
- // 4. Define Thresholds
62
- // If 100% of data is NaN or Undefined, that's almost certainly a bug.
63
62
  if (nanCount === totalItems) {
64
63
  logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is NaN for 100% of ${totalItems} items. Code bug likely.`);
65
64
  } else if (undefinedCount === totalItems) {
66
65
  logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is UNDEFINED for 100% of ${totalItems} items. Code bug likely.`);
67
66
  }
68
- // 5. Nulls are tricky. warn if >90%, but don't error (might be valid logic like "no shorts")
69
67
  else if (nullCount > (totalItems * 0.9)) {
70
68
  logger.log('WARN', `[DataQuality] Calc '${calcName}' field '${key}' is NULL for ${nullCount}/${totalItems} items. Check logic if this is unexpected.`);
71
69
  }
@@ -329,10 +327,7 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
329
327
  }
330
328
 
331
329
  if (Object.keys(standardRes).length) {
332
- // --- NEW: Run Passive Validation ---
333
- // We do this BEFORE marking it completed, but we do NOT stop the write.
334
330
  validateResultPatterns(deps.logger, name, standardRes, calc.manifest.category);
335
- // -----------------------------------
336
331
 
337
332
  standardRes._completed = true;
338
333
  writes.push({
@@ -388,10 +383,10 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
388
383
  * OPTIMIZED: Implements concurrency for both Shard Processing and Write Commits
389
384
  */
390
385
  async function runBatchPriceComputation(config, deps, dateStrings, calcs, targetTickers = []) {
391
- const { logger, db, calculationUtils } = deps; // Ensure calculationUtils is available for retry
386
+ const { logger, db, calculationUtils } = deps;
392
387
  const controller = new ComputationController(config, deps);
393
388
 
394
- // 1. FIX: Call loadMappings() correctly and get the result
389
+ // 1. Call loadMappings() correctly and get the result
395
390
  const mappings = await controller.loader.loadMappings();
396
391
 
397
392
  // 2. Resolve Shards (All or Subset)
@@ -413,12 +408,9 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
413
408
  }
414
409
 
415
410
  // 3. Execution Planning
416
- // CONCURRENCY SETTING:
417
- // Limit outer concurrency (processing shard chunks) to 2 to prevent contention on daily result docs.
418
- // While Firestore handles concurrent writes to the same doc, limiting this avoids excessive retries.
419
411
  const OUTER_CONCURRENCY_LIMIT = 2;
420
412
  const SHARD_BATCH_SIZE = 20;
421
- const WRITE_BATCH_LIMIT = 50; // Keep write batch size small (payload safety)
413
+ const WRITE_BATCH_LIMIT = 50;
422
414
 
423
415
  logger.log('INFO', `[BatchPrice] Execution Plan: ${dateStrings.length} days, ${allShardRefs.length} shards. Concurrency: ${OUTER_CONCURRENCY_LIMIT}.`);
424
416
 
@@ -440,7 +432,6 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
440
432
  // Optional Filtering for Subset Mode
441
433
  if (targetInstrumentIds.length > 0) {
442
434
  const requestedSet = new Set(targetInstrumentIds);
443
- // Iterate over the loaded data and delete anything we didn't ask for
444
435
  for (const loadedInstrumentId in pricesData) {
445
436
  if (!requestedSet.has(loadedInstrumentId)) {
446
437
  delete pricesData[loadedInstrumentId];
@@ -450,26 +441,44 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
450
441
  const writes = [];
451
442
 
452
443
  // --- CALCULATION PHASE ---
453
- // This builds up the array of writes (one per date)
454
444
  for (const dateStr of dateStrings) {
445
+ // --- FIX 2: Manually map math primitives to their alias names ---
446
+ // This matches the ContextBuilder logic in ComputationController
447
+ // and fixes the "Cannot read properties of undefined (reading 'standardDeviation')" error.
455
448
  const context = {
456
449
  mappings,
457
450
  prices: { history: pricesData },
458
451
  date: { today: dateStr },
459
- math: require('../layers/math_primitives.js')
452
+ math: {
453
+ extract: DataExtractor,
454
+ history: HistoryExtractor,
455
+ compute: MathPrimitives,
456
+ aggregate: Aggregators,
457
+ validate: Validators,
458
+ signals: SignalPrimitives,
459
+ schemas: SCHEMAS,
460
+ distribution : DistributionAnalytics,
461
+ TimeSeries: TimeSeries,
462
+ priceExtractor : priceExtractor
463
+ }
460
464
  };
461
465
 
462
466
  for (const calcManifest of calcs) {
463
467
  try {
468
+ // --- LOGGING FIX: Log start of calculation ---
469
+ logger.log('INFO', `[BatchPrice] >> Running ${calcManifest.name} for ${dateStr}...`);
470
+
464
471
  const instance = new calcManifest.class();
465
472
  await instance.process(context);
466
473
  const result = await instance.getResult();
467
474
 
475
+ let hasContent = false;
468
476
  if (result && Object.keys(result).length > 0) {
469
477
  let dataToWrite = result;
470
478
  if (result.by_instrument) dataToWrite = result.by_instrument;
471
479
 
472
480
  if (Object.keys(dataToWrite).length > 0) {
481
+ hasContent = true;
473
482
  const docRef = db.collection(config.resultsCollection).doc(dateStr)
474
483
  .collection(config.resultsSubcollection).doc(calcManifest.category)
475
484
  .collection(config.computationsSubcollection).doc(normalizeName(calcManifest.name));
@@ -481,22 +490,28 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
481
490
  });
482
491
  }
483
492
  }
493
+
494
+ // --- LOGGING FIX: Log success/completion ---
495
+ if (hasContent) {
496
+ logger.log('INFO', `[BatchPrice] \u2714 Finished ${calcManifest.name} for ${dateStr}. Found data.`);
497
+ } else {
498
+ logger.log('INFO', `[BatchPrice] - Finished ${calcManifest.name} for ${dateStr}. No result data.`);
499
+ }
500
+
484
501
  } catch (err) {
485
- logger.log('ERROR', `[BatchPrice] Calc ${calcManifest.name} failed for ${dateStr}`, { error: err.message });
502
+ // --- LOGGING FIX: Explicit failure log ---
503
+ logger.log('ERROR', `[BatchPrice] \u2716 Failed ${calcManifest.name} for ${dateStr}: ${err.message}`);
486
504
  }
487
505
  }
488
506
  }
489
507
 
490
508
  // --- PARALLEL COMMIT PHASE ---
491
- // Instead of committing sequentially via commitBatchInChunks, we process these writes in parallel.
492
- // Since each write targets a DIFFERENT date (different document), parallelizing this is safe and fast.
493
509
  if (writes.length > 0) {
494
510
  const commitBatches = [];
495
511
  for (let i = 0; i < writes.length; i += WRITE_BATCH_LIMIT) {
496
512
  commitBatches.push(writes.slice(i, i + WRITE_BATCH_LIMIT));
497
513
  }
498
514
 
499
- // Use a higher concurrency for commits since they target disjoint documents
500
515
  const commitLimit = pLimit(10);
501
516
 
502
517
  await Promise.all(commitBatches.map((batchWrites, bIndex) => commitLimit(async () => {
@@ -507,7 +522,6 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
507
522
  await calculationUtils.withRetry(() => batch.commit(), `BatchPrice-C${index}-B${bIndex}`);
508
523
  } catch (commitErr) {
509
524
  logger.log('ERROR', `[BatchPrice] Commit failed for Chunk ${index} Batch ${bIndex}.`, { error: commitErr.message });
510
- // We log but don't throw, to allow other batches to succeed
511
525
  }
512
526
  })));
513
527
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.199",
3
+ "version": "1.0.200",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [