bulltrackers-module 1.0.229 → 1.0.231
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:
|
|
3
|
+
* UPDATED: Removed legacy boolean 'true' logic.
|
|
4
4
|
*/
|
|
5
5
|
const { normalizeName } = require('./utils/utils');
|
|
6
6
|
const { checkRootDataAvailability } = require('./data/AvailabilityChecker');
|
|
@@ -10,6 +10,8 @@ const { StandardExecutor } = require('./executor
|
|
|
10
10
|
const { MetaExecutor } = require('./executors/MetaExecutor');
|
|
11
11
|
const { generateProcessId, PROCESS_TYPES } = require('./logger/logger');
|
|
12
12
|
|
|
13
|
+
const STATUS_IMPOSSIBLE = 'IMPOSSIBLE';
|
|
14
|
+
|
|
13
15
|
function groupByPass(manifest) {
|
|
14
16
|
return manifest.reduce((acc, calc) => {
|
|
15
17
|
(acc[calc.pass] = acc[calc.pass] || []).push(calc);
|
|
@@ -17,29 +19,24 @@ function groupByPass(manifest) {
|
|
|
17
19
|
}, {});
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
/**
|
|
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
|
|
28
|
-
*/
|
|
29
22
|
function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus, manifestMap) {
|
|
30
23
|
const report = {
|
|
31
24
|
runnable: [],
|
|
32
|
-
blocked: [],
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
blocked: [],
|
|
26
|
+
impossible: [],
|
|
27
|
+
failedDependency: [],
|
|
28
|
+
reRuns: [],
|
|
29
|
+
skipped: []
|
|
36
30
|
};
|
|
37
31
|
|
|
32
|
+
const isTargetToday = (dateStr === new Date().toISOString().slice(0, 10));
|
|
33
|
+
|
|
38
34
|
const isDepSatisfied = (depName, dailyStatus, manifestMap) => {
|
|
39
35
|
const norm = normalizeName(depName);
|
|
40
36
|
const storedDepHash = dailyStatus[norm];
|
|
41
37
|
const depManifest = manifestMap.get(norm);
|
|
42
38
|
|
|
39
|
+
if (storedDepHash === STATUS_IMPOSSIBLE) return false;
|
|
43
40
|
if (!storedDepHash) return false;
|
|
44
41
|
if (!depManifest) return false;
|
|
45
42
|
if (storedDepHash !== depManifest.hash) return false;
|
|
@@ -52,7 +49,13 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
52
49
|
const storedHash = dailyStatus[cName];
|
|
53
50
|
const currentHash = calc.hash;
|
|
54
51
|
|
|
55
|
-
// 1.
|
|
52
|
+
// 1. Check Impossible
|
|
53
|
+
if (storedHash === STATUS_IMPOSSIBLE) {
|
|
54
|
+
report.skipped.push({ name: cName, reason: 'Permanently Impossible' });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Root Data Check
|
|
56
59
|
const missingRoots = [];
|
|
57
60
|
if (calc.rootDataDependencies) {
|
|
58
61
|
for (const dep of calc.rootDataDependencies) {
|
|
@@ -65,33 +68,50 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
if (missingRoots.length > 0) {
|
|
68
|
-
|
|
71
|
+
if (!isTargetToday) {
|
|
72
|
+
report.impossible.push({ name: cName, reason: `Missing Root Data: ${missingRoots.join(', ')} (Historical)` });
|
|
73
|
+
} else {
|
|
74
|
+
report.blocked.push({ name: cName, reason: `Missing Root Data: ${missingRoots.join(', ')} (Waiting)` });
|
|
75
|
+
}
|
|
69
76
|
continue;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
//
|
|
79
|
+
// 3. Dependency Check
|
|
80
|
+
let dependencyIsImpossible = false;
|
|
73
81
|
const missingDeps = [];
|
|
82
|
+
|
|
74
83
|
if (calc.dependencies) {
|
|
75
84
|
for (const dep of calc.dependencies) {
|
|
85
|
+
const normDep = normalizeName(dep);
|
|
86
|
+
|
|
87
|
+
if (dailyStatus[normDep] === STATUS_IMPOSSIBLE) {
|
|
88
|
+
dependencyIsImpossible = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
if (!isDepSatisfied(dep, dailyStatus, manifestMap)) {
|
|
77
93
|
missingDeps.push(dep);
|
|
78
94
|
}
|
|
79
95
|
}
|
|
80
96
|
}
|
|
81
97
|
|
|
98
|
+
if (dependencyIsImpossible) {
|
|
99
|
+
report.impossible.push({ name: cName, reason: 'Dependency is Impossible' });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
if (missingDeps.length > 0) {
|
|
83
104
|
report.failedDependency.push({ name: cName, missing: missingDeps });
|
|
84
105
|
continue;
|
|
85
106
|
}
|
|
86
107
|
|
|
87
|
-
//
|
|
88
|
-
if (!storedHash) {
|
|
108
|
+
// 4. Hash / State Check (Legacy 'true' logic removed)
|
|
109
|
+
if (!storedHash || storedHash === false) {
|
|
89
110
|
report.runnable.push(calc);
|
|
90
111
|
} else if (storedHash !== currentHash) {
|
|
91
112
|
report.reRuns.push({ name: cName, oldHash: storedHash, newHash: currentHash });
|
|
92
|
-
} else if (storedHash === true) {
|
|
93
|
-
report.reRuns.push({ name: cName, reason: 'Legacy Upgrade' });
|
|
94
113
|
} else {
|
|
114
|
+
// Stored Hash === Current Hash
|
|
95
115
|
report.skipped.push({ name: cName });
|
|
96
116
|
}
|
|
97
117
|
}
|
|
@@ -123,23 +143,23 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
|
|
|
123
143
|
const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
|
|
124
144
|
const analysisReport = analyzeDateExecution(dateStr, calcsInThisPass, rootStatus, dailyStatus, manifestMap);
|
|
125
145
|
|
|
126
|
-
// 4. LOG ANALYSIS
|
|
146
|
+
// 4. LOG ANALYSIS
|
|
127
147
|
if (logger && typeof logger.logDateAnalysis === 'function') {
|
|
128
148
|
logger.logDateAnalysis(dateStr, analysisReport);
|
|
129
149
|
} else {
|
|
130
|
-
|
|
131
|
-
const logMsg = `[Analysis] Date: ${dateStr} | Runnable: ${analysisReport.runnable.length} | Blocked: ${analysisReport.blocked.length} | ReRuns: ${analysisReport.reRuns.length}`;
|
|
150
|
+
const logMsg = `[Analysis] Date: ${dateStr} | Runnable: ${analysisReport.runnable.length} | Blocked: ${analysisReport.blocked.length} | Impossible: ${analysisReport.impossible.length}`;
|
|
132
151
|
if (logger && logger.info) logger.info(logMsg);
|
|
133
152
|
else console.log(logMsg);
|
|
134
153
|
}
|
|
135
154
|
|
|
136
|
-
// 5.
|
|
137
|
-
const
|
|
138
|
-
analysisReport.blocked.forEach(item =>
|
|
139
|
-
analysisReport.failedDependency.forEach(item =>
|
|
155
|
+
// 5. UPDATE STATUS FOR NON-RUNNABLE ITEMS
|
|
156
|
+
const statusUpdates = {};
|
|
157
|
+
analysisReport.blocked.forEach(item => statusUpdates[item.name] = false);
|
|
158
|
+
analysisReport.failedDependency.forEach(item => statusUpdates[item.name] = false);
|
|
159
|
+
analysisReport.impossible.forEach(item => statusUpdates[item.name] = STATUS_IMPOSSIBLE);
|
|
140
160
|
|
|
141
|
-
if (Object.keys(
|
|
142
|
-
await updateComputationStatus(dateStr,
|
|
161
|
+
if (Object.keys(statusUpdates).length > 0) {
|
|
162
|
+
await updateComputationStatus(dateStr, statusUpdates, config, dependencies);
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
// 6. EXECUTE RUNNABLES
|
|
@@ -151,7 +171,12 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
|
|
|
151
171
|
const finalRunList = calcsInThisPass.filter(c => calcsToRunNames.has(normalizeName(c.name)));
|
|
152
172
|
|
|
153
173
|
if (!finalRunList.length) {
|
|
154
|
-
return {
|
|
174
|
+
return {
|
|
175
|
+
date: dateStr,
|
|
176
|
+
updates: {},
|
|
177
|
+
skipped: analysisReport.skipped.length,
|
|
178
|
+
impossible: analysisReport.impossible.length
|
|
179
|
+
};
|
|
155
180
|
}
|
|
156
181
|
|
|
157
182
|
if (logger && logger.log) {
|
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Structured Logging System for Computation Engine
|
|
3
|
-
*
|
|
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 (
|
|
105
|
-
* Handles '
|
|
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 = [],
|
|
114
|
-
skipped = []
|
|
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 = {
|
|
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] (
|
|
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] (
|
|
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;
|
|
@@ -45,12 +45,19 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
45
45
|
await commitBatchInChunks(config, deps, updates, `${name} Results`);
|
|
46
46
|
|
|
47
47
|
// Structured Storage Log
|
|
48
|
-
logger
|
|
48
|
+
if (logger && logger.logStorage) {
|
|
49
|
+
logger.logStorage(pid, name, dStr, mainDocRef.path, totalSize, isSharded);
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
// Update success tracking
|
|
53
|
+
if (calc.manifest.hash) {
|
|
54
|
+
successUpdates[name] = calc.manifest.hash;
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
} catch (e) {
|
|
53
|
-
logger.log
|
|
58
|
+
if (logger && logger.log) {
|
|
59
|
+
logger.log('ERROR', `Commit failed for ${name}`, { processId: pid, error: e.message });
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
|
@@ -62,10 +69,7 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
62
69
|
return successUpdates;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
// ... rest of file (calculateFirestoreBytes, prepareAutoShardedWrites) remains same ...
|
|
66
|
-
// Just ensure prepareAutoShardedWrites uses the provided logger if it logs internal warnings.
|
|
67
72
|
function calculateFirestoreBytes(value) {
|
|
68
|
-
// ... same as before
|
|
69
73
|
if (value === null) return 1;
|
|
70
74
|
if (value === undefined) return 0;
|
|
71
75
|
if (typeof value === 'boolean') return 1;
|
|
@@ -79,8 +83,6 @@ function calculateFirestoreBytes(value) {
|
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
async function prepareAutoShardedWrites(result, docRef, logger) {
|
|
82
|
-
// ... same logic, just ensure existing logs inside here use the logger properly if needed
|
|
83
|
-
// Copied from previous logic, essentially checks size > 900KB and splits
|
|
84
86
|
const SAFETY_THRESHOLD_BYTES = 1000 * 1024;
|
|
85
87
|
const OVERHEAD_ALLOWANCE = 20 * 1024;
|
|
86
88
|
const CHUNK_LIMIT = SAFETY_THRESHOLD_BYTES - OVERHEAD_ALLOWANCE;
|
|
@@ -94,8 +96,6 @@ async function prepareAutoShardedWrites(result, docRef, logger) {
|
|
|
94
96
|
|
|
95
97
|
if ((totalSize + docPathSize) < CHUNK_LIMIT) { const data = { ...result, _completed: true, _sharded: false }; return [{ ref: docRef, data, options: { merge: true } }]; }
|
|
96
98
|
|
|
97
|
-
// Note: We don't log "Sharding..." here anymore because we log the structured event in commitResults
|
|
98
|
-
|
|
99
99
|
for (const [key, value] of Object.entries(result)) {
|
|
100
100
|
if (key.startsWith('_')) continue;
|
|
101
101
|
const keySize = Buffer.byteLength(key, 'utf8') + 1;
|