bulltrackers-module 1.0.653 → 1.0.654
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.
|
@@ -5,16 +5,14 @@
|
|
|
5
5
|
* UPDATED: Enforces Strict One-Shot Policy (Standard -> HighMem -> Dead Letter).
|
|
6
6
|
* UPDATED: Generates Google Cloud Trace Context (traceId/spanId) for end-to-end monitoring.
|
|
7
7
|
* UPDATED: Added Schedule Awareness (Daily, Weekly, Monthly) to filter tasks by date.
|
|
8
|
-
* UPDATED: Force Run now validates Root Data Availability before dispatching.
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
const { getExpectedDateStrings, getEarliestDataDates, normalizeName, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils.js');
|
|
12
11
|
const { groupByPass, analyzeDateExecution } = require('../WorkflowOrchestrator.js');
|
|
13
12
|
const { PubSubUtils } = require('../../core/utils/pubsub_utils');
|
|
14
13
|
const { fetchComputationStatus } = require('../persistence/StatusRepository');
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
const { runFinalSweepCheck } = require('../tools/FinalSweepReporter');
|
|
14
|
+
const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
|
|
15
|
+
const { runFinalSweepCheck } = require('../tools/FinalSweepReporter'); // [NEW]
|
|
18
16
|
const crypto = require('crypto');
|
|
19
17
|
|
|
20
18
|
const OOM_THRESHOLD_MB = 1500; // Unused
|
|
@@ -232,71 +230,92 @@ async function handleForceRun(config, dependencies, computationManifest, reqBody
|
|
|
232
230
|
}
|
|
233
231
|
|
|
234
232
|
// 2. Determine Target Dates
|
|
235
|
-
let
|
|
233
|
+
let candidateDates = [];
|
|
236
234
|
if (dateInput) {
|
|
237
235
|
// Single Date Mode
|
|
238
|
-
|
|
239
|
-
if (Array.isArray(dateInput)) {
|
|
240
|
-
targetDates = dateInput;
|
|
241
|
-
} else {
|
|
242
|
-
targetDates = [dateInput];
|
|
243
|
-
}
|
|
236
|
+
candidateDates = [dateInput];
|
|
244
237
|
} else {
|
|
245
238
|
// All Dates Mode (Backfill)
|
|
246
239
|
logger.log('INFO', `[ForceRun] No date provided. Calculating date range for ${computationName}...`);
|
|
247
240
|
const earliestDates = await getEarliestDataDates(config, dependencies);
|
|
248
241
|
// Calculate from system start until today
|
|
249
|
-
|
|
242
|
+
candidateDates = getExpectedDateStrings(earliestDates.absoluteEarliest, new Date());
|
|
250
243
|
}
|
|
251
244
|
|
|
252
|
-
|
|
253
|
-
logger.log('INFO', `[ForceRun] 🔍 Validating data availability for ${targetDates.length} candidate dates...`);
|
|
254
|
-
|
|
255
|
-
// Sort dates to get efficient min/max for range query
|
|
256
|
-
targetDates.sort();
|
|
257
|
-
const startDate = targetDates[0];
|
|
258
|
-
const endDate = targetDates[targetDates.length - 1];
|
|
245
|
+
logger.log('INFO', `[ForceRun] Checking ${candidateDates.length} candidate dates for runnability...`);
|
|
259
246
|
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
247
|
+
// 3. Filter to only runnable dates using analyzeDateExecution
|
|
248
|
+
const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
|
|
249
|
+
const calcsInPass = groupByPass(computationManifest, manifestItem.pass || "1");
|
|
250
|
+
const targetComputationNormalized = normalizeName(computationName);
|
|
251
|
+
|
|
252
|
+
// Filter to only the target computation
|
|
253
|
+
const targetCalcs = calcsInPass.filter(c => normalizeName(c.name) === targetComputationNormalized);
|
|
254
|
+
|
|
255
|
+
if (targetCalcs.length === 0) {
|
|
256
|
+
throw new Error(`Computation '${computationName}' not found in pass ${manifestItem.pass || "1"}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const runnableDates = [];
|
|
260
|
+
const skippedDates = [];
|
|
261
|
+
|
|
262
|
+
for (const dateStr of candidateDates) {
|
|
263
|
+
// Check root data availability
|
|
264
|
+
const rootDataStatus = await checkRootDataAvailability(dateStr, config, dependencies, DEFINITIVE_EARLIEST_DATES);
|
|
265
|
+
|
|
266
|
+
// Get computation status for this date
|
|
267
|
+
const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
|
|
266
268
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
// Check previous day status if needed
|
|
270
|
+
let prevDailyStatus = null;
|
|
271
|
+
if (targetCalcs.some(c => c.isHistorical)) {
|
|
272
|
+
const prevDate = new Date(dateStr + 'T00:00:00Z');
|
|
273
|
+
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
274
|
+
prevDailyStatus = await fetchComputationStatus(prevDate.toISOString().slice(0, 10), config, dependencies);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Analyze if this computation can run on this date
|
|
278
|
+
const report = analyzeDateExecution(dateStr, targetCalcs, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus);
|
|
270
279
|
|
|
271
|
-
|
|
280
|
+
// Check if the target computation is runnable, needs re-run, or has failed dependencies
|
|
281
|
+
const isRunnable = report.runnable.some(t => normalizeName(t.name) === targetComputationNormalized);
|
|
282
|
+
const needsReRun = report.reRuns.some(t => normalizeName(t.name) === targetComputationNormalized);
|
|
283
|
+
const hasFailedDep = report.failedDependency.some(t => normalizeName(t.name) === targetComputationNormalized);
|
|
284
|
+
const isImpossible = report.impossible.some(t => normalizeName(t.name) === targetComputationNormalized);
|
|
285
|
+
const isBlocked = report.blocked.some(t => normalizeName(t.name) === targetComputationNormalized);
|
|
272
286
|
|
|
273
|
-
if (
|
|
274
|
-
|
|
287
|
+
if (isRunnable || needsReRun || hasFailedDep) {
|
|
288
|
+
runnableDates.push(dateStr);
|
|
289
|
+
} else if (isImpossible) {
|
|
290
|
+
skippedDates.push({ date: dateStr, reason: report.impossible.find(t => normalizeName(t.name) === targetComputationNormalized)?.reason || 'Impossible' });
|
|
291
|
+
} else if (isBlocked) {
|
|
292
|
+
skippedDates.push({ date: dateStr, reason: report.blocked.find(t => normalizeName(t.name) === targetComputationNormalized)?.reason || 'Blocked' });
|
|
275
293
|
} else {
|
|
276
|
-
|
|
277
|
-
skippedStats[reason] = (skippedStats[reason] || 0) + 1;
|
|
294
|
+
skippedDates.push({ date: dateStr, reason: 'Not runnable (unknown reason)' });
|
|
278
295
|
}
|
|
279
296
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
297
|
+
|
|
298
|
+
logger.log('INFO', `[ForceRun] ✅ Found ${runnableDates.length} runnable dates out of ${candidateDates.length} candidates`);
|
|
299
|
+
if (skippedDates.length > 0) {
|
|
300
|
+
logger.log('INFO', `[ForceRun] ⏭️ Skipped ${skippedDates.length} dates: ${skippedDates.slice(0, 5).map(s => `${s.date} (${s.reason})`).join(', ')}${skippedDates.length > 5 ? '...' : ''}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (runnableDates.length === 0) {
|
|
283
304
|
return {
|
|
284
|
-
status: '
|
|
305
|
+
status: 'NO_RUNNABLE_DATES',
|
|
285
306
|
computation: computationName,
|
|
286
|
-
|
|
287
|
-
|
|
307
|
+
mode: dateInput ? 'SINGLE_DATE' : 'ALL_DATES',
|
|
308
|
+
datesChecked: candidateDates.length,
|
|
309
|
+
datesRunnable: 0,
|
|
310
|
+
skippedReasons: skippedDates.slice(0, 10)
|
|
288
311
|
};
|
|
289
312
|
}
|
|
313
|
+
|
|
314
|
+
logger.log('WARN', `[ForceRun] 🚨 MANUALLY Triggering ${computationName} for ${runnableDates.length} runnable dates. Pass: ${manifestItem.pass}`);
|
|
290
315
|
|
|
291
|
-
|
|
292
|
-
logger.log('INFO', `[ForceRun] ⚠️ Filtered impossible dates: ${targetDates.length} requested -> ${validDates.length} valid.`, { skippedStats });
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
logger.log('WARN', `[ForceRun] 🚨 MANUALLY Triggering ${computationName} for ${validDates.length} VALID dates. Pass: ${manifestItem.pass}`);
|
|
296
|
-
|
|
297
|
-
// 4. Construct Tasks
|
|
316
|
+
// 4. Construct Tasks (only for runnable dates)
|
|
298
317
|
const dispatchId = crypto.randomUUID();
|
|
299
|
-
const tasks =
|
|
318
|
+
const tasks = runnableDates.map(date => {
|
|
300
319
|
const traceId = crypto.randomBytes(16).toString('hex');
|
|
301
320
|
const spanId = crypto.randomBytes(8).toString('hex');
|
|
302
321
|
return {
|
|
@@ -312,7 +331,7 @@ async function handleForceRun(config, dependencies, computationManifest, reqBody
|
|
|
312
331
|
};
|
|
313
332
|
});
|
|
314
333
|
|
|
315
|
-
//
|
|
334
|
+
// 4. Batch Publish (Chunked to stay under Pub/Sub limits)
|
|
316
335
|
const CHUNK_SIZE = 250; // Safe batch size
|
|
317
336
|
const topic = (reqBody.resources === 'high-mem')
|
|
318
337
|
? (config.computationTopicHighMem || 'computation-tasks-highmem')
|
|
@@ -337,10 +356,11 @@ async function handleForceRun(config, dependencies, computationManifest, reqBody
|
|
|
337
356
|
return {
|
|
338
357
|
status: 'FORCED',
|
|
339
358
|
computation: computationName,
|
|
340
|
-
mode: dateInput ? '
|
|
341
|
-
|
|
359
|
+
mode: dateInput ? 'SINGLE_DATE' : 'ALL_DATES',
|
|
360
|
+
datesChecked: candidateDates.length,
|
|
361
|
+
datesRunnable: runnableDates.length,
|
|
342
362
|
datesTriggered: dispatchedCount,
|
|
343
|
-
|
|
363
|
+
skippedCount: skippedDates.length,
|
|
344
364
|
targetTopic: topic
|
|
345
365
|
};
|
|
346
366
|
}
|