bulltrackers-module 1.0.276 → 1.0.278
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/CachedDataLoader.js +17 -1
- package/functions/computation-system/data/DependencyFetcher.js +16 -2
- package/functions/computation-system/persistence/ResultCommitter.js +75 -15
- package/functions/computation-system/persistence/ResultsValidator.js +7 -7
- package/functions/computation-system/tools/BuildReporter.js +22 -2
- package/functions/computation-system/utils/data_loader.js +30 -11
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Execution-scoped data loader with caching.
|
|
3
|
+
* UPDATED: Handles Decompression of Shards.
|
|
3
4
|
*/
|
|
4
5
|
const {
|
|
5
6
|
loadDailyInsights,
|
|
@@ -7,6 +8,7 @@ const {
|
|
|
7
8
|
getRelevantShardRefs,
|
|
8
9
|
getPriceShardRefs
|
|
9
10
|
} = require('../utils/data_loader');
|
|
11
|
+
const zlib = require('zlib'); // [NEW]
|
|
10
12
|
|
|
11
13
|
class CachedDataLoader {
|
|
12
14
|
constructor(config, dependencies) {
|
|
@@ -19,6 +21,19 @@ class CachedDataLoader {
|
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
// [NEW] Decompression Helper
|
|
25
|
+
_tryDecompress(data) {
|
|
26
|
+
if (data && data._compressed === true && data.payload) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(zlib.gunzipSync(data.payload).toString());
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error('[CachedDataLoader] Decompression failed', e);
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
async loadMappings() {
|
|
23
38
|
if (this.cache.mappings) return this.cache.mappings;
|
|
24
39
|
const { calculationUtils } = this.deps;
|
|
@@ -52,7 +67,8 @@ class CachedDataLoader {
|
|
|
52
67
|
try {
|
|
53
68
|
const snap = await docRef.get();
|
|
54
69
|
if (!snap.exists) return {};
|
|
55
|
-
|
|
70
|
+
// [UPDATED] Use decompression helper
|
|
71
|
+
return this._tryDecompress(snap.data());
|
|
56
72
|
} catch (e) {
|
|
57
73
|
console.error(`Error loading shard ${docRef.path}:`, e);
|
|
58
74
|
return {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Fetches results from previous computations, handling auto-sharding
|
|
2
|
+
* @fileoverview Fetches results from previous computations, handling auto-sharding and decompression.
|
|
3
3
|
*/
|
|
4
4
|
const { normalizeName } = require('../utils/utils');
|
|
5
|
+
const zlib = require('zlib'); // [NEW]
|
|
5
6
|
|
|
6
7
|
async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config, { db }, includeSelf = false) {
|
|
7
8
|
const manifestMap = new Map(fullManifest.map(c => [normalizeName(c.name), c]));
|
|
@@ -39,7 +40,20 @@ async function fetchExistingResults(dateStr, calcsInPass, fullManifest, config,
|
|
|
39
40
|
const name = names[i];
|
|
40
41
|
if (!doc.exists) return;
|
|
41
42
|
const data = doc.data();
|
|
42
|
-
|
|
43
|
+
|
|
44
|
+
// --- [NEW] DECOMPRESSION LOGIC ---
|
|
45
|
+
if (data._compressed === true && data.payload) {
|
|
46
|
+
try {
|
|
47
|
+
// Firestore returns Buffers automatically
|
|
48
|
+
const unzipped = zlib.gunzipSync(data.payload);
|
|
49
|
+
fetched[name] = JSON.parse(unzipped.toString());
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error(`[Hydration] Failed to decompress ${name}:`, e);
|
|
52
|
+
fetched[name] = {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// --- END NEW LOGIC ---
|
|
56
|
+
else if (data._sharded === true) {
|
|
43
57
|
hydrationPromises.push(hydrateAutoShardedResult(doc.ref, name));
|
|
44
58
|
} else if (data._completed) {
|
|
45
59
|
fetched[name] = data;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Handles saving computation results with observability and Smart Cleanup.
|
|
3
|
+
* UPDATED: Implements GZIP Compression for efficient storage.
|
|
3
4
|
* UPDATED: Implements Content-Based Hashing (ResultHash) for dependency short-circuiting.
|
|
5
|
+
* UPDATED: Auto-enforces Weekend Mode validation for 'Price-Only' computations.
|
|
4
6
|
*/
|
|
5
|
-
const { commitBatchInChunks, generateDataHash } = require('../utils/utils');
|
|
7
|
+
const { commitBatchInChunks, generateDataHash } = require('../utils/utils');
|
|
6
8
|
const { updateComputationStatus } = require('./StatusRepository');
|
|
7
9
|
const { batchStoreSchemas } = require('../utils/schema_capture');
|
|
8
10
|
const { generateProcessId, PROCESS_TYPES } = require('../logger/logger');
|
|
9
11
|
const { HeuristicValidator } = require('./ResultsValidator');
|
|
10
12
|
const validationOverrides = require('../config/validation_overrides');
|
|
11
13
|
const pLimit = require('p-limit');
|
|
14
|
+
const zlib = require('zlib'); // [NEW] Compression Lib
|
|
12
15
|
|
|
13
16
|
const NON_RETRYABLE_ERRORS = [
|
|
14
17
|
'PERMISSION_DENIED', 'DATA_LOSS', 'FAILED_PRECONDITION'
|
|
@@ -26,30 +29,49 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
26
29
|
const pid = generateProcessId(PROCESS_TYPES.STORAGE, passName, dStr);
|
|
27
30
|
|
|
28
31
|
// Options defaults
|
|
29
|
-
const flushMode
|
|
30
|
-
const shardIndexes
|
|
32
|
+
const flushMode = options.flushMode || 'STANDARD';
|
|
33
|
+
const shardIndexes = options.shardIndexes || {};
|
|
31
34
|
const nextShardIndexes = {};
|
|
32
35
|
|
|
33
36
|
const fanOutLimit = pLimit(10);
|
|
34
37
|
|
|
35
38
|
for (const name in stateObj) {
|
|
36
|
-
const calc
|
|
39
|
+
const calc = stateObj[name];
|
|
37
40
|
const execStats = calc._executionStats || { processedUsers: 0, skippedUsers: 0 };
|
|
38
41
|
const currentShardIndex = shardIndexes[name] || 0;
|
|
39
42
|
|
|
40
43
|
const runMetrics = {
|
|
41
|
-
storage:
|
|
44
|
+
storage: { sizeBytes: 0, isSharded: false, shardCount: 1, keys: 0 },
|
|
42
45
|
validation: { isValid: true, anomalies: [] },
|
|
43
46
|
execution: execStats
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
try {
|
|
47
50
|
const result = await calc.getResult();
|
|
48
|
-
const
|
|
51
|
+
const configOverrides = validationOverrides[calc.manifest.name] || {};
|
|
49
52
|
|
|
53
|
+
// --- [NEW] AUTO-ENFORCE WEEKEND MODE FOR PRICE-ONLY CALCS ---
|
|
54
|
+
// If a calculation depends SOLELY on 'price', we assume market closures
|
|
55
|
+
// will cause 0s/Flatlines on weekends, so we enforce lenient validation.
|
|
56
|
+
const dataDeps = calc.manifest.rootDataDependencies || [];
|
|
57
|
+
const isPriceOnly = (dataDeps.length === 1 && dataDeps[0] === 'price');
|
|
58
|
+
|
|
59
|
+
let effectiveOverrides = { ...configOverrides };
|
|
60
|
+
|
|
61
|
+
if (isPriceOnly && !effectiveOverrides.weekend) {
|
|
62
|
+
effectiveOverrides.weekend = {
|
|
63
|
+
maxZeroPct: 100,
|
|
64
|
+
maxFlatlinePct: 100,
|
|
65
|
+
maxNullPct: 100 // Allow full nulls (e.g. holidays)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// -----------------------------------------------------------
|
|
69
|
+
|
|
50
70
|
// Validation
|
|
51
71
|
if (result && Object.keys(result).length > 0) {
|
|
52
|
-
|
|
72
|
+
// [FIX] Added 'dStr' as 3rd argument to match HeuristicValidator signature
|
|
73
|
+
const healthCheck = HeuristicValidator.analyze(calc.manifest.name, result, dStr, effectiveOverrides);
|
|
74
|
+
|
|
53
75
|
if (!healthCheck.valid) {
|
|
54
76
|
runMetrics.validation.isValid = false;
|
|
55
77
|
runMetrics.validation.anomalies.push(healthCheck.reason);
|
|
@@ -61,7 +83,7 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
61
83
|
|
|
62
84
|
const isEmpty = !result || (typeof result === 'object' && Object.keys(result).length === 0);
|
|
63
85
|
|
|
64
|
-
//
|
|
86
|
+
// Calculate Result Hash (Content-Based)
|
|
65
87
|
const resultHash = isEmpty ? 'empty' : generateDataHash(result);
|
|
66
88
|
|
|
67
89
|
// Handle Empty Results
|
|
@@ -73,8 +95,8 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
73
95
|
if (calc.manifest.hash) {
|
|
74
96
|
successUpdates[name] = {
|
|
75
97
|
hash: calc.manifest.hash,
|
|
76
|
-
resultHash: resultHash,
|
|
77
|
-
dependencyResultHashes: calc.manifest.dependencyResultHashes || {},
|
|
98
|
+
resultHash: resultHash,
|
|
99
|
+
dependencyResultHashes: calc.manifest.dependencyResultHashes || {},
|
|
78
100
|
category: calc.manifest.category,
|
|
79
101
|
composition: calc.manifest.composition,
|
|
80
102
|
metrics: runMetrics
|
|
@@ -89,7 +111,7 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
89
111
|
const isMultiDate = resultKeys.length > 0 && resultKeys.every(k => /^\d{4}-\d{2}-\d{2}$/.test(k));
|
|
90
112
|
|
|
91
113
|
if (isMultiDate) {
|
|
92
|
-
const datePromises
|
|
114
|
+
const datePromises = resultKeys.map((historicalDate) => fanOutLimit(async () => {
|
|
93
115
|
const dailyData = result[historicalDate];
|
|
94
116
|
if (!dailyData || Object.keys(dailyData).length === 0) return;
|
|
95
117
|
|
|
@@ -107,8 +129,8 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
107
129
|
if (calc.manifest.hash) {
|
|
108
130
|
successUpdates[name] = {
|
|
109
131
|
hash: calc.manifest.hash,
|
|
110
|
-
resultHash: resultHash,
|
|
111
|
-
dependencyResultHashes: calc.manifest.dependencyResultHashes || {},
|
|
132
|
+
resultHash: resultHash,
|
|
133
|
+
dependencyResultHashes: calc.manifest.dependencyResultHashes || {},
|
|
112
134
|
category: calc.manifest.category,
|
|
113
135
|
composition: calc.manifest.composition,
|
|
114
136
|
metrics: runMetrics
|
|
@@ -134,8 +156,8 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
134
156
|
if (calc.manifest.hash) {
|
|
135
157
|
successUpdates[name] = {
|
|
136
158
|
hash: calc.manifest.hash,
|
|
137
|
-
resultHash: resultHash,
|
|
138
|
-
dependencyResultHashes: calc.manifest.dependencyResultHashes || {},
|
|
159
|
+
resultHash: resultHash,
|
|
160
|
+
dependencyResultHashes: calc.manifest.dependencyResultHashes || {},
|
|
139
161
|
category: calc.manifest.category,
|
|
140
162
|
composition: calc.manifest.composition,
|
|
141
163
|
metrics: runMetrics
|
|
@@ -170,6 +192,44 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
170
192
|
}
|
|
171
193
|
|
|
172
194
|
async function writeSingleResult(result, docRef, name, dateContext, logger, config, deps, startShardIndex = 0, flushMode = 'STANDARD') {
|
|
195
|
+
|
|
196
|
+
// --- [NEW] COMPRESSION STRATEGY ---
|
|
197
|
+
// Try to compress before falling back to complex sharding
|
|
198
|
+
try {
|
|
199
|
+
const jsonString = JSON.stringify(result);
|
|
200
|
+
const rawBuffer = Buffer.from(jsonString);
|
|
201
|
+
|
|
202
|
+
// Only attempt if meaningful size (> 50KB)
|
|
203
|
+
if (rawBuffer.length > 50 * 1024) {
|
|
204
|
+
const compressedBuffer = zlib.gzipSync(rawBuffer);
|
|
205
|
+
|
|
206
|
+
// If compressed fits in one document (< 900KB safety limit)
|
|
207
|
+
if (compressedBuffer.length < 900 * 1024) {
|
|
208
|
+
logger.log('INFO', `[Compression] ${name}: Compressed ${(rawBuffer.length/1024).toFixed(0)}KB -> ${(compressedBuffer.length/1024).toFixed(0)}KB. Saved as Blob.`);
|
|
209
|
+
|
|
210
|
+
const compressedPayload = {
|
|
211
|
+
_compressed: true,
|
|
212
|
+
_completed: true,
|
|
213
|
+
_lastUpdated: new Date().toISOString(),
|
|
214
|
+
payload: compressedBuffer
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Write immediately
|
|
218
|
+
await docRef.set(compressedPayload, { merge: true });
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
totalSize: compressedBuffer.length,
|
|
222
|
+
isSharded: false,
|
|
223
|
+
shardCount: 1,
|
|
224
|
+
nextShardIndex: startShardIndex
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (compErr) {
|
|
229
|
+
logger.log('WARN', `[Compression] Failed to compress ${name}. Falling back to standard sharding.`, compErr);
|
|
230
|
+
}
|
|
231
|
+
// --- END COMPRESSION STRATEGY ---
|
|
232
|
+
|
|
173
233
|
const strategies = [
|
|
174
234
|
{ bytes: 900 * 1024, keys: null },
|
|
175
235
|
{ bytes: 450 * 1024, keys: 10000 },
|
|
@@ -24,11 +24,11 @@ class HeuristicValidator {
|
|
|
24
24
|
const sampleSize = Math.min(totalItems, 100);
|
|
25
25
|
const step = Math.floor(totalItems / sampleSize);
|
|
26
26
|
|
|
27
|
-
let zeroCount
|
|
28
|
-
let nullCount
|
|
29
|
-
let nanCount
|
|
27
|
+
let zeroCount = 0;
|
|
28
|
+
let nullCount = 0;
|
|
29
|
+
let nanCount = 0;
|
|
30
30
|
let emptyVectorCount = 0;
|
|
31
|
-
let analyzedCount
|
|
31
|
+
let analyzedCount = 0;
|
|
32
32
|
|
|
33
33
|
const numericValues = [];
|
|
34
34
|
|
|
@@ -82,9 +82,9 @@ class HeuristicValidator {
|
|
|
82
82
|
|
|
83
83
|
// Default Thresholds
|
|
84
84
|
let thresholds = {
|
|
85
|
-
maxZeroPct:
|
|
86
|
-
maxNullPct:
|
|
87
|
-
maxNanPct:
|
|
85
|
+
maxZeroPct: overrides.maxZeroPct ?? 99,
|
|
86
|
+
maxNullPct: overrides.maxNullPct ?? 90,
|
|
87
|
+
maxNanPct: overrides.maxNanPct ?? 0,
|
|
88
88
|
maxFlatlinePct: overrides.maxFlatlinePct ?? 95
|
|
89
89
|
};
|
|
90
90
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* REFACTORED: Strict 5-category reporting with date-based exclusion logic.
|
|
5
5
|
* UPDATED: Added transactional locking to prevent duplicate reports on concurrent cold starts.
|
|
6
6
|
* UPDATED: Adds 'pass' number to detail records for better waterfall visibility.
|
|
7
|
+
* FIXED: Ensures 'latest' pointer updates even if detail writes fail.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
const { analyzeDateExecution } = require('../WorkflowOrchestrator');
|
|
@@ -242,15 +243,34 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
|
|
|
242
243
|
scanRange: `${datesToCheck[0]} to ${datesToCheck[datesToCheck.length-1]}`
|
|
243
244
|
};
|
|
244
245
|
|
|
246
|
+
// 1. Write the main report header (Specific Version)
|
|
245
247
|
const reportRef = db.collection('computation_build_records').doc(buildId);
|
|
246
248
|
await reportRef.set(reportHeader);
|
|
247
249
|
|
|
250
|
+
// 2. Write Details (Protected)
|
|
251
|
+
// [FIX] We wrap this in try-catch so that if the massive detail write fails,
|
|
252
|
+
// we still update the 'latest' pointer to the new version.
|
|
253
|
+
let detailsSuccess = true;
|
|
248
254
|
if (detailWrites.length > 0) {
|
|
249
255
|
logger.log('INFO', `[BuildReporter] Writing ${detailWrites.length} detail records...`);
|
|
250
|
-
|
|
256
|
+
try {
|
|
257
|
+
await commitBatchInChunks(config, dependencies, detailWrites, 'BuildReportDetails');
|
|
258
|
+
} catch (detailErr) {
|
|
259
|
+
detailsSuccess = false;
|
|
260
|
+
logger.log('ERROR', `[BuildReporter] ⚠️ Failed to write all details, but Report Header is saved.`, detailErr);
|
|
261
|
+
}
|
|
251
262
|
}
|
|
252
263
|
|
|
253
|
-
|
|
264
|
+
// 3. Update 'latest' pointer
|
|
265
|
+
// This now runs even if details failed, preventing the version mismatch bug.
|
|
266
|
+
const latestMetadata = {
|
|
267
|
+
...reportHeader,
|
|
268
|
+
note: detailsSuccess
|
|
269
|
+
? "Latest build report pointer (See subcollection for details)."
|
|
270
|
+
: "Latest build report pointer (WARNING: Partial detail records due to write error)."
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
await db.collection('computation_build_records').doc('latest').set(latestMetadata);
|
|
254
274
|
|
|
255
275
|
logger.log('SUCCESS', `[BuildReporter] Report ${buildId} saved. Re-runs: ${totalReRun}, New: ${totalRun}.`);
|
|
256
276
|
|
|
@@ -4,7 +4,22 @@
|
|
|
4
4
|
* --- NEW: Added streamPortfolioData async generator ---
|
|
5
5
|
* --- FIXED: streamPortfolioData and streamHistoryData now accept optional 'providedRefs' ---
|
|
6
6
|
* --- UPDATE: Added Smart Shard Indexing for specific ticker lookups ---
|
|
7
|
+
* --- UPDATE: Added GZIP Decompression Support for robust data loading ---
|
|
7
8
|
*/
|
|
9
|
+
const zlib = require('zlib'); // [NEW]
|
|
10
|
+
|
|
11
|
+
// [NEW] Helper for decompressing any doc if needed
|
|
12
|
+
function tryDecompress(data) {
|
|
13
|
+
if (data && data._compressed === true && data.payload) {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(zlib.gunzipSync(data.payload).toString());
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.error('[DataLoader] Decompression failed', e);
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
8
23
|
|
|
9
24
|
/** --- Data Loader Sub-Pipes (Stateless, Dependency-Injection) --- */
|
|
10
25
|
|
|
@@ -39,7 +54,10 @@ async function loadDataByRefs(config, deps, refs) {
|
|
|
39
54
|
const snapshots = await withRetry(() => db.getAll(...batchRefs), `getAll(batch ${Math.floor(i / batchSize)})`);
|
|
40
55
|
for (const doc of snapshots) {
|
|
41
56
|
if (!doc.exists) continue;
|
|
42
|
-
const
|
|
57
|
+
const rawData = doc.data();
|
|
58
|
+
// [UPDATED] Decompress if needed
|
|
59
|
+
const data = tryDecompress(rawData);
|
|
60
|
+
|
|
43
61
|
if (data && typeof data === 'object') Object.assign(mergedPortfolios, data);
|
|
44
62
|
else logger.log('WARN', `Doc ${doc.id} exists but data is not an object`, data);
|
|
45
63
|
}
|
|
@@ -68,7 +86,8 @@ async function loadDailyInsights(config, deps, dateString) {
|
|
|
68
86
|
const docSnap = await withRetry(() => docRef.get(), `getInsights(${dateString})`);
|
|
69
87
|
if (!docSnap.exists) { logger.log('WARN', `Insights not found for ${dateString}`); return null; }
|
|
70
88
|
logger.log('TRACE', `Successfully loaded insights for ${dateString}`);
|
|
71
|
-
|
|
89
|
+
// [UPDATED] Decompress
|
|
90
|
+
return tryDecompress(docSnap.data());
|
|
72
91
|
} catch (error) {
|
|
73
92
|
logger.log('ERROR', `Failed to load daily insights for ${dateString}`, { errorMessage: error.message });
|
|
74
93
|
return null;
|
|
@@ -86,7 +105,10 @@ async function loadDailySocialPostInsights(config, deps, dateString) {
|
|
|
86
105
|
const querySnapshot = await withRetry(() => postsCollectionRef.get(), `getSocialPosts(${dateString})`);
|
|
87
106
|
if (querySnapshot.empty) { logger.log('WARN', `No social post insights for ${dateString}`); return null; }
|
|
88
107
|
const postsMap = {};
|
|
89
|
-
querySnapshot.forEach(doc => {
|
|
108
|
+
querySnapshot.forEach(doc => {
|
|
109
|
+
// [UPDATED] Decompress individual posts if needed
|
|
110
|
+
postsMap[doc.id] = tryDecompress(doc.data());
|
|
111
|
+
});
|
|
90
112
|
logger.log('TRACE', `Loaded ${Object.keys(postsMap).length} social post insights`);
|
|
91
113
|
return postsMap;
|
|
92
114
|
} catch (error) {
|
|
@@ -168,12 +190,6 @@ async function getPriceShardRefs(config, deps) {
|
|
|
168
190
|
* when only specific tickers are needed.
|
|
169
191
|
*/
|
|
170
192
|
|
|
171
|
-
/**
|
|
172
|
-
* Ensures the Price Shard Index exists. If not, builds it by scanning all shards.
|
|
173
|
-
* @param {object} config
|
|
174
|
-
* @param {object} deps
|
|
175
|
-
* @returns {Promise<Object>} The lookup map { "instrumentId": "shardDocId" }
|
|
176
|
-
*/
|
|
177
193
|
/**
|
|
178
194
|
* Ensures the Price Shard Index exists. If not, builds it by scanning all shards.
|
|
179
195
|
* [FIX] Added TTL check to ensure new instruments are discovered.
|
|
@@ -205,7 +221,10 @@ async function ensurePriceShardIndex(config, deps) {
|
|
|
205
221
|
|
|
206
222
|
snapshot.forEach(doc => {
|
|
207
223
|
shardCount++;
|
|
208
|
-
|
|
224
|
+
// [UPDATED] Robustly handle compressed shards during indexing
|
|
225
|
+
const rawData = doc.data();
|
|
226
|
+
const data = tryDecompress(rawData);
|
|
227
|
+
|
|
209
228
|
if (data.history) {
|
|
210
229
|
Object.keys(data.history).forEach(instId => {
|
|
211
230
|
index[instId] = doc.id;
|
|
@@ -273,4 +292,4 @@ module.exports = {
|
|
|
273
292
|
getPriceShardRefs,
|
|
274
293
|
ensurePriceShardIndex,
|
|
275
294
|
getRelevantShardRefs
|
|
276
|
-
};
|
|
295
|
+
};
|