bulltrackers-module 1.0.284 → 1.0.285
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.
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Monitor helper for Cloud Workflows.
|
|
3
|
+
* Checks the state of the Audit Ledger to determine if a pass is complete.
|
|
4
|
+
* This function is stateless and receives dependencies via injection.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Checks the status of a specific computation pass.
|
|
9
|
+
* @param {object} req - Express request object (query: date, pass).
|
|
10
|
+
* @param {object} res - Express response object.
|
|
11
|
+
* @param {object} dependencies - Contains db (Firestore), logger.
|
|
12
|
+
*/
|
|
13
|
+
async function checkPassStatus(req, res, dependencies) {
|
|
14
|
+
const { db, logger } = dependencies;
|
|
15
|
+
const { date, pass } = req.query;
|
|
16
|
+
|
|
17
|
+
if (!date || !pass) {
|
|
18
|
+
return res.status(400).json({ error: "Missing 'date' or 'pass' query parameters." });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ledgerPath = `computation_audit_ledger/${date}/passes/${pass}/tasks`;
|
|
22
|
+
logger.log('INFO', `[Monitor] Checking status for ${date} Pass ${pass} at ${ledgerPath}`);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const tasksRef = db.collection(ledgerPath);
|
|
26
|
+
|
|
27
|
+
// 1. Check for Active Tasks (Blocking)
|
|
28
|
+
// If anything is PENDING or IN_PROGRESS, the system is still working.
|
|
29
|
+
const runningSnap = await tasksRef.where('status', 'in', ['PENDING', 'IN_PROGRESS']).get();
|
|
30
|
+
|
|
31
|
+
if (!runningSnap.empty) {
|
|
32
|
+
logger.log('INFO', `[Monitor] Pass ${pass} is RUNNING. Active tasks: ${runningSnap.size}`);
|
|
33
|
+
return res.status(200).json({
|
|
34
|
+
state: 'RUNNING',
|
|
35
|
+
activeCount: runningSnap.size
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. Check for Failures (Retry Condition)
|
|
40
|
+
// If nothing is running, we check if anything ended in FAILED state.
|
|
41
|
+
// We consider these "retryable" by re-triggering the dispatcher.
|
|
42
|
+
const failedSnap = await tasksRef.where('status', '==', 'FAILED').get();
|
|
43
|
+
|
|
44
|
+
if (!failedSnap.empty) {
|
|
45
|
+
logger.log('WARN', `[Monitor] Pass ${pass} finished with FAILURES. Count: ${failedSnap.size}`);
|
|
46
|
+
return res.status(200).json({
|
|
47
|
+
state: 'HAS_FAILURES',
|
|
48
|
+
failureCount: failedSnap.size
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 3. Clean Success
|
|
53
|
+
// No running tasks, no failed tasks.
|
|
54
|
+
logger.log('INFO', `[Monitor] Pass ${pass} COMPLETED successfully.`);
|
|
55
|
+
return res.status(200).json({ state: 'SUCCESS' });
|
|
56
|
+
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.log('ERROR', `[Monitor] Failed to check status: ${error.message}`);
|
|
59
|
+
return res.status(500).json({ error: error.message });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { checkPassStatus };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Cloud Workflows Definition for BullTrackers Computation Pipeline
|
|
2
|
+
# Orchestrates 5 sequential passes with Self-Healing (Retry) logic.
|
|
3
|
+
|
|
4
|
+
main:
|
|
5
|
+
params: [input]
|
|
6
|
+
steps:
|
|
7
|
+
- init:
|
|
8
|
+
assign:
|
|
9
|
+
- project: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
|
|
10
|
+
- location: "europe-west1"
|
|
11
|
+
# If 'date' is provided in input, use it. Otherwise default to today (YYYY-MM-DD).
|
|
12
|
+
- date_to_run: ${default(map.get(input, "date"), text.substring(time.format(sys.now()), 0, 10))}
|
|
13
|
+
- passes: ["1", "2", "3", "4", "5"]
|
|
14
|
+
- max_retries: 3
|
|
15
|
+
- propagation_wait_seconds: 300 # 5 Minutes
|
|
16
|
+
# URL of the new Monitor Function
|
|
17
|
+
- monitor_url: ${"https://europe-west1-" + project + ".cloudfunctions.net/computation-monitor"}
|
|
18
|
+
|
|
19
|
+
# ======================================================
|
|
20
|
+
# MAIN LOOP: Iterate through Passes 1 to 5
|
|
21
|
+
# ======================================================
|
|
22
|
+
- run_passes:
|
|
23
|
+
for:
|
|
24
|
+
value: pass_id
|
|
25
|
+
in: ${passes}
|
|
26
|
+
steps:
|
|
27
|
+
- init_pass_vars:
|
|
28
|
+
assign:
|
|
29
|
+
- attempt_count: 0
|
|
30
|
+
- pass_success: false
|
|
31
|
+
# Construct URL for the specific pass function (e.g. computation-pass-1)
|
|
32
|
+
- dispatcher_url: ${"https://europe-west1-" + project + ".cloudfunctions.net/computation-pass-" + pass_id}
|
|
33
|
+
|
|
34
|
+
# -----------------------------------------------
|
|
35
|
+
# RETRY LOOP: Try to complete the pass up to 3 times
|
|
36
|
+
# -----------------------------------------------
|
|
37
|
+
- pass_retry_loop:
|
|
38
|
+
switch:
|
|
39
|
+
- condition: ${attempt_count < max_retries and not pass_success}
|
|
40
|
+
steps:
|
|
41
|
+
- increment_attempt:
|
|
42
|
+
assign:
|
|
43
|
+
- attempt_count: ${attempt_count + 1}
|
|
44
|
+
|
|
45
|
+
- log_start:
|
|
46
|
+
call: sys.log
|
|
47
|
+
args:
|
|
48
|
+
text: ${"Starting Pass " + pass_id + " (Attempt " + attempt_count + ") for " + date_to_run}
|
|
49
|
+
severity: "INFO"
|
|
50
|
+
|
|
51
|
+
# 1. TRIGGER DISPATCHER (Fire and Forget mechanism via HTTP)
|
|
52
|
+
# The dispatcher analyzes missing data and queues tasks.
|
|
53
|
+
- trigger_dispatcher:
|
|
54
|
+
call: http.get
|
|
55
|
+
args:
|
|
56
|
+
url: ${dispatcher_url}
|
|
57
|
+
query:
|
|
58
|
+
date: ${date_to_run}
|
|
59
|
+
auth:
|
|
60
|
+
type: OIDC
|
|
61
|
+
timeout: 1800 # 30 mins max for dispatch analysis
|
|
62
|
+
result: dispatch_response
|
|
63
|
+
|
|
64
|
+
# 2. PROPAGATION WAIT
|
|
65
|
+
# Wait for dispatcher to queue tasks and workers to start/finish
|
|
66
|
+
- wait_for_propagation:
|
|
67
|
+
call: sys.log
|
|
68
|
+
args:
|
|
69
|
+
text: ${"Pass " + pass_id + " dispatched. Waiting " + propagation_wait_seconds + "s for propagation..."}
|
|
70
|
+
next: sleep_propagation
|
|
71
|
+
|
|
72
|
+
- sleep_propagation:
|
|
73
|
+
call: sys.sleep
|
|
74
|
+
args:
|
|
75
|
+
seconds: ${propagation_wait_seconds}
|
|
76
|
+
|
|
77
|
+
# 3. MONITORING LOOP
|
|
78
|
+
# Poll until RUNNING state clears
|
|
79
|
+
- monitor_loop:
|
|
80
|
+
call: http.get
|
|
81
|
+
args:
|
|
82
|
+
url: ${monitor_url}
|
|
83
|
+
query:
|
|
84
|
+
date: ${date_to_run}
|
|
85
|
+
pass: ${pass_id}
|
|
86
|
+
auth:
|
|
87
|
+
type: OIDC
|
|
88
|
+
result: status_resp
|
|
89
|
+
|
|
90
|
+
- evaluate_status:
|
|
91
|
+
switch:
|
|
92
|
+
# CASE A: Still Running -> Sleep and Poll Again
|
|
93
|
+
- condition: ${status_resp.body.state == "RUNNING"}
|
|
94
|
+
steps:
|
|
95
|
+
- log_running:
|
|
96
|
+
call: sys.log
|
|
97
|
+
args:
|
|
98
|
+
text: ${"Pass " + pass_id + " is RUNNING (" + status_resp.body.activeCount + " active). Waiting..."}
|
|
99
|
+
- sleep_polling:
|
|
100
|
+
call: sys.sleep
|
|
101
|
+
args:
|
|
102
|
+
seconds: 60
|
|
103
|
+
- next: monitor_loop
|
|
104
|
+
|
|
105
|
+
# CASE B: Clean Success -> Mark done, Break Retry Loop
|
|
106
|
+
- condition: ${status_resp.body.state == "SUCCESS"}
|
|
107
|
+
steps:
|
|
108
|
+
- log_success:
|
|
109
|
+
call: sys.log
|
|
110
|
+
args:
|
|
111
|
+
text: ${"Pass " + pass_id + " COMPLETED successfully."}
|
|
112
|
+
severity: "INFO"
|
|
113
|
+
- mark_success:
|
|
114
|
+
assign:
|
|
115
|
+
- pass_success: true
|
|
116
|
+
- next: pass_retry_loop # Will exit loop due to pass_success=true
|
|
117
|
+
|
|
118
|
+
# CASE C: Failures Found -> Continue Retry Loop (will trigger dispatcher again)
|
|
119
|
+
- condition: ${status_resp.body.state == "HAS_FAILURES"}
|
|
120
|
+
steps:
|
|
121
|
+
- log_failure:
|
|
122
|
+
call: sys.log
|
|
123
|
+
args:
|
|
124
|
+
text: ${"Pass " + pass_id + " has " + status_resp.body.failureCount + " FAILURES. Attempting Retry."}
|
|
125
|
+
severity: "WARNING"
|
|
126
|
+
- next: pass_retry_loop
|
|
127
|
+
|
|
128
|
+
# -----------------------------------------------
|
|
129
|
+
# END RETRY LOOP
|
|
130
|
+
# -----------------------------------------------
|
|
131
|
+
|
|
132
|
+
- check_final_status:
|
|
133
|
+
switch:
|
|
134
|
+
- condition: ${not pass_success}
|
|
135
|
+
steps:
|
|
136
|
+
- log_giving_up:
|
|
137
|
+
call: sys.log
|
|
138
|
+
args:
|
|
139
|
+
text: ${"Pass " + pass_id + " failed after " + max_retries + " attempts. Proceeding to next pass with potential gaps."}
|
|
140
|
+
severity: "ERROR"
|
|
141
|
+
|
|
142
|
+
- finish:
|
|
143
|
+
return: "Pipeline Execution Complete"
|
package/index.js
CHANGED
|
@@ -29,8 +29,9 @@ const { handleUpdate } = require('./functions
|
|
|
29
29
|
const { build: buildManifest } = require('./functions/computation-system/context/ManifestBuilder');
|
|
30
30
|
const { dispatchComputationPass } = require('./functions/computation-system/helpers/computation_dispatcher');
|
|
31
31
|
const { handleComputationTask } = require('./functions/computation-system/helpers/computation_worker');
|
|
32
|
-
// [NEW] Import Report Tools
|
|
33
32
|
const { ensureBuildReport, generateBuildReport } = require('./functions/computation-system/tools/BuildReporter');
|
|
33
|
+
// [NEW] Import Monitor
|
|
34
|
+
const { checkPassStatus } = require('./functions/computation-system/helpers/monitor');
|
|
34
35
|
|
|
35
36
|
const dataLoader = require('./functions/computation-system/utils/data_loader');
|
|
36
37
|
const computationUtils = require('./functions/computation-system/utils/utils');
|
|
@@ -51,8 +52,7 @@ const { runBackfillAssetPrices } = require('./functions
|
|
|
51
52
|
// Proxy
|
|
52
53
|
const { handlePost } = require('./functions/appscript-api/index');
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
-
|
|
55
|
+
// Root Indexer
|
|
56
56
|
const { runRootDataIndexer } = require('./functions/root-data-indexer/index');
|
|
57
57
|
|
|
58
58
|
const core = {
|
|
@@ -92,9 +92,10 @@ const computationSystem = {
|
|
|
92
92
|
dataLoader,
|
|
93
93
|
computationUtils,
|
|
94
94
|
buildManifest,
|
|
95
|
-
// [NEW] Export Tools
|
|
96
95
|
ensureBuildReport,
|
|
97
96
|
generateBuildReport,
|
|
97
|
+
// [NEW] Export Monitor Pipe
|
|
98
|
+
checkPassStatus
|
|
98
99
|
};
|
|
99
100
|
|
|
100
101
|
const api = {
|