bulltrackers-module 1.0.104 → 1.0.106
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.
- package/README.MD +222 -222
- package/functions/appscript-api/helpers/errors.js +19 -19
- package/functions/appscript-api/index.js +58 -58
- package/functions/computation-system/helpers/orchestration_helpers.js +647 -113
- package/functions/computation-system/utils/data_loader.js +191 -191
- package/functions/computation-system/utils/utils.js +149 -254
- package/functions/core/utils/firestore_utils.js +433 -433
- package/functions/core/utils/pubsub_utils.js +53 -53
- package/functions/dispatcher/helpers/dispatch_helpers.js +47 -47
- package/functions/dispatcher/index.js +52 -52
- package/functions/etoro-price-fetcher/helpers/handler_helpers.js +124 -124
- package/functions/fetch-insights/helpers/handler_helpers.js +91 -91
- package/functions/generic-api/helpers/api_helpers.js +379 -379
- package/functions/generic-api/index.js +150 -150
- package/functions/invalid-speculator-handler/helpers/handler_helpers.js +75 -75
- package/functions/orchestrator/helpers/discovery_helpers.js +226 -226
- package/functions/orchestrator/helpers/update_helpers.js +92 -92
- package/functions/orchestrator/index.js +147 -147
- package/functions/price-backfill/helpers/handler_helpers.js +116 -123
- package/functions/social-orchestrator/helpers/orchestrator_helpers.js +61 -61
- package/functions/social-task-handler/helpers/handler_helpers.js +288 -288
- package/functions/task-engine/handler_creator.js +78 -78
- package/functions/task-engine/helpers/discover_helpers.js +125 -125
- package/functions/task-engine/helpers/update_helpers.js +118 -118
- package/functions/task-engine/helpers/verify_helpers.js +162 -162
- package/functions/task-engine/utils/firestore_batch_manager.js +258 -258
- package/index.js +105 -113
- package/package.json +45 -45
- package/functions/computation-system/computation_dependencies.json +0 -120
- package/functions/computation-system/helpers/worker_helpers.js +0 -340
- package/functions/computation-system/utils/computation_state_manager.js +0 -178
- package/functions/computation-system/utils/dependency_graph.js +0 -191
- package/functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers.js +0 -160
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Builds a true Directed Acyclic Graph (DAG) for computations.
|
|
3
|
-
*
|
|
4
|
-
* This refactored utility ingests calculation classes and a dependency config.
|
|
5
|
-
* It builds an adjacency list, performs a topological sort (Kahn's algorithm)
|
|
6
|
-
* to detect cycles, and provides the graph structure to the orchestrator and worker.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Represents a single calculation node in the graph.
|
|
11
|
-
*/
|
|
12
|
-
class ComputationNode {
|
|
13
|
-
constructor(category, name, calcClass) {
|
|
14
|
-
this.category = category;
|
|
15
|
-
this.name = name;
|
|
16
|
-
this.calcClass = calcClass;
|
|
17
|
-
this.children = new Set(); // Nodes that depend on this one
|
|
18
|
-
this.parents = new Set(); // Nodes this one depends on
|
|
19
|
-
this.requires = []; // <-- ADDED: Stores data requirements
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Builds and validates the computation DAG.
|
|
25
|
-
*/
|
|
26
|
-
class ComputationGraph {
|
|
27
|
-
/**
|
|
28
|
-
* @param {object} unifiedCalculations - The calculations object from 'aiden-shared-calculations-unified'.
|
|
29
|
-
* @param {object} dependencyConfig - The JSON config object with dependency definitions.
|
|
30
|
-
* @param {object} [logger=console] - A logger instance.
|
|
31
|
-
*/
|
|
32
|
-
constructor(unifiedCalculations, dependencyConfig, logger = console) {
|
|
33
|
-
this.logger = logger;
|
|
34
|
-
this.nodes = new Map(); // Map<string, ComputationNode>
|
|
35
|
-
this.sortedNodes = []; // Array of node names, in valid execution order
|
|
36
|
-
this.isBuilt = false;
|
|
37
|
-
|
|
38
|
-
// --- MODIFIED: Pass requirements config to _registerNodes ---
|
|
39
|
-
this._registerNodes(unifiedCalculations, dependencyConfig.requirements || {});
|
|
40
|
-
this._buildAdjacencyLists(dependencyConfig.dependencies || {});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Pass 1: Register all calculations as nodes in the graph.
|
|
45
|
-
* --- MODIFIED: Added requirementsConfig parameter ---
|
|
46
|
-
*/
|
|
47
|
-
_registerNodes(unifiedCalculations, requirementsConfig) {
|
|
48
|
-
this.logger.log('INFO', '[DepGraph] Pass 1: Registering all calculation nodes...');
|
|
49
|
-
for (const category in unifiedCalculations) {
|
|
50
|
-
if (category === 'utils') continue;
|
|
51
|
-
|
|
52
|
-
for (const subKey in unifiedCalculations[category]) {
|
|
53
|
-
const item = unifiedCalculations[category][subKey];
|
|
54
|
-
|
|
55
|
-
// Handle nested directories (e.g., 'historical', 'meta', 'backtests')
|
|
56
|
-
if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
|
|
57
|
-
for (const calcName in item) {
|
|
58
|
-
if (typeof item[calcName] === 'function') {
|
|
59
|
-
// --- MODIFIED: Create node, set requirements ---
|
|
60
|
-
const node = new ComputationNode(category, calcName, item[calcName]);
|
|
61
|
-
node.requires = requirementsConfig[calcName] || [];
|
|
62
|
-
this.nodes.set(calcName, node);
|
|
63
|
-
// --- END MODIFICATION ---
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// Handle regular calcs at the root of the category
|
|
68
|
-
else if (typeof item === 'function') {
|
|
69
|
-
const calcName = subKey;
|
|
70
|
-
// --- MODIFIED: Create node, set requirements ---
|
|
71
|
-
const node = new ComputationNode(category, calcName, item[calcName]);
|
|
72
|
-
node.requires = requirementsConfig[calcName] || [];
|
|
73
|
-
this.nodes.set(calcName, node);
|
|
74
|
-
// --- END MODIFICATION ---
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
this.logger.log('INFO', `[DepGraph] Pass 1: Registered ${this.nodes.size} total nodes.`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Pass 2: Build parent/child relationships from the dependency config.
|
|
83
|
-
* (This function remains unchanged as it only reads the 'dependencies' key)
|
|
84
|
-
*/
|
|
85
|
-
_buildAdjacencyLists(dependencies) {
|
|
86
|
-
this.logger.log('INFO', '[DepGraph] Pass 2: Building adjacency lists...');
|
|
87
|
-
for (const nodeName in dependencies) {
|
|
88
|
-
const parentNames = dependencies[nodeName];
|
|
89
|
-
const childNode = this.nodes.get(nodeName);
|
|
90
|
-
|
|
91
|
-
if (!childNode) {
|
|
92
|
-
this.logger.log('WARN', `[DepGraph] Dependency config lists node "${nodeName}", but it was not found in registered calculations. Skipping.`);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
for (const parentName of parentNames) {
|
|
97
|
-
const parentNode = this.nodes.get(parentName);
|
|
98
|
-
if (!parentNode) {
|
|
99
|
-
this.logger.log('WARN', `[DepGraph] Node "${nodeName}" lists dependency "${parentName}", but it was not found. Skipping dependency.`);
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Create the links
|
|
104
|
-
childNode.parents.add(parentName);
|
|
105
|
-
parentNode.children.add(nodeName);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
this.logger.log('INFO', '[DepGraph] Pass 2: Adjacency lists built.');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Pass 3: Perform Topological Sort (Kahn's Algorithm) to find execution order
|
|
113
|
-
* and detect cycles. This is the "build" step.
|
|
114
|
-
* (This function remains unchanged)
|
|
115
|
-
*/
|
|
116
|
-
build() {
|
|
117
|
-
if (this.isBuilt) return;
|
|
118
|
-
this.logger.log('INFO', '[DepGraph] Pass 3: Running topological sort (Kahn\'s Algorithm)...');
|
|
119
|
-
|
|
120
|
-
const inDegree = new Map(); // Map<string, number>
|
|
121
|
-
const queue = []; // Queue of node names
|
|
122
|
-
|
|
123
|
-
// Initialize in-degree (parent count) for all nodes
|
|
124
|
-
for (const [name, node] of this.nodes.entries()) {
|
|
125
|
-
inDegree.set(name, node.parents.size);
|
|
126
|
-
if (node.parents.size === 0) {
|
|
127
|
-
queue.push(name);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
while (queue.length > 0) {
|
|
132
|
-
const nodeName = queue.shift();
|
|
133
|
-
this.sortedNodes.push(nodeName);
|
|
134
|
-
|
|
135
|
-
const node = this.nodes.get(nodeName);
|
|
136
|
-
if (!node) continue;
|
|
137
|
-
|
|
138
|
-
for (const childName of node.children) {
|
|
139
|
-
const newDegree = inDegree.get(childName) - 1;
|
|
140
|
-
inDegree.set(childName, newDegree);
|
|
141
|
-
if (newDegree === 0) {
|
|
142
|
-
queue.push(childName);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Check for cycles
|
|
148
|
-
if (this.sortedNodes.length < this.nodes.size) {
|
|
149
|
-
const missing = [...this.nodes.keys()].filter(n => !this.sortedNodes.includes(n));
|
|
150
|
-
this.logger.log('ERROR', `[DepGraph] CRITICAL: Cycle detected! ${missing.length} nodes could not be sorted.`, {
|
|
151
|
-
unprocessedNodes: missing
|
|
152
|
-
});
|
|
153
|
-
throw new Error(`Cycle detected in computation graph. Unprocessed nodes: ${missing.join(', ')}`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
this.logger.log('SUCCESS', `[DepGraph] Pass 3: Topological sort complete. Valid DAG confirmed.`);
|
|
157
|
-
this.isBuilt = true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Returns all nodes that have no dependencies (graph roots).
|
|
162
|
-
* @returns {ComputationNode[]}
|
|
163
|
-
*/
|
|
164
|
-
getRootNodes() {
|
|
165
|
-
if (!this.isBuilt) this.build();
|
|
166
|
-
return this.sortedNodes
|
|
167
|
-
.map(name => this.nodes.get(name))
|
|
168
|
-
.filter(node => node.parents.size === 0);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Returns a node by its name.
|
|
173
|
-
* @param {string} nodeName
|
|
174
|
-
* @returns {ComputationNode | undefined}
|
|
175
|
-
*/
|
|
176
|
-
getNode(nodeName) {
|
|
177
|
-
return this.nodes.get(nodeName);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Returns all registered node objects.
|
|
182
|
-
* @returns {ComputationNode[]}
|
|
183
|
-
*/
|
|
184
|
-
getAllNodes() {
|
|
185
|
-
return Array.from(this.nodes.values());
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
module.exports = {
|
|
190
|
-
ComputationGraph
|
|
191
|
-
};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Main pipe: pipe.maintenance.runSpeculatorCleanup
|
|
3
|
-
* REFACTORED: Now stateless and receives dependencies.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { FieldValue } = require('@google-cloud/firestore');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Main pipe: pipe.maintenance.runSpeculatorCleanup
|
|
10
|
-
* Orchestrates the cleanup process.
|
|
11
|
-
* @param {object} config - Configuration object.
|
|
12
|
-
* @param {object} dependencies - Contains db, logger.
|
|
13
|
-
*/
|
|
14
|
-
exports.runCleanup = async (config, dependencies) => {
|
|
15
|
-
const { logger } = dependencies;
|
|
16
|
-
logger.log('INFO', '[CleanupHelpers] Running cleanup orchestrator...');
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
// Start cleanup for pending users
|
|
20
|
-
const { batch: batchAfterPending, count: pendingRemoved } = await cleanupPendingSpeculators(config, dependencies);
|
|
21
|
-
|
|
22
|
-
// Continue with the same batch for stale speculators
|
|
23
|
-
const { batch: finalBatch, count: staleRemoved } = await cleanupStaleSpeculators(config, dependencies, batchAfterPending);
|
|
24
|
-
|
|
25
|
-
if (pendingRemoved > 0 || staleRemoved > 0) {
|
|
26
|
-
await finalBatch.commit();
|
|
27
|
-
logger.log('SUCCESS', `[CleanupHelpers] Cleanup commit successful. Removed ${pendingRemoved} pending, ${staleRemoved} stale speculators.`);
|
|
28
|
-
return { pendingRemoved, staleRemoved };
|
|
29
|
-
} else {
|
|
30
|
-
logger.log('SUCCESS', '[CleanupHelpers] No stale users found in pending or blocks.');
|
|
31
|
-
return { pendingRemoved: 0, staleRemoved: 0 };
|
|
32
|
-
}
|
|
33
|
-
} catch (error) {
|
|
34
|
-
logger.log('ERROR', '[CleanupHelpers] FATAL error during cleanup orchestration', { errorMessage: error.message, errorStack: error.stack });
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Internal sub-pipe for cleaning pending speculators.
|
|
41
|
-
*/
|
|
42
|
-
async function cleanupPendingSpeculators(config, dependencies) {
|
|
43
|
-
const { db, logger } = dependencies;
|
|
44
|
-
logger.log('INFO', '[CleanupHelpers] Starting pending speculator cleanup...');
|
|
45
|
-
|
|
46
|
-
const batch = db.batch(); // Use db from dependencies
|
|
47
|
-
let stalePendingUsersRemoved = 0;
|
|
48
|
-
const pendingCollectionRef = db.collection(config.pendingSpeculatorsCollectionName); // Use db
|
|
49
|
-
const staleThreshold = new Date();
|
|
50
|
-
staleThreshold.setHours(staleThreshold.getHours() - config.pendingGracePeriodHours);
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const pendingSnapshot = await pendingCollectionRef.get();
|
|
54
|
-
if (pendingSnapshot.empty) {
|
|
55
|
-
logger.log('INFO', '[CleanupHelpers] Pending speculators collection is empty.');
|
|
56
|
-
return { batch, count: 0 };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (const doc of pendingSnapshot.docs) {
|
|
60
|
-
const pendingData = doc.data().users || {};
|
|
61
|
-
const updates = {};
|
|
62
|
-
let updatesInDoc = 0;
|
|
63
|
-
|
|
64
|
-
for (const userId in pendingData) {
|
|
65
|
-
const timestamp = pendingData[userId]?.toDate ? pendingData[userId].toDate() : null;
|
|
66
|
-
if (timestamp && timestamp < staleThreshold) {
|
|
67
|
-
updates[`users.${userId}`] = FieldValue.delete();
|
|
68
|
-
stalePendingUsersRemoved++;
|
|
69
|
-
updatesInDoc++;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (updatesInDoc > 0) {
|
|
74
|
-
logger.log('TRACE', `[CleanupHelpers] Marking ${updatesInDoc} users for removal from pending doc ${doc.id}`);
|
|
75
|
-
batch.update(doc.ref, updates);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
logger.log('INFO', `[CleanupHelpers] Marked ${stalePendingUsersRemoved} total stale pending users for removal.`);
|
|
79
|
-
} catch (error) {
|
|
80
|
-
logger.log('ERROR', '[CleanupHelpers] Error cleaning pending speculators', { errorMessage: error.message });
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
return { batch, count: stalePendingUsersRemoved };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Internal sub-pipe for cleaning stale speculators.
|
|
88
|
-
*/
|
|
89
|
-
async function cleanupStaleSpeculators(config, dependencies, batch) {
|
|
90
|
-
const { db, logger } = dependencies;
|
|
91
|
-
logger.log('INFO', '[CleanupHelpers] Starting stale speculator cleanup from blocks...');
|
|
92
|
-
let totalUsersRemoved = 0;
|
|
93
|
-
const blocksCollectionRef = db.collection(config.speculatorBlocksCollectionName); // Use db
|
|
94
|
-
const gracePeriodDate = new Date();
|
|
95
|
-
gracePeriodDate.setDate(gracePeriodDate.getDate() - config.activityGracePeriodDays);
|
|
96
|
-
const blockCountsUpdate = {};
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const blocksSnapshot = await blocksCollectionRef.get();
|
|
100
|
-
if (blocksSnapshot.empty) {
|
|
101
|
-
logger.log('INFO', '[CleanupHelpers] Speculator blocks collection is empty.');
|
|
102
|
-
return { batch, count: 0 };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
for (const doc of blocksSnapshot.docs) {
|
|
106
|
-
const blockId = doc.id;
|
|
107
|
-
const blockData = doc.data();
|
|
108
|
-
const users = blockData.users || {};
|
|
109
|
-
let usersRemovedFromBlock = 0;
|
|
110
|
-
const updates = {};
|
|
111
|
-
|
|
112
|
-
for (const userKey in users) {
|
|
113
|
-
const userId = userKey.split('.')[1];
|
|
114
|
-
if (!userId) continue;
|
|
115
|
-
|
|
116
|
-
const userData = users[userKey];
|
|
117
|
-
const lastHeldTimestamp = userData.lastHeldSpeculatorAsset?.toDate ? userData.lastHeldSpeculatorAsset.toDate() : null;
|
|
118
|
-
|
|
119
|
-
if (lastHeldTimestamp && lastHeldTimestamp < gracePeriodDate) {
|
|
120
|
-
updates[userKey] = FieldValue.delete();
|
|
121
|
-
usersRemovedFromBlock++;
|
|
122
|
-
|
|
123
|
-
if (userData.instruments && Array.isArray(userData.instruments)) {
|
|
124
|
-
userData.instruments.forEach(instrumentId => {
|
|
125
|
-
const instrumentBlockKey = `${instrumentId}_${blockId}`;
|
|
126
|
-
if (!blockCountsUpdate[instrumentBlockKey]) {
|
|
127
|
-
blockCountsUpdate[instrumentBlockKey] = 0;
|
|
128
|
-
}
|
|
129
|
-
blockCountsUpdate[instrumentBlockKey]--;
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (usersRemovedFromBlock > 0) {
|
|
136
|
-
logger.log('TRACE', `[CleanupHelpers] Marking ${usersRemovedFromBlock} users for removal from block ${blockId}.`);
|
|
137
|
-
batch.update(doc.ref, updates);
|
|
138
|
-
totalUsersRemoved += usersRemovedFromBlock;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (totalUsersRemoved > 0 && Object.keys(blockCountsUpdate).length > 0) {
|
|
143
|
-
const countsRef = db.doc(config.speculatorBlockCountsDocPath); // Use db
|
|
144
|
-
const finalCountUpdates = {};
|
|
145
|
-
for (const key in blockCountsUpdate) {
|
|
146
|
-
finalCountUpdates[`counts.${key}`] = FieldValue.increment(blockCountsUpdate[key]);
|
|
147
|
-
}
|
|
148
|
-
logger.log('TRACE', '[CleanupHelpers] Staging block count decrements.', { updates: finalCountUpdates });
|
|
149
|
-
batch.set(countsRef, finalCountUpdates, { merge: true });
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
logger.log('INFO', `[CleanupHelpers] Marked ${totalUsersRemoved} total stale speculators for removal from blocks.`);
|
|
153
|
-
|
|
154
|
-
} catch (error) {
|
|
155
|
-
logger.log('ERROR', '[CleanupHelpers] Error cleaning stale speculators', { errorMessage: error.message });
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return { batch, count: totalUsersRemoved };
|
|
160
|
-
}
|