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
- - finish:
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}'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.555",
3
+ "version": "1.0.556",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [