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
- * @fileoverview
3
- * Main "Pass Runner" for the V2 Computation System.
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
- // --- NEW: Parallel processing batch size ---
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); if (!passToRun) return logger.log('ERROR', '[PassRunner] No pass defined. Aborting.');
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
- const yesterday = new Date(); yesterday.setUTCDate(yesterday.getUTCDate()-1);
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
- // const earliestDates = await getEarliestDataDates(config, dependencies); // <-- 1. COMMENT OUT OR REMOVE THIS BROKEN CALL
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
- // Use the exact dates you provided:
35
- const earliestPortfolio = new Date('2025-09-25T00:00:00Z'); // Earliest of Normal (09-25) and Spec (09-29)
36
- const earliestHistory = new Date('2025-11-05T00:00:00Z'); // Both Normal and Spec history
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, // <-- Use the real date
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
- // --- END: NEW HARDCODED DATES ---
51
-
52
-
38
+
53
39
  const passes = groupByPass(computationManifest);
54
- const calcsInThisPass = passes[passToRun] || []; if (!calcsInThisPass.length) return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
55
-
56
- // --- 3. THIS LOGIC BLOCK (from our last fix) IS NOW CORRECT ---
57
- // It will consume the clean, hardcoded 'earliestDates' map and work as intended.
58
- const requiredRootData = new Set();
59
- calcsInThisPass.forEach(c => {
60
- (c.rootDataDependencies || []).forEach(dep => requiredRootData.add(dep));
61
- });
62
-
63
- let earliestStartDateForPass = null;
64
- if (requiredRootData.size > 0) {
65
- let latestOfEarliestDates = new Date(0); // Start at epoch
66
- const farFutureSentinelYear = 2999; // Keep as a safety check for any future issues
67
- let hasAtLeastOneValidDate = false;
68
-
69
- requiredRootData.forEach(dep => {
70
- const earliestDateForDep = earliestDates[dep];
71
-
72
- // This check is now just a safeguard
73
- if (earliestDateForDep && earliestDateForDep.getUTCFullYear() < farFutureSentinelYear) {
74
- if (earliestDateForDep > latestOfEarliestDates) {
75
- latestOfEarliestDates = earliestDateForDep;
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
- if (hasAtLeastOneValidDate) {
86
- earliestStartDateForPass = latestOfEarliestDates;
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
- // Use the pass-specific date. Fall back to absolute earliest, then config.
91
- const firstDate = earliestStartDateForPass || earliestDates.absoluteEarliest;
92
- const startDateUTC = firstDate
93
- ? new Date(Date.UTC(firstDate.getUTCFullYear(), firstDate.getUTCMonth(), firstDate.getUTCDate()))
94
- : new Date(config.earliestComputationDate+'T00:00:00Z');
95
-
96
- logger.log('INFO', `[PassRunner] Pass ${passToRun} requires data: [${Array.from(requiredRootData).join(', ')}].`);
97
- logger.log('INFO', `[PassRunner] Determined start date for this pass: ${startDateUTC.toISOString().slice(0, 10)}`);
98
- // --- END OF LOGIC BLOCK ---
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
- const standardCalcs = calcsInThisPass.filter(c => c.type==='standard');
104
- const metaCalcs = calcsInThisPass.filter(c => c.type==='meta');
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) { logger.log('WARN', `[PassRunner] Skipping ${dateStr} for Pass ${passToRun}: No root data.`);return;}
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
- const { standardCalcsToRun, metaCalcsToRun } = filterCalculations(standardCalcs, metaCalcs, rootData.status, existingResults, passToRun, dateStr, logger,dateStr === firstDayOfBackfill );
112
- if (standardCalcsToRun.length === 0 && metaCalcsToRun.length === 0) {logger.log('INFO', `[PassRunner] All calcs for ${dateStr} Pass ${passToRun} are already complete. Skipping.`);return;}
113
- if (standardCalcsToRun.length) await runStandardComputationPass(dateToProcess, standardCalcsToRun, `Pass ${passToRun} (Standard)`, config, dependencies, rootData);
114
- if (metaCalcsToRun.length) await runMetaComputationPass(dateToProcess, metaCalcsToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, rootData);
115
- } catch (err) {logger.log('ERROR', `[PassRunner] FAILED Pass ${passToRun} for ${dateStr}`, { errorMessage: err.message, stack: err.stack });}
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.139",
3
+ "version": "1.0.140",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [