bulltrackers-module 1.0.238 → 1.0.239

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,18 +1,18 @@
1
1
  /**
2
2
  * FILENAME: bulltrackers-module/functions/computation-system/helpers/computation_dispatcher.js
3
- * PURPOSE: Dispatches computation tasks to Pub/Sub for scalable execution.
4
- * REFACTORED: Now uses WorkflowOrchestrator for helper functions.
3
+ * PURPOSE: Dispatches granular computation tasks (1 task = 1 calculation/date).
4
+ * UPDATED: Implements "Atomic Task Dispatch" for maximum reliability.
5
5
  */
6
6
 
7
- const { getExpectedDateStrings } = require('../utils/utils.js');
8
- const { groupByPass } = require('../WorkflowOrchestrator.js');
9
- const { PubSubUtils } = require('../../core/utils/pubsub_utils');
7
+ const { getExpectedDateStrings, normalizeName } = require('../utils/utils.js');
8
+ const { groupByPass } = require('../WorkflowOrchestrator.js');
9
+ const { PubSubUtils } = require('../../core/utils/pubsub_utils');
10
10
 
11
11
  const TOPIC_NAME = 'computation-tasks';
12
12
 
13
13
  /**
14
14
  * Dispatches computation tasks for a specific pass.
15
- * Instead of running them, it queues them in Pub/Sub.
15
+ * Generates one Pub/Sub message per calculation per date.
16
16
  */
