bulltrackers-module 1.0.249 → 1.0.250

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.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @fileoverview Root Data Indexer
3
+ * Runs daily to index exactly what data is available for every date.
4
+ * Creates a "Source of Truth" map in Firestore for the Dispatcher/Computation System.
5
+ */
6
+
7
+ const { FieldValue } = require('@google-cloud/firestore');
8
+ const pLimit = require('p-limit');
9
+
10
+ // Hardcoded verification blocks as per logic requirements
11
+ const CANARY_BLOCK_ID = '19M';
12
+ const CANARY_PART_ID = 'part_0';
13
+ const PRICE_SHARD_ID = 'shard_0';
14
+
15
+ /**
16
+ * Main pipe: pipe.maintenance.runRootDataIndexer
17
+ */
18
+ exports.runRootDataIndexer = async (config, dependencies) => {
19
+ const { db, logger } = dependencies;
20
+ const {
21
+ availabilityCollection,
22
+ earliestDate,
23
+ collections
24
+ } = config;
25
+
26
+ logger.log('INFO', '[RootDataIndexer] Starting Root Data Availability Scan...');
27
+
28
+ // 1. Pre-fetch Price Data Availability (Optimization)
29
+ // Instead of reading the shard N times, we read it once and map available dates.
30
+ const priceAvailabilitySet = new Set();
31
+ try {
32
+ const priceShardRef = db.collection(collections.prices).doc(PRICE_SHARD_ID);
33
+ const priceSnap = await priceShardRef.get();
34
+ if (priceSnap.exists) {
35
+ const data = priceSnap.data();
36
+ // Iterate over all instruments in this shard to find any available dates
37
+ Object.values(data).forEach(instrument => {
38
+ if (instrument.prices) {
39
+ Object.keys(instrument.prices).forEach(dateKey => {
40
+ // Validate format YYYY-MM-DD
41
+ if (/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) {
42
+ priceAvailabilitySet.add(dateKey);
43
+ }
44
+ });
45
+ }
46
+ });
47
+ }
48
+ logger.log('INFO', `[RootDataIndexer] Loaded price availability map. Found prices for ${priceAvailabilitySet.size} unique dates.`);
49
+ } catch (e) {
50
+ logger.log('ERROR', `[RootDataIndexer] Failed to load price shard ${PRICE_SHARD_ID}. Price availability will be false.`, { error: e.message });
51
+ }
52
+
53
+ // 2. Determine Date Range (Earliest -> Tomorrow)
54
+ const start = new Date(earliestDate || '2023-01-01');
55
+ const end = new Date();
56
+ end.setDate(end.getDate() + 1); // Look ahead 1 day
57
+
58
+ const datesToScan = [];
59
+ for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
60
+ datesToScan.push(d.toISOString().slice(0, 10));
61
+ }
62
+
63
+ logger.log('INFO', `[RootDataIndexer] Scanning ${datesToScan.length} dates from ${datesToScan[0]} to ${datesToScan[datesToScan.length-1]}`);
64
+
65
+ // 3. Scan in Parallel
66
+ const limit = pLimit(20); // Concurrent date checks
67
+ let updatesCount = 0;
68
+
69
+ const promises = datesToScan.map(dateStr => limit(async () => {
70
+ try {
71
+ const availability = {
72
+ date: dateStr,
73
+ lastUpdated: FieldValue.serverTimestamp(),
74
+
75
+ // Defaults
76
+ hasPortfolio: false,
77
+ hasHistory: false,
78
+ hasSocial: false,
79
+ hasInsights: false,
80
+ hasPrices: false,
81
+
82
+ // Detailed breakdown
83
+ details: {
84
+ normalPortfolio: false,
85
+ speculatorPortfolio: false,
86
+ normalHistory: false,
87
+ speculatorHistory: false
88
+ }
89
+ };
90
+
91
+ // A. User Portfolios (Normal) - /NormalUserPortfolios/19M/snapshots/{date}/parts/part_0
92
+ const normPortRef = db.collection(collections.normalPortfolios)
93
+ .doc(CANARY_BLOCK_ID)
94
+ .collection('snapshots')
95
+ .doc(dateStr)
96
+ .collection('parts')
97
+ .doc(CANARY_PART_ID);
98
+
99
+ // B. Speculator Portfolios - /SpeculatorPortfolios/19M/snapshots/{date}/parts/part_0
100
+ const specPortRef = db.collection(collections.speculatorPortfolios)
101
+ .doc(CANARY_BLOCK_ID)
102
+ .collection('snapshots')
103
+ .doc(dateStr)
104
+ .collection('parts')
105
+ .doc(CANARY_PART_ID);
106
+
107
+ // C. User Trade History - /NormalUserTradeHistory/19M/snapshots/{date}/parts/part_0
108
+ const normHistRef = db.collection(collections.normalHistory)
109
+ .doc(CANARY_BLOCK_ID)
110
+ .collection('snapshots')
111
+ .doc(dateStr)
112
+ .collection('parts')
113
+ .doc(CANARY_PART_ID);
114
+
115
+ // D. Speculator Trade History - /SpeculatorTradeHistory/19M/snapshots/{date}/parts/part_0
116
+ const specHistRef = db.collection(collections.speculatorHistory)
117
+ .doc(CANARY_BLOCK_ID)
118
+ .collection('snapshots')
119
+ .doc(dateStr)
120
+ .collection('parts')
121
+ .doc(CANARY_PART_ID);
122
+
123
+ // E. Insights Data - /daily_instrument_insights/{date}
124
+ const insightsRef = db.collection(collections.insights).doc(dateStr);
125
+
126
+ // F. Social Data - /daily_social_insights/{date}/posts (Check for ANY doc)
127
+ const socialPostsRef = db.collection(collections.social).doc(dateStr).collection('posts');
128
+
129
+ // --- EXECUTE CHECKS ---
130
+ const [
131
+ normPortSnap, specPortSnap,
132
+ normHistSnap, specHistSnap,
133
+ insightsSnap, socialQuerySnap
134
+ ] = await Promise.all([
135
+ normPortRef.get(), specPortRef.get(),
136
+ normHistRef.get(), specHistRef.get(),
137
+ insightsRef.get(),
138
+ socialPostsRef.limit(1).get()
139
+ ]);
140
+
141
+ // Evaluate Findings
142
+ availability.details.normalPortfolio = normPortSnap.exists;
143
+ availability.details.speculatorPortfolio = specPortSnap.exists;
144
+ availability.hasPortfolio = normPortSnap.exists || specPortSnap.exists;
145
+
146
+ availability.details.normalHistory = normHistSnap.exists;
147
+ availability.details.speculatorHistory = specHistSnap.exists;
148
+ availability.hasHistory = normHistSnap.exists || specHistSnap.exists;
149
+
150
+ availability.hasInsights = insightsSnap.exists;
151
+ availability.hasSocial = !socialQuerySnap.empty;
152
+
153
+ // G. Prices (From pre-loaded set)
154
+ availability.hasPrices = priceAvailabilitySet.has(dateStr);
155
+
156
+ // Write to Index
157
+ await db.collection(availabilityCollection).doc(dateStr).set(availability);
158
+ updatesCount++;
159
+
160
+ } catch (e) {
161
+ logger.log('ERROR', `[RootDataIndexer] Failed to index ${dateStr}`, { error: e.message });
162
+ }
163
+ }));
164
+
165
+ await Promise.all(promises);
166
+ logger.log('SUCCESS', `[RootDataIndexer] Indexing complete. Updated ${updatesCount} dates.`);
167
+ return { success: true, count: updatesCount };
168
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.249",
3
+ "version": "1.0.250",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -18,7 +18,8 @@
18
18
  "functions/appscript-api/",
19
19
  "functions/social-orchestrator/",
20
20
  "functions/social-task-handler/",
21
- "functions/price-backfill/"
21
+ "functions/price-backfill/",
22
+ "functions/root-data-indexer/"
22
23
  ],
23
24
  "keywords": [
24
25
  "bulltrackers",