bulltrackers-module 1.0.182 → 1.0.184
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,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FIXED: computation_controller.js
|
|
3
|
-
* V3.
|
|
3
|
+
* V3.4: Adds Price Loading & Context Injection.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { DataExtractor,
|
|
@@ -23,7 +23,7 @@ class DataLoader {
|
|
|
23
23
|
constructor(config, dependencies) {
|
|
24
24
|
this.config = config;
|
|
25
25
|
this.deps = dependencies;
|
|
26
|
-
this.cache = { mappings: null, insights: new Map(), social: new Map() };
|
|
26
|
+
this.cache = { mappings: null, insights: new Map(), social: new Map(), prices: null };
|
|
27
27
|
}
|
|
28
28
|
async loadMappings() {
|
|
29
29
|
if (this.cache.mappings) return this.cache.mappings;
|
|
@@ -43,6 +43,39 @@ class DataLoader {
|
|
|
43
43
|
this.cache.social.set(dateStr, social);
|
|
44
44
|
return social;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* NEW: Loads sharded price data for Meta calculations
|
|
48
|
+
*/
|
|
49
|
+
async loadPrices() {
|
|
50
|
+
if (this.cache.prices) return this.cache.prices;
|
|
51
|
+
const { db, logger } = this.deps;
|
|
52
|
+
const collection = this.config.priceCollection || 'asset_prices';
|
|
53
|
+
|
|
54
|
+
logger.log('INFO', `[DataLoader] Loading all price shards from ${collection}...`);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const snapshot = await db.collection(collection).get();
|
|
58
|
+
if (snapshot.empty) return { history: {} };
|
|
59
|
+
|
|
60
|
+
const historyMap = {};
|
|
61
|
+
|
|
62
|
+
snapshot.forEach(doc => {
|
|
63
|
+
const shardData = doc.data();
|
|
64
|
+
// Merge shard keys (instrumentIds) into main map
|
|
65
|
+
if (shardData) Object.assign(historyMap, shardData);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
logger.log('INFO', `[DataLoader] Loaded prices for ${Object.keys(historyMap).length} instruments.`);
|
|
69
|
+
|
|
70
|
+
// Cache as an object with 'history' map to match priceExtractor expectations
|
|
71
|
+
this.cache.prices = { history: historyMap };
|
|
72
|
+
return this.cache.prices;
|
|
73
|
+
|
|
74
|
+
} catch (e) {
|
|
75
|
+
logger.log('ERROR', `[DataLoader] Failed to load prices: ${e.message}`);
|
|
76
|
+
return { history: {} };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
46
79
|
}
|
|
47
80
|
|
|
48
81
|
class ContextBuilder {
|
|
@@ -76,7 +109,7 @@ class ContextBuilder {
|
|
|
76
109
|
insights: { today: insights?.today, yesterday: insights?.yesterday },
|
|
77
110
|
social: { today: socialData?.today, yesterday: socialData?.yesterday },
|
|
78
111
|
mappings: mappings || {},
|
|
79
|
-
math: {
|
|
112
|
+
math: {
|
|
80
113
|
extract: DataExtractor,
|
|
81
114
|
history: HistoryExtractor,
|
|
82
115
|
compute: MathPrimitives,
|
|
@@ -103,6 +136,7 @@ class ContextBuilder {
|
|
|
103
136
|
mappings,
|
|
104
137
|
insights,
|
|
105
138
|
socialData,
|
|
139
|
+
prices, // <--- ADDED THIS
|
|
106
140
|
computedDependencies,
|
|
107
141
|
previousComputedDependencies,
|
|
108
142
|
config,
|
|
@@ -113,8 +147,9 @@ class ContextBuilder {
|
|
|
113
147
|
date: { today: dateStr },
|
|
114
148
|
insights: { today: insights?.today, yesterday: insights?.yesterday },
|
|
115
149
|
social: { today: socialData?.today, yesterday: socialData?.yesterday },
|
|
150
|
+
prices: prices || {}, // <--- INJECTED HERE
|
|
116
151
|
mappings: mappings || {},
|
|
117
|
-
math: {
|
|
152
|
+
math: {
|
|
118
153
|
extract: DataExtractor,
|
|
119
154
|
history: HistoryExtractor,
|
|
120
155
|
compute: MathPrimitives,
|
|
@@ -142,22 +177,22 @@ class ComputationExecutor {
|
|
|
142
177
|
this.loader = dataLoader;
|
|
143
178
|
}
|
|
144
179
|
|
|
145
|
-
/**
|
|
146
|
-
* UPDATED: Accepts yesterdayPortfolioData AND historyData separately.
|
|
147
|
-
*/
|
|
148
180
|
async executePerUser(calcInstance, metadata, dateStr, portfolioData, yesterdayPortfolioData, historyData, computedDeps, prevDeps) {
|
|
149
181
|
const { logger } = this.deps;
|
|
150
182
|
const targetUserType = metadata.userType;
|
|
151
183
|
const mappings = await this.loader.loadMappings();
|
|
152
184
|
const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await this.loader.loadInsights(dateStr) } : null;
|
|
185
|
+
|
|
153
186
|
for (const [userId, todayPortfolio] of Object.entries(portfolioData)) {
|
|
154
187
|
const yesterdayPortfolio = yesterdayPortfolioData ? yesterdayPortfolioData[userId] : null;
|
|
155
188
|
const todayHistory = historyData ? historyData[userId] : null;
|
|
156
189
|
const actualUserType = todayPortfolio.PublicPositions ? SCHEMAS.USER_TYPES.SPECULATOR : SCHEMAS.USER_TYPES.NORMAL;
|
|
190
|
+
|
|
157
191
|
if (targetUserType !== 'all') {
|
|
158
192
|
const mappedTarget = (targetUserType === 'speculator') ? SCHEMAS.USER_TYPES.SPECULATOR : SCHEMAS.USER_TYPES.NORMAL;
|
|
159
193
|
if (mappedTarget !== actualUserType) continue;
|
|
160
194
|
}
|
|
195
|
+
|
|
161
196
|
const context = ContextBuilder.buildPerUserContext({
|
|
162
197
|
todayPortfolio, yesterdayPortfolio,
|
|
163
198
|
todayHistory,
|
|
@@ -166,6 +201,7 @@ class ComputationExecutor {
|
|
|
166
201
|
previousComputedDependencies: prevDeps,
|
|
167
202
|
config: this.config, deps: this.deps
|
|
168
203
|
});
|
|
204
|
+
|
|
169
205
|
try { await calcInstance.process(context); }
|
|
170
206
|
catch (e) { logger.log('WARN', `Calc ${metadata.name} failed for user ${userId}: ${e.message}`); }
|
|
171
207
|
}
|
|
@@ -173,10 +209,20 @@ class ComputationExecutor {
|
|
|
173
209
|
|
|
174
210
|
async executeOncePerDay(calcInstance, metadata, dateStr, computedDeps, prevDeps) {
|
|
175
211
|
const mappings = await this.loader.loadMappings();
|
|
212
|
+
|
|
213
|
+
// Load standard dependencies
|
|
176
214
|
const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await this.loader.loadInsights(dateStr) } : null;
|
|
177
215
|
const social = metadata.rootDataDependencies?.includes('social') ? { today: await this.loader.loadSocial(dateStr) } : null;
|
|
216
|
+
|
|
217
|
+
// NEW: Load Price dependencies if required
|
|
218
|
+
let prices = null;
|
|
219
|
+
if (metadata.rootDataDependencies?.includes('price')) {
|
|
220
|
+
prices = await this.loader.loadPrices();
|
|
221
|
+
}
|
|
222
|
+
|
|
178
223
|
const context = ContextBuilder.buildMetaContext({
|
|
179
224
|
dateStr, metadata, mappings, insights, socialData: social,
|
|
225
|
+
prices, // Pass prices to builder
|
|
180
226
|
computedDependencies: computedDeps,
|
|
181
227
|
previousComputedDependencies: prevDeps,
|
|
182
228
|
config: this.config, deps: this.deps
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
checkRootDataAvailability,
|
|
8
8
|
fetchExistingResults,
|
|
9
9
|
fetchComputationStatus,
|
|
10
|
-
updateComputationStatus,
|
|
10
|
+
updateComputationStatus,
|
|
11
11
|
runStandardComputationPass,
|
|
12
12
|
runMetaComputationPass,
|
|
13
13
|
checkRootDependencies
|
|
@@ -36,7 +36,7 @@ async function runComputationPass(config, dependencies, computationManifest) {
|
|
|
36
36
|
return logger.log('WARN', `[PassRunner] No calcs for Pass ${passToRun}. Exiting.`);
|
|
37
37
|
|
|
38
38
|
const passEarliestDate = earliestDates.absoluteEarliest;
|
|
39
|
-
const endDateUTC
|
|
39
|
+
const endDateUTC = new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() - 1));
|
|
40
40
|
const allExpectedDates = getExpectedDateStrings(passEarliestDate, endDateUTC);
|
|
41
41
|
|
|
42
42
|
const standardCalcs = calcsInThisPass.filter(c => c.type === 'standard');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FILENAME: bulltrackers-module/functions/computation-system/helpers/orchestration_helpers.js
|
|
3
|
+
* FIXED: Explicit Logging + Honest Status Updates
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
const { ComputationController } = require('../controllers/computation_controller');
|
|
@@ -35,7 +36,6 @@ function checkRootDependencies(calcManifest, rootDataStatus) {
|
|
|
35
36
|
* Checks for the availability of all required root data for a specific date.
|
|
36
37
|
*/
|
|
37
38
|
async function checkRootDataAvailability(dateStr, config, dependencies, earliestDates) {
|
|
38
|
-
// ... [Unchanged content of checkRootDataAvailability] ...
|
|
39
39
|
const { logger } = dependencies;
|
|
40
40
|
const dateToProcess = new Date(dateStr + 'T00:00:00Z');
|
|
41
41
|
let portfolioRefs = [], historyRefs = [];
|
|
@@ -64,10 +64,6 @@ async function checkRootDataAvailability(dateStr, config, dependencies, earliest
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
* --- DEPRECATED: Old per-date fetch ---
|
|
69
|
-
* Keeps compatibility but logic moves to fetchGlobalComputationStatus
|
|
70
|
-
*/
|
|
71
67
|
async function fetchComputationStatus(dateStr, config, { db }) {
|
|
72
68
|
const collection = config.computationStatusCollection || 'computation_status';
|
|
73
69
|
const docRef = db.collection(collection).doc(dateStr);
|
|
@@ -75,10 +71,6 @@ async function fetchComputationStatus(dateStr, config, { db }) {
|
|
|
75
71
|
return snap.exists ? snap.data() : {};
|
|
76
72
|
}
|
|
77
73
|
|
|
78
|
-
/**
|
|
79
|
-
* --- NEW: Fetches the SINGLE GLOBAL status document ---
|
|
80
|
-
* Loads the entire history of statuses in one read.
|
|
81
|
-
*/
|
|
82
74
|
async function fetchGlobalComputationStatus(config, { db }) {
|
|
83
75
|
const collection = config.computationStatusCollection || 'computation_status';
|
|
84
76
|
const docRef = db.collection(collection).doc('global_status');
|
|
@@ -86,9 +78,6 @@ async function fetchGlobalComputationStatus(config, { db }) {
|
|
|
86
78
|
return snap.exists ? snap.data() : {};
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
/**
|
|
90
|
-
* --- DEPRECATED: Old per-date update ---
|
|
91
|
-
*/
|
|
92
81
|
async function updateComputationStatus(dateStr, updates, config, { db }) {
|
|
93
82
|
if (!updates || Object.keys(updates).length === 0) return;
|
|
94
83
|
const collection = config.computationStatusCollection || 'computation_status';
|
|
@@ -96,17 +85,11 @@ async function updateComputationStatus(dateStr, updates, config, { db }) {
|
|
|
96
85
|
await docRef.set(updates, { merge: true });
|
|
97
86
|
}
|
|
98
87
|
|
|
99
|
-
/**
|
|
100
|
-
* --- NEW: Batch Updates to Global Document ---
|
|
101
|
-
* Accepts a map of { "YYYY-MM-DD": { calcName: true, ... } }
|
|
102
|
-
* and writes them using dot notation to avoid overwriting other dates.
|
|
103
|
-
*/
|
|
104
88
|
async function updateGlobalComputationStatus(updatesByDate, config, { db }) {
|
|
105
89
|
if (!updatesByDate || Object.keys(updatesByDate).length === 0) return;
|
|
106
90
|
const collection = config.computationStatusCollection || 'computation_status';
|
|
107
91
|
const docRef = db.collection(collection).doc('global_status');
|
|
108
92
|
|
|
109
|
-
// Flatten to dot notation for Firestore update: "2023-10-27.calcName": true
|
|
110
93
|
const flattenUpdates = {};
|
|
111
94
|
for (const [date, statuses] of Object.entries(updatesByDate)) {
|
|
112
95
|
for (const [calc, status] of Object.entries(statuses)) {
|
|
@@ -117,7 +100,6 @@ async function updateGlobalComputationStatus(updatesByDate, config, { db }) {
|
|
|
117
100
|
try {
|
|
118
101
|
await docRef.update(flattenUpdates);
|
|
119
102
|
} catch (err) {
|
|
120
|
-
// If doc doesn't exist (first run), update fails. Use set({merge:true}).
|
|
121
103
|
if (err.code === 5) { // NOT_FOUND
|
|
122
104
|
const deepObj = {};
|
|
123
105
|
for (const [date, statuses] of Object.entries(updatesByDate)) {
|
|
@@ -130,10 +112,6 @@ async function updateGlobalComputationStatus(updatesByDate, config, { db }) {
|
|
|
130
112
|
}
|
|
131
113
|
}
|
|
132
114
|
|
|
133
|
-
/**
|
|
134
|
-
* --- UPDATED: fetchExistingResults ---
|
|
135
|
-
* (Unchanged, keeps fetching results per date as this is heavy data)
|
|
136
|
-
*/
|
|
137
115
|
async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config, { db }, includeSelf = false) {
|
|
138
116
|
const manifestMap = new Map(fullManifest.map(c => [normalizeName(c.name), c]));
|
|
139
117
|
const calcsToFetch = new Set();
|
|
@@ -158,10 +136,6 @@ async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config,
|
|
|
158
136
|
return fetched;
|
|
159
137
|
}
|
|
160
138
|
|
|
161
|
-
/**
|
|
162
|
-
* --- UPDATED: streamAndProcess ---
|
|
163
|
-
* (Unchanged)
|
|
164
|
-
*/
|
|
165
139
|
async function streamAndProcess(dateStr, state, passName, config, deps, rootData, portfolioRefs, historyRefs, fetchedDeps, previousFetchedDeps) {
|
|
166
140
|
const { logger } = deps;
|
|
167
141
|
const controller = new ComputationController(config, deps);
|
|
@@ -211,10 +185,6 @@ async function streamAndProcess(dateStr, state, passName, config, deps, rootData
|
|
|
211
185
|
logger.log('INFO', `[${passName}] Streaming complete.`);
|
|
212
186
|
}
|
|
213
187
|
|
|
214
|
-
/**
|
|
215
|
-
* --- UPDATED: runStandardComputationPass ---
|
|
216
|
-
* Now accepts `skipStatusWrite` and returns `successUpdates`
|
|
217
|
-
*/
|
|
218
188
|
async function runStandardComputationPass(date, calcs, passName, config, deps, rootData, fetchedDeps, previousFetchedDeps, skipStatusWrite = false) {
|
|
219
189
|
const dStr = date.toISOString().slice(0, 10);
|
|
220
190
|
const logger = deps.logger;
|
|
@@ -232,6 +202,9 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
|
|
|
232
202
|
const inst = new c.class();
|
|
233
203
|
inst.manifest = c;
|
|
234
204
|
state[normalizeName(c.name)] = inst;
|
|
205
|
+
|
|
206
|
+
// LOG: Explicitly say what calculation is being processed (Initialized)
|
|
207
|
+
logger.log('INFO', `${c.name} calculation running for ${dStr}`);
|
|
235
208
|
}
|
|
236
209
|
catch(e) {
|
|
237
210
|
logger.log('WARN', `Failed to init ${c.name}`);
|
|
@@ -240,14 +213,9 @@ async function runStandardComputationPass(date, calcs, passName, config, deps, r
|
|
|
240
213
|
|
|
241
214
|
await streamAndProcess(dStr, state, passName, config, deps, fullRoot, rootData.portfolioRefs, rootData.historyRefs, fetchedDeps, previousFetchedDeps);
|
|
242
215
|
|
|
243
|
-
// Return the updates instead of just writing them
|
|
244
216
|
return await commitResults(state, dStr, passName, config, deps, skipStatusWrite);
|
|
245
217
|
}
|
|
246
218
|
|
|
247
|
-
/**
|
|
248
|
-
* --- UPDATED: runMetaComputationPass ---
|
|
249
|
-
* Now accepts `skipStatusWrite` and returns `successUpdates`
|
|
250
|
-
*/
|
|
251
219
|
async function runMetaComputationPass(date, calcs, passName, config, deps, fetchedDeps, previousFetchedDeps, rootData, skipStatusWrite = false) {
|
|
252
220
|
const controller = new ComputationController(config, deps);
|
|
253
221
|
const dStr = date.toISOString().slice(0, 10);
|
|
@@ -255,6 +223,9 @@ async function runMetaComputationPass(date, calcs, passName, config, deps, fetch
|
|
|
255
223
|
|
|
256
224
|
for (const mCalc of calcs) {
|
|
257
225
|
try {
|
|
226
|
+
// LOG: Explicitly say what calculation is being processed
|
|
227
|
+
deps.logger.log('INFO', `${mCalc.name} calculation running for ${dStr}`);
|
|
228
|
+
|
|
258
229
|
const inst = new mCalc.class();
|
|
259
230
|
inst.manifest = mCalc;
|
|
260
231
|
await controller.executor.executeOncePerDay(inst, mCalc, dStr, fetchedDeps, previousFetchedDeps);
|
|
@@ -267,7 +238,7 @@ async function runMetaComputationPass(date, calcs, passName, config, deps, fetch
|
|
|
267
238
|
|
|
268
239
|
/**
|
|
269
240
|
* --- UPDATED: commitResults ---
|
|
270
|
-
*
|
|
241
|
+
* Includes Explicit Result Logging and Honest Status Reporting.
|
|
271
242
|
*/
|
|
272
243
|
async function commitResults(stateObj, dStr, passName, config, deps, skipStatusWrite = false) {
|
|
273
244
|
const writes = [], schemas = [], sharded = {};
|
|
@@ -277,14 +248,29 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
277
248
|
const calc = stateObj[name];
|
|
278
249
|
try {
|
|
279
250
|
const result = await calc.getResult();
|
|
280
|
-
|
|
251
|
+
|
|
252
|
+
// If null/undefined, log as Failed/Unknown immediately
|
|
253
|
+
if (!result) {
|
|
254
|
+
deps.logger.log('INFO', `${name} calculation for ${dStr} ran, result : Failed (Empty Result)`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
281
258
|
const standardRes = {};
|
|
259
|
+
let hasData = false;
|
|
260
|
+
|
|
282
261
|
for (const key in result) {
|
|
283
262
|
if (key.startsWith('sharded_')) {
|
|
284
263
|
const sData = result[key];
|
|
285
|
-
for (const c in sData) {
|
|
286
|
-
|
|
264
|
+
for (const c in sData) {
|
|
265
|
+
sharded[c] = sharded[c] || {};
|
|
266
|
+
Object.assign(sharded[c], sData[c]);
|
|
267
|
+
}
|
|
268
|
+
if (Object.keys(sData).length > 0) hasData = true;
|
|
269
|
+
} else {
|
|
270
|
+
standardRes[key] = result[key];
|
|
271
|
+
}
|
|
287
272
|
}
|
|
273
|
+
|
|
288
274
|
if (Object.keys(standardRes).length) {
|
|
289
275
|
standardRes._completed = true;
|
|
290
276
|
writes.push({
|
|
@@ -293,7 +279,9 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
293
279
|
.collection(config.computationsSubcollection).doc(name),
|
|
294
280
|
data: standardRes
|
|
295
281
|
});
|
|
282
|
+
hasData = true;
|
|
296
283
|
}
|
|
284
|
+
|
|
297
285
|
if (calc.manifest.class.getSchema) {
|
|
298
286
|
const { class: _cls, ...safeMetadata } = calc.manifest;
|
|
299
287
|
schemas.push({
|
|
@@ -304,9 +292,19 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
304
292
|
});
|
|
305
293
|
}
|
|
306
294
|
|
|
307
|
-
|
|
295
|
+
// --- EXPLICIT LOGGING & STATUS UPDATE ---
|
|
296
|
+
if (hasData) {
|
|
297
|
+
successUpdates[name] = true;
|
|
298
|
+
deps.logger.log('INFO', `${name} calculation for ${dStr} ran, result : Succeeded`);
|
|
299
|
+
} else {
|
|
300
|
+
// It ran without error, but produced no content (e.g. no data met criteria)
|
|
301
|
+
deps.logger.log('INFO', `${name} calculation for ${dStr} ran, result : Unknown (No Data Written)`);
|
|
302
|
+
}
|
|
308
303
|
|
|
309
|
-
} catch (e) {
|
|
304
|
+
} catch (e) {
|
|
305
|
+
deps.logger.log('ERROR', `Commit failed ${name}: ${e.message}`);
|
|
306
|
+
deps.logger.log('INFO', `${name} calculation for ${dStr} ran, result : Failed (Exception)`);
|
|
307
|
+
}
|
|
310
308
|
}
|
|
311
309
|
|
|
312
310
|
if (schemas.length) batchStoreSchemas(deps, config, schemas).catch(()=>{});
|