bulltrackers-module 1.0.234 → 1.0.236

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,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Main Orchestrator. Coordinates the topological execution.
3
- * UPDATED: Removed legacy boolean 'true' logic.
3
+ * UPDATED: Detects Category changes to trigger migration/cleanup.
4
+ * FIX: Decouples migration detection from hash verification to handle simultaneous code & category changes.
4
5
  */
5
6
  const { normalizeName } = require('./utils/utils');
6
7
  const { checkRootDataAvailability } = require('./data/AvailabilityChecker');
@@ -33,22 +34,34 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
33
34
 
34
35
  const isDepSatisfied = (depName, dailyStatus, manifestMap) => {
35
36
  const norm = normalizeName(depName);
36
- const storedDepHash = dailyStatus[norm];
37
+ const stored = dailyStatus[norm]; // Now an object or null
37
38
  const depManifest = manifestMap.get(norm);
38
39
 
39
- if (storedDepHash === STATUS_IMPOSSIBLE) return false;
40
- if (!storedDepHash) return false;
40
+ if (!stored) return false;
41
+
42
+ // Handle IMPOSSIBLE flag (stored as object property or legacy string check)
43
+ if (stored.hash === STATUS_IMPOSSIBLE) return false;
44
+
41
45
  if (!depManifest) return false;
42
- if (storedDepHash !== depManifest.hash) return false;
46
+ if (stored.hash !== depManifest.hash) return false;
43
47
 
44
48
  return true;
45
49
  };
46
50
 
