bulltrackers-module 1.0.694 → 1.0.695

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.
@@ -11,6 +11,8 @@ const { PubSubUtils } = require('../../core/utils/pubsub_utils');
11
11
  const { fetchComputationStatus } = require('../persistence/StatusRepository');
12
12
  const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
13
13
  const { runFinalSweepCheck } = require('../tools/FinalSweepReporter');
14
+ const { resolveDependencyChain } = require('./on_demand_helpers');
15
+ const { checkRootDependencies } = require('../data/AvailabilityChecker');
14
16
  // 1. IMPORT SNAPSHOT SERVICE
15
17
  const { generateDailySnapshots } = require('../services/SnapshotService');
16
18
  const crypto = require('crypto');
@@ -318,6 +320,16 @@ async function handleForceRun(config, dependencies, computationManifest, reqBody
318
320
  const manifestItem = computationManifest.find(c => normalizeName(c.name) === normalizeName(computationName));
319
321
  if (!manifestItem) throw new Error(`Computation '${computationName}' not found.`);
320
322
 
323
+ // --- STEP 1: RESOLVE FULL ANCESTRY ---
324
+ // We get the full chain of dependencies (ancestors) for the target.
325
+ // This includes the target itself and all upstream computations.
326
+ const chainPasses = resolveDependencyChain(computationName, computationManifest);
327
+ const allAncestors = chainPasses.flatMap(p => p.computations); // Flat list of all required names
328
+
329
+ // Create a map for quick lookup of ancestor manifests
330
+ const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
331
+
332
+ // --- STEP 2: DETERMINE CANDIDATE DATES ---
321
333
  let candidateDates = [];
322
334
  if (dateInput) {
323
335
  candidateDates = [dateInput];
@@ -327,50 +339,85 @@ async function handleForceRun(config, dependencies, computationManifest, reqBody
327
339
  candidateDates = getExpectedDateStrings(earliest.absoluteEarliest, new Date());
328
340
  }
329
341
 
330
- logger.log('INFO', `[ForceRun] Checking ${candidateDates.length} candidate dates for runnability...`);
342
+ logger.log('INFO', `[ForceRun] Checking ${candidateDates.length} candidate dates for runnability (Deep Check)...`);
331
343
 
332
344
  const runnableDates = [];
333
345
  const skippedDates = [];
334
- const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
335
-
336
- const targetComp = { ...manifestItem, schedule: null };
337
346
  const targetComputationNormalized = normalizeName(computationName);
347
+ // Remove schedule constraints for the force run assessment
348
+ const targetComp = { ...manifestItem, schedule: null };
338
349
 
339
350
  for (const date of candidateDates) {
351
+ // --- STEP 3: DEEP ROOT CHECK ---
352
+ // Before even asking if the *target* is runnable, we ask:
353
+ // "Are the raw ingredients available for the ENTIRE chain?"
354
+
355
+ // Fetch Root Data Status for this date once
356
+ const availability = await checkRootDataAvailability(date, config, dependencies, DEFINITIVE_EARLIEST_DATES);
357
+ const rootStatus = availability ? availability.status : null;
358
+
359
+ if (!rootStatus) {
360
+ skippedDates.push({ date, reason: 'Availability Index Missing' });
361
+ continue;
362
+ }
363
+
364
+ let deepImpossibleReason = null;
365
+
366
+ // Check EVERY ancestor's root requirements
367
+ for (const ancName of allAncestors) {
368
+ const ancManifest = manifestMap.get(normalizeName(ancName));
369
+ if (!ancManifest) continue;
370
+
371
+ // Re-use the standard checker for each ancestor
372
+ const ancCheck = checkRootDependencies(ancManifest, rootStatus);
373
+
374
+ if (!ancCheck.canRun) {
375
+ // If an ancestor cannot exist, the target cannot exist.
376
+ deepImpossibleReason = `Ancestor '${ancName}' is missing roots: ${ancCheck.missing.join(', ')}`;
377
+ break;
378
+ }
379
+ }
380
+
381
+ if (deepImpossibleReason) {
382
+ // Skip this date entirely - it is strictly impossible.
383
+ skippedDates.push({ date, reason: deepImpossibleReason });
384
+ continue;
385
+ }
386
+
387
+ // --- STEP 4: STANDARD RUNNABILITY ---
388
+ // If deep roots are fine, we proceed to the standard check.
389
+ // This handles logic like "Waiting for yesterday" or "Already Complete"
340
390
  const result = await assessDateRunnability(date, [targetComp], config, dependencies, manifestMap);
341
- if (!result) {
342
- skippedDates.push({ date, reason: 'Root data unavailable' });
343
- continue;
391
+
392
+ if (!result) {
393
+ skippedDates.push({ date, reason: 'Assessment Failed' });
394
+ continue;
344
395
  }
345
396
 
346
397
  const { report } = result;
347
398
  const isRunnable = report.runnable.some(t => normalizeName(t.name) === targetComputationNormalized);
348
399
  const needsReRun = report.reRuns.some(t => normalizeName(t.name) === targetComputationNormalized);
349
400
  const hasFailedDep = report.failedDependency.some(t => normalizeName(t.name) === targetComputationNormalized);
350
- const isImpossible = report.impossible.some(t => normalizeName(t.name) === targetComputationNormalized);
351
- const isBlocked = report.blocked.some(t => normalizeName(t.name) === targetComputationNormalized);
352
401
  const isSkipped = report.skipped.some(t => normalizeName(t.name) === targetComputationNormalized);
353
402
 
354
- // For force runs: treat skipped computations (already stored with valid hash) as runnable
355
- // They will overwrite with the same result, which is fine for testing
356
- // Only mark as impossible if root data or dependencies don't exist at all
357
- if (isImpossible) {
358
- skippedDates.push({ date, reason: report.impossible.find(t => normalizeName(t.name) === targetComputationNormalized)?.reason || 'Impossible' });
359
- } else if (isRunnable || needsReRun || hasFailedDep || isSkipped) {
360
- // Runnable, needs re-run, has failed deps (but not impossible), or skipped (already stored)
361
- // All of these are runnable for force runs - will overwrite existing results if needed
403
+ // NOTE: hasFailedDep is ALLOWED here because we are "Forcing" it.
404
+ // We know the roots exist (checked above), so the missing dependency is likely just
405
+ // "Not Computed Yet", which is exactly what the user wants to fix manually.
406
+ if (isRunnable || needsReRun || hasFailedDep || isSkipped) {
362
407
  runnableDates.push(date);
363
- } else if (isBlocked) {
364
- // Blocked usually means waiting for data - for force runs, if root data exists, still runnable
365
- // Only skip if truly impossible (handled above)
408
+ } else if (report.blocked.length > 0) {
409
+ // Blocked usually means "Waiting for yesterday"
410
+ // For force runs, we often want to override this, but if it's strictly blocked
411
+ // by logic, we might still count it. Usually, we treat it as runnable.
366
412
  runnableDates.push(date);
367
413
  } else {
368
- // Unknown state - for force runs, if root data exists (which it does, since result is not null), treat as runnable
369
- logger.log('WARN', `[ForceRun] Computation ${computationName} in unknown state for ${date}, treating as runnable`);
370
- runnableDates.push(date);
414
+ const imp = report.impossible.find(t => normalizeName(t.name) === targetComputationNormalized);
415
+ skippedDates.push({ date, reason: imp ? imp.reason : 'Unknown State' });
371
416
  }
372
417
  }
373
418
 
419
+ // ... (Remainder of the function remains the same: dispatching tasks) ...
420
+
374
421
  logger.log('INFO', `[ForceRun] ✅ Found ${runnableDates.length} runnable dates out of ${candidateDates.length} candidates`);
375
422
 
376
423
  if (runnableDates.length === 0) {
@@ -384,13 +431,12 @@ async function handleForceRun(config, dependencies, computationManifest, reqBody
384
431
  };
385
432
  }
386
433
 
387
- logger.log('WARN', `[ForceRun] 🚨 MANUALLY Triggering ${computationName} for ${runnableDates.length} runnable dates. Pass: ${manifestItem.pass}`);
388
-
434
+ // Dispatch Logic
389
435
  const topic = (reqBody.resources === 'high-mem')
390
436
  ? (config.computationTopicHighMem || 'computation-tasks-highmem')
391
437
  : (config.computationTopicStandard || 'computation-tasks');
392
438
 
393
- const dispatchId = crypto.randomUUID();
439
+ const dispatchId = require('crypto').randomUUID();
394
440
  const tasks = runnableDates.map(date =>
395
441
  createTaskPayload(manifestItem, date, manifestItem.pass || "1", dispatchId, reqBody.resources, 'MANUAL_FORCE_API')
396
442
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.694",
3
+ "version": "1.0.695",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [