bulltrackers-module 1.0.732 → 1.0.733
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/orchestrator/index.js +19 -17
- package/index.js +8 -29
- package/package.json +1 -1
- package/functions/computation-system/WorkflowOrchestrator.js +0 -213
- package/functions/computation-system/config/monitoring_config.js +0 -31
- package/functions/computation-system/config/validation_overrides.js +0 -10
- package/functions/computation-system/context/ContextFactory.js +0 -143
- package/functions/computation-system/context/ManifestBuilder.js +0 -379
- package/functions/computation-system/data/AvailabilityChecker.js +0 -236
- package/functions/computation-system/data/CachedDataLoader.js +0 -325
- package/functions/computation-system/data/DependencyFetcher.js +0 -455
- package/functions/computation-system/executors/MetaExecutor.js +0 -279
- package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
- package/functions/computation-system/executors/StandardExecutor.js +0 -465
- package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
- package/functions/computation-system/helpers/computation_worker.js +0 -375
- package/functions/computation-system/helpers/monitor.js +0 -64
- package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
- package/functions/computation-system/layers/extractors.js +0 -1097
- package/functions/computation-system/layers/index.js +0 -40
- package/functions/computation-system/layers/mathematics.js +0 -522
- package/functions/computation-system/layers/profiling.js +0 -537
- package/functions/computation-system/layers/validators.js +0 -170
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
- package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
- package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
- package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
- package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
- package/functions/computation-system/logger/logger.js +0 -297
- package/functions/computation-system/persistence/ContractValidator.js +0 -81
- package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
- package/functions/computation-system/persistence/ResultCommitter.js +0 -283
- package/functions/computation-system/persistence/ResultsValidator.js +0 -130
- package/functions/computation-system/persistence/RunRecorder.js +0 -142
- package/functions/computation-system/persistence/StatusRepository.js +0 -52
- package/functions/computation-system/reporter_epoch.js +0 -6
- package/functions/computation-system/scripts/UpdateContracts.js +0 -128
- package/functions/computation-system/services/SnapshotService.js +0 -148
- package/functions/computation-system/simulation/Fabricator.js +0 -285
- package/functions/computation-system/simulation/SeededRandom.js +0 -41
- package/functions/computation-system/simulation/SimRunner.js +0 -51
- package/functions/computation-system/system_epoch.js +0 -2
- package/functions/computation-system/tools/BuildReporter.js +0 -531
- package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
- package/functions/computation-system/tools/DeploymentValidator.js +0 -536
- package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
- package/functions/computation-system/topology/HashManager.js +0 -55
- package/functions/computation-system/topology/ManifestLoader.js +0 -47
- package/functions/computation-system/utils/data_loader.js +0 -675
- package/functions/computation-system/utils/schema_capture.js +0 -121
- package/functions/computation-system/utils/utils.js +0 -188
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {
|
|
3
|
-
* type: uploaded file
|
|
4
|
-
* fileName: computation-system/data/CachedDataLoader.js
|
|
5
|
-
* }
|
|
6
|
-
*/
|
|
7
|
-
const {
|
|
8
|
-
loadDailyInsights,
|
|
9
|
-
loadDailySocialPostInsights,
|
|
10
|
-
getRelevantShardRefs,
|
|
11
|
-
getPriceShardRefs,
|
|
12
|
-
loadVerificationProfiles,
|
|
13
|
-
loadPopularInvestorRankings,
|
|
14
|
-
loadPIRatings,
|
|
15
|
-
loadPIPageViews,
|
|
16
|
-
loadWatchlistMembership: loadWatchlistMembershipData,
|
|
17
|
-
loadPIAlertHistory,
|
|
18
|
-
loadPIWatchlistData,
|
|
19
|
-
loadPopularInvestorMasterList
|
|
20
|
-
} = require('../utils/data_loader');
|
|
21
|
-
const { getAvailabilityWindow } = require('../data/AvailabilityChecker');
|
|
22
|
-
const zlib = require('zlib');
|
|
23
|
-
|
|
24
|
-
// [NEW] Mapping of Loader Methods to Availability Flags
|
|
25
|
-
const LOADER_DEPENDENCY_MAP = {
|
|
26
|
-
'loadRankings': 'piRankings',
|
|
27
|
-
'loadRatings': 'piRatings',
|
|
28
|
-
'loadPageViews': 'piPageViews',
|
|
29
|
-
'loadWatchlistMembership': 'watchlistMembership',
|
|
30
|
-
'loadAlertHistory': 'piAlertHistory',
|
|
31
|
-
'loadInsights': 'hasInsights',
|
|
32
|
-
'loadSocial': 'hasSocial'
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// [NEW] Mapping of Loader Methods to Collection Config Keys / Defaults
|
|
36
|
-
// This allows us to construct references for Batch Reading without executing the opaque loader functions.
|
|
37
|
-
const LOADER_COLLECTION_MAP = {
|
|
38
|
-
'loadRatings': { configKey: 'piRatingsCollection', default: 'PIRatingsData' },
|
|
39
|
-
'loadPageViews': { configKey: 'piPageViewsCollection', default: 'PIPageViewsData' },
|
|
40
|
-
'loadWatchlistMembership': { configKey: 'watchlistMembershipCollection', default: 'WatchlistMembershipData' },
|
|
41
|
-
'loadAlertHistory': { configKey: 'piAlertHistoryCollection', default: 'PIAlertHistoryData' },
|
|
42
|
-
'loadInsights': { configKey: 'insightsCollectionName', default: 'daily_instrument_insights' },
|
|
43
|
-
'loadRankings': { configKey: 'popularInvestorRankingsCollection', default: 'popular_investor_rankings' }
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
class CachedDataLoader {
|
|
47
|
-
constructor(config, dependencies) {
|
|
48
|
-
this.config = config;
|
|
49
|
-
this.deps = dependencies;
|
|
50
|
-
this.cache = {
|
|
51
|
-
mappings: null,
|
|
52
|
-
insights: new Map(),
|
|
53
|
-
social: new Map(),
|
|
54
|
-
verifications: null,
|
|
55
|
-
rankings: new Map(),
|
|
56
|
-
ratings: new Map(),
|
|
57
|
-
pageViews: new Map(),
|
|
58
|
-
watchlistMembership: new Map(),
|
|
59
|
-
alertHistory: new Map(),
|
|
60
|
-
piWatchlistData: new Map(),
|
|
61
|
-
piMasterList: null
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
_tryDecompress(data) {
|
|
66
|
-
if (data && data._compressed === true && data.payload) {
|
|
67
|
-
try {
|
|
68
|
-
return JSON.parse(zlib.gunzipSync(data.payload).toString());
|
|
69
|
-
} catch (e) {
|
|
70
|
-
console.error('[CachedDataLoader] Decompression failed', e);
|
|
71
|
-
return {};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return data;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ... [Existing single-day load methods remain unchanged] ...
|
|
78
|
-
async loadMappings() {
|
|
79
|
-
if (this.cache.mappings) return this.cache.mappings;
|
|
80
|
-
const { calculationUtils } = this.deps;
|
|
81
|
-
this.cache.mappings = await calculationUtils.loadInstrumentMappings();
|
|
82
|
-
return this.cache.mappings;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async loadInsights(dateStr) {
|
|
86
|
-
if (this.cache.insights.has(dateStr)) return this.cache.insights.get(dateStr);
|
|
87
|
-
const collectionName = this.config.insightsCollectionName || 'daily_instrument_insights';
|
|
88
|
-
const path = `${collectionName}/${dateStr}`;
|
|
89
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'insights' from: ${path}`);
|
|
90
|
-
const promise = loadDailyInsights(this.config, this.deps, dateStr);
|
|
91
|
-
this.cache.insights.set(dateStr, promise);
|
|
92
|
-
return promise;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async loadSocial(dateStr) {
|
|
96
|
-
if (this.cache.social.has(dateStr)) return this.cache.social.get(dateStr);
|
|
97
|
-
const collectionName = this.config.socialInsightsCollection || 'daily_social_insights';
|
|
98
|
-
const path = `${collectionName}/${dateStr}`;
|
|
99
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'social' from: ${path}`);
|
|
100
|
-
const promise = loadDailySocialPostInsights(this.config, this.deps, dateStr);
|
|
101
|
-
this.cache.social.set(dateStr, promise);
|
|
102
|
-
return promise;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async loadVerifications() {
|
|
106
|
-
if (this.cache.verifications) return this.cache.verifications;
|
|
107
|
-
const collectionName = this.config.verificationsCollection || 'verification_profiles';
|
|
108
|
-
const path = `${collectionName}`;
|
|
109
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'verifications' from: ${path}`);
|
|
110
|
-
const verifications = await loadVerificationProfiles(this.config, this.deps);
|
|
111
|
-
this.cache.verifications = verifications;
|
|
112
|
-
return verifications;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async loadRankings(dateStr) {
|
|
116
|
-
if (this.cache.rankings.has(dateStr)) return this.cache.rankings.get(dateStr);
|
|
117
|
-
const collectionName = this.config.popularInvestorRankingsCollection || 'popular_investor_rankings';
|
|
118
|
-
const path = `${collectionName}/${dateStr}`;
|
|
119
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'rankings' from: ${path}`);
|
|
120
|
-
const promise = loadPopularInvestorRankings(this.config, this.deps, dateStr);
|
|
121
|
-
this.cache.rankings.set(dateStr, promise);
|
|
122
|
-
return promise;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async loadRatings(dateStr) {
|
|
126
|
-
if (this.cache.ratings.has(dateStr)) return this.cache.ratings.get(dateStr);
|
|
127
|
-
const collectionName = this.config.piRatingsCollection || 'PIRatingsData';
|
|
128
|
-
const path = `${collectionName}/${dateStr}`;
|
|
129
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'ratings' from: ${path}`);
|
|
130
|
-
const promise = loadPIRatings(this.config, this.deps, dateStr);
|
|
131
|
-
this.cache.ratings.set(dateStr, promise);
|
|
132
|
-
return promise;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async loadPageViews(dateStr) {
|
|
136
|
-
if (this.cache.pageViews.has(dateStr)) return this.cache.pageViews.get(dateStr);
|
|
137
|
-
const collectionName = this.config.piPageViewsCollection || 'PIPageViewsData';
|
|
138
|
-
const path = `${collectionName}/${dateStr}`;
|
|
139
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'pageViews' from: ${path}`);
|
|
140
|
-
const promise = loadPIPageViews(this.config, this.deps, dateStr);
|
|
141
|
-
this.cache.pageViews.set(dateStr, promise);
|
|
142
|
-
return promise;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async loadWatchlistMembership(dateStr) {
|
|
146
|
-
if (this.cache.watchlistMembership.has(dateStr)) return this.cache.watchlistMembership.get(dateStr);
|
|
147
|
-
const collectionName = this.config.watchlistMembershipCollection || 'WatchlistMembershipData';
|
|
148
|
-
const path = `${collectionName}/${dateStr}`;
|
|
149
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'watchlistMembership' from: ${path}`);
|
|
150
|
-
const promise = loadWatchlistMembershipData(this.config, this.deps, dateStr);
|
|
151
|
-
this.cache.watchlistMembership.set(dateStr, promise);
|
|
152
|
-
return promise;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async loadAlertHistory(dateStr) {
|
|
156
|
-
if (this.cache.alertHistory.has(dateStr)) return this.cache.alertHistory.get(dateStr);
|
|
157
|
-
const collectionName = this.config.piAlertHistoryCollection || 'PIAlertHistoryData';
|
|
158
|
-
const path = `${collectionName}/${dateStr}`;
|
|
159
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Loading Root Data 'alertHistory' from: ${path}`);
|
|
160
|
-
const promise = loadPIAlertHistory(this.config, this.deps, dateStr);
|
|
161
|
-
this.cache.alertHistory.set(dateStr, promise);
|
|
162
|
-
return promise;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async loadPIWatchlistData(piCid) {
|
|
166
|
-
const piCidStr = String(piCid);
|
|
167
|
-
if (this.cache.piWatchlistData.has(piCidStr)) return this.cache.piWatchlistData.get(piCidStr);
|
|
168
|
-
const promise = loadPIWatchlistData(this.config, this.deps, piCidStr);
|
|
169
|
-
this.cache.piWatchlistData.set(piCidStr, promise);
|
|
170
|
-
return promise;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async getPriceShardReferences() {
|
|
174
|
-
return getPriceShardRefs(this.config, this.deps);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async getSpecificPriceShardReferences(targetInstrumentIds) {
|
|
178
|
-
return getRelevantShardRefs(this.config, this.deps, targetInstrumentIds);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async loadPriceShard(docRef) {
|
|
182
|
-
try {
|
|
183
|
-
const snap = await docRef.get();
|
|
184
|
-
if (!snap.exists) return {};
|
|
185
|
-
return this._tryDecompress(snap.data());
|
|
186
|
-
} catch (e) {
|
|
187
|
-
console.error(`Error loading shard ${docRef.path}:`, e);
|
|
188
|
-
return {};
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async loadPIMasterList() {
|
|
193
|
-
if (this.cache.piMasterList) return this.cache.piMasterList;
|
|
194
|
-
const data = await loadPopularInvestorMasterList(this.config, this.deps);
|
|
195
|
-
this.cache.piMasterList = data;
|
|
196
|
-
return data;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// --- [UPDATED] Batched Series Loading Logic ---
|
|
200
|
-
/**
|
|
201
|
-
* Optimistically loads a series of root data over a lookback period using Batch Reads.
|
|
202
|
-
* 1. Checks Availability Index (Range Query).
|
|
203
|
-
* 2. Constructs Refs for all existing dates.
|
|
204
|
-
* 3. Fetches all in ONE db.getAll() request.
|
|
205
|
-
*/
|
|
206
|
-
async loadSeries(loaderMethod, dateStr, lookbackDays) {
|
|
207
|
-
if (!this[loaderMethod]) throw new Error(`[CachedDataLoader] Unknown method ${loaderMethod}`);
|
|
208
|
-
|
|
209
|
-
// 1. Calculate Date Range
|
|
210
|
-
const endDate = new Date(dateStr);
|
|
211
|
-
const startDate = new Date(endDate);
|
|
212
|
-
startDate.setUTCDate(startDate.getUTCDate() - (lookbackDays - 1));
|
|
213
|
-
|
|
214
|
-
const startStr = startDate.toISOString().slice(0, 10);
|
|
215
|
-
const endStr = endDate.toISOString().slice(0, 10);
|
|
216
|
-
|
|
217
|
-
// 2. Pre-flight: Fetch Availability Window
|
|
218
|
-
let availabilityMap = new Map();
|
|
219
|
-
try {
|
|
220
|
-
availabilityMap = await getAvailabilityWindow(this.deps, startStr, endStr);
|
|
221
|
-
} catch (e) {
|
|
222
|
-
console.warn(`[CachedDataLoader] Availability check failed for series. Falling back to optimistic batch fetch. Error: ${e.message}`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// 3. Identify Collection & Required Flag
|
|
226
|
-
const collectionInfo = LOADER_COLLECTION_MAP[loaderMethod];
|
|
227
|
-
const requiredFlag = LOADER_DEPENDENCY_MAP[loaderMethod];
|
|
228
|
-
|
|
229
|
-
if (!collectionInfo) {
|
|
230
|
-
// Fallback for methods not in the batch map (use legacy parallel loop)
|
|
231
|
-
return this._loadSeriesLegacy(loaderMethod, dateStr, lookbackDays);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const collectionName = this.config[collectionInfo.configKey] || collectionInfo.default;
|
|
235
|
-
const batchRefs = [];
|
|
236
|
-
const dateKeyMap = []; // Keep track of which date corresponds to which ref index
|
|
237
|
-
|
|
238
|
-
// 4. Build Batch References
|
|
239
|
-
for (let i = 0; i < lookbackDays; i++) {
|
|
240
|
-
const d = new Date(endDate);
|
|
241
|
-
d.setUTCDate(d.getUTCDate() - i);
|
|
242
|
-
const dString = d.toISOString().slice(0, 10);
|
|
243
|
-
|
|
244
|
-
// Check Availability
|
|
245
|
-
const dayStatus = availabilityMap.get(dString);
|
|
246
|
-
let shouldFetch = false;
|
|
247
|
-
|
|
248
|
-
if (availabilityMap.size > 0) {
|
|
249
|
-
// If index exists, trust it
|
|
250
|
-
if (dayStatus && (!requiredFlag || dayStatus[requiredFlag])) {
|
|
251
|
-
shouldFetch = true;
|
|
252
|
-
}
|
|
253
|
-
} else {
|
|
254
|
-
// If index check failed/empty, try optimistically
|
|
255
|
-
shouldFetch = true;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (shouldFetch) {
|
|
259
|
-
const ref = this.deps.db.collection(collectionName).doc(dString);
|
|
260
|
-
batchRefs.push(ref);
|
|
261
|
-
dateKeyMap.push(dString);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 5. Execute Batch Read
|
|
266
|
-
const results = {};
|
|
267
|
-
let foundCount = 0;
|
|
268
|
-
|
|
269
|
-
if (batchRefs.length > 0) {
|
|
270
|
-
// Log summary of all paths being loaded
|
|
271
|
-
const paths = batchRefs.map(ref => ref.path).join(', ');
|
|
272
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] 📂 Batch loading ${batchRefs.length} documents for '${loaderMethod}': ${paths}`);
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
const snapshots = await this.deps.db.getAll(...batchRefs);
|
|
276
|
-
|
|
277
|
-
snapshots.forEach((snap, index) => {
|
|
278
|
-
if (snap.exists) {
|
|
279
|
-
const dString = dateKeyMap[index];
|
|
280
|
-
const rawData = snap.data();
|
|
281
|
-
|
|
282
|
-
// Decompress and clean data
|
|
283
|
-
const decompressed = this._tryDecompress(rawData);
|
|
284
|
-
|
|
285
|
-
// Handle standard data shapes (removing metadata fields if necessary)
|
|
286
|
-
// Most root data loaders return the full object, so we do too.
|
|
287
|
-
// Specific logic from data_loader.js (like stripping 'date' key) is handled here generically
|
|
288
|
-
// or by the consumer. For series data, returning the whole object is usually safer.
|
|
289
|
-
|
|
290
|
-
// Special handling for cleaner output (mimicking data_loader.js logic)
|
|
291
|
-
if (loaderMethod === 'loadRatings' || loaderMethod === 'loadPageViews' ||
|
|
292
|
-
loaderMethod === 'loadWatchlistMembership' || loaderMethod === 'loadAlertHistory') {
|
|
293
|
-
const { date, lastUpdated, ...cleanData } = decompressed;
|
|
294
|
-
results[dString] = cleanData;
|
|
295
|
-
} else if (loaderMethod === 'loadRankings') {
|
|
296
|
-
results[dString] = decompressed.Items || [];
|
|
297
|
-
} else {
|
|
298
|
-
results[dString] = decompressed;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
foundCount++;
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
} catch (err) {
|
|
305
|
-
console.warn(`[CachedDataLoader] Batch fetch failed for ${loaderMethod}: ${err.message}. Falling back to individual fetches.`);
|
|
306
|
-
return this._loadSeriesLegacy(loaderMethod, dateStr, lookbackDays);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const summary = {
|
|
311
|
-
dates: Object.keys(results).sort(),
|
|
312
|
-
data: results,
|
|
313
|
-
found: foundCount,
|
|
314
|
-
requested: lookbackDays
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
this.deps.logger?.log('INFO', `[CachedDataLoader] ✅ Loaded ${foundCount}/${lookbackDays} dates for '${loaderMethod}' (found: ${summary.dates.join(', ') || 'none'})`);
|
|
318
|
-
|
|
319
|
-
return summary;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Legacy Fallback: Loads series using parallel promises (for custom/unmapped loaders)
|
|
324
|
-
*/
|
|
325
|
-
async _loadSeriesLegacy(loaderMethod, dateStr, lookbackDays) {
|
|
326
|
-
const results = {};
|
|
327
|
-
const promises = [];
|
|
328
|
-
const endDate = new Date(dateStr);
|
|
329
|
-
|
|
330
|
-
for (let i = 0; i < lookbackDays; i++) {
|
|
331
|
-
const d = new Date(endDate);
|
|
332
|
-
d.setUTCDate(d.getUTCDate() - i);
|
|
333
|
-
const dString = d.toISOString().slice(0, 10);
|
|
334
|
-
|
|
335
|
-
// Log path for legacy loader (will be logged by individual load methods)
|
|
336
|
-
promises.push(
|
|
337
|
-
this[loaderMethod](dString)
|
|
338
|
-
.then(data => ({ date: dString, data }))
|
|
339
|
-
.catch(() => ({ date: dString, data: null }))
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const loaded = await Promise.all(promises);
|
|
344
|
-
loaded.forEach(({ date, data }) => {
|
|
345
|
-
if (data) results[date] = data;
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
return {
|
|
349
|
-
dates: Object.keys(results).sort(),
|
|
350
|
-
data: results,
|
|
351
|
-
found: Object.keys(results).length,
|
|
352
|
-
requested: lookbackDays
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
module.exports = { CachedDataLoader };
|