47
51
  for (const calc of calcsInPass) {
48
52
  const cName = normalizeName(calc.name);
49
- const storedHash = dailyStatus[cName];
53
+ const stored = dailyStatus[cName]; // Object { hash, category }
54
+
55
+ const storedHash = stored ? stored.hash : null;
56
+ const storedCategory = stored ? stored.category : null;
50
57
  const currentHash = calc.hash;
51
58
 
59
+ // [SMART MIGRATION] Detect if category changed, independent of hash check
60
+ let migrationOldCategory = null;
61
+ if (storedCategory && storedCategory !== calc.category) {
62
+ migrationOldCategory = storedCategory;
63
+ }
64
+
52
65
  // 1. Check Impossible
53
66
  if (storedHash === STATUS_IMPOSSIBLE) {
54
67
  report.skipped.push({ name: cName, reason: 'Permanently Impossible' });
@@ -83,8 +96,9 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
83
96
  if (calc.dependencies) {
84
97
  for (const dep of calc.dependencies) {
85
98
  const normDep = normalizeName(dep);
99
+ const depStored = dailyStatus[normDep];
86
100
 
87
- if (dailyStatus[normDep] === STATUS_IMPOSSIBLE) {
101
+ if (depStored && depStored.hash === STATUS_IMPOSSIBLE) {
88
102
  dependencyIsImpossible = true;
89
103
  break;
90
104
  }
@@ -105,13 +119,28 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
105
119
  continue;
106
120
  }
107
121
 
108
- // 4. Hash / State Check (Legacy 'true' logic removed)
109
- if (!storedHash || storedHash === false) {
122
+ // 4. Hash & Category Check (Smart Migration Logic)
123
+ if (!storedHash) {
110
124
  report.runnable.push(calc);
111
125
  } else if (storedHash !== currentHash) {
112
- report.reRuns.push({ name: cName, oldHash: storedHash, newHash: currentHash });
126
+ // Hash Mismatch (Code Changed).
127
+ // Pass migration info here too, in case category ALSO changed.
128
+ report.reRuns.push({
129
+ name: cName,
130
+ oldHash: storedHash,
131
+ newHash: currentHash,
132
+ previousCategory: migrationOldCategory
133
+ });
134
+ } else if (migrationOldCategory) {
135
+ // Hash Matches, BUT category changed. Force Re-run.
136
+ report.reRuns.push({
137
+ name: cName,
138
+ reason: 'Category Migration',
139
+ previousCategory: migrationOldCategory,
140
+ newCategory: calc.category
141
+ });
113
142
  } else {
114
- // Stored Hash === Current Hash
143
+ // Stored Hash === Current Hash AND Category matches
115
144
  report.skipped.push({ name: cName });
116
145
  }
117
146
  }
@@ -122,7 +151,7 @@ function analyzeDateExecution(dateStr, calcsInPass, rootDataStatus, dailyStatus,
122
151
  async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, dependencies, computationManifest) {
123
152
  const { logger } = dependencies;
124
153
  const orchestratorPid = generateProcessId(PROCESS_TYPES.ORCHESTRATOR, passToRun, dateStr);
125
- const dateToProcess = new Date(dateStr + 'T00:00:00Z');
154
+ const dateToProcess = new Date(dateStr + 'T00:00:00Z');
126
155
 
127
156
  // 1. Fetch State
128
157
  const dailyStatus = await fetchComputationStatus(dateStr, config, dependencies);
@@ -154,21 +183,42 @@ async function runDateComputation(dateStr, passToRun, calcsInThisPass, config, d
154
183
 
155
184
  // 5. UPDATE STATUS FOR NON-RUNNABLE ITEMS
156
185
  const statusUpdates = {};
157
- analysisReport.blocked.forEach(item => statusUpdates[item.name] = false);
158
- analysisReport.failedDependency.forEach(item => statusUpdates[item.name] = false);
159
- analysisReport.impossible.forEach(item => statusUpdates[item.name] = STATUS_IMPOSSIBLE);
186
+
187
+ analysisReport.blocked.forEach(item => statusUpdates[item.name] = { hash: false, category: 'unknown' });
188
+ analysisReport.failedDependency.forEach(item => statusUpdates[item.name] = { hash: false, category: 'unknown' });
189
+ analysisReport.impossible.forEach(item => statusUpdates[item.name] = { hash: STATUS_IMPOSSIBLE, category: 'unknown' });
160
190
 
161
191
  if (Object.keys(statusUpdates).length > 0) {
162
192
  await updateComputationStatus(dateStr, statusUpdates, config, dependencies);
163
193
  }
164
194
 
165
195
  // 6. EXECUTE RUNNABLES
196
+
197
+ // [SMART MIGRATION] Build map of items needing cleanup
198
+ const migrationMap = {};
199
+ analysisReport.reRuns.forEach(item => {
200
+ if (item.previousCategory) {
201
+ migrationMap[normalizeName(item.name)] = item.previousCategory;
202
+ }
203
+ });
204
+
166
205
  const calcsToRunNames = new Set([
167
206
  ...analysisReport.runnable.map(c => c.name),
168
- ...analysisReport.reRuns.map(c => c.name)
207
+ ...analysisReport.reRuns.map(c => c.name)
169
208
  ]);
170
209
 
171
- const finalRunList = calcsInThisPass.filter(c => calcsToRunNames.has(normalizeName(c.name)));
210
+ // [SMART MIGRATION] Create Safe Copies with previousCategory attached
211
+ // We clone the manifest object so we don't pollute the global cache with run-specific flags
212
+ const finalRunList = calcsInThisPass
213
+ .filter(c => calcsToRunNames.has(normalizeName(c.name)))
214
+ .map(c => {
215
+ const clone = { ...c }; // Shallow copy
216
+ const prevCat = migrationMap[normalizeName(c.name)];
217
+ if (prevCat) {
218
+ clone.previousCategory = prevCat;
219
+ }
220
+ return clone;
221
+ });
172
222
 
173
223
  if (!finalRunList.length) {
174
224
  return {
@@ -4,6 +4,8 @@
4
4
  const { generateCodeHash, LEGACY_MAPPING } = require('../topology/HashManager.js');
5
5
  const { normalizeName } = require('../utils/utils');
6
6
 
7
+ const SYSTEM_EPOCH = require('../system_epoch');
8
+
7
9
  // Import Layers
8
10
  const MathematicsLayer = require('../layers/mathematics');
9
11
  const ExtractorsLayer = require('../layers/extractors');
@@ -99,7 +101,7 @@ function buildManifest(productLinesToRun = [], calculations) {
99
101
  const dependencies = Class.getDependencies().map(normalizeName);
100
102
  const codeStr = Class.toString();
101
103
 
102
- let compositeHashString = generateCodeHash(codeStr);
104
+ let compositeHashString = generateCodeHash(codeStr) + `|EPOCH:${SYSTEM_EPOCH}`; // Here we build the hash
103
105
  const usedDeps = [];
104
106
 
105
107
  for (const [layerName, exportsMap] of Object.entries(LAYER_TRIGGERS)) {
@@ -166,8 +168,12 @@ function buildManifest(productLinesToRun = [], calculations) {
166
168
  }
167
169
 
168
170
  const productLineEndpoints = [];
171
+
172
+ // [UPDATE] Check if we should run ALL product lines (if empty or wildcard)
173
+ const runAll = !productLinesToRun || productLinesToRun.length === 0 || productLinesToRun.includes('*');
174
+
169
175
  for (const [name, entry] of manifestMap.entries()) {
170
- if (productLinesToRun.includes(entry.category) || entry.sourcePackage === 'core') {
176
+ if (runAll || productLinesToRun.includes(entry.category) || entry.sourcePackage === 'core') {
171
177
  productLineEndpoints.push(name);
172
178
  }
173
179
  }
@@ -69,7 +69,17 @@ async function runBatchPriceComputation(config, deps, dateStrings, calcs, target
69
69
  .doc(calcManifest.category)
70
70
  .collection(config.computationsSubcollection)
71
71
  .doc(normalizeName(calcManifest.name));
72
- writes.push({ ref: docRef, data: { ...dataToWrite, _completed: true }, options: { merge: true } });
72
+
73
+ // [UPDATE] Add _lastUpdated timestamp
74
+ writes.push({
75
+ ref: docRef,
76
+ data: {
77
+ ...dataToWrite,
78
+ _completed: true,
79
+ _lastUpdated: new Date().toISOString()
80
+ },
81
+ options: { merge: true }
82
+ });
73
83
  }
74
84
  }
75
85
  } catch (err) { logger.log('ERROR', `[BatchPrice] \u2716 Failed ${calcManifest.name} for ${dateStr}: ${err.message}`); }
@@ -6,10 +6,10 @@
6
6
 
7
7
  const { runDateComputation, groupByPass } = require('../WorkflowOrchestrator.js');
8
8
  const { getManifest } = require('../topology/ManifestLoader');
9
- const { StructuredLogger } = require('../logger/logger'); // <--- CRITICAL IMPORT
9
+ const { StructuredLogger } = require('../logger/logger');
10
10
 
11
11
  // 1. IMPORT CALCULATIONS
12
- // We import the specific package containing your strategies (gem, pyro, core, etc.)
12
+ // We import the specific package containing the strategies (gem, pyro, core, etc.)
13
13
  let calculationPackage;
14
14
  try {
15
15
  // Primary: Try to load from the installed npm package
@@ -33,17 +33,13 @@ const calculations = calculationPackage.calculations;
33
33
  async function handleComputationTask(message, config, dependencies) {
34
34
 
35
35
  // 2. INITIALIZE SYSTEM LOGGER
36
- // We ignore the generic 'dependencies.logger' and instantiate our specialized one.
37
- // This ensures methods like 'logDateAnalysis' and 'logStorage' exist.
38
36
  const systemLogger = new StructuredLogger({
39
37
  minLevel: config.minLevel || 'INFO',
40
38
  enableStructured: true,
41
- ...config // Apply any other relevant config overrides
39
+ ...config
42
40
  });
43
41
 
44
42
  // 3. OVERRIDE DEPENDENCIES
45
- // We create a new dependencies object to pass downstream.
46
- // This fixes the "logger.logStorage is not a function" error in ResultCommitter.js
47
43
  const runDependencies = {
48
44
  ...dependencies,
49
45
  logger: systemLogger
@@ -57,7 +53,7 @@ async function handleComputationTask(message, config, dependencies) {
57
53
  computationManifest = getManifest(
58
54
  config.activeProductLines || [],
59
55
  calculations,
60
- runDependencies // Pass the updated dependencies
56
+ runDependencies
61
57
  );
62
58
  } catch (manifestError) {
63
59
  logger.log('FATAL', `[Worker] Failed to load Manifest: ${manifestError.message}`);
@@ -111,7 +107,7 @@ async function handleComputationTask(message, config, dependencies) {
111
107
  pass,
112
108
  calcsInThisPass,
113
109
  config,
114
- runDependencies, // <--- IMPORTANT: Pass the dependencies with the System Logger
110
+ runDependencies,
115
111
  computationManifest
116
112
  );
117
113
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Handles saving computation results with observability.
2
+ * @fileoverview Handles saving computation results with observability and Smart Cleanup.
3
3
  */
4
4
  const { commitBatchInChunks } = require('./FirestoreUtils');
5
5
  const { updateComputationStatus } = require('./StatusRepository');
@@ -9,6 +9,7 @@ const { generateProcessId, PROCESS_TYPES } = require('../logger/logger');
9
9
  async function commitResults(stateObj, dStr, passName, config, deps, skipStatusWrite = false) {
10
10
  const successUpdates = {};
11
11
  const schemas = [];
12
+ const cleanupTasks = []; // Tasks to delete old data
12
13
  const { logger } = deps;
13
14
  const pid = generateProcessId(PROCESS_TYPES.STORAGE, passName, dStr);
14
15
 
@@ -16,7 +17,23 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
16
17
  const calc = stateObj[name];
17
18
  try {
18
19
  const result = await calc.getResult();
19
- if (!result) continue;
20
+
21
+ // [UPDATE] Validate Result: Check for Null, Empty Object, or Zero
22
+ const isEmpty = !result ||
23
+ (typeof result === 'object' && Object.keys(result).length === 0) ||
24
+ (typeof result === 'number' && result === 0);
25
+
26
+ if (isEmpty) {
27
+ // [UPDATE] Mark status as FALSE (Failed/Empty) so it re-runs or is flagged
28
+ if (calc.manifest.hash) {
29
+ successUpdates[name] = {
30
+ hash: false,
31
+ category: calc.manifest.category
32
+ };
33
+ }
34
+ // Do not store empty results
35
+ continue;
36
+ }
20
37
 
21
38
  const mainDocRef = deps.db.collection(config.resultsCollection)
22
39
  .doc(dStr)
@@ -49,9 +66,18 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
49
66
  logger.logStorage(pid, name, dStr, mainDocRef.path, totalSize, isSharded);
50
67
  }
51
68
 
52
- // Update success tracking
69
+ // Update success tracking (Include Category)
53
70
  if (calc.manifest.hash) {
54
- successUpdates[name] = calc.manifest.hash;
71
+ successUpdates[name] = {
72
+ hash: calc.manifest.hash,
73
+ category: calc.manifest.category
74
+ };
75
+ }
76
+
77
+ // CHECK FOR MIGRATION CLEANUP
78
+ if (calc.manifest.previousCategory && calc.manifest.previousCategory !== calc.manifest.category) {
79
+ logger.log('INFO', `[Migration] Scheduled cleanup for ${name} from '${calc.manifest.previousCategory}'`);
80
+ cleanupTasks.push(deleteOldCalculationData(dStr, calc.manifest.previousCategory, name, config, deps));
55
81
  }
56
82
  }
57
83
  } catch (e) {
@@ -63,12 +89,58 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
63
89
 
64
90
  if (schemas.length) batchStoreSchemas(deps, config, schemas).catch(() => {});
65
91
 
92
+ // Execute Cleanup Tasks (orphaned data from category changes)
93
+ if (cleanupTasks.length > 0) {
94
+ await Promise.allSettled(cleanupTasks);
95
+ }
96
+
66
97
  if (!skipStatusWrite && Object.keys(successUpdates).length > 0) {
67
98
  await updateComputationStatus(dStr, successUpdates, config, deps);
68
99
  }
69
100
  return successUpdates;
70
101
  }
71
102
 
103
+ /**
104
+ * Deletes result documents from a previous category location.
105
+ * Must handle standard docs AND sharded docs (subcollections).
106
+ */
107
+ async function deleteOldCalculationData(dateStr, oldCategory, calcName, config, deps) {
108
+ const { db, logger, calculationUtils } = deps;
109
+ const { withRetry } = calculationUtils || { withRetry: (fn) => fn() };
110
+
111
+ try {
112
+ const oldDocRef = db.collection(config.resultsCollection)
113
+ .doc(dateStr)
114
+ .collection(config.resultsSubcollection)
115
+ .doc(oldCategory)
116
+ .collection(config.computationsSubcollection)
117
+ .doc(calcName);
118
+
119
+ // 1. Check for Shards Subcollection
120
+ const shardsCol = oldDocRef.collection('_shards');
121
+ const shardsSnap = await withRetry(() => shardsCol.listDocuments(), 'ListOldShards');
122
+
123
+ const batch = db.batch();
124
+ let ops = 0;
125
+
126
+ // Delete shards
127
+ for (const shardDoc of shardsSnap) {
128
+ batch.delete(shardDoc);
129
+ ops++;
130
+ }
131
+
132
+ // Delete main doc
133
+ batch.delete(oldDocRef);
134
+ ops++;
135
+
136
+ await withRetry(() => batch.commit(), 'CleanupOldCategory');
137
+ logger.log('INFO', `[Migration] Cleaned up ${ops} docs for ${calcName} in old category '${oldCategory}'`);
138
+
139
+ } catch (e) {
140
+ logger.log('WARN', `[Migration] Failed to clean up old data for ${calcName}: ${e.message}`);
141
+ }
142
+ }
143
+
72
144
  function calculateFirestoreBytes(value) {
73
145
  if (value === null) return 1;
74
146
  if (value === undefined) return 0;
@@ -94,7 +166,16 @@ async function prepareAutoShardedWrites(result, docRef, logger) {
94
166
  let currentChunkSize = 0;
95
167
  let shardIndex = 0;
96
168
 
97
- if ((totalSize + docPathSize) < CHUNK_LIMIT) { const data = { ...result, _completed: true, _sharded: false }; return [{ ref: docRef, data, options: { merge: true } }]; }
169
+ // [UPDATE] Add _lastUpdated to non-sharded writes
170
+ if ((totalSize + docPathSize) < CHUNK_LIMIT) {
171
+ const data = {
172
+ ...result,
173
+ _completed: true,
174
+ _sharded: false,
175
+ _lastUpdated: new Date().toISOString()
176
+ };
177
+ return [{ ref: docRef, data, options: { merge: true } }];
178
+ }
98
179
 
99
180
  for (const [key, value] of Object.entries(result)) {
100
181
  if (key.startsWith('_')) continue;
@@ -114,7 +195,12 @@ async function prepareAutoShardedWrites(result, docRef, logger) {
114
195
 
115
196
  if (Object.keys(currentChunk).length > 0) { writes.push({ ref: shardCollection.doc(`shard_${shardIndex}`), data: currentChunk, options: { merge: false } }); }
116
197
 
117
- const pointerData = { _completed: true, _sharded: true, _shardCount: shardIndex + 1, _lastUpdated: new Date().toISOString() };
198
+ const pointerData = {
199
+ _completed: true,
200
+ _sharded: true,
201
+ _shardCount: shardIndex + 1,
202
+ _lastUpdated: new Date().toISOString()
203
+ };
118
204
  writes.push({ ref: docRef, data: pointerData, options: { merge: false } });
119
205
  return writes;
120
206
  }
@@ -1,28 +1,54 @@
1
1
  /**
2
2
  * @fileoverview Manages computation status tracking in Firestore.
3
+ * UPDATED: Supports Schema V2 (Object with Category) for smart migrations.
3
4
  */
4
5
 
5
6
  async function fetchComputationStatus(dateStr, config, { db }) {
6
- // FIX: Check dateStr directly, or define 'key' before checking it.
7
7
  if (!dateStr) throw new Error('fetchStatus requires a key');
8
8
 
9
- const key = dateStr;
10
9
  const collection = config.computationStatusCollection || 'computation_status';
11
- const docRef = db.collection(collection).doc(key);
10
+ const docRef = db.collection(collection).doc(dateStr);
12
11
 
13
12
  const snap = await docRef.get();
14
- return snap.exists ? snap.data() : {};
13
+ if (!snap.exists) return {};
14
+
15
+ const rawData = snap.data();
16
+ const normalized = {};
17
+
18
+ // Normalize V1 (String) to V2 (Object)
19
+ for (const [name, value] of Object.entries(rawData)) {
20
+ if (typeof value === 'string') {
21
+ normalized[name] = { hash: value, category: null }; // Legacy entry
22
+ } else {
23
+ normalized[name] = value; // V2 entry { hash, category }
24
+ }
25
+ }
26
+
27
+ return normalized;
15
28
  }
16
29
 
17
30
  async function updateComputationStatus(dateStr, updates, config, { db }) {
18
31
  if (!dateStr) throw new Error('updateStatus requires a key');
19
-
20
32
  if (!updates || Object.keys(updates).length === 0) return;
21
33
 
22
34
  const collection = config.computationStatusCollection || 'computation_status';
23
35
  const docRef = db.collection(collection).doc(dateStr);
24
36
 
25
- await docRef.set(updates, { merge: true });
37
+ // We expect updates to be an object: { "CalcName": { hash: "...", category: "..." } }
38
+ // But result committer might still pass strings if we don't update it.
39
+ // We will enforce the structure here just in case.
40
+
41
+ const safeUpdates = {};
42
+ for (const [key, val] of Object.entries(updates)) {
43
+ if (typeof val === 'string') {
44
+ // Fallback if caller wasn't updated (shouldn't happen with full patch)
45
+ safeUpdates[key] = { hash: val, category: 'unknown', lastUpdated: new Date() };
46
+ } else {
47
+ safeUpdates[key] = { ...val, lastUpdated: new Date() };
48
+ }
49
+ }
50
+
51
+ await docRef.set(safeUpdates, { merge: true });
26
52
  return true;
27
53
  }
28
54
 
@@ -0,0 +1,2 @@
1
+ // Change this string to force a global re-computation
2
+ module.exports = "v1.0-epoch-1";
@@ -267,5 +267,5 @@ module.exports = {
267
267
  streamHistoryData,
268
268
  getPriceShardRefs,
269
269
  ensurePriceShardIndex,
270
- getRelevantShardRefs // Export new function
270
+ getRelevantShardRefs
271
271
  };
@@ -166,21 +166,21 @@ async function getEarliestDataDates(config, deps) {
166
166
  };
167
167
 
168
168
  const earliestPortfolioDate = getMinDate(investorDate, speculatorDate);
169
- const earliestHistoryDate = getMinDate(investorHistoryDate, speculatorHistoryDate);
170
- const earliestInsightsDate = getMinDate(insightsDate);
171
- const earliestSocialDate = getMinDate(socialDate);
172
- const earliestPriceDate = getMinDate(priceDate);
173
- const absoluteEarliest = getMinDate(earliestPortfolioDate, earliestHistoryDate, earliestInsightsDate, earliestSocialDate, earliestPriceDate);
169
+ const earliestHistoryDate = getMinDate(investorHistoryDate, speculatorHistoryDate);
170
+ const earliestInsightsDate = getMinDate(insightsDate);
171
+ const earliestSocialDate = getMinDate(socialDate);
172
+ const earliestPriceDate = getMinDate(priceDate);
173
+ const absoluteEarliest = getMinDate(earliestPortfolioDate, earliestHistoryDate, earliestInsightsDate, earliestSocialDate, earliestPriceDate);
174
174
 
175
175
  const fallbackDate = new Date(config.earliestComputationDate + 'T00:00:00Z' || '2023-01-01T00:00:00Z');
176
176
 
177
177
  return {
178
- portfolio: earliestPortfolioDate || new Date('2999-12-31T00:00:00Z'),
179
- history: earliestHistoryDate || new Date('2999-12-31T00:00:00Z'),
180
- insights: earliestInsightsDate || new Date('2999-12-31T00:00:00Z'),
181
- social: earliestSocialDate || new Date('2999-12-31T00:00:00Z'),
182
- price: earliestPriceDate || new Date('2999-12-31T00:00:00Z'),
183
- absoluteEarliest: absoluteEarliest || fallbackDate
178
+ portfolio: earliestPortfolioDate || new Date('2999-12-31T00:00:00Z'),
179
+ history: earliestHistoryDate || new Date('2999-12-31T00:00:00Z'),
180
+ insights: earliestInsightsDate || new Date('2999-12-31T00:00:00Z'),
181
+ social: earliestSocialDate || new Date('2999-12-31T00:00:00Z'),
182
+ price: earliestPriceDate || new Date('2999-12-31T00:00:00Z'),
183
+ absoluteEarliest: absoluteEarliest || fallbackDate
184
184
  };
185
185
  }
186
186
 
@@ -211,5 +211,5 @@ module.exports = {
211
211
  getExpectedDateStrings,
212
212
  getEarliestDataDates,
213
213
  generateCodeHash,
214
- withRetry // Exported here
214
+ withRetry
215
215
  };
package/index.js CHANGED
@@ -3,82 +3,109 @@
3
3
  * Export the pipes!
4
4
  */
5
5
 
6
- // Import the PubSub Module
7
- const pubsubModule = require('./functions/core/utils/pubsub_utils');
6
+ // Core utilities
7
+ const pubsubUtils = require('./functions/core/utils/pubsub_utils');
8
+ const { IntelligentHeaderManager } = require('./functions/core/utils/intelligent_header_manager');
9
+ const { IntelligentProxyManager } = require('./functions/core/utils/intelligent_proxy_manager');
10
+ const { FirestoreBatchManager } = require('./functions/task-engine/utils/firestore_batch_manager');
11
+ const firestoreUtils = require('./functions/core/utils/firestore_utils');
12
+
13
+ // Orchestrator
14
+ const { runDiscoveryOrchestrator, runUpdateOrchestrator } = require('./functions/orchestrator/index');
15
+ const { checkDiscoveryNeed, getDiscoveryCandidates, dispatchDiscovery } = require('./functions/orchestrator/helpers/discovery_helpers');
16
+ const { getUpdateTargets, dispatchUpdates } = require('./functions/orchestrator/helpers/update_helpers');
17
+
18
+ // Dispatcher
19
+ const { handleRequest: dispatchRequest } = require('./functions/dispatcher/index');
20
+ const { dispatchTasksInBatches } = require('./functions/dispatcher/helpers/dispatch_helpers');
21
+
22
+ // Task Engine
23
+ const { handleRequest: taskRequest } = require('./functions/task-engine/handler_creator');
24
+ const { handleDiscover } = require('./functions/task-engine/helpers/discover_helpers');
25
+ const { handleVerify } = require('./functions/task-engine/helpers/verify_helpers');
26
+ const { handleUpdate } = require('./functions/task-engine/helpers/update_helpers');
27
+
28
+ // Computation System
29
+ const { build: buildManifest } = require('./functions/computation-system/context/ManifestBuilder');
30
+ const { runDateComputation: runComputationPass } = require('./functions/computation-system/WorkflowOrchestrator');
31
+ const { dispatchComputationPass } = require('./functions/computation-system/helpers/computation_dispatcher');
32
+ const { handleComputationTask } = require('./functions/computation-system/helpers/computation_worker');
33
+ const dataLoader = require('./functions/computation-system/utils/data_loader');
34
+ const computationUtils = require('./functions/computation-system/utils/utils');
35
+
36
+ // API
37
+ const { createApiApp } = require('./functions/generic-api/index');
38
+ const apiHelpers = require('./functions/generic-api/helpers/api_helpers');
39
+
40
+ // Maintenance
41
+ const { runCleanup } = require('./functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers');
42
+ const { handleInvalidSpeculator } = require('./functions/invalid-speculator-handler/helpers/handler_helpers');
43
+ const { fetchAndStoreInsights } = require('./functions/fetch-insights/helpers/handler_helpers');
44
+ const { fetchAndStorePrices } = require('./functions/etoro-price-fetcher/helpers/handler_helpers');
45
+ const { runSocialOrchestrator } = require('./functions/social-orchestrator/helpers/orchestrator_helpers');
46
+ const { handleSocialTask } = require('./functions/social-task-handler/helpers/handler_helpers');
47
+ const { runBackfillAssetPrices } = require('./functions/price-backfill/helpers/handler_helpers');
48
+
49
+ // Proxy
50
+ const { handlePost } = require('./functions/appscript-api/index');
8
51
 
9
- // Core
10
52
  const core = {
11
- IntelligentHeaderManager: require('./functions/core/utils/intelligent_header_manager').IntelligentHeaderManager,
12
- IntelligentProxyManager: require('./functions/core/utils/intelligent_proxy_manager').IntelligentProxyManager,
13
- FirestoreBatchManager: require('./functions/task-engine/utils/firestore_batch_manager').FirestoreBatchManager,
14
- firestoreUtils: require('./functions/core/utils/firestore_utils'),
15
-
16
- // EXPORT FIX:
17
- pubsubUtils: pubsubModule, // Keeps stateless function access
18
- PubSubUtils: pubsubModule.PubSubUtils // Exposes the Class for 'new pipe.core.PubSubUtils()'
53
+ IntelligentHeaderManager,
54
+ IntelligentProxyManager,
55
+ FirestoreBatchManager,
56
+ firestoreUtils,
57
+ pubsubUtils,
58
+ PubSubUtils: pubsubUtils.PubSubUtils,
19
59
  };
20
60
 
21
- // Orchestrator
22
61
  const orchestrator = {
23
- runDiscoveryOrchestrator: require('./functions/orchestrator/index').runDiscoveryOrchestrator,
24
- runUpdateOrchestrator: require('./functions/orchestrator/index').runUpdateOrchestrator,
25
- checkDiscoveryNeed: require('./functions/orchestrator/helpers/discovery_helpers').checkDiscoveryNeed,
26
- getDiscoveryCandidates: require('./functions/orchestrator/helpers/discovery_helpers').getDiscoveryCandidates,
27
- dispatchDiscovery: require('./functions/orchestrator/helpers/discovery_helpers').dispatchDiscovery,
28
- getUpdateTargets: require('./functions/orchestrator/helpers/update_helpers').getUpdateTargets,
29
- dispatchUpdates: require('./functions/orchestrator/helpers/update_helpers').dispatchUpdates
62
+ runDiscoveryOrchestrator,
63
+ runUpdateOrchestrator,
64
+ checkDiscoveryNeed,
65
+ getDiscoveryCandidates,
66
+ dispatchDiscovery,
67
+ getUpdateTargets,
68
+ dispatchUpdates,
30
69
  };
31
70
 
32
- // Dispatcher
33
71
  const dispatcher = {
34
- handleRequest: require('./functions/dispatcher/index').handleRequest,
35
- dispatchTasksInBatches: require('./functions/dispatcher/helpers/dispatch_helpers').dispatchTasksInBatches
72
+ handleRequest: dispatchRequest,
73
+ dispatchTasksInBatches,
36
74
  };
37
75
 
38
- // Task Engine
39
76
  const taskEngine = {
40
- handleRequest: require('./functions/task-engine/handler_creator').handleRequest,
41
- handleDiscover: require('./functions/task-engine/helpers/discover_helpers').handleDiscover,
42
- handleVerify: require('./functions/task-engine/helpers/verify_helpers').handleVerify,
43
- handleUpdate: require('./functions/task-engine/helpers/update_helpers').handleUpdate
77
+ handleRequest: taskRequest,
78
+ handleDiscover,
79
+ handleVerify,
80
+ handleUpdate,
44
81
  };
45
82
 
46
- // --- UPDATED IMPORT: Point to the new Context Domain ---
47
- const { build: buildManifestFunc } = require('./functions/computation-system/context/ManifestBuilder');
48
-
49
- // Computation System
50
83
  const computationSystem = {
51
- // UPDATED: Point to the new Workflow Orchestrator
52
- runComputationPass: require('./functions/computation-system/WorkflowOrchestrator').runComputationPass,
53
-
54
- // These helpers wrap the Orchestrator, so they stay, but we updated their internals (see below)
55
- dispatchComputationPass: require('./functions/computation-system/helpers/computation_dispatcher').dispatchComputationPass,
56
- handleComputationTask: require('./functions/computation-system/helpers/computation_worker').handleComputationTask,
57
-
58
- // Utils
59
- dataLoader: require('./functions/computation-system/utils/data_loader'),
60
- computationUtils: require('./functions/computation-system/utils/utils'),
61
- buildManifest: buildManifestFunc
84
+ runComputationPass,
85
+ dispatchComputationPass,
86
+ handleComputationTask,
87
+ dataLoader,
88
+ computationUtils,
89
+ buildManifest,
62
90
  };
63
91
 
64
- // API
65
92
  const api = {
66
- createApiApp: require('./functions/generic-api/index').createApiApp,
67
- helpers: require('./functions/generic-api/helpers/api_helpers')
93
+ createApiApp,
94
+ helpers: apiHelpers,
68
95
  };
69
96
 
70
- // Maintenance
71
97
  const maintenance = {
72
- runSpeculatorCleanup: require('./functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers').runCleanup,
73
- handleInvalidSpeculator: require('./functions/invalid-speculator-handler/helpers/handler_helpers').handleInvalidSpeculator,
74
- runFetchInsights: require('./functions/fetch-insights/helpers/handler_helpers').fetchAndStoreInsights,
75
- runFetchPrices: require('./functions/etoro-price-fetcher/helpers/handler_helpers').fetchAndStorePrices,
76
- runSocialOrchestrator: require('./functions/social-orchestrator/helpers/orchestrator_helpers').runSocialOrchestrator,
77
- handleSocialTask: require('./functions/social-task-handler/helpers/handler_helpers').handleSocialTask,
78
- runBackfillAssetPrices: require('./functions/price-backfill/helpers/handler_helpers').runBackfillAssetPrices
98
+ runSpeculatorCleanup: runCleanup,
99
+ handleInvalidSpeculator,
100
+ runFetchInsights: fetchAndStoreInsights,
101
+ runFetchPrices: fetchAndStorePrices,
102
+ runSocialOrchestrator,
103
+ handleSocialTask,
104
+ runBackfillAssetPrices,
79
105
  };
80
106
 
81
- // Proxy
82
- const proxy = { handlePost: require('./functions/appscript-api/index').handlePost };
107
+ const proxy = { handlePost };
83
108
 
84
- module.exports = { pipe: { core, orchestrator, dispatcher, taskEngine, computationSystem, api, maintenance, proxy } };
109
+ module.exports = {
110
+ pipe: { core, orchestrator, dispatcher, taskEngine, computationSystem, api, maintenance, proxy },
111
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.234",
3
+ "version": "1.0.236",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [