bulltrackers-module 1.0.266 → 1.0.268
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,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Main Orchestrator. Coordinates the topological execution.
|
|
3
3
|
* UPDATED: Implements Smart Audit logic to detect WHY a hash mismatch occurred.
|
|
4
|
+
* FIX: Added 'Audit Upgrade' check to force re-run if composition metadata is missing.
|
|
4
5
|
*/
|
|
5
6
|
const { normalizeName, DEFINITIVE_EARLIEST_DATES } = require('./utils/utils');
|
|
6
7
|
const { checkRootDataAvailability, checkRootDependencies } = require('./data/AvailabilityChecker');
|
|
@@ -50,6 +51,7 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
50
51
|
const markRunnable = (isReRun = false, reRunDetails = null) => {
|
|
51
52
|
if (isReRun) report.reRuns.push(reRunDetails);
|
|
52
53
|
else report.runnable.push({ name: cName, ...reRunDetails });
|
|
54
|
+
// Simulate success so dependents can pass their check
|
|
53
55
|
simulationStatus[cName] = { hash: currentHash, category: calc.category, composition: calc.composition };
|
|
54
56
|
};
|
|
55
57
|
|
|
@@ -116,22 +118,17 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
116
118
|
const newComp = calc.composition;
|
|
117
119
|
|
|
118
120
|
if (oldComp && newComp) {
|
|
119
|
-
// 1. Check Code
|
|
120
121
|
if (oldComp.code !== newComp.code) {
|
|
121
122
|
changeReason = "Code Changed";
|
|
122
123
|
}
|
|
123
|
-
// 2. Check Layers
|
|
124
124
|
else if (JSON.stringify(oldComp.layers) !== JSON.stringify(newComp.layers)) {
|
|
125
|
-
// Find specific layer
|
|
126
125
|
const changedLayers = [];
|
|
127
126
|
for(const lKey in newComp.layers) {
|
|
128
127
|
if (newComp.layers[lKey] !== oldComp.layers[lKey]) changedLayers.push(lKey);
|
|
129
128
|
}
|
|
130
129
|
changeReason = `Layer Update: [${changedLayers.join(', ')}]`;
|
|
131
130
|
}
|
|
132
|
-
// 3. Check Dependencies
|
|
133
131
|
else if (JSON.stringify(oldComp.deps) !== JSON.stringify(newComp.deps)) {
|
|
134
|
-
// Find specific dep
|
|
135
132
|
const changedDeps = [];
|
|
136
133
|
for(const dKey in newComp.deps) {
|
|
137
134
|
if (newComp.deps[dKey] !== oldComp.deps[dKey]) changedDeps.push(dKey);
|
|
@@ -150,12 +147,21 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
|
|
|
150
147
|
oldHash: storedHash,
|
|
151
148
|
newHash: currentHash,
|
|
152
149
|
previousCategory: migrationOldCategory,
|
|
153
|
-
reason: changeReason
|
|
150
|
+
reason: changeReason
|
|
154
151
|
});
|
|
155
152
|
}
|
|
156
153
|
else if (migrationOldCategory) {
|
|
157
154
|
markRunnable(true, { name: cName, reason: 'Category Migration', previousCategory: migrationOldCategory, newCategory: calc.category });
|
|
158
155
|
}
|
|
156
|
+
// [CRITICAL FIX] Audit Upgrade Check: Force re-run if hash matches but composition is missing (Legacy Record)
|
|
157
|
+
else if (!stored.composition) {
|
|
158
|
+
markRunnable(true, {
|
|
159
|
+
name: cName,
|
|
160
|
+
oldHash: storedHash,
|
|
161
|
+
newHash: currentHash,
|
|
162
|
+
reason: 'Audit Upgrade (Populating Composition Metadata)'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
159
165
|
else {
|
|
160
166
|
report.skipped.push({ name: cName });
|
|
161
167
|
simulationStatus[cName] = { hash: currentHash, category: calc.category, composition: calc.composition };
|
|
@@ -1,14 +1,14 @@
|
|
|
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: Now reports specific reasons for Re-Runs.
|
|
4
|
+
* UPDATED: Shards report details to subcollections to bypass 1MB limit on 'Invalidate All' scenarios.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
const { analyzeDateExecution } = require('../WorkflowOrchestrator');
|
|
9
8
|
const { fetchComputationStatus } = require('../persistence/StatusRepository');
|
|
10
9
|
const { normalizeName, getExpectedDateStrings, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils');
|
|
11
10
|
const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
|
|
11
|
+
const { commitBatchInChunks } = require('../persistence/FirestoreUtils'); // Reuse chunker
|
|
12
12
|
const pLimit = require('p-limit');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
@@ -21,13 +21,11 @@ const packageVersion = pac
|
|
|
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) {
|
|
@@ -37,7 +35,7 @@ async function ensureBuildReport(config, dependencies, manifest) {
|
|
|
37
35
|
|
|
38
36
|
logger.log('INFO', `[BuildReporter] 🚀 New Version Detected (${packageVersion}). Auto-running Pre-flight Report...`);
|
|
39
37
|
|
|
40
|
-
//
|
|
38
|
+
// Scope: 90 days is fine now that we shard the output
|
|
41
39
|
await generateBuildReport(config, dependencies, manifest, 90, buildId);
|
|
42
40
|
|
|
43
41
|
} catch (e) {
|
|
@@ -46,7 +44,7 @@ async function ensureBuildReport(config, dependencies, manifest) {
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
/**
|
|
49
|
-
* Generates the report and saves to Firestore.
|
|
47
|
+
* Generates the report and saves to Firestore (Sharded).
|
|
50
48
|
*/
|
|
51
49
|
async function generateBuildReport(config, dependencies, manifest, daysBack = 90, customBuildId = null) {
|
|
52
50
|
const { db, logger } = dependencies;
|
|
@@ -54,7 +52,6 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
54
52
|
|
|
55
53
|
logger.log('INFO', `[BuildReporter] Generating Build Report: ${buildId} (Scope: ${daysBack} days)...`);
|
|
56
54
|
|
|
57
|
-
// 1. Determine Date Range
|
|
58
55
|
const today = new Date();
|
|
59
56
|
const startDate = new Date();
|
|
60
57
|
startDate.setDate(today.getDate() - daysBack);
|
|
@@ -62,37 +59,33 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
62
59
|
const datesToCheck = getExpectedDateStrings(startDate, today);
|
|
63
60
|
const manifestMap = new Map(manifest.map(c => [normalizeName(c.name), c]));
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
// Main Report Header (Summary Only)
|
|
63
|
+
const reportHeader = {
|
|
66
64
|
buildId,
|
|
67
65
|
packageVersion: packageVersion,
|
|
68
66
|
generatedAt: new Date().toISOString(),
|
|
69
67
|
summary: {},
|
|
70
|
-
|
|
68
|
+
_sharded: true // Flag to tell UI/Tools to look in subcollection
|
|
71
69
|
};
|
|
72
70
|
|
|
73
71
|
let totalReRuns = 0;
|
|
74
72
|
let totalNew = 0;
|
|
73
|
+
const detailWrites = []; // Accumulate writes for batching
|
|
75
74
|
|
|
76
|
-
// 2. PARALLEL PROCESSING
|
|
77
75
|
const limit = pLimit(20);
|
|
78
76
|
|
|
79
77
|
const processingPromises = datesToCheck.map(dateStr => limit(async () => {
|
|
80
78
|
try {
|
|
81
|
-
// [IMPROVED] Fetch all statuses in parallel
|
|
82
79
|
const fetchPromises = [
|
|
83
|
-
// A. Real status
|
|
84
80
|
fetchComputationStatus(dateStr, config, dependencies),
|
|
85
|
-
// C. Real Root Data
|
|
86
81
|
checkRootDataAvailability(dateStr, config, dependencies, DEFINITIVE_EARLIEST_DATES)
|
|
87
82
|
];
|
|
88
83
|
|
|
89
|
-
// B. Yesterday's Status (only if needed)
|
|
90
84
|
let prevDateStr = null;
|
|
91
85
|
if (manifest.some(c => c.isHistorical)) {
|
|
92
86
|
const prevDate = new Date(dateStr + 'T00:00:00Z');
|
|
93
87
|
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
94
88
|
prevDateStr = prevDate.toISOString().slice(0, 10);
|
|
95
|
-
|
|
96
89
|
if (prevDate >= DEFINITIVE_EARLIEST_DATES.absoluteEarliest) {
|
|
97
90
|
fetchPromises.push(fetchComputationStatus(prevDateStr, config, dependencies));
|
|
98
91
|
}
|
|
@@ -101,18 +94,13 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
101
94
|
const results = await Promise.all(fetchPromises);
|
|
102
95
|
const dailyStatus = results[0];
|
|
103
96
|
const availability = results[1];
|
|
104
|
-
// If we fetched prevStatus, it's at index 2
|
|
105
97
|
const prevDailyStatus = (prevDateStr && results[2]) ? results[2] : (prevDateStr ? {} : null);
|
|
106
|
-
|
|
107
98
|
const rootDataStatus = availability ? availability.status : { hasPortfolio: false, hasHistory: false, hasSocial: false, hasInsights: false, hasPrices: false };
|
|
108
99
|
|
|
109
|
-
// D. Run Logic Analysis
|
|
110
100
|
const analysis = analyzeDateExecution(dateStr, manifest, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus);
|
|
111
101
|
|
|
112
|
-
// E. Format Findings
|
|
113
102
|
const dateSummary = { willRun: [], willReRun: [], blocked: [], impossible: [] };
|
|
114
103
|
|
|
115
|
-
// Pass the generated "Reason" string through to the report
|
|
116
104
|
analysis.runnable.forEach (item => dateSummary.willRun.push ({ name: item.name, reason: "New / No Previous Record" }));
|
|
117
105
|
analysis.reRuns.forEach (item => dateSummary.willReRun.push ({ name: item.name, reason: item.reason || "Hash Mismatch" }));
|
|
118
106
|
analysis.impossible.forEach (item => dateSummary.impossible.push ({ name: item.name, reason: item.reason }));
|
|
@@ -120,12 +108,19 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
120
108
|
|
|
121
109
|
const hasUpdates = dateSummary.willRun.length || dateSummary.willReRun.length || dateSummary.blocked.length || dateSummary.impossible.length;
|
|
122
110
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
111
|
+
if (hasUpdates) {
|
|
112
|
+
// Prepare Write for Subcollection
|
|
113
|
+
const detailRef = db.collection('computation_build_records').doc(buildId).collection('details').doc(dateStr);
|
|
114
|
+
detailWrites.push({
|
|
115
|
+
ref: detailRef,
|
|
116
|
+
data: dateSummary
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
stats: { new: dateSummary.willRun.length, rerun: dateSummary.willReRun.length }
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
129
124
|
|
|
130
125
|
} catch (err) {
|
|
131
126
|
logger.log('ERROR', `[BuildReporter] Error analyzing date ${dateStr}: ${err.message}`);
|
|
@@ -135,30 +130,34 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
135
130
|
|
|
136
131
|
const results = await Promise.all(processingPromises);
|
|
137
132
|
|
|
138
|
-
// 3. Aggregate Results
|
|
139
133
|
results.forEach(res => {
|
|
140
|
-
if (res
|
|
141
|
-
reportData.dates[res.dateStr] = res.dateSummary;
|
|
134
|
+
if (res) {
|
|
142
135
|
totalNew += res.stats.new;
|
|
143
136
|
totalReRuns += res.stats.rerun;
|
|
144
137
|
}
|
|
145
138
|
});
|
|
146
139
|
|
|
147
|
-
|
|
140
|
+
reportHeader.summary = { totalReRuns, totalNew, scanRange: `${datesToCheck[0]} to ${datesToCheck[datesToCheck.length-1]}` };
|
|
148
141
|
|
|
149
|
-
//
|
|
142
|
+
// 1. Write Header
|
|
150
143
|
const reportRef = db.collection('computation_build_records').doc(buildId);
|
|
151
|
-
await reportRef.set(
|
|
144
|
+
await reportRef.set(reportHeader);
|
|
145
|
+
|
|
146
|
+
// 2. Batch Write Details (Using FirestoreUtils to handle batching constraints)
|
|
147
|
+
if (detailWrites.length > 0) {
|
|
148
|
+
logger.log('INFO', `[BuildReporter] Writing ${detailWrites.length} detail records...`);
|
|
149
|
+
await commitBatchInChunks(config, dependencies, detailWrites, 'BuildReportDetails');
|
|
150
|
+
}
|
|
152
151
|
|
|
153
|
-
//
|
|
154
|
-
await db.collection('computation_build_records').doc('latest').set({ ...
|
|
152
|
+
// 3. Update 'latest' pointer (Summary only)
|
|
153
|
+
await db.collection('computation_build_records').doc('latest').set({ ...reportHeader, note: "Latest build report pointer (See subcollection for details)." });
|
|
155
154
|
|
|
156
155
|
logger.log('SUCCESS', `[BuildReporter] Report ${buildId} saved. Re-runs: ${totalReRuns}, New: ${totalNew}.`);
|
|
157
156
|
|
|
158
157
|
return {
|
|
159
158
|
success: true,
|
|
160
159
|
reportId: buildId,
|
|
161
|
-
summary:
|
|
160
|
+
summary: reportHeader.summary
|
|
162
161
|
};
|
|
163
162
|
}
|
|
164
163
|
|