17
17
  async function dispatchComputationPass(config, dependencies, computationManifest) {
18
18
  const { logger } = dependencies;
@@ -21,17 +21,18 @@ async function dispatchComputationPass(config, dependencies, computationManifest
21
21
 
22
22
  if (!passToRun) { return logger.log('ERROR', '[Dispatcher] No pass defined (COMPUTATION_PASS_TO_RUN). Aborting.'); }
23
23
 
24
- // 1. Validate Pass Existence
24
+ // 1. Get Calculations for this Pass
25
25
  const passes = groupByPass(computationManifest);
26
26
  const calcsInThisPass = passes[passToRun] || [];
27
27
 
28
28
  if (!calcsInThisPass.length) { return logger.log('WARN', `[Dispatcher] No calcs for Pass ${passToRun}. Exiting.`); }
29
29
 
30
- const calcNames = calcsInThisPass.map(c => c.name).join(', ');
31
- logger.log('INFO', `🚀 [Dispatcher] Preparing PASS ${passToRun}.`);
32
- logger.log('INFO', `[Dispatcher] Included Calculations: [${calcNames}]`);
30
+ const calcNames = calcsInThisPass.map(c => c.name);
31
+ logger.log('INFO', `🚀 [Dispatcher] Preparing PASS ${passToRun} (Granular Mode).`);
32
+ logger.log('INFO', `[Dispatcher] Target Calculations: [${calcNames.join(', ')}]`);
33
33
 
34
34
  // 2. Determine Date Range
35
+ // (You can make this dynamic or config-driven)
35
36
  const earliestDates = {
36
37
  portfolio: new Date('2025-09-25T00:00:00Z'),
37
38
  history: new Date('2025-11-05T00:00:00Z'),
@@ -44,35 +45,34 @@ async function dispatchComputationPass(config, dependencies, computationManifest
44
45
  const endDateUTC = new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() - 1));
45
46
  const allExpectedDates = getExpectedDateStrings(passEarliestDate, endDateUTC);
46
47
 
47
- logger.log('INFO', `[Dispatcher] Dispatches checks for ${allExpectedDates.length} dates (${allExpectedDates[0]} to ${allExpectedDates[allExpectedDates.length - 1]}). Workers will validate dependencies.`);
48
-
49
- // 3. Dispatch Messages
50
- let dispatchedCount = 0;
51
- const BATCH_SIZE = 50;
52
-
53
- // We can publish in parallel batches
54
- const chunks = [];
55
- for (let i = 0; i < allExpectedDates.length; i += BATCH_SIZE) { chunks.push(allExpectedDates.slice(i, i + BATCH_SIZE)); }
56
-
57
- for (const chunk of chunks) {
58
- const messages = chunk.map(dateStr => ({
59
- json: {
60
- action: 'RUN_COMPUTATION_DATE',
48
+ // 3. Generate Granular Tasks (Cartesian Product: Dates x Calculations)
49
+ const allTasks = [];
50
+
51
+ for (const dateStr of allExpectedDates) {
52
+ for (const calc of calcsInThisPass) {
53
+ allTasks.push({
54
+ action: 'RUN_COMPUTATION_DATE', // Maintained for compatibility
61
55
  date: dateStr,
62
56
  pass: passToRun,
57
+ computation: normalizeName(calc.name), // CRITICAL: Target specific calc
63
58
  timestamp: Date.now()
64
- }
65
- }));
66
-
67
- try {
68
- await pubsubUtils.publishMessageBatch(TOPIC_NAME, messages);
69
- dispatchedCount += messages.length;
70
- logger.log('INFO', `[Dispatcher] Dispatched batch of ${messages.length} tasks.`);
71
- } catch (err) { logger.log('ERROR', `[Dispatcher] Failed to dispatch batch: ${err.message}`); }
59
+ });
60
+ }
72
61
  }
73
62
 
74
- logger.log('INFO', `[Dispatcher] Finished. Dispatched ${dispatchedCount} checks for Pass ${passToRun}.`);
75
- return { dispatched: dispatchedCount };
63
+ logger.log('INFO', `[Dispatcher] Generated ${allTasks.length} atomic tasks (${allExpectedDates.length} days × ${calcsInThisPass.length} calcs).`);
64
+
65
+ // 4. Batch Dispatch
66
+ // We send tasks in batches to Pub/Sub to be efficient,
67
+ // but the WORKERS will process them individually.
68
+ await pubsubUtils.batchPublishTasks(dependencies, {
69
+ topicName: TOPIC_NAME,
70
+ tasks: allTasks,
71
+ taskType: `computation-pass-${passToRun}`,
72
+ maxPubsubBatchSize: 100 // Safe batch size
73
+ });
74
+
75
+ return { dispatched: allTasks.length };
76
76
  }
77
77
 
78
78
  module.exports = { dispatchComputationPass };
@@ -1,34 +1,27 @@
1
1
  /**
2
2
  * FILENAME: computation-system/helpers/computation_worker.js
3
3
  * PURPOSE: Consumes computation tasks from Pub/Sub and executes them.
4
- * UPDATED: Instantiates the internal StructuredLogger to replace the generic injected logger.
4
+ * UPDATED: Supports Granular Execution (Single Calculation filtering).
5
5
  */
6
6
 
7
7
  const { runDateComputation, groupByPass } = require('../WorkflowOrchestrator.js');
8
8
  const { getManifest } = require('../topology/ManifestLoader');
9
9
  const { StructuredLogger } = require('../logger/logger');
10
+ const { normalizeName } = require('../utils/utils');
10
11
 
11
12
  // 1. IMPORT CALCULATIONS
12
- // We import the specific package containing the strategies (gem, pyro, core, etc.)
13
13
  let calculationPackage;
14
14
  try {
15
- // Primary: Try to load from the installed npm package
16
15
  calculationPackage = require('aiden-shared-calculations-unified');
17
16
  } catch (e) {
18
17
  console.error("FATAL: Could not load 'aiden-shared-calculations-unified'. Ensure it is installed or linked.");
19
18
  throw e;
20
19
  }
21
20
 
22
-
23
- // The package exports { calculations: { ... }, utils: { ... } }
24
21
  const calculations = calculationPackage.calculations;
25
22
 
26
23
  /**
27
24
  * Handles a single Pub/Sub message for a computation task.
28
- * Supports both Gen 1 (Message) and Gen 2 (CloudEvent) formats.
29
- * @param {object} message - The Pub/Sub message payload.
30
- * @param {object} config - System configuration.
31
- * @param {object} dependencies - Injected dependencies (db, generic logger, etc.).
32
25
  */
33
26
  async function handleComputationTask(message, config, dependencies) {
34
27
 
@@ -45,7 +38,7 @@ async function handleComputationTask(message, config, dependencies) {
45
38
  logger: systemLogger
46
39
  };
47
40
 
48
- const { logger } = runDependencies; // Use this for local logging
41
+ const { logger } = runDependencies;
49
42
 
50
43
  // 4. LAZY LOAD MANIFEST
51
44
  let computationManifest;
@@ -86,19 +79,37 @@ async function handleComputationTask(message, config, dependencies) {
86
79
  return;
87
80
  }
88
81
 
89
- const { date, pass } = data;
82
+ const { date, pass, computation } = data; // Extract 'computation'
83
+
90
84
  if (!date || !pass) {
91
85
  logger.log('ERROR', `[Worker] Missing date or pass in payload: ${JSON.stringify(data)}`);
92
86
  return;
93
87
  }
94
88
 
95
- logger.log('INFO', `[Worker] Received task: Date=${date}, Pass=${pass}`);
96
-
89
+ // Load Full Pass
97
90
  const passes = groupByPass(computationManifest);
98
- const calcsInThisPass = passes[pass] || [];
91
+ let calcsInThisPass = passes[pass] || [];
99
92
 
93
+ // --- GRANULAR FILTERING ---
94
+ if (computation) {
95
+ const targetName = normalizeName(computation);
96
+ const targetCalc = calcsInThisPass.find(c => normalizeName(c.name) === targetName);
97
+
98
+ if (!targetCalc) {
99
+ logger.log('WARN', `[Worker] Targeted computation '${computation}' not found in Pass ${pass}. Skipping.`);
100
+ return;
101
+ }
102
+
103
+ // We run ONLY this calculation
104
+ calcsInThisPass = [targetCalc];
105
+ logger.log('INFO', `[Worker] Granular Mode: Running ONLY ${targetCalc.name} for ${date}`);
106
+ } else {
107
+ logger.log('INFO', `[Worker] Bulk Mode: Running ${calcsInThisPass.length} calculations for ${date}`);
108
+ }
109
+ // ---------------------------
110
+
100
111
  if (!calcsInThisPass.length) {
101
- logger.log('WARN', `[Worker] No calculations found for Pass ${pass}.`);
112
+ logger.log('WARN', `[Worker] No calculations found to run.`);
102
113
  return;
103
114
  }
104
115
 
@@ -112,14 +123,15 @@ async function handleComputationTask(message, config, dependencies) {
112
123
  );
113
124
 
114
125
  if (result && result.updates && Object.keys(result.updates).length > 0) {
115
- logger.log('INFO', `[Worker] Successfully processed ${date} (Pass ${pass}). Updates: ${Object.keys(result.updates).length}`);
126
+ logger.log('INFO', `[Worker] Success ${date}. Updated: ${Object.keys(result.updates).join(', ')}`);
116
127
  } else {
117
- logger.log('INFO', `[Worker] Processed ${date} (Pass ${pass}) - No updates.`);
128
+ // In Granular Mode, this is common (e.g. if hash matched)
129
+ logger.log('INFO', `[Worker] Completed ${date} - No DB Writes (Up to date or skipped).`);
118
130
  }
119
131
 
120
132
  } catch (err) {
121
133
  logger.log('ERROR', `[Worker] Fatal error processing task: ${err.message}`, { stack: err.stack });
122
- throw err;
134
+ throw err; // Throwing ensures Pub/Sub retries this specific computation
123
135
  }
124
136
  }
125
137
 
@@ -48,7 +48,7 @@ async function ensureBuildReport(config, dependencies, manifest) {
48
48
  * @param {number} daysBack - Days to simulate (default 7)
49
49
  * @param {string} customBuildId - Optional ID override
50
50
  */
51
- async function generateBuildReport(config, dependencies, manifest, daysBack = 7, customBuildId = null) {
51
+ async function generateBuildReport(config, dependencies, manifest, daysBack = 90, customBuildId = null) {
52
52
  const { db, logger } = dependencies;
53
53
  const buildId = customBuildId || `manual_${Date.now()}`;
54
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.238",
3
+ "version": "1.0.239",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [