bulltrackers-module 1.0.138 → 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,97 +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
- const earliestDates = await getEarliestDataDates(config, dependencies);
29
-
30
- // --- REMOVE OLD/REPLACE WITH NEW LOGIC FOR PROBLEM 3 & 4 ---
31
- // const firstDate = earliestDates.absoluteEarliest; // <-- REMOVE
32
- // const startDateUTC = firstDate ? new Date(Date.UTC(firstDate.getUTCFullYear(), firstDate.getUTCMonth(), firstDate.getUTCDate())) : new Date(config.earliestComputationDate+'T00:00:00Z'); // <-- REMOVE
33
-
21
+
22
+ // Hardcoded earliest dates
23
+ logger.log('INFO', 'Using hardcoded earliest data dates to bypass faulty discovery.');
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');
28
+
29
+ const earliestDates = {
30
+ portfolio: earliestPortfolio,
31
+ history: earliestHistory,
32
+ social: earliestSocial,
33
+ insights: earliestInsights,
34
+ absoluteEarliest: [earliestPortfolio, earliestHistory, earliestSocial, earliestInsights].reduce((a, b) => a < b ? a : b)
35
+ };
36
+
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)}`);
38
+
34
39
  const passes = groupByPass(computationManifest);
35
- const calcsInThisPass = passes[passToRun] || []; if (!calcsInThisPass.length) return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
36
-
37
- // --- START: NEW LOGIC FOR PROBLEM 3 & 4 ---
38
- // Determine the earliest start date based *only* on data required for THIS pass.
39
- const requiredRootData = new Set();
40
- calcsInThisPass.forEach(c => {
41
- (c.rootDataDependencies || []).forEach(dep => requiredRootData.add(dep));
42
- });
43
-
44
- let earliestStartDateForPass = null;
45
- if (requiredRootData.size > 0) {
46
- // Find the LATEST of the earliest dates for all required data types.
47
- // e.g., If portfolio starts 09-25 and social starts 10-30, a calc
48
- // needing both must start on 10-30.
49
- let latestOfEarliestDates = new Date(0); // Start at epoch
50
- requiredRootData.forEach(dep => {
51
- const earliestDateForDep = earliestDates[dep]; // e.g., earliestDates.portfolio
52
- if (earliestDateForDep && earliestDateForDep > latestOfEarliestDates) {
53
- latestOfEarliestDates = earliestDateForDep;
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;
54
63
  }
55
- });
64
+ }
56
65
 
57
- if (latestOfEarliestDates.getTime() > 0) {
58
- 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)}`);
59
75
  }
60
76
  }
61
-
62
- // Use the pass-specific date. Fall back to absolute earliest, then config.
63
- const firstDate = earliestStartDateForPass || earliestDates.absoluteEarliest;
64
- const startDateUTC = firstDate
65
- ? new Date(Date.UTC(firstDate.getUTCFullYear(), firstDate.getUTCMonth(), firstDate.getUTCDate()))
66
- : new Date(config.earliestComputationDate+'T00:00:00Z');
67
-
68
- logger.log('INFO', `[PassRunner] Pass ${passToRun} requires data: [${Array.from(requiredRootData).join(', ')}].`);
69
- logger.log('INFO', `[PassRunner] Determined start date for this pass: ${startDateUTC.toISOString().slice(0, 10)}`);
70
- // --- END: NEW LOGIC ---
71
-
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()));
72
90
  const allExpectedDates = getExpectedDateStrings(startDateUTC, endDateUTC);
73
- const firstDayOfBackfill = allExpectedDates.length > 0 ? allExpectedDates[0] : null; // --- MOVED FROM ABOVE ---
74
91
 
75
- const standardCalcs = calcsInThisPass.filter(c => c.type==='standard');
76
- 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
+ // ============================================
77
100
  const processDate = async (dateStr) => {
78
- const dateToProcess = new Date(dateStr+'T00:00:00Z');
101
+ const dateToProcess = new Date(dateStr + 'T00:00:00Z');
102
+
79
103
  try {
104
+ // Check root data availability
80
105
  const rootData = await checkRootDataAvailability(dateStr, config, dependencies, earliestDates);
81
- 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
82
112
  const existingResults = await fetchExistingResults(dateStr, calcsInThisPass, computationManifest, config, dependencies);
83
- const { standardCalcsToRun, metaCalcsToRun } = filterCalculations(standardCalcs, metaCalcs, rootData.status, existingResults, passToRun, dateStr, logger,dateStr === firstDayOfBackfill );
84
- if (standardCalcsToRun.length === 0 && metaCalcsToRun.length === 0) {logger.log('INFO', `[PassRunner] All calcs for ${dateStr} Pass ${passToRun} are already complete. Skipping.`);return;}
85
- if (standardCalcsToRun.length) await runStandardComputationPass(dateToProcess, standardCalcsToRun, `Pass ${passToRun} (Standard)`, config, dependencies, rootData);
86
- if (metaCalcsToRun.length) await runMetaComputationPass(dateToProcess, metaCalcsToRun, `Pass ${passToRun} (Meta)`, config, dependencies, existingResults, rootData);
87
- } 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
+ }
88
198
  };
199
+
200
+ // Process in batches
89
201
  logger.log('INFO', `[PassRunner] Processing ${allExpectedDates.length} total dates in batches of ${PARALLEL_BATCH_SIZE}...`);
90
202
  for (let i = 0; i < allExpectedDates.length; i += PARALLEL_BATCH_SIZE) {
91
203
  const batch = allExpectedDates.slice(i, i + PARALLEL_BATCH_SIZE);
92
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]})`);
93
205
  await Promise.all(batch.map(dateStr => processDate(dateStr)));
94
206
  }
207
+
95
208
  logger.log('INFO', `[PassRunner] Pass ${passToRun} orchestration finished.`);
96
209
  }
97
210
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.138",
3
+ "version": "1.0.140",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [