bulltrackers-module 1.0.259 → 1.0.261
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.
- package/functions/computation-system/helpers/computation_dispatcher.js +82 -44
- package/functions/computation-system/helpers/computation_worker.js +48 -43
- package/functions/computation-system/onboarding.md +712 -503
- package/functions/computation-system/persistence/ResultCommitter.js +160 -76
- package/functions/computation-system/persistence/RunRecorder.js +123 -28
- package/functions/computation-system/tools/BuildReporter.js +28 -79
- package/functions/computation-system/utils/schema_capture.js +31 -2
- package/index.js +2 -4
- package/package.json +1 -1
|
@@ -1,14 +1,13 @@
|
|
|
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:
|
|
4
|
+
* UPDATED: Optimized with Parallel Status Fetches inside the date loop.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { analyzeDateExecution } = require('../WorkflowOrchestrator');
|
|
8
8
|
const { fetchComputationStatus } = require('../persistence/StatusRepository');
|
|
9
9
|
const { normalizeName, getExpectedDateStrings, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils');
|
|
10
10
|
const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
|
|
11
|
-
const { FieldValue } = require('@google-cloud/firestore');
|
|
12
11
|
const pLimit = require('p-limit');
|
|
13
12
|
const path = require('path');
|
|
14
13
|
const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
@@ -21,11 +20,7 @@ const packageVersion = packageJson.version;
|
|
|
21
20
|
async function ensureBuildReport(config, dependencies, manifest) {
|
|
22
21
|
const { db, logger } = dependencies;
|
|
23
22
|
const now = new Date();
|
|
24
|
-
|
|
25
|
-
// BuildId still includes timestamp for uniqueness
|
|
26
23
|
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')}`;
|
|
27
|
-
|
|
28
|
-
// Reference to "latest" doc
|
|
29
24
|
const latestRef = db.collection('computation_build_records').doc('latest');
|
|
30
25
|
|
|
31
26
|
try {
|
|
@@ -38,10 +33,8 @@ async function ensureBuildReport(config, dependencies, manifest) {
|
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
logger.log('INFO', `[BuildReporter] 🚀 New Version Detected (${packageVersion}). Auto-running Pre-flight Report...`);
|
|
41
|
-
|
|
42
36
|
await generateBuildReport(config, dependencies, manifest, 90, buildId);
|
|
43
|
-
|
|
44
|
-
// Update "latest" pointer
|
|
37
|
+
|
|
45
38
|
await latestRef.set({
|
|
46
39
|
packageVersion,
|
|
47
40
|
buildId,
|
|
@@ -55,11 +48,6 @@ async function ensureBuildReport(config, dependencies, manifest) {
|
|
|
55
48
|
|
|
56
49
|
/**
|
|
57
50
|
* Generates the report and saves to Firestore.
|
|
58
|
-
* @param {object} config
|
|
59
|
-
* @param {object} dependencies
|
|
60
|
-
* @param {Array} manifest
|
|
61
|
-
* @param {number} daysBack - Days to simulate (Default 90)
|
|
62
|
-
* @param {string} customBuildId - Optional ID override
|
|
63
51
|
*/
|
|
64
52
|
async function generateBuildReport(config, dependencies, manifest, daysBack = 90, customBuildId = null) {
|
|
65
53
|
const { db, logger } = dependencies;
|
|
@@ -86,99 +74,61 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
86
74
|
let totalReRuns = 0;
|
|
87
75
|
let totalNew = 0;
|
|
88
76
|
|
|
89
|
-
// 2. PARALLEL PROCESSING
|
|
90
|
-
// Run 20 reads in parallel.
|
|
77
|
+
// 2. PARALLEL PROCESSING
|
|
91
78
|
const limit = pLimit(20);
|
|
92
79
|
|
|
93
80
|
const processingPromises = datesToCheck.map(dateStr => limit(async () => {
|
|
94
81
|
try {
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
82
|
+
// [IMPROVED] Fetch all statuses in parallel
|
|
83
|
+
const fetchPromises = [
|
|
84
|
+
// A. Real status
|
|
85
|
+
fetchComputationStatus(dateStr, config, dependencies),
|
|
86
|
+
// C. Real Root Data
|
|
87
|
+
checkRootDataAvailability(dateStr, config, dependencies, DEFINITIVE_EARLIEST_DATES)
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// B. Yesterday's Status (only if needed)
|
|
91
|
+
let prevDateStr = null;
|
|
103
92
|
if (manifest.some(c => c.isHistorical)) {
|
|
104
93
|
const prevDate = new Date(dateStr + 'T00:00:00Z');
|
|
105
94
|
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
106
|
-
|
|
95
|
+
prevDateStr = prevDate.toISOString().slice(0, 10);
|
|
107
96
|
|
|
108
|
-
// Ensure we don't look before the dawn of time
|
|
109
97
|
if (prevDate >= DEFINITIVE_EARLIEST_DATES.absoluteEarliest) {
|
|
110
|
-
|
|
111
|
-
} else {
|
|
112
|
-
prevDailyStatus = {}; // Pre-epoch is valid empty context
|
|
98
|
+
fetchPromises.push(fetchComputationStatus(prevDateStr, config, dependencies));
|
|
113
99
|
}
|
|
114
100
|
}
|
|
115
101
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const availability =
|
|
119
|
-
|
|
102
|
+
const results = await Promise.all(fetchPromises);
|
|
103
|
+
const dailyStatus = results[0];
|
|
104
|
+
const availability = results[1];
|
|
105
|
+
// If we fetched prevStatus, it's at index 2
|
|
106
|
+
const prevDailyStatus = (prevDateStr && results[2]) ? results[2] : (prevDateStr ? {} : null);
|
|
107
|
+
|
|
120
108
|
const rootDataStatus = availability ? availability.status : {
|
|
121
|
-
hasPortfolio: false,
|
|
122
|
-
hasHistory: false,
|
|
123
|
-
hasSocial: false,
|
|
124
|
-
hasInsights: false,
|
|
125
|
-
hasPrices: false
|
|
109
|
+
hasPortfolio: false, hasHistory: false, hasSocial: false, hasInsights: false, hasPrices: false
|
|
126
110
|
};
|
|
127
111
|
|
|
128
112
|
// D. Run Logic Analysis
|
|
129
|
-
// Now passes prevDailyStatus to enable the "Blocked if yesterday missing" logic
|
|
130
113
|
const analysis = analyzeDateExecution(dateStr, manifest, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus);
|
|
131
114
|
|
|
132
115
|
// E. Format Findings
|
|
133
116
|
const dateSummary = {
|
|
134
|
-
willRun: [],
|
|
135
|
-
willReRun: [],
|
|
136
|
-
blocked: [],
|
|
137
|
-
impossible: []
|
|
117
|
+
willRun: [], willReRun: [], blocked: [], impossible: []
|
|
138
118
|
};
|
|
139
119
|
|
|
140
|
-
|
|
141
|
-
analysis.
|
|
142
|
-
|
|
143
|
-
});
|
|
120
|
+
analysis.runnable.forEach(item => dateSummary.willRun.push({ name: item.name, reason: "New / No Previous Record" }));
|
|
121
|
+
analysis.reRuns.forEach(item => dateSummary.willReRun.push({ name: item.name, reason: item.previousCategory ? "Migration" : "Hash Mismatch" }));
|
|
122
|
+
analysis.impossible.forEach(item => dateSummary.impossible.push({ name: item.name, reason: item.reason }));
|
|
123
|
+
[...analysis.blocked, ...analysis.failedDependency].forEach(item => dateSummary.blocked.push({ name: item.name, reason: item.reason || 'Dependency' }));
|
|
144
124
|
|
|
145
|
-
// -- Re-Runs (Hash Mismatch / Migration) --
|
|
146
|
-
analysis.reRuns.forEach(item => {
|
|
147
|
-
let reason = "Hash Mismatch";
|
|
148
|
-
let details = `Old: ${item.oldHash?.substring(0,6)}... New: ${item.newHash?.substring(0,6)}...`;
|
|
149
|
-
|
|
150
|
-
if (item.previousCategory) {
|
|
151
|
-
reason = "Migration";
|
|
152
|
-
details = `Moving ${item.previousCategory} -> ${item.newCategory}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
dateSummary.willReRun.push({ name: item.name, reason, details });
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// -- Impossible (Permanent) --
|
|
159
|
-
analysis.impossible.forEach(item => {
|
|
160
|
-
dateSummary.impossible.push({ name: item.name, reason: item.reason });
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// -- Blocked (Retriable) --
|
|
164
|
-
analysis.blocked.forEach(item => {
|
|
165
|
-
dateSummary.blocked.push({ name: item.name, reason: item.reason });
|
|
166
|
-
});
|
|
167
|
-
analysis.failedDependency.forEach(item => {
|
|
168
|
-
dateSummary.blocked.push({ name: item.name, reason: `Dependency Missing: ${item.missing.join(', ')}` });
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Return result for aggregation
|
|
172
125
|
const hasUpdates = dateSummary.willRun.length || dateSummary.willReRun.length || dateSummary.blocked.length || dateSummary.impossible.length;
|
|
173
126
|
|
|
174
127
|
return {
|
|
175
128
|
dateStr,
|
|
176
129
|
dateSummary,
|
|
177
130
|
hasUpdates,
|
|
178
|
-
stats: {
|
|
179
|
-
new: dateSummary.willRun.length,
|
|
180
|
-
rerun: dateSummary.willReRun.length
|
|
181
|
-
}
|
|
131
|
+
stats: { new: dateSummary.willRun.length, rerun: dateSummary.willReRun.length }
|
|
182
132
|
};
|
|
183
133
|
|
|
184
134
|
} catch (err) {
|
|
@@ -187,7 +137,6 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
187
137
|
}
|
|
188
138
|
}));
|
|
189
139
|
|
|
190
|
-
// Wait for all dates to process
|
|
191
140
|
const results = await Promise.all(processingPromises);
|
|
192
141
|
|
|
193
142
|
// 3. Aggregate Results
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Schema capture utility for computation outputs
|
|
3
3
|
* This module batches and stores pre-defined static schemas in Firestore.
|
|
4
|
+
* UPDATED: Added schema validation to prevent silent batch failures.
|
|
4
5
|
*/
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Validates a schema object before storage.
|
|
9
|
+
* Checks for circular references and size limits.
|
|
10
|
+
* @param {object} schema
|
|
11
|
+
* @returns {object} { valid: boolean, reason: string }
|
|
12
|
+
*/
|
|
13
|
+
function validateSchema(schema) {
|
|
14
|
+
try {
|
|
15
|
+
// 1. Detect circular references
|
|
16
|
+
const jsonStr = JSON.stringify(schema);
|
|
17
|
+
|
|
18
|
+
// 2. Ensure it's not too large (Firestore limit: 1MB, reserve 100KB for metadata)
|
|
19
|
+
const size = Buffer.byteLength(jsonStr);
|
|
20
|
+
if (size > 900 * 1024) {
|
|
21
|
+
return { valid: false, reason: `Schema exceeds 900KB limit (${(size/1024).toFixed(2)} KB)` };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { valid: true };
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return { valid: false, reason: `Serialization failed: ${e.message}` };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
6
30
|
/**
|
|
7
31
|
* Batch store schemas for multiple computations.
|
|
8
|
-
* This function now expects a fully-formed schema, not sample output.
|
|
9
|
-
* It strictly stamps a 'lastUpdated' field to support stale-schema filtering in the API.
|
|
10
32
|
*
|
|
11
33
|
* @param {object} dependencies - Contains db, logger
|
|
12
34
|
* @param {object} config - Configuration object
|
|
@@ -31,6 +53,13 @@ async function batchStoreSchemas(dependencies, config, schemas) {
|
|
|
31
53
|
continue;
|
|
32
54
|
}
|
|
33
55
|
|
|
56
|
+
// [IMPROVED] Validate before adding to batch
|
|
57
|
+
const validation = validateSchema(item.schema);
|
|
58
|
+
if (!validation.valid) {
|
|
59
|
+
logger.log('WARN', `[SchemaCapture] Invalid schema for ${item.name}: ${validation.reason}`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
const docRef = db.collection(schemaCollection).doc(item.name);
|
|
35
64
|
|
|
36
65
|
// Critical: Always overwrite 'lastUpdated' to now
|
package/index.js
CHANGED
|
@@ -27,7 +27,6 @@ const { handleUpdate } = require('./functions
|
|
|
27
27
|
|
|
28
28
|
// Computation System
|
|
29
29
|
const { build: buildManifest } = require('./functions/computation-system/context/ManifestBuilder');
|
|
30
|
-
// const { runDateComputation: runComputationPass } = require('./functions/computation-system/WorkflowOrchestrator'); Depreciated
|
|
31
30
|
const { dispatchComputationPass } = require('./functions/computation-system/helpers/computation_dispatcher');
|
|
32
31
|
const { handleComputationTask } = require('./functions/computation-system/helpers/computation_worker');
|
|
33
32
|
// [NEW] Import Report Tools
|
|
@@ -54,7 +53,7 @@ const { handlePost } = require('./functions
|
|
|
54
53
|
|
|
55
54
|
// NEW
|
|
56
55
|
|
|
57
|
-
const { runRootDataIndexer } = require('./functions/root-data-indexer/index');
|
|
56
|
+
const { runRootDataIndexer } = require('./functions/root-data-indexer/index');
|
|
58
57
|
|
|
59
58
|
const core = {
|
|
60
59
|
IntelligentHeaderManager,
|
|
@@ -88,7 +87,6 @@ const taskEngine = {
|
|
|
88
87
|
};
|
|
89
88
|
|
|
90
89
|
const computationSystem = {
|
|
91
|
-
// runComputationPass, Depreciated
|
|
92
90
|
dispatchComputationPass,
|
|
93
91
|
handleComputationTask,
|
|
94
92
|
dataLoader,
|
|
@@ -112,7 +110,7 @@ const maintenance = {
|
|
|
112
110
|
runSocialOrchestrator,
|
|
113
111
|
handleSocialTask,
|
|
114
112
|
runBackfillAssetPrices,
|
|
115
|
-
runRootDataIndexer,
|
|
113
|
+
runRootDataIndexer,
|
|
116
114
|
};
|
|
117
115
|
|
|
118
116
|
const proxy = { handlePost };
|