bulltrackers-module 1.0.657 → 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.
Files changed (23) hide show
  1. package/functions/api-v2/routes/popular_investors.js +80 -0
  2. package/functions/computation-system/data/AvailabilityChecker.js +163 -317
  3. package/functions/computation-system/data/CachedDataLoader.js +158 -222
  4. package/functions/computation-system/data/DependencyFetcher.js +201 -406
  5. package/functions/computation-system/executors/MetaExecutor.js +176 -280
  6. package/functions/computation-system/executors/StandardExecutor.js +325 -383
  7. package/functions/computation-system/helpers/computation_dispatcher.js +294 -699
  8. package/functions/computation-system/helpers/computation_worker.js +3 -2
  9. package/functions/computation-system/legacy/AvailabilityCheckerOld.js +382 -0
  10. package/functions/computation-system/legacy/CachedDataLoaderOld.js +357 -0
  11. package/functions/computation-system/legacy/DependencyFetcherOld.js +478 -0
  12. package/functions/computation-system/legacy/MetaExecutorold.js +364 -0
  13. package/functions/computation-system/legacy/StandardExecutorold.js +476 -0
  14. package/functions/computation-system/legacy/computation_dispatcherold.js +944 -0
  15. package/functions/computation-system/persistence/ResultCommitter.js +137 -188
  16. package/functions/computation-system/services/SnapshotService.js +129 -0
  17. package/functions/computation-system/tools/BuildReporter.js +12 -7
  18. package/functions/computation-system/utils/data_loader.js +213 -238
  19. package/package.json +3 -2
  20. package/functions/computation-system/workflows/bulltrackers_pipeline.yaml +0 -163
  21. package/functions/computation-system/workflows/data_feeder_pipeline.yaml +0 -115
  22. package/functions/computation-system/workflows/datafeederpipelineinstructions.md +0 -30
  23. 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 modulepkg = require('../../../../bulltrackers-module/package.json');
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
- moduleVersion = "1"
35
- console.warn('[BuildReporter] Could not resolve Module version', e.message); }
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 calcpkg = require('../../../../calculations/package.json')
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();