bulltrackers-module 1.0.268 → 1.0.270
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,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Main Orchestrator. Coordinates the topological execution.
|
|
3
|
-
* UPDATED:
|
|
4
|
-
*
|
|
3
|
+
* UPDATED: Removed 'Permanently Impossible' optimization to ensure full visibility/recovery.
|
|
4
|
+
* UPDATED: Includes 'Audit Upgrade' check.
|
|
5
5
|
*/
|
|
6
6
|
const { normalizeName, DEFINITIVE_EARLIEST_DATES } = require('./utils/utils');
|
|
7
7
|
const { checkRootDataAvailability, checkRootDependencies } = require('./data/AvailabilityChecker');
|
|
@@ -17,7 +17,6 @@ function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[c
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Analyzes whether calculations should run, be skipped, or are blocked.
|
|
20
|
-
* Now performs Deep Hash Analysis to explain Re-Runs.
|
|
21
20
|
*/
|
|
22
21
|
function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus = null) {
|
|
23
22
|
const report = { runnable: [], blocked: [], impossible: [], failedDependency: [], reRuns: [], skipped: [] };
|
|
@@ -58,16 +57,16 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
58
57
|
let migrationOldCategory = null;
|
|
59
58
|
if (storedCategory && storedCategory !== calc.category) { migrationOldCategory = storedCategory; }
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
60
|
+
// [REMOVED] The "Permanently Impossible" optimization block was here.
|
|
61
|
+
// Removal ensures we re-check Root Data every time, allowing for visibility and recovery.
|
|
65
62
|
|
|
63
|
+
// 1. Check Root Data (The Primary Gate)
|
|
66
64
|
const rootCheck = checkRootDependencies(calc, rootDataStatus);
|
|
67
65
|
|
|
68
66
|
if (!rootCheck.canRun) {
|
|
69
67
|
const missingStr = rootCheck.missing.join(', ');
|
|
70
68
|
if (!isTargetToday) {
|
|
69
|
+
// If previously impossible, this confirms it. If previously run, this is a regression.
|
|
71
70
|
markImpossible(`Missing Root Data: ${missingStr} (Historical)`, 'NO_DATA');
|
|
72
71
|
} else {
|
|
73
72
|
report.blocked.push({ name: cName, reason: `Missing Root Data: ${missingStr} (Waiting)` });
|
|
@@ -75,6 +74,7 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
75
74
|
continue;
|
|
76
75
|
}
|
|
77
76
|
|
|
77
|
+
// 2. Check Dependencies
|
|
78
78
|
let dependencyIsImpossible = false;
|
|
79
79
|
const missingDeps = [];
|
|
80
80
|
if (calc.dependencies) {
|
|
@@ -95,6 +95,7 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
95
95
|
}
|
|
96
96
|
if (missingDeps.length > 0) { report.failedDependency.push({ name: cName, missing: missingDeps }); continue; }
|
|
97
97
|
|
|
98
|
+
// 3. Check Historical Continuity
|
|
98
99
|
if (calc.isHistorical && prevDailyStatus) {
|
|
99
100
|
const yesterday = new Date(dateStr + 'T00:00:00Z');
|
|
100
101
|
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
@@ -107,12 +108,12 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
//
|
|
111
|
+
// 4. Check Hash / Composition (The Audit Gate)
|
|
111
112
|
if (!storedHash) {
|
|
112
113
|
markRunnable(false, { reason: "New Calculation" });
|
|
113
114
|
}
|
|
114
115
|
else if (storedHash !== currentHash) {
|
|
115
|
-
// Smart Logic
|
|
116
|
+
// Smart Audit Logic
|
|
116
117
|
let changeReason = "Hash Mismatch (Unknown)";
|
|
117
118
|
const oldComp = stored.composition;
|
|
118
119
|
const newComp = calc.composition;
|
|
@@ -153,7 +154,7 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
153
154
|
else if (migrationOldCategory) {
|
|
154
155
|
markRunnable(true, { name: cName, reason: 'Category Migration', previousCategory: migrationOldCategory, newCategory: calc.category });
|
|
155
156
|
}
|
|
156
|
-
//
|
|
157
|
+
// Audit Upgrade Check
|
|
157
158
|
else if (!stored.composition) {
|
|
158
159
|
markRunnable(true, {
|
|
159
160
|
name: cName,
|
|
@@ -163,7 +164,7 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
163
164
|
});
|
|
164
165
|
}
|
|
165
166
|
else {
|
|
166
|
-
report.skipped.push({ name: cName });
|
|
167
|
+
report.skipped.push({ name: cName, reason: "Up To Date" });
|
|
167
168
|
simulationStatus[cName] = { hash: currentHash, category: calc.category, composition: calc.composition };
|
|
168
169
|
}
|
|
169
170
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Change this string to force a global re-computation
|
|
2
|
-
module.exports = "
|
|
2
|
+
module.exports = "v2.0-epoch-2";
|
|
@@ -1,22 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Build Reporter & Auto-Runner.
|
|
3
3
|
* Generates a "Pre-Flight" report of what the computation system WILL do.
|
|
4
|
-
*
|
|
4
|
+
* REFACTORED: Strict 5-category reporting with date-based exclusion logic.
|
|
5
|
+
* UPDATED: Added meta stats to compare Included vs Expected computation counts per date.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const { analyzeDateExecution } = require('../WorkflowOrchestrator');
|
|
8
9
|
const { fetchComputationStatus } = require('../persistence/StatusRepository');
|
|
9
10
|
const { normalizeName, getExpectedDateStrings, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils');
|
|
10
11
|
const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
|
|
11
|
-
const { commitBatchInChunks } = require('../persistence/FirestoreUtils');
|
|
12
|
+
const { commitBatchInChunks } = require('../persistence/FirestoreUtils');
|
|
12
13
|
const pLimit = require('p-limit');
|
|
13
14
|
const path = require('path');
|
|
14
15
|
const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
15
16
|
const packageVersion = packageJson.version;
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Helper: Determines if a calculation should be excluded from the report
|
|
20
|
+
* because the date is prior to the earliest possible data existence.
|
|
21
|
+
*/
|
|
22
|
+
function isDateBeforeAvailability(dateStr, calcManifest) {
|
|
23
|
+
const targetDate = new Date(dateStr + 'T00:00:00Z');
|
|
24
|
+
const deps = calcManifest.rootDataDependencies || [];
|
|
25
|
+
|
|
26
|
+
// If no data dependencies, it's always valid (e.g., pure math)
|
|
27
|
+
if (deps.length === 0) return false;
|
|
28
|
+
|
|
29
|
+
for (const dep of deps) {
|
|
30
|
+
// Map dependency name to start date
|
|
31
|
+
let startDate = null;
|
|
32
|
+
if (dep === 'portfolio') startDate = DEFINITIVE_EARLIEST_DATES.portfolio;
|
|
33
|
+
else if (dep === 'history') startDate = DEFINITIVE_EARLIEST_DATES.history;
|
|
34
|
+
else if (dep === 'social') startDate = DEFINITIVE_EARLIEST_DATES.social;
|
|
35
|
+
else if (dep === 'insights') startDate = DEFINITIVE_EARLIEST_DATES.insights;
|
|
36
|
+
else if (dep === 'price') startDate = DEFINITIVE_EARLIEST_DATES.price;
|
|
37
|
+
|
|
38
|
+
// If we have a start date and the target is BEFORE it, exclude this calc.
|
|
39
|
+
if (startDate && targetDate < startDate) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
17
46
|
/**
|
|
18
47
|
* AUTO-RUN ENTRY POINT
|
|
19
|
-
* Checks if a report for the current version exists. If not, runs it.
|
|
20
48
|
*/
|
|
21
49
|
async function ensureBuildReport(config, dependencies, manifest) {
|
|
22
50
|
const { db, logger } = dependencies;
|
|
@@ -34,8 +62,6 @@ async function ensureBuildReport(config, dependencies, manifest) {
|
|
|
34
62
|
}
|
|
35
63
|
|
|
36
64
|
logger.log('INFO', `[BuildReporter] 🚀 New Version Detected (${packageVersion}). Auto-running Pre-flight Report...`);
|
|
37
|
-
|
|
38
|
-
// Scope: 90 days is fine now that we shard the output
|
|
39
65
|
await generateBuildReport(config, dependencies, manifest, 90, buildId);
|
|
40
66
|
|
|
41
67
|
} catch (e) {
|
|
@@ -59,18 +85,18 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
59
85
|
const datesToCheck = getExpectedDateStrings(startDate, today);
|
|
60
86
|
const manifestMap = new Map(manifest.map(c => [normalizeName(c.name), c]));
|
|
61
87
|
|
|
62
|
-
// Main Report Header
|
|
88
|
+
// Main Report Header
|
|
63
89
|
const reportHeader = {
|
|
64
90
|
buildId,
|
|
65
91
|
packageVersion: packageVersion,
|
|
66
92
|
generatedAt: new Date().toISOString(),
|
|
67
|
-
summary: {},
|
|
68
|
-
_sharded: true
|
|
93
|
+
summary: {},
|
|
94
|
+
_sharded: true
|
|
69
95
|
};
|
|
70
96
|
|
|
71
|
-
let
|
|
72
|
-
let
|
|
73
|
-
const detailWrites = [];
|
|
97
|
+
let totalRun = 0;
|
|
98
|
+
let totalReRun = 0;
|
|
99
|
+
const detailWrites = [];
|
|
74
100
|
|
|
75
101
|
const limit = pLimit(20);
|
|
76
102
|
|
|
@@ -99,28 +125,78 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
99
125
|
|
|
100
126
|
const analysis = analyzeDateExecution(dateStr, manifest, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus);
|
|
101
127
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
128
|
+
// ---------------------------------------------------------
|
|
129
|
+
// STRICT 5-CATEGORY MAPPING
|
|
130
|
+
// ---------------------------------------------------------
|
|
131
|
+
const dateSummary = {
|
|
132
|
+
run: [], // New / No Hash / "Runnable"
|
|
133
|
+
rerun: [], // Hash Mismatch / Category Migration
|
|
134
|
+
blocked: [], // Missing Data (Today) / Dependency Missing
|
|
135
|
+
impossible: [], // Missing Data (Historical) / Impossible Dependency
|
|
136
|
+
uptodate: [], // Hash Match (Previously "Skipped")
|
|
137
|
+
|
|
138
|
+
// [NEW] Metadata for Verification
|
|
139
|
+
meta: {
|
|
140
|
+
totalIncluded: 0,
|
|
141
|
+
totalExpected: 0,
|
|
142
|
+
match: false
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Calculate Expected Count (Computations in manifest that exist for this date)
|
|
147
|
+
const expectedCount = manifest.filter(c => !isDateBeforeAvailability(dateStr, c)).length;
|
|
148
|
+
dateSummary.meta.totalExpected = expectedCount;
|
|
149
|
+
|
|
150
|
+
// Helper to push only if date is valid for this specific calc
|
|
151
|
+
const pushIfValid = (targetArray, item, extraReason = null) => {
|
|
152
|
+
const calcManifest = manifestMap.get(item.name);
|
|
153
|
+
if (calcManifest && isDateBeforeAvailability(dateStr, calcManifest)) {
|
|
154
|
+
return; // EXCLUDED: Date is before data exists
|
|
155
|
+
}
|
|
156
|
+
targetArray.push({ name: item.name, reason: item.reason || extraReason });
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// 1. RUN (New)
|
|
160
|
+
analysis.runnable.forEach(item => pushIfValid(dateSummary.run, item, "New Calculation"));
|
|
161
|
+
|
|
162
|
+
// 2. RE-RUN (Hash Mismatch)
|
|
163
|
+
analysis.reRuns.forEach(item => pushIfValid(dateSummary.rerun, item, "Hash Mismatch"));
|
|
164
|
+
|
|
165
|
+
// 3. BLOCKED (Temporary Issues)
|
|
166
|
+
// Merging 'blocked' and 'failedDependency' as both are temporary blocks
|
|
167
|
+
analysis.blocked.forEach(item => pushIfValid(dateSummary.blocked, item));
|
|
168
|
+
analysis.failedDependency.forEach(item => pushIfValid(dateSummary.blocked, item, "Dependency Missing"));
|
|
169
|
+
|
|
170
|
+
// 4. IMPOSSIBLE (Permanent Issues)
|
|
171
|
+
analysis.impossible.forEach(item => pushIfValid(dateSummary.impossible, item));
|
|
172
|
+
|
|
173
|
+
// 5. UP-TO-DATE (Previously "Skipped")
|
|
174
|
+
analysis.skipped.forEach(item => pushIfValid(dateSummary.uptodate, item, "Up To Date"));
|
|
175
|
+
|
|
176
|
+
// Calculate Included Count
|
|
177
|
+
const includedCount = dateSummary.run.length +
|
|
178
|
+
dateSummary.rerun.length +
|
|
179
|
+
dateSummary.blocked.length +
|
|
180
|
+
dateSummary.impossible.length +
|
|
181
|
+
dateSummary.uptodate.length;
|
|
182
|
+
|
|
183
|
+
dateSummary.meta.totalIncluded = includedCount;
|
|
184
|
+
dateSummary.meta.match = (includedCount === expectedCount);
|
|
185
|
+
|
|
186
|
+
if (!dateSummary.meta.match) {
|
|
187
|
+
logger.log('WARN', `[BuildReporter] ⚠️ Mismatch on ${dateStr}: Expected ${expectedCount} but got ${includedCount}.`);
|
|
122
188
|
}
|
|
123
|
-
|
|
189
|
+
|
|
190
|
+
// ALWAYS WRITE THE REPORT (No filtering based on activity)
|
|
191
|
+
const detailRef = db.collection('computation_build_records').doc(buildId).collection('details').doc(dateStr);
|
|
192
|
+
detailWrites.push({
|
|
193
|
+
ref: detailRef,
|
|
194
|
+
data: dateSummary
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
stats: { run: dateSummary.run.length, rerun: dateSummary.rerun.length }
|
|
199
|
+
};
|
|
124
200
|
|
|
125
201
|
} catch (err) {
|
|
126
202
|
logger.log('ERROR', `[BuildReporter] Error analyzing date ${dateStr}: ${err.message}`);
|
|
@@ -132,27 +208,28 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
132
208
|
|
|
133
209
|
results.forEach(res => {
|
|
134
210
|
if (res) {
|
|
135
|
-
|
|
136
|
-
|
|
211
|
+
totalRun += res.stats.run;
|
|
212
|
+
totalReRun += res.stats.rerun;
|
|
137
213
|
}
|
|
138
214
|
});
|
|
139
215
|
|
|
140
|
-
reportHeader.summary = {
|
|
216
|
+
reportHeader.summary = {
|
|
217
|
+
totalReRuns: totalReRun,
|
|
218
|
+
totalNew: totalRun,
|
|
219
|
+
scanRange: `${datesToCheck[0]} to ${datesToCheck[datesToCheck.length-1]}`
|
|
220
|
+
};
|
|
141
221
|
|
|
142
|
-
// 1. Write Header
|
|
143
222
|
const reportRef = db.collection('computation_build_records').doc(buildId);
|
|
144
223
|
await reportRef.set(reportHeader);
|
|
145
224
|
|
|
146
|
-
// 2. Batch Write Details (Using FirestoreUtils to handle batching constraints)
|
|
147
225
|
if (detailWrites.length > 0) {
|
|
148
226
|
logger.log('INFO', `[BuildReporter] Writing ${detailWrites.length} detail records...`);
|
|
149
227
|
await commitBatchInChunks(config, dependencies, detailWrites, 'BuildReportDetails');
|
|
150
228
|
}
|
|
151
229
|
|
|
152
|
-
// 3. Update 'latest' pointer (Summary only)
|
|
153
230
|
await db.collection('computation_build_records').doc('latest').set({ ...reportHeader, note: "Latest build report pointer (See subcollection for details)." });
|
|
154
231
|
|
|
155
|
-
logger.log('SUCCESS', `[BuildReporter] Report ${buildId} saved. Re-runs: ${
|
|
232
|
+
logger.log('SUCCESS', `[BuildReporter] Report ${buildId} saved. Re-runs: ${totalReRun}, New: ${totalRun}.`);
|
|
156
233
|
|
|
157
234
|
return {
|
|
158
235
|
success: true,
|