bulltrackers-module 1.0.267 → 1.0.269
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,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Build Reporter & Auto-Runner.
|
|
3
3
|
* Generates a "Pre-Flight" report of what the computation system WILL do.
|
|
4
|
-
* UPDATED:
|
|
5
|
-
* UPDATED:
|
|
4
|
+
* UPDATED: Shards report details to subcollections to bypass 1MB limit.
|
|
5
|
+
* UPDATED: Explicitly reports SKIPPED items for 100% visibility.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const { analyzeDateExecution } = require('../WorkflowOrchestrator');
|
|
9
9
|
const { fetchComputationStatus } = require('../persistence/StatusRepository');
|
|
10
10
|
const { normalizeName, getExpectedDateStrings, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils');
|
|
11
11
|
const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
|
|
12
|
+
const { commitBatchInChunks } = require('../persistence/FirestoreUtils'); // Reuse chunker
|
|
12
13
|
const pLimit = require('p-limit');
|
|
13
14
|
const path = require('path');
|
|
14
15
|
const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
@@ -16,18 +17,15 @@ const packageVersion = pac
|
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* AUTO-RUN ENTRY POINT
|
|
19
|
-
* Checks if a report for the current version exists. If not, runs it.
|
|
20
20
|
*/
|
|
21
21
|
async function ensureBuildReport(config, dependencies, manifest) {
|
|
22
22
|
const { db, logger } = dependencies;
|
|
23
23
|
const now = new Date();
|
|
24
|
-
// Create a standardized build ID
|
|
25
24
|
const buildId = `v${packageVersion}_${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}_${String(now.getHours()).padStart(2,'0')}-${String(now.getMinutes()).padStart(2,'0')}-${String(now.getSeconds()).padStart(2,'0')}`;
|
|
26
25
|
const latestRef = db.collection('computation_build_records').doc('latest');
|
|
27
26
|
|
|
28
27
|
try {
|
|
29
28
|
const latestDoc = await latestRef.get();
|
|
30
|
-
// Check using 'packageVersion' key to match what we store
|
|
31
29
|
const priorVersion = latestDoc.exists ? latestDoc.data().packageVersion : null;
|
|
32
30
|
|
|
33
31
|
if (priorVersion === packageVersion) {
|
|
@@ -36,8 +34,6 @@ async function ensureBuildReport(config, dependencies, manifest) {
|
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
logger.log('INFO', `[BuildReporter] 🚀 New Version Detected (${packageVersion}). Auto-running Pre-flight Report...`);
|
|
39
|
-
|
|
40
|
-
// Run generation. This function handles writing the 'latest' document with FULL data.
|
|
41
37
|
await generateBuildReport(config, dependencies, manifest, 90, buildId);
|
|
42
38
|
|
|
43
39
|
} catch (e) {
|
|
@@ -46,7 +42,7 @@ async function ensureBuildReport(config, dependencies, manifest) {
|
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
/**
|
|
49
|
-
* Generates the report and saves to Firestore.
|
|
45
|
+
* Generates the report and saves to Firestore (Sharded).
|
|
50
46
|
*/
|
|
51
47
|
async function generateBuildReport(config, dependencies, manifest, daysBack = 90, customBuildId = null) {
|
|
52
48
|
const { db, logger } = dependencies;
|
|
@@ -54,7 +50,6 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
54
50
|
|
|
55
51
|
logger.log('INFO', `[BuildReporter] Generating Build Report: ${buildId} (Scope: ${daysBack} days)...`);
|
|
56
52
|
|
|
57
|
-
// 1. Determine Date Range
|
|
58
53
|
const today = new Date();
|
|
59
54
|
const startDate = new Date();
|
|
60
55
|
startDate.setDate(today.getDate() - daysBack);
|
|
@@ -62,37 +57,33 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
62
57
|
const datesToCheck = getExpectedDateStrings(startDate, today);
|
|
63
58
|
const manifestMap = new Map(manifest.map(c => [normalizeName(c.name), c]));
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
// Main Report Header (Summary Only)
|
|
61
|
+
const reportHeader = {
|
|
66
62
|
buildId,
|
|
67
63
|
packageVersion: packageVersion,
|
|
68
64
|
generatedAt: new Date().toISOString(),
|
|
69
65
|
summary: {},
|
|
70
|
-
|
|
66
|
+
_sharded: true
|
|
71
67
|
};
|
|
72
68
|
|
|
73
69
|
let totalReRuns = 0;
|
|
74
70
|
let totalNew = 0;
|
|
71
|
+
const detailWrites = [];
|
|
75
72
|
|
|
76
|
-
// 2. PARALLEL PROCESSING
|
|
77
73
|
const limit = pLimit(20);
|
|
78
74
|
|
|
79
75
|
const processingPromises = datesToCheck.map(dateStr => limit(async () => {
|
|
80
76
|
try {
|
|
81
|
-
// [IMPROVED] Fetch all statuses in parallel
|
|
82
77
|
const fetchPromises = [
|
|
83
|
-
// A. Real status
|
|
84
78
|
fetchComputationStatus(dateStr, config, dependencies),
|
|
85
|
-
// C. Real Root Data
|
|
86
79
|
checkRootDataAvailability(dateStr, config, dependencies, DEFINITIVE_EARLIEST_DATES)
|
|
87
80
|
];
|
|
88
81
|
|
|
89
|
-
// B. Yesterday's Status (only if needed)
|
|
90
82
|
let prevDateStr = null;
|
|
91
83
|
if (manifest.some(c => c.isHistorical)) {
|
|
92
84
|
const prevDate = new Date(dateStr + 'T00:00:00Z');
|
|
93
85
|
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
94
86
|
prevDateStr = prevDate.toISOString().slice(0, 10);
|
|
95
|
-
|
|
96
87
|
if (prevDate >= DEFINITIVE_EARLIEST_DATES.absoluteEarliest) {
|
|
97
88
|
fetchPromises.push(fetchComputationStatus(prevDateStr, config, dependencies));
|
|
98
89
|
}
|
|
@@ -101,31 +92,36 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
101
92
|
const results = await Promise.all(fetchPromises);
|
|
102
93
|
const dailyStatus = results[0];
|
|
103
94
|
const availability = results[1];
|
|
104
|
-
// If we fetched prevStatus, it's at index 2
|
|
105
95
|
const prevDailyStatus = (prevDateStr && results[2]) ? results[2] : (prevDateStr ? {} : null);
|
|
106
|
-
|
|
107
96
|
const rootDataStatus = availability ? availability.status : { hasPortfolio: false, hasHistory: false, hasSocial: false, hasInsights: false, hasPrices: false };
|
|
108
97
|
|
|
109
|
-
// D. Run Logic Analysis
|
|
110
98
|
const analysis = analyzeDateExecution(dateStr, manifest, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus);
|
|
111
99
|
|
|
112
|
-
//
|
|
113
|
-
const dateSummary = { willRun: [], willReRun: [], blocked: [], impossible: [] };
|
|
100
|
+
// [NEW] Added 'skipped' to the summary object
|
|
101
|
+
const dateSummary = { willRun: [], willReRun: [], blocked: [], impossible: [], skipped: [] };
|
|
114
102
|
|
|
115
|
-
// Pass the generated "Reason" string through to the report
|
|
116
103
|
analysis.runnable.forEach (item => dateSummary.willRun.push ({ name: item.name, reason: "New / No Previous Record" }));
|
|
117
104
|
analysis.reRuns.forEach (item => dateSummary.willReRun.push ({ name: item.name, reason: item.reason || "Hash Mismatch" }));
|
|
118
105
|
analysis.impossible.forEach (item => dateSummary.impossible.push ({ name: item.name, reason: item.reason }));
|
|
119
106
|
[...analysis.blocked, ...analysis.failedDependency].forEach(item => dateSummary.blocked.push({ name: item.name, reason: item.reason || 'Dependency' }));
|
|
120
|
-
|
|
121
|
-
const hasUpdates = dateSummary.willRun.length || dateSummary.willReRun.length || dateSummary.blocked.length || dateSummary.impossible.length;
|
|
122
107
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
108
|
+
// [NEW] Map skipped items so the math adds up to 92
|
|
109
|
+
analysis.skipped.forEach (item => dateSummary.skipped.push ({ name: item.name, reason: item.reason || "Up To Date" }));
|
|
110
|
+
|
|
111
|
+
// Update: We write the report if there is ANY data, not just updates
|
|
112
|
+
// This ensures full visibility into the day's state
|
|
113
|
+
if (dateSummary.willRun.length || dateSummary.willReRun.length || dateSummary.blocked.length || dateSummary.impossible.length || dateSummary.skipped.length) {
|
|
114
|
+
const detailRef = db.collection('computation_build_records').doc(buildId).collection('details').doc(dateStr);
|
|
115
|
+
detailWrites.push({
|
|
116
|
+
ref: detailRef,
|
|
117
|
+
data: dateSummary
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
stats: { new: dateSummary.willRun.length, rerun: dateSummary.willReRun.length }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
129
125
|
|
|
130
126
|
} catch (err) {
|
|
131
127
|
logger.log('ERROR', `[BuildReporter] Error analyzing date ${dateStr}: ${err.message}`);
|
|
@@ -135,30 +131,31 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
135
131
|
|
|
136
132
|
const results = await Promise.all(processingPromises);
|
|
137
133
|
|
|
138
|
-
// 3. Aggregate Results
|
|
139
134
|
results.forEach(res => {
|
|
140
|
-
if (res
|
|
141
|
-
reportData.dates[res.dateStr] = res.dateSummary;
|
|
135
|
+
if (res) {
|
|
142
136
|
totalNew += res.stats.new;
|
|
143
137
|
totalReRuns += res.stats.rerun;
|
|
144
138
|
}
|
|
145
139
|
});
|
|
146
140
|
|
|
147
|
-
|
|
141
|
+
reportHeader.summary = { totalReRuns, totalNew, scanRange: `${datesToCheck[0]} to ${datesToCheck[datesToCheck.length-1]}` };
|
|
148
142
|
|
|
149
|
-
// 4. Store Report
|
|
150
143
|
const reportRef = db.collection('computation_build_records').doc(buildId);
|
|
151
|
-
await reportRef.set(
|
|
144
|
+
await reportRef.set(reportHeader);
|
|
145
|
+
|
|
146
|
+
if (detailWrites.length > 0) {
|
|
147
|
+
logger.log('INFO', `[BuildReporter] Writing ${detailWrites.length} detail records...`);
|
|
148
|
+
await commitBatchInChunks(config, dependencies, detailWrites, 'BuildReportDetails');
|
|
149
|
+
}
|
|
152
150
|
|
|
153
|
-
|
|
154
|
-
await db.collection('computation_build_records').doc('latest').set({ ...reportData, note: "Latest build report pointer." });
|
|
151
|
+
await db.collection('computation_build_records').doc('latest').set({ ...reportHeader, note: "Latest build report pointer (See subcollection for details)." });
|
|
155
152
|
|
|
156
153
|
logger.log('SUCCESS', `[BuildReporter] Report ${buildId} saved. Re-runs: ${totalReRuns}, New: ${totalNew}.`);
|
|
157
154
|
|
|
158
155
|
return {
|
|
159
156
|
success: true,
|
|
160
157
|
reportId: buildId,
|
|
161
|
-
summary:
|
|
158
|
+
summary: reportHeader.summary
|
|
162
159
|
};
|
|
163
160
|
}
|
|
164
161
|
|