bulltrackers-module 1.0.241 → 1.0.242

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.
@@ -2,12 +2,14 @@
2
2
  * @fileoverview Build Reporter & Auto-Runner.
3
3
  * Generates a "Pre-Flight" report of what the computation system WILL do.
4
4
  * Simulates execution logic (Hash Mismatches) respecting DEFINITIVE start dates.
5
+ * UPDATED: Implements Parallel Execution to prevent DEADLINE_EXCEEDED on 90-day scans.
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 { FieldValue } = require('@google-cloud/firestore');
12
+ const pLimit = require('p-limit');
11
13
 
12
14
  // Attempt to load package.json to get version. Path depends on where this is invoked.
13
15
  let packageVersion = '1.0.300';
@@ -60,8 +62,6 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
60
62
  const startDate = new Date();
61
63
  startDate.setDate(today.getDate() - daysBack);
62
64
 
63
- // We check UP TO yesterday usually, as today might be partial.
64
- // But let's check today too to see immediate effects.
65
65
  const datesToCheck = getExpectedDateStrings(startDate, today);
66
66
  const manifestMap = new Map(manifest.map(c => [normalizeName(c.name), c]));
67
67
 
@@ -76,86 +76,106 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
76
76
  let totalReRuns = 0;
77
77
  let totalNew = 0;
78
78
 
