bulltrackers-module 1.0.229 → 1.0.230

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,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Main Orchestrator. Coordinates the topological execution.
3
- * UPDATED: Includes defensive checks for Logger compatibility.
3
+ * UPDATED: Implements 'IMPOSSIBLE' state logic for missing root data on historical dates.
4
4
  */
5
5
  const { normalizeName } = require('./utils/utils');
6
6
  const { checkRootDataAvailability } = require('./data/AvailabilityChecker');
@@ -10,6 +10,9 @@ const { StandardExecutor } = require('./executor
10
10
  const { MetaExecutor } = require('./executors/MetaExecutor');
11
11
  const { generateProcessId, PROCESS_TYPES } = require('./logger/logger');
12
12
 
13
+ // New Status Constant
14
+ const STATUS_IMPOSSIBLE = 'IMPOSSIBLE';
15
+
13
16
  function groupByPass(manifest) {
14
17
  return manifest.reduce((acc, calc) => {
15
18
  (acc[calc.pass] = acc[calc.pass] || []).push(calc);
@@ -18,28 +21,32 @@ function groupByPass(manifest) {
18
21
  }
19
22
 
20
23
  /**
21
- * Performs strict analysis of what can run based on availability and hash states.
22
- * AIRTIGHT LOGIC:
23
- * 1. Missing Root Data -> Blocked (Writes 'false' to DB)
24
- * 2. Missing/Stale Dependency -> FailedDependency (Writes 'false' to DB)
25
- * 3. Hash Mismatch -> ReRun (Cascade or Code Change)
26
- * 4. No Result -> Run
27
- * 5. Result Exists & Hash Match -> Skip
24
+ * Performs strict analysis of what can run.
25
+ * IMPOSSIBLE LOGIC:
26
+ * 1. If Root Data is missing AND Date != Today -> IMPOSSIBLE.
27
+ * 2. If Dependency is IMPOSSIBLE -> IMPOSSIBLE.
28
+ * 3. IMPOSSIBLE items are written to DB to prevent future retries.
28
29
  */
29
30
  function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus, manifestMap) {
30
31
  const report = {
31
32
  runnable: [],
32
- blocked: [], // Missing Root Data
33
- failedDependency: [], // Missing Dependency OR Stale Dependency
33
+ blocked: [], // Missing Root Data (Today - Retriable)
34
+ impossible: [], // Missing Root Data (Historical) or Dependency Impossible
35
+ failedDependency: [], // Missing/Stale Dependency (Transient)
34
36
  reRuns: [], // Hash Mismatch
35
37
  skipped: [] // Already done & valid
36
38
  };
37
39
 
40
+ const isTargetToday = (dateStr === new Date().toISOString().slice(0, 10));
41
+
38
42
  const isDepSatisfied = (depName, dailyStatus, manifestMap) => {
39
43
  const norm = normalizeName(depName);
40
44
  const storedDepHash = dailyStatus[norm];
41
45
  const depManifest = manifestMap.get(norm);
42
46
 
47
+ // Check 1: Is dependency IMPOSSIBLE? (Logic handled in main loop, but safe to check here)
48
+ if (storedDepHash === STATUS_IMPOSSIBLE) return false;
49
+
43
50
  if (!storedDepHash) return false;
44
51
  if (!depManifest) return false;
45
52
  if (storedDepHash !== depManifest.hash) return false;
@@ -52,7 +59,13 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
52
59
  const storedHash = dailyStatus[cName];
53
60
  const currentHash = calc.hash;
54
61
 
55
- // 1. Root Data Check
62
+ // 1. Check if ALREADY marked IMPOSSIBLE
63
+ if (storedHash === STATUS_IMPOSSIBLE) {
64
+ report.skipped.push({ name: cName, reason: 'Permanently Impossible' });
65
+ continue;
66
+ }
67
+
68
+ // 2. Root Data Check
56
69
  const missingRoots = [];
57
70
  if (calc.rootDataDependencies) {
58
71
  for (const dep of calc.rootDataDependencies) {
@@ -65,27 +78,49 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
65
78
  }
66
79
 
67
80
  if (missingRoots.length > 0) {
68
- report.blocked.push({ name: cName, reason: `Missing Root Data: ${missingRoots.join(', ')}` });
81
+ // LOGIC: If date is NOT today, missing root data is fatal and permanent.
82
+ if (!isTargetToday) {
83
+ report.impossible.push({ name: cName, reason: `Missing Root Data: ${missingRoots.join(', ')} (Historical)` });
84
+ } else {
85
+ report.blocked.push({ name: cName, reason: `Missing Root Data: ${missingRoots.join(', ')} (Waiting)` });
86
+ }
69
87
  continue;
70
88
  }
71
89
 
72
- // 2. Dependency Check
90
+ // 3. Dependency Check
91
+ let dependencyIsImpossible = false;
73
92
  const missingDeps = [];
93
+
74
94
  if (calc.dependencies) {
75
95
  for (const dep of calc.dependencies) {
96
+ const normDep = normalizeName(dep);
97
+
98
+ // Check if the dependency is marked IMPOSSIBLE in the DB
99
+ if (dailyStatus[normDep] === STATUS_IMPOSSIBLE) {
100
+ dependencyIsImpossible = true;
101
+ // We can break early, if one input is impossible, the result is impossible.
102
+ break;
103
+ }
104
+
76
105
  if (!isDepSatisfied(dep, dailyStatus, manifestMap)) {
77
106
  missingDeps.push(dep);
78
107
  }
79
108
  }
80
109
  }
81
110
 
111
+ if (dependencyIsImpossible) {
112
+ // Propagate the Impossible Status
113
+ report.impossible.push({ name: cName, reason: 'Dependency is Impossible' });
114
+ continue;
115
+ }
116
+
82
117
  if (missingDeps.length > 0) {
83
118
  report.failedDependency.push({ name: cName, missing: missingDeps });
84
119
  continue;
85
120
  }
86
121
 
87
- // 3. Hash / State Check
88
- if (!storedHash) {
122
+ // 4. Hash / State Check
123
+ if (!storedHash || storedHash === false) { // false indicates previous transient failure
89
124
  report.runnable.push(calc);
90
125
  } else if (storedHash !== currentHash) {
91
126
  report.reRuns.push({ name: cName, oldHash: storedHash, newHash: currentHash });
@@ -123,23 +158,28 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
123
158
  const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
124
159
  const analysisReport = analyzeDateExecution(dateStr, calcsInThisPass, rootStatus, dailyStatus, manifestMap);
125
160
 
126
- // 4. LOG ANALYSIS (SAFEGUARDED)
161
+ // 4. LOG ANALYSIS
127
162
  if (logger && typeof logger.logDateAnalysis === 'function') {
128
163
  logger.logDateAnalysis(dateStr, analysisReport);
129
164
  } else {
130
- // Fallback if injected logger is outdated
131
- const logMsg = `[Analysis] Date: ${dateStr} | Runnable: ${analysisReport.runnable.length} | Blocked: ${analysisReport.blocked.length} | ReRuns: ${analysisReport.reRuns.length}`;
165
+ // Safe fallback
166
+ const logMsg = `[Analysis] Date: ${dateStr} | Runnable: ${analysisReport.runnable.length} | Blocked: ${analysisReport.blocked.length} | Impossible: ${analysisReport.impossible.length}`;
132
167
  if (logger && logger.info) logger.info(logMsg);
133
168
  else console.log(logMsg);
134
169
  }
135
170
 
136
- // 5. MARK FAILURES (Write 'false' to DB for blocked items)
137
- const failureUpdates = {};
138
- analysisReport.blocked.forEach(item => failureUpdates[item.name] = false);
139
- analysisReport.failedDependency.forEach(item => failureUpdates[item.name] = false);
171
+ // 5. UPDATE STATUS FOR NON-RUNNABLE ITEMS
172
+ const statusUpdates = {};
173
+
174
+ // A. Mark BLOCKED as 'false' (Transient Failure)
175
+ analysisReport.blocked.forEach(item => statusUpdates[item.name] = false);
176
+ analysisReport.failedDependency.forEach(item => statusUpdates[item.name] = false);
177
+
178
+ // B. Mark IMPOSSIBLE as 'IMPOSSIBLE' (Permanent Failure - Overwrites existing status)
179
+ analysisReport.impossible.forEach(item => statusUpdates[item.name] = STATUS_IMPOSSIBLE);
140
180
 
141
- if (Object.keys(failureUpdates).length > 0) {
142
- await updateComputationStatus(dateStr, failureUpdates, config, dependencies);
181
+ if (Object.keys(statusUpdates).length > 0) {
182
+ await updateComputationStatus(dateStr, statusUpdates, config, dependencies);
143
183
  }
144
184
 
145
185
  // 6. EXECUTE RUNNABLES
@@ -151,7 +191,12 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
151
191
  const finalRunList = calcsInThisPass.filter(c => calcsToRunNames.has(normalizeName(c.name)));
152
192
 
153
193
  if (!finalRunList.length) {
154
- return { date: dateStr, updates: {}, skipped: analysisReport.skipped.length };
194
+ return {
195
+ date: dateStr,
196
+ updates: {},
197
+ skipped: analysisReport.skipped.length,
198
+ impossible: analysisReport.impossible.length
199
+ };
155
200
  }
156
201
 
157
202
  if (logger && logger.log) {
@@ -1,26 +1,14 @@
1
1
  /**
2
2
  * @fileoverview Structured Logging System for Computation Engine
3
- * Provides comprehensive tracking with process IDs, context, and filtering capabilities.
4
- * UPDATED: Fixed Date Analysis keys to match WorkflowOrchestrator (failedDependency vs resolving).
3
+ * UPDATED: Added 'impossible' category handling to Date Analysis.
5
4
  */
6
5
 
7
6
  const crypto = require('crypto');
8
7
 
9
- /**
10
- * Log Levels (Ordered by Severity)
11
- */
12
8
  const LOG_LEVELS = {
13
- TRACE: 0,
14
- DEBUG: 1,
15
- INFO: 2,
16
- WARN: 3,
17
- ERROR: 4,
18
- FATAL: 5
9
+ TRACE: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4, FATAL: 5
19
10
  };
20
11
 
21
- /**
22
- * Process Types for Tracking
23
- */
24
12
  const PROCESS_TYPES = {
25
13
  MANIFEST: 'manifest',
26
14
  ORCHESTRATOR: 'orchestrator',
@@ -31,17 +19,11 @@ const PROCESS_TYPES = {
31
19
  DISPATCH: 'dispatch'
32
20
  };
33
21
 
34
- /**
35
- * Generates a deterministic process ID.
36
- */
37
22
  function generateProcessId(type, identifier, date = '') {
38
23
  const input = `${type}|${identifier}|${date}`;
39
24
  return crypto.createHash('sha256').update(input).digest('hex').substring(0, 16);
40
25
  }
41
26
 
42
- /**
43
- * Formats a log entry into structured JSON
44
- */
45
27
  function formatLogEntry(entry) {
46
28
  return JSON.stringify({
47
29
  timestamp: entry.timestamp,
@@ -68,7 +50,6 @@ class StructuredLogger {
68
50
  includeStackTrace: config.includeStackTrace !== false,
69
51
  ...config
70
52
  };
71
-
72
53
  this.activeProcesses = new Map();
73
54
  }
74
55
 
@@ -78,7 +59,6 @@ class StructuredLogger {
78
59
  : crypto.randomBytes(8).toString('hex');
79
60
 
80
61
  const processLogger = new ProcessLogger(this, processType, processId, computationName, date);
81
-
82
62
  this.activeProcesses.set(processId, {
83
63
  type: processType,
84
64
  computationName,
@@ -86,7 +66,6 @@ class StructuredLogger {
86
66
  startTime: Date.now(),
87
67
  logger: processLogger
88
68
  });
89
-
90
69
  return processLogger;
91
70
  }
92
71
 
@@ -101,17 +80,17 @@ class StructuredLogger {
101
80
  }
102
81
 
103
82
  /**
104
- * The Main Date Analysis Logger (FIXED)
105
- * Handles 'failedDependency', 'skipped', and 'blocked' correctly.
83
+ * The Main Date Analysis Logger (UPDATED)
84
+ * Handles 'impossible' category for permanent failures.
106
85
  */
107
86
  logDateAnalysis(dateStr, analysisReport) {
108
- // Destructure with SAFETY DEFAULTS to prevent crashes
109
87
  const {
110
88
  runnable = [],
111
89
  blocked = [],
90
+ impossible = [], // New Category
112
91
  reRuns = [],
113
- failedDependency = [], // Matches WorkflowOrchestrator
114
- skipped = [] // Matches WorkflowOrchestrator
92
+ failedDependency = [],
93
+ skipped = []
115
94
  } = analysisReport;
116
95
 
117
96
  // 1. Structured Output
@@ -125,6 +104,7 @@ class StructuredLogger {
125
104
  stats: {
126
105
  runnable: runnable.length,
127
106
  blocked: blocked.length,
107
+ impossible: impossible.length,
128
108
  reRuns: reRuns.length,
129
109
  failedDependency: failedDependency.length,
130
110
  skipped: skipped.length
@@ -135,7 +115,10 @@ class StructuredLogger {
135
115
 
136
116
  // 2. Human Readable Output
137
117
  if (this.config.enableConsole) {
138
- const symbols = { info: 'ℹ️', warn: '⚠️', check: '✅', block: '⛔', link: '🔗', cycle: '🔄', skip: '⏭️' };
118
+ const symbols = {
119
+ info: 'ℹ️', warn: '⚠️', check: '✅', block: '⛔',
120
+ cycle: '🔄', skip: '⏭️', dead: '💀'
121
+ };
139
122
 
140
123
  console.log(`\n🔍 === DATE ANALYSIS REPORT: ${dateStr} ===`);
141
124
 
@@ -146,29 +129,34 @@ class StructuredLogger {
146
129
  });
147
130
  }
148
131
 
149
- // Updated to handle failedDependency instead of resolving
150
132
  if (failedDependency.length) {
151
- console.log(`\n${symbols.block} [FAILED DEPENDENCIES] (Missing upstream data)`);
133
+ console.log(`\n${symbols.block} [FAILED DEPENDENCIES] (Upstream failure)`);
152
134
  failedDependency.forEach(item => {
153
- // item.missing comes from Orchestrator (not item.missingDeps)
154
135
  console.log(` • ${item.name}: Missing ${item.missing ? item.missing.join(', ') : 'dependencies'}`);
155
136
  });
156
137
  }
157
138
 
139
+ // NEW: Impossible items
140
+ if (impossible.length) {
141
+ console.log(`\n${symbols.dead} [IMPOSSIBLE] (Permanent Failure - Will not retry)`);
142
+ impossible.forEach(item => {
143
+ console.log(` • ${item.name}: ${item.reason}`);
144
+ });
145
+ }
146
+
158
147
  if (runnable.length) {
159
148
  console.log(`\n${symbols.check} [READY TO RUN]`);
160
149
  runnable.forEach(item => console.log(` • ${item.name}`));
161
150
  }
162
151
 
163
152
  if (blocked.length) {
164
- console.log(`\n${symbols.warn} [BLOCKED] (Missing Root Data)`);
153
+ console.log(`\n${symbols.warn} [BLOCKED] (Waiting for data - Retriable)`);
165
154
  blocked.forEach(item => {
166
155
  console.log(` • ${item.name}: ${item.reason}`);
167
156
  });
168
157
  }
169
158
 
170
159
  if (skipped.length) {
171
- // Optional: Only show count if list is huge
172
160
  if (skipped.length > 10) {
173
161
  console.log(`\n${symbols.skip} [SKIPPED] (${skipped.length} calculations up-to-date)`);
174
162
  } else {
@@ -264,7 +252,6 @@ class StructuredLogger {
264
252
  }
265
253
  }
266
254
 
267
- // ... ProcessLogger and Templates (unchanged) ...
268
255
  class ProcessLogger {
269
256
  constructor(parent, processType, processId, computationName, date) {
270
257
  this.parent = parent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.229",
3
+ "version": "1.0.230",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [