bulltrackers-module 1.0.555 → 1.0.556
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.
|
@@ -132,7 +132,6 @@ async function getStableDateSession(config, dependencies, passToRun, dateLimitSt
|
|
|
132
132
|
return allDates;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
// =============================================================================
|
|
136
135
|
// MAIN ENTRY POINT
|
|
137
136
|
// =============================================================================
|
|
138
137
|
async function dispatchComputationPass(config, dependencies, computationManifest, reqBody = {}) {
|
|
@@ -144,14 +143,100 @@ async function dispatchComputationPass(config, dependencies, computationManifest
|
|
|
144
143
|
else if (action === 'SWEEP') {
|
|
145
144
|
return handleSweepDispatch(config, dependencies, computationManifest, reqBody);
|
|
146
145
|
}
|
|
147
|
-
// [NEW] Handler for Final Forensics Reporting
|
|
148
146
|
else if (action === 'REPORT') {
|
|
149
147
|
return handleFinalSweepReporting(config, dependencies, computationManifest, reqBody);
|
|
150
148
|
}
|
|
149
|
+
// [NEW] FORCE RUN HANDLER
|
|
150
|
+
else if (action === 'FORCE_RUN') {
|
|
151
|
+
return handleForceRun(config, dependencies, computationManifest, reqBody);
|
|
152
|
+
}
|
|
151
153
|
|
|
152
154
|
return handleStandardDispatch(config, dependencies, computationManifest, reqBody);
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
// =============================================================================
|
|
158
|
+
// NEW: Force Run Handler (Bypasses Checks)
|
|
159
|
+
// =============================================================================
|
|
160
|
+
async function handleForceRun(config, dependencies, computationManifest, reqBody) {
|
|
161
|
+
const { logger } = dependencies;
|
|
162
|
+
const pubsubUtils = new PubSubUtils(dependencies);
|
|
163
|
+
const computationName = reqBody.computation; // Required
|
|
164
|
+
const dateInput = reqBody.date; // Optional (YYYY-MM-DD)
|
|
165
|
+
|
|
166
|
+
if (!computationName) {
|
|
167
|
+
throw new Error('Force Run requires "computation" name.');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 1. Verify Computation Exists
|
|
171
|
+
const manifestItem = computationManifest.find(c => normalizeName(c.name) === normalizeName(computationName));
|
|
172
|
+
if (!manifestItem) {
|
|
173
|
+
throw new Error(`Computation '${computationName}' not found in manifest.`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 2. Determine Target Dates
|
|
177
|
+
let targetDates = [];
|
|
178
|
+
if (dateInput) {
|
|
179
|
+
// Single Date Mode
|
|
180
|
+
targetDates = [dateInput];
|
|
181
|
+
} else {
|
|
182
|
+
// All Dates Mode (Backfill)
|
|
183
|
+
logger.log('INFO', `[ForceRun] No date provided. Calculating date range for ${computationName}...`);
|
|
184
|
+
const earliestDates = await getEarliestDataDates(config, dependencies);
|
|
185
|
+
// Calculate from system start until today
|
|
186
|
+
targetDates = getExpectedDateStrings(earliestDates.absoluteEarliest, new Date());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
logger.log('WARN', `[ForceRun] 🚨 MANUALLY Triggering ${computationName} for ${targetDates.length} dates. Pass: ${manifestItem.pass}`);
|
|
190
|
+
|
|
191
|
+
// 3. Construct Tasks
|
|
192
|
+
const dispatchId = crypto.randomUUID();
|
|
193
|
+
const tasks = targetDates.map(date => {
|
|
194
|
+
const traceId = crypto.randomBytes(16).toString('hex');
|
|
195
|
+
const spanId = crypto.randomBytes(8).toString('hex');
|
|
196
|
+
return {
|
|
197
|
+
action: 'RUN_COMPUTATION_DATE',
|
|
198
|
+
computation: manifestItem.name,
|
|
199
|
+
date: date,
|
|
200
|
+
pass: manifestItem.pass || "1",
|
|
201
|
+
dispatchId: dispatchId,
|
|
202
|
+
triggerReason: 'MANUAL_FORCE_API',
|
|
203
|
+
resources: reqBody.resources || 'standard',
|
|
204
|
+
// Trace context allows you to find these specific runs in Cloud Trace
|
|
205
|
+
traceContext: { traceId, spanId, sampled: true }
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// 4. Batch Publish (Chunked to stay under Pub/Sub limits)
|
|
210
|
+
const CHUNK_SIZE = 250; // Safe batch size
|
|
211
|
+
const topic = (reqBody.resources === 'high-mem')
|
|
212
|
+
? (config.computationTopicHighMem || 'computation-tasks-highmem')
|
|
213
|
+
: (config.computationTopicStandard || 'computation-tasks');
|
|
214
|
+
|
|
215
|
+
let dispatchedCount = 0;
|
|
216
|
+
const chunks = [];
|
|
217
|
+
for (let i = 0; i < tasks.length; i += CHUNK_SIZE) {
|
|
218
|
+
chunks.push(tasks.slice(i, i + CHUNK_SIZE));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Publish chunks sequentially to avoid memory spikes
|
|
222
|
+
for (const chunk of chunks) {
|
|
223
|
+
await pubsubUtils.batchPublishTasks(dependencies, {
|
|
224
|
+
topicName: topic,
|
|
225
|
+
tasks: chunk,
|
|
226
|
+
taskType: 'manual-force-run'
|
|
227
|
+
});
|
|
228
|
+
dispatchedCount += chunk.length;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
status: 'FORCED',
|
|
233
|
+
computation: computationName,
|
|
234
|
+
mode: dateInput ? 'SINGLE_DATE' : 'ALL_DATES',
|
|
235
|
+
datesTriggered: dispatchedCount,
|
|
236
|
+
targetTopic: topic
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
155
240
|
// =============================================================================
|
|
156
241
|
// NEW: Final Sweep Reporting Handler
|
|
157
242
|
// =============================================================================
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Force Run Control Script
|
|
3
|
+
* Usage:
|
|
4
|
+
* node scripts/force_run.js <ComputationName> [YYYY-MM-DD]
|
|
5
|
+
* * Examples:
|
|
6
|
+
* node scripts/force_run.js TestSystemProbe 2026-01-02 (Runs specific day)
|
|
7
|
+
* node scripts/force_run.js TestSystemProbe (Runs ALL days from start)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
require('dotenv').config(); // Load environment variables
|
|
11
|
+
const { PubSub } = require('@google-cloud/pubsub');
|
|
12
|
+
|
|
13
|
+
// CONFIGURATION
|
|
14
|
+
const PROJECT_ID = process.env.GCP_PROJECT_ID || 'stocks-12345';
|
|
15
|
+
const TOPIC_NAME = process.env.PUBSUB_TOPIC_DISPATCH || 'dispatch-topic'; // Must match orchestrator_config.js
|
|
16
|
+
const SYSTEM_START_DATE = '2026-01-01'; // The beginning of time for your system
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const computationName = args[0];
|
|
21
|
+
const specificDate = args[1];
|
|
22
|
+
|
|
23
|
+
if (!computationName) {
|
|
24
|
+
console.error('❌ Error: Computation Name is required.');
|
|
25
|
+
console.log('Usage: node scripts/force_run.js <ComputationName> [YYYY-MM-DD]');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const pubsub = new PubSub({ projectId: PROJECT_ID });
|
|
30
|
+
const topic = pubsub.topic(TOPIC_NAME);
|
|
31
|
+
|
|
32
|
+
// 1. Logic: Single Date vs. All Dates
|
|
33
|
+
if (specificDate) {
|
|
34
|
+
await triggerComputation(topic, computationName, specificDate);
|
|
35
|
+
} else {
|
|
36
|
+
console.log(`⚠️ No date provided. Triggering REPLAY for ${computationName} from ${SYSTEM_START_DATE} to Today...`);
|
|
37
|
+
|
|
38
|
+
let currentDate = new Date(SYSTEM_START_DATE);
|
|
39
|
+
const today = new Date();
|
|
40
|
+
|
|
41
|
+
while (currentDate <= today) {
|
|
42
|
+
const dateStr = currentDate.toISOString().split('T')[0];
|
|
43
|
+
await triggerComputation(topic, computationName, dateStr);
|
|
44
|
+
|
|
45
|
+
// Move to next day
|
|
46
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
47
|
+
|
|
48
|
+
// Small throttle to prevent flooding Pub/Sub quota if years of data
|
|
49
|
+
await new Promise(r => setTimeout(r, 100));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function triggerComputation(topic, computation, date) {
|
|
55
|
+
const payload = {
|
|
56
|
+
computationName: computation,
|
|
57
|
+
date: date,
|
|
58
|
+
force: true, // Tells Orchestrator to ignore "Already Complete" status
|
|
59
|
+
source: 'manual-cli' // Audit trail
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const dataBuffer = Buffer.from(JSON.stringify(payload));
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const messageId = await topic.publishMessage({ data: dataBuffer });
|
|
66
|
+
console.log(`✅ [${date}] Triggered ${computation} (Msg ID: ${messageId})`);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`❌ [${date}] Failed to trigger ${computation}:`, error.message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main().catch(console.error);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Cloud Workflows: Precision Cursor-Based Orchestrator
|
|
1
|
+
# Cloud Workflows: Precision Cursor-Based Orchestrator with Manual Override
|
|
2
2
|
main:
|
|
3
3
|
params: [input]
|
|
4
4
|
steps:
|
|
@@ -9,6 +9,12 @@ main:
|
|
|
9
9
|
- current_date: '${text.split(time.format(sys.now()), "T")[0]}'
|
|
10
10
|
- date_to_run: '${default(map.get(input, "date"), current_date)}'
|
|
11
11
|
|
|
12
|
+
# --- 🔀 NEW: CHECK FOR MANUAL OVERRIDE ---
|
|
13
|
+
- check_manual_override:
|
|
14
|
+
switch:
|
|
15
|
+
- condition: '${map.get(input, "action") == "FORCE_RUN"}'
|
|
16
|
+
next: execute_manual_force_run
|
|
17
|
+
|
|
12
18
|
# --- PHASE 1: EXECUTION (Standard + High Mem Retry) ---
|
|
13
19
|
- run_sequential_passes:
|
|
14
20
|
for:
|
|
@@ -80,7 +86,7 @@ main:
|
|
|
80
86
|
- next_loop_retry:
|
|
81
87
|
next: sequential_date_loop
|
|
82
88
|
|
|
83
|
-
# --- VERIFICATION & SWEEP ---
|
|
89
|
+
# --- VERIFICATION & SWEEP (CRITICAL LOGIC PRESERVED) ---
|
|
84
90
|
- verify_pass_completion:
|
|
85
91
|
call: http.post
|
|
86
92
|
args:
|
|
@@ -116,8 +122,6 @@ main:
|
|
|
116
122
|
seconds: '${int(sweep_task.eta)}'
|
|
117
123
|
|
|
118
124
|
# --- PHASE 2: FINAL FORENSIC REPORTING ---
|
|
119
|
-
# Triggered after ALL execution attempts for this pass (Standard -> Verify -> HighMem Sweep)
|
|
120
|
-
# We ask the dispatcher to run the FinalSweepReporter for the target date.
|
|
121
125
|
- run_final_forensics:
|
|
122
126
|
for:
|
|
123
127
|
value: pass_id
|
|
@@ -139,5 +143,21 @@ main:
|
|
|
139
143
|
args:
|
|
140
144
|
text: '${"📝 FINAL REPORT: Pass " + pass_id + " -> " + report_res.body.issuesFound + " detailed forensic documents created."}'
|
|
141
145
|
|
|
142
|
-
-
|
|
143
|
-
return: "Pipeline Complete with Forensic Analysis"
|
|
146
|
+
- finish_standard:
|
|
147
|
+
return: "Pipeline Complete with Forensic Analysis"
|
|
148
|
+
|
|
149
|
+
# --- 🚨 MANUAL OVERRIDE EXECUTION PATH ---
|
|
150
|
+
- execute_manual_force_run:
|
|
151
|
+
call: http.post
|
|
152
|
+
args:
|
|
153
|
+
url: '${"https://europe-west1-" + project + ".cloudfunctions.net/computation-dispatcher"}'
|
|
154
|
+
body:
|
|
155
|
+
action: 'FORCE_RUN'
|
|
156
|
+
computation: '${input.computation}'
|
|
157
|
+
date: '${map.get(input, "date")}' # Can be null for ALL_DATES
|
|
158
|
+
resources: '${map.get(input, "resources")}'
|
|
159
|
+
auth: { type: OIDC }
|
|
160
|
+
result: force_res
|
|
161
|
+
|
|
162
|
+
- finish_manual:
|
|
163
|
+
return: '${force_res.body}'
|