bulltrackers-module 1.0.658 → 1.0.659
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/functions/computation-system/data/AvailabilityChecker.js +163 -317
- package/functions/computation-system/data/CachedDataLoader.js +158 -222
- package/functions/computation-system/data/DependencyFetcher.js +201 -406
- package/functions/computation-system/executors/MetaExecutor.js +176 -280
- package/functions/computation-system/executors/StandardExecutor.js +325 -383
- package/functions/computation-system/helpers/computation_dispatcher.js +294 -699
- package/functions/computation-system/helpers/computation_worker.js +3 -2
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +382 -0
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +357 -0
- package/functions/computation-system/legacy/DependencyFetcherOld.js +478 -0
- package/functions/computation-system/legacy/MetaExecutorold.js +364 -0
- package/functions/computation-system/legacy/StandardExecutorold.js +476 -0
- package/functions/computation-system/legacy/computation_dispatcherold.js +944 -0
- package/functions/computation-system/persistence/ResultCommitter.js +137 -188
- package/functions/computation-system/services/SnapshotService.js +129 -0
- package/functions/computation-system/tools/BuildReporter.js +12 -7
- package/functions/computation-system/utils/data_loader.js +213 -238
- package/package.json +3 -2
- package/functions/computation-system/workflows/bulltrackers_pipeline.yaml +0 -163
- package/functions/computation-system/workflows/data_feeder_pipeline.yaml +0 -115
- package/functions/computation-system/workflows/datafeederpipelineinstructions.md +0 -30
- package/functions/computation-system/workflows/morning_prep_pipeline.yaml +0 -55
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Snapshot Service.
|
|
3
|
+
* Creates a complete "Frozen State" of the day's data in GCS.
|
|
4
|
+
* UPDATED: Now snapshots ALL data types (Ratings, Verification, Metadata, etc).
|
|
5
|
+
*/
|
|
6
|
+
const zlib = require('zlib');
|
|
7
|
+
const { Storage } = require('@google-cloud/storage');
|
|
8
|
+
const storage = new Storage();
|
|
9
|
+
const dataLoader = require('../utils/data_loader');
|
|
10
|
+
|
|
11
|
+
async function generateDailySnapshots(dateStr, config, deps) {
|
|
12
|
+
const { logger } = deps;
|
|
13
|
+
logger.log('INFO', `[SnapshotService] 📸 Starting Full System Snapshot for ${dateStr}`);
|
|
14
|
+
|
|
15
|
+
const bucketName = config.gcsBucketName || 'bulltrackers';
|
|
16
|
+
const bucket = storage.bucket(bucketName);
|
|
17
|
+
|
|
18
|
+
// parallelize independent fetches
|
|
19
|
+
await Promise.all([
|
|
20
|
+
snapshotPortfolios(dateStr, bucket, config, deps), // Heavy
|
|
21
|
+
snapshotSocial(dateStr, bucket, config, deps), // Heavy
|
|
22
|
+
snapshotHistory(dateStr, bucket, config, deps), // Heavy (JSONL)
|
|
23
|
+
snapshotRatings(dateStr, bucket, config, deps), // Sharded
|
|
24
|
+
snapshotVerification(dateStr, bucket, config, deps),// Collection Group
|
|
25
|
+
snapshotRankings(dateStr, bucket, config, deps), // Single Doc
|
|
26
|
+
snapshotMetadata(dateStr, bucket, config, deps) // Small Docs (Insights, Alerts, Watchlist)
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
logger.log('INFO', `[SnapshotService] ✅ Full System Snapshot Complete.`);
|
|
30
|
+
return { status: 'OK', date: dateStr };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// --- HEAVY DATA HANDLERS (As before) ---
|
|
34
|
+
|
|
35
|
+
async function snapshotPortfolios(dateStr, bucket, config, deps) {
|
|
36
|
+
const file = bucket.file(`${dateStr}/snapshots/portfolios.json.gz`);
|
|
37
|
+
if ((await file.exists())[0] && !config.forceSnapshot) return;
|
|
38
|
+
|
|
39
|
+
// Fetch ALL types (PI + SignedIn) to ensure complete context
|
|
40
|
+
const refs = await dataLoader.getPortfolioPartRefs(config, deps, dateStr, ['ALL']);
|
|
41
|
+
const data = await dataLoader.loadDataByRefs(config, deps, refs);
|
|
42
|
+
if (Object.keys(data).length > 0) {
|
|
43
|
+
await file.save(JSON.stringify(data), { gzip: true });
|
|
44
|
+
deps.logger.log('INFO', `[Snapshot] Saved ${Object.keys(data).length} portfolios.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function snapshotSocial(dateStr, bucket, config, deps) {
|
|
49
|
+
const file = bucket.file(`${dateStr}/snapshots/social.json.gz`);
|
|
50
|
+
if ((await file.exists())[0] && !config.forceSnapshot) return;
|
|
51
|
+
|
|
52
|
+
const data = await dataLoader.loadDailySocialPostInsights(config, deps, dateStr);
|
|
53
|
+
await file.save(JSON.stringify(data), { gzip: true });
|
|
54
|
+
deps.logger.log('INFO', `[Snapshot] Saved Social Data.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function snapshotHistory(dateStr, bucket, config, deps) {
|
|
58
|
+
const file = bucket.file(`${dateStr}/snapshots/history.jsonl.gz`);
|
|
59
|
+
if ((await file.exists())[0] && !config.forceSnapshot) return;
|
|
60
|
+
|
|
61
|
+
const refs = await dataLoader.getHistoryPartRefs(config, deps, dateStr, ['ALL']);
|
|
62
|
+
const gcsStream = file.createWriteStream({ gzip: true });
|
|
63
|
+
|
|
64
|
+
// Stream line-by-line
|
|
65
|
+
const BATCH_SIZE = 10;
|
|
66
|
+
for (let i = 0; i < refs.length; i += BATCH_SIZE) {
|
|
67
|
+
const batchRefs = refs.slice(i, i + BATCH_SIZE);
|
|
68
|
+
const dataMap = await dataLoader.loadDataByRefs(config, deps, batchRefs);
|
|
69
|
+
let chunk = '';
|
|
70
|
+
for (const [uid, h] of Object.entries(dataMap)) chunk += JSON.stringify({ [uid]: h }) + '\n';
|
|
71
|
+
if (!gcsStream.write(chunk)) await new Promise(r => gcsStream.once('drain', r));
|
|
72
|
+
}
|
|
73
|
+
gcsStream.end();
|
|
74
|
+
await new Promise((resolve, reject) => { gcsStream.on('finish', resolve); gcsStream.on('error', reject); });
|
|
75
|
+
deps.logger.log('INFO', `[Snapshot] Saved History (JSONL).`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- NEW HANDLERS (The "All Data" Expansion) ---
|
|
79
|
+
|
|
80
|
+
async function snapshotRatings(dateStr, bucket, config, deps) {
|
|
81
|
+
const file = bucket.file(`${dateStr}/snapshots/ratings.json.gz`);
|
|
82
|
+
if ((await file.exists())[0] && !config.forceSnapshot) return;
|
|
83
|
+
|
|
84
|
+
// Load RAW ratings from Firestore shards
|
|
85
|
+
const data = await dataLoader.loadPIRatings(config, deps, dateStr);
|
|
86
|
+
await file.save(JSON.stringify(data), { gzip: true });
|
|
87
|
+
deps.logger.log('INFO', `[Snapshot] Saved Ratings for ${Object.keys(data).length} PIs.`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function snapshotVerification(dateStr, bucket, config, deps) {
|
|
91
|
+
const file = bucket.file(`${dateStr}/snapshots/verification.json.gz`);
|
|
92
|
+
if ((await file.exists())[0] && !config.forceSnapshot) return;
|
|
93
|
+
|
|
94
|
+
// Load global verification profiles (expensive scan)
|
|
95
|
+
const data = await dataLoader.loadVerificationProfiles(config, deps);
|
|
96
|
+
await file.save(JSON.stringify(data), { gzip: true });
|
|
97
|
+
deps.logger.log('INFO', `[Snapshot] Saved ${Object.keys(data).length} Verification Profiles.`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function snapshotRankings(dateStr, bucket, config, deps) {
|
|
101
|
+
const file = bucket.file(`${dateStr}/snapshots/rankings.json.gz`);
|
|
102
|
+
if ((await file.exists())[0] && !config.forceSnapshot) return;
|
|
103
|
+
|
|
104
|
+
const data = await dataLoader.loadPopularInvestorRankings(config, deps, dateStr);
|
|
105
|
+
if (data) await file.save(JSON.stringify(data), { gzip: true });
|
|
106
|
+
deps.logger.log('INFO', `[Snapshot] Saved Rankings.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function snapshotMetadata(dateStr, bucket, config, deps) {
|
|
110
|
+
// Bundle small files into one "metadata.json" or keep separate. Separate is safer for loaders.
|
|
111
|
+
const ops = [
|
|
112
|
+
{ name: 'insights', fn: () => dataLoader.loadDailyInsights(config, deps, dateStr) },
|
|
113
|
+
{ name: 'page_views', fn: () => dataLoader.loadPIPageViews(config, deps, dateStr) },
|
|
114
|
+
{ name: 'watchlist', fn: () => dataLoader.loadWatchlistMembership(config, deps, dateStr) },
|
|
115
|
+
{ name: 'alerts', fn: () => dataLoader.loadPIAlertHistory(config, deps, dateStr) },
|
|
116
|
+
{ name: 'master_list', fn: () => dataLoader.loadPopularInvestorMasterList(config, deps) } // Not date bound usually, but good to snapshot state
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
for (const op of ops) {
|
|
120
|
+
const file = bucket.file(`${dateStr}/snapshots/${op.name}.json.gz`);
|
|
121
|
+
if ((await file.exists())[0] && !config.forceSnapshot) continue;
|
|
122
|
+
|
|
123
|
+
const data = await op.fn();
|
|
124
|
+
if (data) await file.save(JSON.stringify(data), { gzip: true });
|
|
125
|
+
}
|
|
126
|
+
deps.logger.log('INFO', `[Snapshot] Saved Metadata files.`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { generateDailySnapshots };
|
|
@@ -14,6 +14,7 @@ const SYSTEM_EPOCH = require('../system_epoch');
|
|
|
14
14
|
const REPORTER_EPOCH = require('../reporter_epoch');
|
|
15
15
|
const pLimit = require('p-limit');
|
|
16
16
|
const path = require('path');
|
|
17
|
+
const fs = require('fs');
|
|
17
18
|
const crypto = require('crypto');
|
|
18
19
|
|
|
19
20
|
const BUILD_RECORDS_COLLECTION = 'computation_build_records';
|
|
@@ -27,20 +28,24 @@ function getPackageVersions() {
|
|
|
27
28
|
let calcVersion = 'unknown';
|
|
28
29
|
|
|
29
30
|
try {
|
|
30
|
-
const
|
|
31
|
+
const modulePkgPath = path.join(__dirname, '../../../../bulltrackers-module/package.json');
|
|
32
|
+
const modulepkg = JSON.parse(fs.readFileSync(modulePkgPath, 'utf8'));
|
|
31
33
|
moduleVersion = modulepkg.version;
|
|
32
34
|
|
|
33
|
-
} catch (e) {
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
} catch (e) {
|
|
36
|
+
moduleVersion = "1"
|
|
37
|
+
console.warn('[BuildReporter] Could not resolve Module version', e.message);
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
try {
|
|
38
|
-
const
|
|
41
|
+
const calcPkgPath = path.join(__dirname, '../../../../calculations/package.json');
|
|
42
|
+
const calcpkg = JSON.parse(fs.readFileSync(calcPkgPath, 'utf8'));
|
|
39
43
|
calcVersion = calcpkg.version;
|
|
40
44
|
|
|
41
45
|
} catch (e2) {
|
|
42
46
|
calcVersion = "1"
|
|
43
|
-
console.warn('[BuildReporter] Could not resolve Calculations version', e2.message);
|
|
47
|
+
console.warn('[BuildReporter] Could not resolve Calculations version', e2.message);
|
|
48
|
+
}
|
|
44
49
|
|
|
45
50
|
|
|
46
51
|
return { moduleVersion, calcVersion };
|
|
@@ -257,7 +262,7 @@ async function generateBuildReport(config, dependencies, manifest) {
|
|
|
257
262
|
const today = new Date();
|
|
258
263
|
let dynamicDaysBack = 90;
|
|
259
264
|
if (absoluteEarliest) {
|
|
260
|
-
const diffTime = Math.abs(today - absoluteEarliest);
|
|
265
|
+
const diffTime = Math.abs(today.getTime() - absoluteEarliest.getTime());
|
|
261
266
|
dynamicDaysBack = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 2;
|
|
262
267
|
}
|
|
263
268
|
const startDate = new Date();
|