bulltrackers-module 1.0.777 → 1.0.778

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,9 +1,9 @@
1
1
  /**
2
- * @fileoverview Scheduler V3.3: Smart Reconciliation & Future-Aligned Backfills
2
+ * @fileoverview Scheduler V3.4: Enhanced Logging for Verification
3
3
  * * * 1. Reconcile: Checks ENTIRE graph for stale hashes, triggers ROOTS if needed.
4
4
  * * 2. Purge: Actively removes tasks that don't match the current deployment hash.
5
5
  * * 3. Watchdog: Recovers "Zombie" tasks (running but stuck).
6
- * * * UPDATE: Backfills now respect the 'next available window' instead of running instantly.
6
+ * * 4. Logging: Detailed output for Tombstoned/Existing tasks to verify ETA logic.
7
7
  */
8
8
 
9
9
  const { CloudTasksClient } = require('@google-cloud/tasks');
@@ -18,8 +18,8 @@ const config = require('../config/bulltrackers.config');
18
18
 
19
19
  // Config
20
20
  const CLOUD_TASKS_CONCURRENCY = 20;
21
- const PLANNING_LOOKBACK_DAYS = 7; // Look back to ensure recent history is correct
22
- const PLANNING_LOOKAHEAD_HOURS = 24; // Schedule future tasks
21
+ const PLANNING_LOOKBACK_DAYS = 7;
22
+ const PLANNING_LOOKAHEAD_HOURS = 24;
23
23
  const ZOMBIE_THRESHOLD_MINUTES = 15;
24
24
 
25
25
  // Cache singleton instances
@@ -45,7 +45,6 @@ async function initialize() {
45
45
 
46
46
  /**
47
47
  * ENTRY POINT 1: The Planner
48
- * Trigger: Cloud Scheduler -> "0 * * * *" (Hourly)
49
48
  */
50
49
  async function planComputations(req, res) {
51
50
  try {
@@ -62,7 +61,6 @@ async function planComputations(req, res) {
62
61
 
63
62
  console.log(`[Planner] Reconciling window: ${windowStart.toISOString()} to ${windowEnd.toISOString()}`);
64
63
 
65
- // Helper to find Roots for any given computation (Pass 1..N)
66
64
  const manifestMap = new Map(manifest.map(m => [m.name, m]));
67
65
  const getRoots = (entry, visited = new Set()) => {
68
66
  if (visited.has(entry.name)) return [];
@@ -74,7 +72,7 @@ async function planComputations(req, res) {
74
72
  .flatMap(p => getRoots(p, visited));
75
73
  };
76
74
 
77
- const tasksToSchedule = new Map(); // Use Map to deduplicate by Task Name
75
+ const tasksToSchedule = new Map();
78
76
  const stats = { checked: 0, scheduled: 0, mismatched: 0, missing: 0 };
79
77
 
80
78
  const targetDates = [];
@@ -89,9 +87,7 @@ async function planComputations(req, res) {
89
87
  const dateStr = dateObj.toISOString().split('T')[0];
90
88
  const dailyStatus = await stateRepository.getDailyStatus(dateStr);
91
89
 
92
- // Iterate ALL computations (not just Pass 1) to find stale nodes
93
90
  for (const entry of manifest) {
94
- // If this specific entry is not scheduled for today, skip it
95
91
  if (!shouldRunOnDate(entry.schedule, dateObj)) continue;
96
92
 
97
93
  stats.checked++;
@@ -107,15 +103,13 @@ async function planComputations(req, res) {
107
103
  }
108
104
 
109
105
  if (reason) {
110
- // If entry is stale, we must schedule its ROOT(s) to trigger the chain
111
106
  const roots = getRoots(entry);
112
107
 
113
108
  roots.forEach(root => {
114
- // Unique Task Key: RootName + Date + Hash
115
109
  const taskKey = `root-${toKebab(root.originalName)}-${dateStr}-${root.hash}`;
116
110
 
117
111
  if (!tasksToSchedule.has(taskKey)) {
118
- // NEW: Calculate the NEXT valid run window instead of using the past date
112
+ // Calculate proper ETA (Next valid window)
119
113
  const runAt = getNextRunWindow(root.schedule, dateObj);
120
114
 
121
115
  tasksToSchedule.set(taskKey, {
@@ -260,31 +254,21 @@ function shouldRunOnDate(schedule, dateObj) {
260
254
  return true;
261
255
  }
262
256
 
263
- /**
264
- * Calculates the run time.
265
- * If the Target Date's schedule is in the PAST, it projects execution to the NEXT available window (Today or Tomorrow).
266
- */
267
257
  function getNextRunWindow(schedule, targetDateObj) {
268
258
  const [h, m] = (schedule.time || '02:00').split(':').map(Number);
269
-
270
- // 1. Calculate the ideal run time on the TARGET date
271
259
  let runTime = new Date(targetDateObj);
272
260
  runTime.setUTCHours(h, m, 0, 0);
273
261
 
274
262
  const now = Date.now();
275
-
276
- // 2. If ideal time is in the past, move it to the *next* occurrence relative to NOW
263
+ // If idealized time is in the past, shift to NEXT available window relative to NOW
277
264
  if (runTime.getTime() < now) {
278
265
  const nextWindow = new Date();
279
266
  nextWindow.setUTCHours(h, m, 0, 0);
280
-
281
- // If today's window has passed, move to tomorrow
282
267
  if (nextWindow.getTime() <= now) {
283
268
  nextWindow.setUTCDate(nextWindow.getUTCDate() + 1);
284
269
  }
285
270
  return nextWindow.getTime() / 1000;
286
271
  }
287
-
288
272
  return runTime.getTime() / 1000;
289
273
  }
290
274
 
@@ -334,10 +318,17 @@ async function dispatchTasks(tasks) {
334
318
  return { status: 'scheduled' };
335
319
 
336
320
  } catch (e) {
321
+ // ALREADY_EXISTS (6) or CONFLICT (409)
337
322
  if (e.code === 6 || e.code === 409) {
338
- console.warn(`[Planner] Task name collision (Tombstone?): ${t.computation} @ ${t.targetDate}`);
323
+ // ENHANCED LOGGING:
324
+ const eta = t.runAtSeconds
325
+ ? new Date(t.runAtSeconds * 1000).toISOString()
326
+ : 'Immediate';
327
+
328
+ console.warn(`[Planner] ⚠️ Task collision (Tombstone/Exists): ${t.computation} | Target: ${t.targetDate} | Planned ETA: ${eta} | Hash: ${t.configHash}`);
339
329
  return { status: 'exists' };
340
330
  }
331
+
341
332
  console.error(`[Planner] Failed task ${t.computation}: ${e.message}`);
342
333
  return { status: 'error' };
343
334
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.777",
3
+ "version": "1.0.778",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [