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,255 +1,150 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Computation system sub-pipes and utils.
|
|
3
|
-
* REFACTORED: Now stateless and receive dependencies where needed.
|
|
4
|
-
* DYNAMIC: Categorization logic is
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { FieldValue, FieldPath } = require('@google-cloud/firestore');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* (Stateless, as taskFunction will receive dependencies)
|
|
151
|
-
*/
|
|
152
|
-
async function processJobsInParallel(jobs, taskFunction, passName, config) {
|
|
153
|
-
// This function itself doesn't need dependencies,
|
|
154
|
-
// but the 'taskFunction' it calls *will* receive them from its caller.
|
|
155
|
-
const { logger } = require("sharedsetup")(__filename); // Use local logger for this static util
|
|
156
|
-
const results = [];
|
|
157
|
-
const maxConcurrentDates = config.maxConcurrentDates || 3;
|
|
158
|
-
if (jobs.length > 0) {
|
|
159
|
-
logger.log('INFO', `[${passName}] Processing ${jobs.length} jobs with ${maxConcurrentDates} parallel workers.`);
|
|
160
|
-
jobs.sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
161
|
-
for (let i = 0; i < jobs.length; i += maxConcurrentDates) {
|
|
162
|
-
const jobBatch = jobs.slice(i, i + maxConcurrentDates);
|
|
163
|
-
const promises = jobBatch.map(job => taskFunction(new Date(job.date + 'T00:00:00Z'), job.missing));
|
|
164
|
-
results.push(...await Promise.allSettled(promises));
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
logger.log('INFO', `[${passName}] No jobs to process.`);
|
|
168
|
-
}
|
|
169
|
-
return results;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Internal helper: Finds the earliest date document in a collection.
|
|
174
|
-
*/
|
|
175
|
-
async function getFirstDateFromCollection(config, dependencies, collectionName) {
|
|
176
|
-
// --- MODIFIED: Get withRetry from dependencies ---
|
|
177
|
-
const { db, logger, calculationUtils } = dependencies;
|
|
178
|
-
const { withRetry } = calculationUtils;
|
|
179
|
-
// --- END MODIFIED ---
|
|
180
|
-
let earliestDate = null;
|
|
181
|
-
try {
|
|
182
|
-
const blockDocRefs = await withRetry(
|
|
183
|
-
() => db.collection(collectionName).listDocuments(), // Use db
|
|
184
|
-
`GetBlocks(${collectionName})`
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
if (blockDocRefs.length === 0) {
|
|
188
|
-
logger.log('WARN', `No block documents found in collection: ${collectionName}`);
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
for (const blockDocRef of blockDocRefs) {
|
|
193
|
-
const snapshotQuery = blockDocRef.collection(config.snapshotsSubcollection)
|
|
194
|
-
.where(FieldPath.documentId(), '>=', '2000-01-01')
|
|
195
|
-
.orderBy(FieldPath.documentId(), 'asc')
|
|
196
|
-
.limit(1);
|
|
197
|
-
|
|
198
|
-
const snapshotSnap = await withRetry(
|
|
199
|
-
() => snapshotQuery.get(),
|
|
200
|
-
`GetEarliestSnapshot(${blockDocRef.path})`
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
if (!snapshotSnap.empty && /^\d{4}-\d{2}-\d{2}$/.test(snapshotSnap.docs[0].id)) {
|
|
204
|
-
const foundDate = new Date(snapshotSnap.docs[0].id + 'T00:00:00Z');
|
|
205
|
-
if (!earliestDate || foundDate < earliestDate) {
|
|
206
|
-
earliestDate = foundDate;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
} catch (e) {
|
|
212
|
-
logger.log('ERROR', `GetFirstDate failed for ${collectionName}`, { errorMessage: e.message });
|
|
213
|
-
}
|
|
214
|
-
return earliestDate;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Sub-pipe: pipe.computationSystem.computationUtils.getFirstDateFromSourceData
|
|
219
|
-
* @param {object} config - The computation system configuration object.
|
|
220
|
-
* @param {object} dependencies - Contains db, logger, calculationUtils.
|
|
221
|
-
* @returns {Promise<Date>} The earliest date found or a default fallback date.
|
|
222
|
-
*/
|
|
223
|
-
async function getFirstDateFromSourceData(config, dependencies) {
|
|
224
|
-
const { logger } = dependencies;
|
|
225
|
-
logger.log('INFO', 'Querying for the earliest date from source portfolio data...');
|
|
226
|
-
|
|
227
|
-
// Pass dependencies to sub-pipe
|
|
228
|
-
const investorDate = await getFirstDateFromCollection(config, dependencies, config.normalUserPortfolioCollection);
|
|
229
|
-
const speculatorDate = await getFirstDateFromCollection(config, dependencies, config.speculatorPortfolioCollection);
|
|
230
|
-
|
|
231
|
-
let earliestDate;
|
|
232
|
-
if (investorDate && speculatorDate) {
|
|
233
|
-
earliestDate = investorDate < speculatorDate ? investorDate : speculatorDate;
|
|
234
|
-
} else {
|
|
235
|
-
earliestDate = investorDate || speculatorDate;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (earliestDate) {
|
|
239
|
-
logger.log('INFO', `Found earliest source data date: ${earliestDate.toISOString().slice(0, 10)}`);
|
|
240
|
-
return earliestDate;
|
|
241
|
-
} else {
|
|
242
|
-
const fallbackDate = new Date(config.earliestComputationDate + 'T00:00:00Z' || '2023-01-01T00:00:00Z');
|
|
243
|
-
logger.log('WARN', `No source data found. Defaulting first date to: ${fallbackDate.toISOString().slice(0, 10)}`);
|
|
244
|
-
return fallbackDate;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
module.exports = {
|
|
249
|
-
FieldValue, FieldPath,
|
|
250
|
-
// unifiedUtils: utils, // This is no longer defined here
|
|
251
|
-
categorizeCalculations, // EXPORT THE NEW FUNCTION
|
|
252
|
-
// withRetry, // This is no longer defined here
|
|
253
|
-
commitBatchInChunks,
|
|
254
|
-
getExpectedDateStrings, processJobsInParallel, getFirstDateFromSourceData,
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Computation system sub-pipes and utils.
|
|
3
|
+
* REFACTORED: Now stateless and receive dependencies where needed.
|
|
4
|
+
* DYNAMIC: Categorization logic is removed, replaced by manifest.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FieldValue, FieldPath } = require('@google-cloud/firestore');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes a calculation name to kebab-case.
|
|
11
|
+
* @param {string} name
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
function normalizeName(name) {
|
|
15
|
+
return name.replace(/_/g, '-');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sub-pipe: pipe.computationSystem.computationUtils.commitBatchInChunks
|
|
20
|
+
* @param {object} config - The computation system configuration object.
|
|
21
|
+
* @param {object} dependencies - Contains db, logger, calculationUtils.
|
|
22
|
+
* @param {Array<object>} writes - Array of { ref: DocumentReference, data: object }.
|
|
23
|
+
* @param {string} operationName - Name for logging.
|
|
24
|
+
*/
|
|
25
|
+
async function commitBatchInChunks(config, dependencies, writes, operationName) {
|
|
26
|
+
// --- MODIFIED: Get withRetry from dependencies ---
|
|
27
|
+
const { db, logger, calculationUtils } = dependencies;
|
|
28
|
+
const { withRetry } = calculationUtils;
|
|
29
|
+
// --- END MODIFIED ---
|
|
30
|
+
|
|
31
|
+
const batchSizeLimit = config.batchSizeLimit || 450;
|
|
32
|
+
|
|
33
|
+
if (writes.length === 0) {
|
|
34
|
+
logger.log('WARN', `[${operationName}] No writes to commit.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (let i = 0; i < writes.length; i += batchSizeLimit) {
|
|
38
|
+
const batch = db.batch(); // Use db
|
|
39
|
+
const chunk = writes.slice(i, i + batchSizeLimit);
|
|
40
|
+
chunk.forEach(write => batch.set(write.ref, write.data, { merge: true }));
|
|
41
|
+
|
|
42
|
+
const chunkNum = Math.floor(i / batchSizeLimit) + 1;
|
|
43
|
+
const totalChunks = Math.ceil(writes.length / batchSizeLimit);
|
|
44
|
+
await withRetry(
|
|
45
|
+
() => batch.commit(),
|
|
46
|
+
`${operationName} (Chunk ${chunkNum}/${totalChunks})`
|
|
47
|
+
);
|
|
48
|
+
logger.log('INFO', `[${operationName}] Committed chunk ${chunkNum}/${totalChunks} (${chunk.length} ops).`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sub-pipe: pipe.computationSystem.computationUtils.getExpectedDateStrings
|
|
54
|
+
* (Stateless)
|
|
55
|
+
*/
|
|
56
|
+
function getExpectedDateStrings(startDate, endDate) {
|
|
57
|
+
const dateStrings = [];
|
|
58
|
+
if (startDate <= endDate) {
|
|
59
|
+
const startUTC = new Date(Date.UTC(startDate.getUTCFullYear(), startDate.getUTCMonth(), startDate.getUTCDate()));
|
|
60
|
+
const endUTC = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate()));
|
|
61
|
+
for (let d = startUTC; d <= endUTC; d.setUTCDate(d.getUTCDate() + 1)) {
|
|
62
|
+
dateStrings.push(new Date(d).toISOString().slice(0, 10));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return dateStrings;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Internal helper: Finds the earliest date document in a collection.
|
|
70
|
+
*/
|
|
71
|
+
async function getFirstDateFromCollection(config, dependencies, collectionName) {
|
|
72
|
+
// --- MODIFIED: Get withRetry from dependencies ---
|
|
73
|
+
const { db, logger, calculationUtils } = dependencies;
|
|
74
|
+
const { withRetry } = calculationUtils;
|
|
75
|
+
// --- END MODIFIED ---
|
|
76
|
+
let earliestDate = null;
|
|
77
|
+
try {
|
|
78
|
+
const blockDocRefs = await withRetry(
|
|
79
|
+
() => db.collection(collectionName).listDocuments(), // Use db
|
|
80
|
+
`GetBlocks(${collectionName})`
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (blockDocRefs.length === 0) {
|
|
84
|
+
logger.log('WARN', `No block documents found in collection: ${collectionName}`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const blockDocRef of blockDocRefs) {
|
|
89
|
+
const snapshotQuery = blockDocRef.collection(config.snapshotsSubcollection)
|
|
90
|
+
.where(FieldPath.documentId(), '>=', '2000-01-01')
|
|
91
|
+
.orderBy(FieldPath.documentId(), 'asc')
|
|
92
|
+
.limit(1);
|
|
93
|
+
|
|
94
|
+
const snapshotSnap = await withRetry(
|
|
95
|
+
() => snapshotQuery.get(),
|
|
96
|
+
`GetEarliestSnapshot(${blockDocRef.path})`
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (!snapshotSnap.empty && /^\d{4}-\d{2}-\d{2}$/.test(snapshotSnap.docs[0].id)) {
|
|
100
|
+
const foundDate = new Date(snapshotSnap.docs[0].id + 'T00:00:00Z');
|
|
101
|
+
if (!earliestDate || foundDate < earliestDate) {
|
|
102
|
+
earliestDate = foundDate;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
} catch (e) {
|
|
108
|
+
logger.log('ERROR', `GetFirstDate failed for ${collectionName}`, { errorMessage: e.message });
|
|
109
|
+
}
|
|
110
|
+
return earliestDate;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Sub-pipe: pipe.computationSystem.computationUtils.getFirstDateFromSourceData
|
|
115
|
+
* @param {object} config - The computation system configuration object.
|
|
116
|
+
* @param {object} dependencies - Contains db, logger, calculationUtils.
|
|
117
|
+
* @returns {Promise<Date>} The earliest date found or a default fallback date.
|
|
118
|
+
*/
|
|
119
|
+
async function getFirstDateFromSourceData(config, dependencies) {
|
|
120
|
+
const { logger } = dependencies;
|
|
121
|
+
logger.log('INFO', 'Querying for the earliest date from source portfolio data...');
|
|
122
|
+
|
|
123
|
+
// Pass dependencies to sub-pipe
|
|
124
|
+
const investorDate = await getFirstDateFromCollection(config, dependencies, config.normalUserPortfolioCollection);
|
|
125
|
+
const speculatorDate = await getFirstDateFromCollection(config, dependencies, config.speculatorPortfolioCollection);
|
|
126
|
+
|
|
127
|
+
let earliestDate;
|
|
128
|
+
if (investorDate && speculatorDate) {
|
|
129
|
+
earliestDate = investorDate < speculatorDate ? investorDate : speculatorDate;
|
|
130
|
+
} else {
|
|
131
|
+
earliestDate = investorDate || speculatorDate;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (earliestDate) {
|
|
135
|
+
logger.log('INFO', `Found earliest source data date: ${earliestDate.toISOString().slice(0, 10)}`);
|
|
136
|
+
return earliestDate;
|
|
137
|
+
} else {
|
|
138
|
+
const fallbackDate = new Date(config.earliestComputationDate + 'T00:00:00Z' || '2023-01-01T00:00:00Z');
|
|
139
|
+
logger.log('WARN', `No source data found. Defaulting first date to: ${fallbackDate.toISOString().slice(0, 10)}`);
|
|
140
|
+
return fallbackDate;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
FieldValue, FieldPath,
|
|
146
|
+
normalizeName,
|
|
147
|
+
commitBatchInChunks,
|
|
148
|
+
getExpectedDateStrings,
|
|
149
|
+
getFirstDateFromSourceData,
|
|
255
150
|
};
|