bulltrackers-module 1.0.139 → 1.0.140
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,125 +1,210 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* This orchestrator is designed to be run by a separate Cloud Function for each "pass".
|
|
6
|
-
* It reads its pass number from the config and executes only those calculations.
|
|
7
|
-
* This file contains the high-level "manual" of steps. The "how-to" logic
|
|
8
|
-
* is extracted into 'computation_system_utils.js'.
|
|
9
|
-
* --- MODIFIED: To use getEarliestDataDates and pass the date map to the orchestrator helpers. ---
|
|
10
|
-
* --- MODIFIED: To run date processing in parallel batches. ---
|
|
11
|
-
* --- MODIFIED: To fetch ALL existing results to enable incremental (skip) logic. ---
|
|
2
|
+
* FIXED: computation_pass_runner.js
|
|
3
|
+
* Now calculates earliest date PER CALCULATION, not per pass
|
|
12
4
|
*/
|
|
13
5
|
|
|
14
|
-
// --- MODIFIED: Renamed fetchDependenciesForPass to fetchExistingResults ---
|
|
15
6
|
const { groupByPass, checkRootDataAvailability, fetchExistingResults, filterCalculations, runStandardComputationPass, runMetaComputationPass } = require('./orchestration_helpers.js');
|
|
16
|
-
// --- MODIFIED: Import getEarliestDataDates ---
|
|
17
7
|
const { getExpectedDateStrings, getEarliestDataDates } = require('../utils/utils.js');
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
const PARALLEL_BATCH_SIZE = 7; // Process a week at a time
|
|
9
|
+
const PARALLEL_BATCH_SIZE = 7;
|
|
21
10
|
|
|
22
11
|
async function runComputationPass(config, dependencies, computationManifest) {
|
|
23
12
|
const { logger } = dependencies;
|
|
24
|
-
const passToRun = String(config.COMPUTATION_PASS_TO_RUN);
|
|
13
|
+
const passToRun = String(config.COMPUTATION_PASS_TO_RUN);
|
|
14
|
+
if (!passToRun) return logger.log('ERROR', '[PassRunner] No pass defined. Aborting.');
|
|
15
|
+
|
|
25
16
|
logger.log('INFO', `🚀 Starting PASS ${passToRun}...`);
|
|
26
|
-
|
|
17
|
+
|
|
18
|
+
const yesterday = new Date();
|
|
19
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
27
20
|
const endDateUTC = new Date(Date.UTC(yesterday.getUTCFullYear(), yesterday.getUTCMonth(), yesterday.getUTCDate()));
|
|
28
21
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
// --- START: 2. NEW HARDCODED DATES (V2 - CORRECTED) ---
|
|
22
|
+
// Hardcoded earliest dates
|
|
32
23
|
logger.log('INFO', 'Using hardcoded earliest data dates to bypass faulty discovery.');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const earliestSocial = new Date('2025-10-30T00:00:00Z'); // daily_social_insights
|
|
38
|
-
const earliestInsights = new Date('2025-08-26T00:00:00Z'); // daily_instrument_insights (Your new date)
|
|
24
|
+
const earliestPortfolio = new Date('2025-09-25T00:00:00Z');
|
|
25
|
+
const earliestHistory = new Date('2025-11-05T00:00:00Z');
|
|
26
|
+
const earliestSocial = new Date('2025-10-30T00:00:00Z');
|
|
27
|
+
const earliestInsights = new Date('2025-08-26T00:00:00Z');
|
|
39
28
|
|
|
40
29
|
const earliestDates = {
|
|
41
30
|
portfolio: earliestPortfolio,
|
|
42
31
|
history: earliestHistory,
|
|
43
32
|
social: earliestSocial,
|
|
44
|
-
insights: earliestInsights,
|
|
45
|
-
// Calculate absoluteEarliest based on all real data
|
|
33
|
+
insights: earliestInsights,
|
|
46
34
|
absoluteEarliest: [earliestPortfolio, earliestHistory, earliestSocial, earliestInsights].reduce((a, b) => a < b ? a : b)
|
|
47
35
|
};
|
|
48
36
|
|
|
49
37
|
logger.log('INFO', `Hardcoded map: portfolio=${earliestDates.portfolio.toISOString().slice(0,10)}, history=${earliestDates.history.toISOString().slice(0,10)}, social=${earliestDates.social.toISOString().slice(0,10)}, insights=${earliestDates.insights.toISOString().slice(0,10)}`);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
|
|
53
39
|
const passes = groupByPass(computationManifest);
|
|
54
|
-
const calcsInThisPass = passes[passToRun] || [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
hasAtLeastOneValidDate = true;
|
|
78
|
-
} else if (earliestDateForDep) {
|
|
79
|
-
logger.log('INFO', `[PassRunner] Dependency '${dep}' is not available (date: ${earliestDateForDep.toISOString().slice(0, 10)}). Calcs requiring it will be skipped.`);
|
|
80
|
-
} else {
|
|
81
|
-
logger.log('WARN', `[PassRunner] Dependency '${dep}' has no earliest date defined in hardcoded map.`);
|
|
40
|
+
const calcsInThisPass = passes[passToRun] || [];
|
|
41
|
+
if (!calcsInThisPass.length) return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
|
|
42
|
+
|
|
43
|
+
// ============================================
|
|
44
|
+
// NEW: Calculate earliest date PER CALCULATION
|
|
45
|
+
// ============================================
|
|
46
|
+
const calcEarliestDates = new Map();
|
|
47
|
+
|
|
48
|
+
for (const calc of calcsInThisPass) {
|
|
49
|
+
const deps = calc.rootDataDependencies || [];
|
|
50
|
+
|
|
51
|
+
if (deps.length === 0) {
|
|
52
|
+
// No dependencies = can run from the absolute earliest
|
|
53
|
+
calcEarliestDates.set(calc.name, earliestDates.absoluteEarliest);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Find the LATEST earliest date among THIS calculation's dependencies
|
|
58
|
+
let latestEarliest = new Date(0);
|
|
59
|
+
for (const dep of deps) {
|
|
60
|
+
const depDate = earliestDates[dep];
|
|
61
|
+
if (depDate && depDate > latestEarliest) {
|
|
62
|
+
latestEarliest = depDate;
|
|
82
63
|
}
|
|
83
|
-
}
|
|
64
|
+
}
|
|
84
65
|
|
|
85
|
-
|
|
86
|
-
|
|
66
|
+
// If this is a historical calculation, add 1 day (needs yesterday)
|
|
67
|
+
if (calc.isHistorical) {
|
|
68
|
+
const adjusted = new Date(latestEarliest);
|
|
69
|
+
adjusted.setUTCDate(adjusted.getUTCDate() + 1);
|
|
70
|
+
calcEarliestDates.set(calc.name, adjusted);
|
|
71
|
+
logger.log('TRACE', `[PassRunner] ${calc.name}: earliest=${adjusted.toISOString().slice(0,10)} (historical, needs ${latestEarliest.toISOString().slice(0,10)} + 1 day)`);
|
|
72
|
+
} else {
|
|
73
|
+
calcEarliestDates.set(calc.name, latestEarliest);
|
|
74
|
+
logger.log('TRACE', `[PassRunner] ${calc.name}: earliest=${latestEarliest.toISOString().slice(0,10)}`);
|
|
87
75
|
}
|
|
88
76
|
}
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
logger.log('INFO', `
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
77
|
+
|
|
78
|
+
// The pass can start from the EARLIEST calculation's start date
|
|
79
|
+
const passEarliestDate = new Date(Math.min(...Array.from(calcEarliestDates.values()).map(d => d.getTime())));
|
|
80
|
+
|
|
81
|
+
logger.log('INFO', `[PassRunner] Pass ${passToRun} analysis:`);
|
|
82
|
+
logger.log('INFO', ` Total calculations: ${calcsInThisPass.length}`);
|
|
83
|
+
logger.log('INFO', ` Pass can start from: ${passEarliestDate.toISOString().slice(0,10)}`);
|
|
84
|
+
logger.log('INFO', ` Individual calculation date ranges calculated.`);
|
|
85
|
+
|
|
86
|
+
// ============================================
|
|
87
|
+
// Generate date range from earliest to yesterday
|
|
88
|
+
// ============================================
|
|
89
|
+
const startDateUTC = new Date(Date.UTC(passEarliestDate.getUTCFullYear(), passEarliestDate.getUTCMonth(), passEarliestDate.getUTCDate()));
|
|
100
90
|
const allExpectedDates = getExpectedDateStrings(startDateUTC, endDateUTC);
|
|
101
|
-
const firstDayOfBackfill = allExpectedDates.length > 0 ? allExpectedDates[0] : null; // --- MOVED FROM ABOVE ---
|
|
102
91
|
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
logger.log('INFO', `[PassRunner] Processing ${allExpectedDates.length} total dates from ${allExpectedDates[0]} to ${allExpectedDates[allExpectedDates.length-1]}`);
|
|
93
|
+
|
|
94
|
+
const standardCalcs = calcsInThisPass.filter(c => c.type === 'standard');
|
|
95
|
+
const metaCalcs = calcsInThisPass.filter(c => c.type === 'meta');
|
|
96
|
+
|
|
97
|
+
// ============================================
|
|
98
|
+
// Process each date
|
|
99
|
+
// ============================================
|
|
105
100
|
const processDate = async (dateStr) => {
|
|
106
|
-
const dateToProcess = new Date(dateStr+'T00:00:00Z');
|
|
101
|
+
const dateToProcess = new Date(dateStr + 'T00:00:00Z');
|
|
102
|
+
|
|
107
103
|
try {
|
|
104
|
+
// Check root data availability
|
|
108
105
|
const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
|
|
109
|
-
if (!rootData) {
|
|
106
|
+
if (!rootData) {
|
|
107
|
+
logger.log('WARN', `[PassRunner] Skipping ${dateStr} for Pass ${passToRun}: No root data.`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Fetch existing results
|
|
110
112
|
const existingResults = await fetchExistingResults(dateStr, calcsInThisPass, computationManifest, config, dependencies);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
|
|
114
|
+
// ============================================
|
|
115
|
+
// NEW: Filter based on per-calculation earliest dates
|
|
116
|
+
// ============================================
|
|
117
|
+
const standardCalcsToRun = standardCalcs.filter(c => {
|
|
118
|
+
// Skip if already exists
|
|
119
|
+
if (existingResults[c.name]) {
|
|
120
|
+
logger.log('TRACE', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Result already exists.`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Skip if date is before this calculation's earliest date
|
|
125
|
+
const calcEarliestDate = calcEarliestDates.get(c.name);
|
|
126
|
+
if (calcEarliestDate && dateToProcess < calcEarliestDate) {
|
|
127
|
+
logger.log('TRACE', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Date before calc's earliest (${calcEarliestDate.toISOString().slice(0,10)}).`);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check root data dependencies
|
|
132
|
+
const missingDeps = (c.rootDataDependencies || []).filter(dep => {
|
|
133
|
+
if (dep === 'portfolio') return !rootData.status.hasPortfolio;
|
|
134
|
+
if (dep === 'insights') return !rootData.status.hasInsights;
|
|
135
|
+
if (dep === 'social') return !rootData.status.hasSocial;
|
|
136
|
+
if (dep === 'history') return !rootData.status.hasHistory;
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (missingDeps.length > 0) {
|
|
141
|
+
logger.log('INFO', `[Pass ${passToRun}] Skipping ${c.name} for ${dateStr}. Missing deps: ${missingDeps.join(', ')}`);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const metaCalcsToRun = metaCalcs.filter(c => {
|
|
149
|
+
if (existingResults[c.name]) return false;
|
|
150
|
+
|
|
151
|
+
const calcEarliestDate = calcEarliestDates.get(c.name);
|
|
152
|
+
if (calcEarliestDate && dateToProcess < calcEarliestDate) {
|
|
153
|
+
logger.log('TRACE', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Date before calc's earliest.`);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check root dependencies
|
|
158
|
+
const missingRootDeps = (c.rootDataDependencies || []).filter(dep => {
|
|
159
|
+
if (dep === 'portfolio') return !rootData.status.hasPortfolio;
|
|
160
|
+
if (dep === 'insights') return !rootData.status.hasInsights;
|
|
161
|
+
if (dep === 'social') return !rootData.status.hasSocial;
|
|
162
|
+
if (dep === 'history') return !rootData.status.hasHistory;
|
|
163
|
+
return false;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (missingRootDeps.length > 0) {
|
|
167
|
+
logger.log('INFO', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Missing root deps: ${missingRootDeps.join(', ')}`);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check computed dependencies
|
|
172
|
+
const missingComputedDeps = (c.dependencies || []).filter(d => !existingResults[d]);
|
|
173
|
+
if (missingComputedDeps.length > 0) {
|
|
174
|
+
logger.log('WARN', `[Pass ${passToRun} Meta] Skipping ${c.name} for ${dateStr}. Missing computed deps: ${missingComputedDeps.join(', ')}`);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return true;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (standardCalcsToRun.length === 0 && metaCalcsToRun.length === 0) {
|
|
182
|
+
logger.log('INFO', `[PassRunner] All eligible calcs for ${dateStr} Pass ${passToRun} are already complete. Skipping.`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
logger.log('INFO', `[PassRunner] Running ${dateStr}: ${standardCalcsToRun.length} standard, ${metaCalcsToRun.length} meta`);
|
|
187
|
+
|
|
188
|
+
if (standardCalcsToRun.length) {
|
|
189
|
+
await runStandardComputationPass(dateToProcess, standardCalcsToRun, `Pass ${passToRun} (Standard)`, config, dependencies, rootData);
|
|
190
|
+
}
|
|
191
|
+
if (metaCalcsToRun.length) {
|
|
192
|
+
await runMetaComputationPass(dateToProcess, metaCalcsToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, rootData);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
} catch (err) {
|
|
196
|
+
logger.log('ERROR', `[PassRunner] FAILED Pass ${passToRun} for ${dateStr}`, { errorMessage: err.message, stack: err.stack });
|
|
197
|
+
}
|
|
116
198
|
};
|
|
199
|
+
|
|
200
|
+
// Process in batches
|
|
117
201
|
logger.log('INFO', `[PassRunner] Processing ${allExpectedDates.length} total dates in batches of ${PARALLEL_BATCH_SIZE}...`);
|
|
118
202
|
for (let i = 0; i < allExpectedDates.length; i += PARALLEL_BATCH_SIZE) {
|
|
119
203
|
const batch = allExpectedDates.slice(i, i + PARALLEL_BATCH_SIZE);
|
|
120
204
|
logger.log('INFO', `[PassRunner] Processing batch ${Math.floor(i / PARALLEL_BATCH_SIZE) + 1}/${Math.ceil(allExpectedDates.length / PARALLEL_BATCH_SIZE)} (Dates: ${batch[0]}...${batch[batch.length-1]})`);
|
|
121
205
|
await Promise.all(batch.map(dateStr => processDate(dateStr)));
|
|
122
206
|
}
|
|
207
|
+
|
|
123
208
|
logger.log('INFO', `[PassRunner] Pass ${passToRun} orchestration finished.`);
|
|
124
209
|
}
|
|
125
210
|
|