bulltrackers-module 1.0.304 โ†’ 1.0.306

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,7 +1,6 @@
1
1
  /**
2
2
  * FILENAME: computation-system/helpers/computation_dispatcher.js
3
- * PURPOSE: Sequential Cursor-Based Dispatcher.
4
- * IMPLEMENTS: Dirty-Date Discovery, Forensics Rerouting, and Satiation Sweeps.
3
+ * PURPOSE: Sequential Cursor-Based Dispatcher with Hyper-Verbose Telemetry.
5
4
  */
6
5
 
7
6
  const { getExpectedDateStrings, getEarliestDataDates, normalizeName, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils.js');
@@ -11,12 +10,8 @@ const { fetchComputationStatus } = require('../persistence/StatusRepository');
11
10
  const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
12
11
 
13
12
  const OOM_THRESHOLD_MB = 1500;
14
- const SECONDS_PER_CALC_MARGIN = 25; // 20s base + safety margin
13
+ const SECONDS_PER_CALC_MARGIN = 25;
15
14
 
16
- /**
17
- * Checks if specific tasks on a date need a high-memory reroute.
18
- * Returns only tasks that failed on 'standard' and haven't been tried on 'high-mem'.
19
- */
20
15
  async function getHighMemReroutes(db, date, pass, tasks) {
21
16
  const reroutes = [];
22
17
  for (const task of tasks) {
@@ -26,7 +21,6 @@ async function getHighMemReroutes(db, date, pass, tasks) {
26
21
 
27
22
  if (doc.exists) {
28
23
  const data = doc.data();
29
- // Check if it failed due to memory and hasn't been attempted on high-mem yet
30
24
  const isOOM = (data.status === 'FAILED' || data.status === 'CRASH') &&
31
25
  (data.resourceTier !== 'high-mem') &&
32
26
  ((data.peakMemoryMB > OOM_THRESHOLD_MB) || (data.error && /memory/i.test(data.error.message)));
@@ -43,42 +37,71 @@ async function dispatchComputationPass(config, dependencies, computationManifest
43
37
  const { logger, db } = dependencies;
44
38
  const pubsubUtils = new PubSubUtils(dependencies);
45
39
 
46
- // Inputs from Workflow Cursor
47
- const passToRun = String(reqBody.pass || config.COMPUTATION_PASS_TO_RUN);
40
+ // 1. Capture Inputs
41
+ const passToRun = String(reqBody.pass || config.COMPUTATION_PASS_TO_RUN || "1");
48
42
  const targetCursorN = parseInt(reqBody.cursorIndex || 1);
49
- const dateLimitStr = reqBody.date || config.date;
43
+ const dateLimitStr = reqBody.date || config.date || "2025-01-01";
44
+
45
+ logger.log('INFO', `[Dispatcher] ๐Ÿš€ STARTING DISPATCH: Pass ${passToRun}, Cursor ${targetCursorN}, Limit ${dateLimitStr}`);
50
46
 
51
47
  const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
52
48
  const passes = groupByPass(computationManifest);
53
49
  const calcsInThisPass = passes[passToRun] || [];
54
50
 
55
51
  if (!calcsInThisPass.length) {
52
+ logger.log('WARN', `[Dispatcher] ๐Ÿ›‘ No calculations found for Pass ${passToRun}. Moving to next pass.`);
56
53
  return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0 };
57
54
  }
58
55
 
59
- // 1. Discover all "Dirty" Dates (Dates needing work)
56
+ // 2. Discover Discovery Boundaries
60
57
  const earliestDates = await getEarliestDataDates(config, dependencies);
61
- const allDates = getExpectedDateStrings(earliestDates.absoluteEarliest, new Date(dateLimitStr + 'T00:00:00Z'));
58
+ logger.log('INFO', `[Dispatcher] Discovery Boundaries: Earliest=${earliestDates.absoluteEarliest.toISOString().slice(0,10)}, Limit=${dateLimitStr}`);
59
+
60
+ const allDates = getExpectedDateStrings(earliestDates.absoluteEarliest, new Date(dateLimitStr + 'T00:00:00Z'));
62
61
 
62
+ if (allDates.length === 0) {
63
+ logger.log('ERROR', `[Dispatcher] โŒ Date range is empty. Check if dateLimit is before earliest data.`);
64
+ return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0 };
65
+ }
66
+
67
+ // 3. Date Scanning Loop
63
68
  const dirtyDates = [];
69
+ let blockedCount = 0;
70
+ let upToDateCount = 0;
71
+
72
+ logger.log('INFO', `[Dispatcher] Scanning ${allDates.length} dates for work...`);
73
+
64
74
  for (const d of allDates) {
65
75
  const dailyStatus = await fetchComputationStatus(d, config, dependencies);
66
76
  const availability = await checkRootDataAvailability(d, config, dependencies, DEFINITIVE_EARLIEST_DATES);
67
77
 
78
+ // Detailed check on availability status
79
+ if (!availability || !availability.status.hasPrices) {
80
+ // Log every 30 days to avoid log spam if data is missing for long periods
81
+ if (allDates.indexOf(d) % 30 === 0) logger.log('DEBUG', `[Dispatcher] ${d}: Root Data Index Missing or Price=false.`);
82
+ blockedCount++;
83
+ continue;
84
+ }
85
+
68
86
  const report = analyzeDateExecution(d, calcsInThisPass, availability.status, dailyStatus, manifestMap, null);
69
87
  const tasks = [...report.runnable, ...report.reRuns];
70
88
 
71
89
  if (tasks.length > 0) {
90
+ logger.log('INFO', `[Dispatcher] โœจ Found Dirty Date: ${d} (${tasks.length} tasks)`);
72
91
  dirtyDates.push({ date: d, tasks });
92
+ } else {
93
+ upToDateCount++;
73
94
  }
74
95
  }
75
96
 
97
+ logger.log('INFO', `[Dispatcher] Scan Complete: ${dirtyDates.length} dirty, ${upToDateCount} up-to-date, ${blockedCount} blocked/missing data.`);
98
+
76
99
  let selectedDate = null;
77
100
  let selectedTasks = [];
78
101
  let isReroute = false;
79
102
  let isSweep = false;
80
103
 
81
- // 2. Logic: Prioritize Reroute for N-1
104
+ // 4. Cursor Logic
82
105
  if (targetCursorN > 1 && (targetCursorN - 2) < dirtyDates.length) {
83
106
  const prevEntry = dirtyDates[targetCursorN - 2];
84
107
  const reroutes = await getHighMemReroutes(db, prevEntry.date, passToRun, prevEntry.tasks);
@@ -87,34 +110,35 @@ async function dispatchComputationPass(config, dependencies, computationManifest
87
110
  selectedDate = prevEntry.date;
88
111
  selectedTasks = reroutes;
89
112
  isReroute = true;
90
- logger.log('INFO', `[Dispatcher] Reroute detected for ${selectedDate}. Pausing N increment.`);
113
+ logger.log('INFO', `[Dispatcher] ๐Ÿ”„ Reroute detected for ${selectedDate}. Retrying same cursor position with High-Mem.`);
91
114
  }
92
115
  }
93
116
 
94
- // 3. Logic: N-th Dirty Date or Final Sweep
95
117
  if (!selectedDate) {
96
118
  if (targetCursorN <= dirtyDates.length) {
97
119
  const entry = dirtyDates[targetCursorN - 1];
98
120
  selectedDate = entry.date;
99
121
  selectedTasks = entry.tasks;
122
+ logger.log('INFO', `[Dispatcher] Selecting Dirty Date #${targetCursorN}: ${selectedDate}`);
100
123
  } else {
101
- // Final Satiation Sweep: Check if anything was missed (recovery)
102
124
  if (dirtyDates.length > 0) {
103
125
  isSweep = true;
104
126
  selectedDate = dirtyDates[0].date;
105
127
  selectedTasks = dirtyDates[0].tasks;
128
+ logger.log('INFO', `[Dispatcher] ๐Ÿงน Satiation Sweep: Checking earliest dirty date ${selectedDate}`);
106
129
  }
107
130
  }
108
131
  }
109
132
 
110
- // 4. Signal Pass Completion
133
+ // 5. Termination Check
111
134
  if (!selectedDate) {
135
+ logger.log('SUCCESS', `[Dispatcher] โœ… Pass ${passToRun} is fully satiated. Signalling MOVE_TO_NEXT_PASS.`);
112
136
  return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0, etaSeconds: 0 };
113
137
  }
114
138
 
115
- // 5. Dispatch to PubSub (Standard vs. High-Mem)
116
- const standardTasks = selectedTasks.filter(t => t.resources !== 'high-mem').map(t => ({ ...t, date: selectedDate, pass: passToRun }));
117
- const highMemTasks = selectedTasks.filter(t => t.resources === 'high-mem').map(t => ({ ...t, date: selectedDate, pass: passToRun }));
139
+ // 6. Pub/Sub Dispatch
140
+ const standardTasks = selectedTasks.filter(t => t.resources !== 'high-mem').map(t => ({ ...t, action: 'RUN_COMPUTATION_DATE', computation: t.name, date: selectedDate, pass: passToRun }));
141
+ const highMemTasks = selectedTasks.filter(t => t.resources === 'high-mem').map(t => ({ ...t, action: 'RUN_COMPUTATION_DATE', computation: t.name, date: selectedDate, pass: passToRun }));
118
142
 
119
143
  const pubPromises = [];
120
144
  if (standardTasks.length > 0) {
@@ -135,13 +159,13 @@ async function dispatchComputationPass(config, dependencies, computationManifest
135
159
 
136
160
  const etaSeconds = Math.max(20, selectedTasks.length * SECONDS_PER_CALC_MARGIN);
137
161
 
138
- logger.log('INFO', `[Dispatcher] ${isReroute ? 'Reroute' : (isSweep ? 'Sweep' : 'Standard')} Run: ${selectedDate}. Tasks: ${selectedTasks.length}. ETA: ${etaSeconds}s`);
162
+ logger.log('INFO', `[Dispatcher] ๐Ÿ›ฐ๏ธ DISPATCHED ${selectedTasks.length} tasks for ${selectedDate}. ETA ${etaSeconds}s.`);
139
163
 
140
164
  return {
141
165
  status : isSweep ? 'RECOVERY' : 'CONTINUE_PASS',
142
166
  dateProcessed : selectedDate,
143
167
  dispatched : selectedTasks.length,
144
- n_cursor_ignored: isReroute, // Tell workflow to stay on same N
168
+ n_cursor_ignored: isReroute,
145
169
  etaSeconds : etaSeconds
146
170
  };
147
171
  }
@@ -1,5 +1,5 @@
1
1
  # Cloud Workflows: Precision Cursor-Based Orchestrator
2
- # PURPOSE: Orchestrates 5 passes with deterministic pacing and syntax fixes.
2
+ # PURPOSE: Orchestrates 5 passes with dynamic date detection and cursor logic.
3
3
 
4
4
  main:
5
5
  params: [input]
@@ -8,7 +8,14 @@ main:
8
8
  assign:
9
9
  - project: '${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}'
10
10
  - passes: ["1", "2", "3", "4", "5"]
11
- - date_to_run: '${default(map.get(input, "date"), "2025-01-01")}'
11
+ # Dynamically calculate today's date (YYYY-MM-DD) if no date is provided in input
12
+ - current_date: '${text.split(time.format(sys.now()), "T")[0]}'
13
+ - date_to_run: '${default(map.get(input, "date"), current_date)}'
14
+
15
+ - log_start:
16
+ call: sys.log
17
+ args:
18
+ text: '${"Starting Pipeline execution. Target Date Limit: " + date_to_run}'
12
19
 
13
20
  - run_sequential_passes:
14
21
  for:
@@ -37,12 +44,12 @@ main:
37
44
 
38
45
  - evaluate_dispatch:
39
46
  switch:
40
- # State 1: Pass exhausted and Satiation Sweep complete
47
+ # State 1: Dispatcher signal to move to the next pass
41
48
  - condition: '${dispatch_res.body.status == "MOVE_TO_NEXT_PASS"}'
42
49
  assign:
43
50
  - pass_complete: true
44
51
 
45
- # State 2: Tasks dispatched (Standard, Reroute, or Recovery Sweep)
52
+ # State 2: Tasks were dispatched
46
53
  - condition: '${dispatch_res.body.dispatched > 0}'
47
54
  steps:
48
55
  - log_dispatch:
@@ -55,7 +62,7 @@ main:
55
62
  seconds: '${int(dispatch_res.body.etaSeconds)}'
56
63
  - update_cursor:
57
64
  assign:
58
- # If n_cursor_ignored is true (Reroute or Sweep Recovery), we stay on same N
65
+ # If n_cursor_ignored is true, stay on same N to retry (e.g. for high-mem)
59
66
  - n_cursor: '${if(dispatch_res.body.n_cursor_ignored, n_cursor, n_cursor + 1)}'
60
67
  - next_loop:
61
68
  next: sequential_date_loop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.304",
3
+ "version": "1.0.306",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [