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;
|
|
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
|
-
//
|
|
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
|
-
//
|
|
56
|
+
// 2. Discover Discovery Boundaries
|
|
60
57
|
const earliestDates = await getEarliestDataDates(config, dependencies);
|
|
61
|
-
|
|
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
|
-
//
|
|
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}.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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]
|
|
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,
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|