79
- // 2. Iterate Dates & Simulate
80
- for (const dateStr of datesToCheck) {
81
- // A. Fetch REAL status from DB (What ran previously?)
82
- const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
83
-
84
- // B. SMART MOCK Root Data
85
- // We only mock "True" if the date is logically valid for that data type.
86
- // This prevents impossible calculations (e.g. 2025-09-11 requiring History which starts 2025-11-05)
87
- // from showing as "WillRun".
88
- const dateObj = new Date(dateStr + 'T00:00:00Z');
89
-
90
- const mockRootDataStatus = {
91
- hasPortfolio: dateObj >= DEFINITIVE_EARLIEST_DATES.portfolio,
92
- hasHistory: dateObj >= DEFINITIVE_EARLIEST_DATES.history,
93
- hasSocial: dateObj >= DEFINITIVE_EARLIEST_DATES.social,
94
- hasInsights: dateObj >= DEFINITIVE_EARLIEST_DATES.insights,
95
- hasPrices: dateObj >= DEFINITIVE_EARLIEST_DATES.price
96
- };
97
-
98
- // C. Run Logic Analysis
99
- // Pass ENTIRE manifest to see global state
100
- const analysis = analyzeDateExecution(dateStr, manifest, mockRootDataStatus, dailyStatus, manifestMap);
101
-
102
- // D. Format Findings
103
- const dateSummary = {
104
- willRun: [],
105
- willReRun: [],
106
- blocked: [],
107
- impossible: [] // New explicit category
108
- };
109
-
110
- // -- Runnable (New) --
111
- analysis.runnable.forEach(item => {
112
- dateSummary.willRun.push({ name: item.name, reason: "New / No Previous Record" });
113
- });
114
-
115
- // -- Re-Runs (Hash Mismatch / Migration) --
116
- analysis.reRuns.forEach(item => {
117
- let reason = "Hash Mismatch";
118
- let details = `Old: ${item.oldHash?.substring(0,6)}... New: ${item.newHash?.substring(0,6)}...`;
79
+ // 2. PARALLEL PROCESSING (Fix for DEADLINE_EXCEEDED)
80
+ // Run 20 reads in parallel.
81
+ // 90 days / 20 concurrent = ~5 batches = ~2-3 seconds total vs 45+ seconds sequentially.
82
+ const limit = pLimit(20);
83
+
84
+ const processingPromises = datesToCheck.map(dateStr => limit(async () => {
85
+ try {
86
+ // A. Fetch REAL status from DB (What ran previously?)
87
+ const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
88
+
89
+ // B. SMART MOCK Root Data
90
+ const dateObj = new Date(dateStr + 'T00:00:00Z');
91
+ const mockRootDataStatus = {
92
+ hasPortfolio: dateObj >= DEFINITIVE_EARLIEST_DATES.portfolio,
93
+ hasHistory: dateObj >= DEFINITIVE_EARLIEST_DATES.history,
94
+ hasSocial: dateObj >= DEFINITIVE_EARLIEST_DATES.social,
95
+ hasInsights: dateObj >= DEFINITIVE_EARLIEST_DATES.insights,
96
+ hasPrices: dateObj >= DEFINITIVE_EARLIEST_DATES.price
97
+ };
98
+
99
+ // C. Run Logic Analysis
100
+ const analysis = analyzeDateExecution(dateStr, manifest, mockRootDataStatus, dailyStatus, manifestMap);
101
+
102
+ // D. Format Findings
103
+ const dateSummary = {
104
+ willRun: [],
105
+ willReRun: [],
106
+ blocked: [],
107
+ impossible: []
108
+ };
109
+
110
+ // -- Runnable (New) --
111
+ analysis.runnable.forEach(item => {
112
+ dateSummary.willRun.push({ name: item.name, reason: "New / No Previous Record" });
113
+ });
114
+
115
+ // -- Re-Runs (Hash Mismatch / Migration) --
116
+ analysis.reRuns.forEach(item => {
117
+ let reason = "Hash Mismatch";
118
+ let details = `Old: ${item.oldHash?.substring(0,6)}... New: ${item.newHash?.substring(0,6)}...`;
119
+
120
+ if (item.previousCategory) {
121
+ reason = "Migration";
122
+ details = `Moving ${item.previousCategory} -> ${item.newCategory}`;
123
+ }
124
+
125
+ dateSummary.willReRun.push({ name: item.name, reason, details });
126
+ });
127
+
128
+ // -- Impossible (Permanent) --
129
+ analysis.impossible.forEach(item => {
130
+ dateSummary.impossible.push({ name: item.name, reason: item.reason });
131
+ });
132
+
133
+ // -- Blocked (Retriable) --
134
+ analysis.blocked.forEach(item => {
135
+ dateSummary.blocked.push({ name: item.name, reason: item.reason });
136
+ });
137
+ analysis.failedDependency.forEach(item => {
138
+ dateSummary.blocked.push({ name: item.name, reason: `Dependency Missing: ${item.missing.join(', ')}` });
139
+ });
140
+
141
+ // Return result for aggregation
142
+ const hasUpdates = dateSummary.willRun.length || dateSummary.willReRun.length || dateSummary.blocked.length || dateSummary.impossible.length;
119
143
 
120
- if (item.previousCategory) {
121
- reason = "Migration";
122
- details = `Moving ${item.previousCategory} -> ${item.newCategory}`;
123
- }
124
-
125
- dateSummary.willReRun.push({ name: item.name, reason, details });
126
- });
127
-
128
- // -- Impossible (Permanent) --
129
- // Mapped from analysis.impossible (Missing Root Data or Explicit Impossible)
130
- analysis.impossible.forEach(item => {
131
- dateSummary.impossible.push({ name: item.name, reason: item.reason }); // e.g. "Permanently Impossible"
132
- });
133
-
134
- // -- Blocked (Retriable) --
135
- // Mapped from analysis.blocked (Waiting for data) AND failedDependency
136
- analysis.blocked.forEach(item => {
137
- dateSummary.blocked.push({ name: item.name, reason: item.reason });
138
- });
139
- analysis.failedDependency.forEach(item => {
140
- // If a dependency failed, it's blocked, not necessarily impossible (unless dependency is impossible)
141
- dateSummary.blocked.push({ name: item.name, reason: `Dependency Missing: ${item.missing.join(', ')}` });
142
- });
143
-
144
- // Only add date to report if something interesting is happening
145
- if (dateSummary.willRun.length || dateSummary.willReRun.length || dateSummary.blocked.length || dateSummary.impossible.length) {
146
- reportData.dates[dateStr] = dateSummary;
147
- totalNew += dateSummary.willRun.length;
148
- totalReRuns += dateSummary.willReRun.length;
144
+ return {
145
+ dateStr,
146
+ dateSummary,
147
+ hasUpdates,
148
+ stats: {
149
+ new: dateSummary.willRun.length,
150
+ rerun: dateSummary.willReRun.length
151
+ }
152
+ };
153
+
154
+ } catch (err) {
155
+ logger.log('ERROR', `[BuildReporter] Error analyzing date ${dateStr}: ${err.message}`);
156
+ return null;
149
157
  }
150
- }
158
+ }));
159
+
160
+ // Wait for all dates to process
161
+ const results = await Promise.all(processingPromises);
162
+
163
+ // 3. Aggregate Results
164
+ results.forEach(res => {
165
+ if (res && res.hasUpdates) {
166
+ reportData.dates[res.dateStr] = res.dateSummary;
167
+ totalNew += res.stats.new;
168
+ totalReRuns += res.stats.rerun;
169
+ }
170
+ });
151
171
 
152
172
  reportData.summary = { totalReRuns, totalNew, scanRange: `${datesToCheck[0]} to ${datesToCheck[datesToCheck.length-1]}` };
153
173
 
154
- // 3. Store Report
174
+ // 4. Store Report
155
175
  const reportRef = db.collection('computation_build_records').doc(buildId);
156
176
  await reportRef.set(reportData);
157
177
 
158
- // 4. Update 'latest' pointer
178
+ // 5. Update 'latest' pointer
159
179
  await db.collection('computation_build_records').doc('latest').set({
160
180
  ...reportData,
161
181
  note: "Latest build report pointer."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.241",
3
+ "version": "1.0.242",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [