bulltrackers-module 1.0.631 → 1.0.633
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/context/ContextFactory.js +39 -18
- package/functions/computation-system/data/AvailabilityChecker.js +27 -11
- package/functions/computation-system/data/DependencyFetcher.js +125 -178
- package/functions/computation-system/executors/MetaExecutor.js +47 -98
- package/functions/computation-system/executors/StandardExecutor.js +11 -25
- package/functions/computation-system/persistence/ResultCommitter.js +4 -3
- package/functions/computation-system/utils/data_loader.js +105 -143
- package/package.json +1 -1
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* UPDATED: Sends 'isInitialWrite: true' for robust cleanup.
|
|
6
6
|
* UPDATED: Support for historical rankings in Meta Context.
|
|
7
7
|
* UPDATED: Added support for loading Series Data (Root & Results) for lookbacks.
|
|
8
|
+
* UPDATED: Builds Manifest Lookup for DependencyFetcher.
|
|
8
9
|
*/
|
|
9
10
|
const { normalizeName } = require('../utils/utils');
|
|
10
11
|
const { CachedDataLoader } = require('../data/CachedDataLoader');
|
|
11
12
|
const { ContextFactory } = require('../context/ContextFactory');
|
|
12
13
|
const { commitResults } = require('../persistence/ResultCommitter');
|
|
13
|
-
const { fetchResultSeries } = require('../data/DependencyFetcher');
|
|
14
|
+
const { fetchResultSeries, fetchDependencies } = require('../data/DependencyFetcher');
|
|
15
|
+
const { getManifest } = require('../topology/ManifestLoader');
|
|
14
16
|
|
|
15
17
|
class MetaExecutor {
|
|
16
18
|
static async run(date, calcs, passName, config, deps, rootData, fetchedDeps, previousFetchedDeps) {
|
|
@@ -18,6 +20,12 @@ class MetaExecutor {
|
|
|
18
20
|
const { logger, db } = deps;
|
|
19
21
|
const loader = new CachedDataLoader(config, deps);
|
|
20
22
|
|
|
23
|
+
// --- [FIXED] Build Global Manifest Lookup using getManifest ---
|
|
24
|
+
// getManifest is typically cached, so this is cheap.
|
|
25
|
+
const allManifests = getManifest(config.productLines, config.calculationsDirectory, deps);
|
|
26
|
+
const manifestLookup = {};
|
|
27
|
+
allManifests.forEach(m => { manifestLookup[normalizeName(m.name)] = m.category; });
|
|
28
|
+
|
|
21
29
|
// [FIX] Check if any meta calculation needs history
|
|
22
30
|
const needsHistory = calcs.some(c => c.isHistorical);
|
|
23
31
|
let rankingsYesterday = null;
|
|
@@ -47,82 +55,64 @@ class MetaExecutor {
|
|
|
47
55
|
let ratings = null, pageViews = null, watchlistMembership = null, alertHistory = null;
|
|
48
56
|
if (needsNewRootData) {
|
|
49
57
|
const loadPromises = [];
|
|
58
|
+
|
|
59
|
+
// Ratings
|
|
50
60
|
if (calcs.some(c => c.rootDataDependencies?.includes('ratings'))) {
|
|
51
61
|
loadPromises.push(loader.loadRatings(dStr).then(r => { ratings = r; }).catch(e => {
|
|
52
|
-
// Only catch if ALL calcs allow missing roots
|
|
53
62
|
const allAllowMissing = calcs.every(c => {
|
|
54
63
|
const needsRatings = c.rootDataDependencies?.includes('ratings');
|
|
55
64
|
return !needsRatings || c.canHaveMissingRoots === true;
|
|
56
65
|
});
|
|
57
|
-
if (allAllowMissing) {
|
|
58
|
-
ratings = null;
|
|
59
|
-
} else {
|
|
60
|
-
throw e; // Re-throw if any calc requires this data
|
|
61
|
-
}
|
|
66
|
+
if (allAllowMissing) { ratings = null; } else { throw e; }
|
|
62
67
|
}));
|
|
63
68
|
}
|
|
69
|
+
|
|
70
|
+
// PageViews
|
|
64
71
|
if (calcs.some(c => c.rootDataDependencies?.includes('pageViews'))) {
|
|
65
72
|
loadPromises.push(loader.loadPageViews(dStr).then(pv => { pageViews = pv; }).catch(e => {
|
|
66
73
|
const allAllowMissing = calcs.every(c => {
|
|
67
74
|
const needsPageViews = c.rootDataDependencies?.includes('pageViews');
|
|
68
75
|
return !needsPageViews || c.canHaveMissingRoots === true;
|
|
69
76
|
});
|
|
70
|
-
if (allAllowMissing) {
|
|
71
|
-
pageViews = null;
|
|
72
|
-
} else {
|
|
73
|
-
throw e;
|
|
74
|
-
}
|
|
77
|
+
if (allAllowMissing) { pageViews = null; } else { throw e; }
|
|
75
78
|
}));
|
|
76
79
|
}
|
|
80
|
+
|
|
81
|
+
// Watchlist
|
|
77
82
|
if (calcs.some(c => c.rootDataDependencies?.includes('watchlist'))) {
|
|
78
83
|
loadPromises.push(loader.loadWatchlistMembership(dStr).then(w => { watchlistMembership = w; }).catch(e => {
|
|
79
84
|
const allAllowMissing = calcs.every(c => {
|
|
80
85
|
const needsWatchlist = c.rootDataDependencies?.includes('watchlist');
|
|
81
86
|
return !needsWatchlist || c.canHaveMissingRoots === true;
|
|
82
87
|
});
|
|
83
|
-
if (allAllowMissing) {
|
|
84
|
-
watchlistMembership = null;
|
|
85
|
-
} else {
|
|
86
|
-
throw e;
|
|
87
|
-
}
|
|
88
|
+
if (allAllowMissing) { watchlistMembership = null; } else { throw e; }
|
|
88
89
|
}));
|
|
89
90
|
}
|
|
91
|
+
|
|
92
|
+
// Alerts
|
|
90
93
|
if (calcs.some(c => c.rootDataDependencies?.includes('alerts'))) {
|
|
91
94
|
loadPromises.push(loader.loadAlertHistory(dStr).then(a => { alertHistory = a; }).catch(e => {
|
|
92
95
|
const allAllowMissing = calcs.every(c => {
|
|
93
96
|
const needsAlerts = c.rootDataDependencies?.includes('alerts');
|
|
94
97
|
return !needsAlerts || c.canHaveMissingRoots === true;
|
|
95
98
|
});
|
|
96
|
-
if (allAllowMissing) {
|
|
97
|
-
alertHistory = null;
|
|
98
|
-
} else {
|
|
99
|
-
throw e;
|
|
100
|
-
}
|
|
99
|
+
if (allAllowMissing) { alertHistory = null; } else { throw e; }
|
|
101
100
|
}));
|
|
102
101
|
}
|
|
102
|
+
|
|
103
103
|
await Promise.all(loadPromises);
|
|
104
104
|
|
|
105
105
|
// [FIX] Enforce canHaveMissingRoots - validate after loading
|
|
106
106
|
for (const c of calcs) {
|
|
107
107
|
const deps = c.rootDataDependencies || [];
|
|
108
108
|
const canSkip = c.canHaveMissingRoots === true;
|
|
109
|
-
|
|
110
|
-
// Helper to check if a specific root is missing
|
|
111
109
|
const isMissing = (key, val) => deps.includes(key) && (val === null || val === undefined);
|
|
112
110
|
|
|
113
111
|
if (!canSkip) {
|
|
114
|
-
if (isMissing('ratings', ratings)) {
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
if (isMissing('
|
|
118
|
-
throw new Error(`[MetaExecutor] Missing required root 'pageViews' for ${c.name}`);
|
|
119
|
-
}
|
|
120
|
-
if (isMissing('watchlist', watchlistMembership)) {
|
|
121
|
-
throw new Error(`[MetaExecutor] Missing required root 'watchlist' for ${c.name}`);
|
|
122
|
-
}
|
|
123
|
-
if (isMissing('alerts', alertHistory)) {
|
|
124
|
-
throw new Error(`[MetaExecutor] Missing required root 'alerts' for ${c.name}`);
|
|
125
|
-
}
|
|
112
|
+
if (isMissing('ratings', ratings)) throw new Error(`[MetaExecutor] Missing required root 'ratings' for ${c.name}`);
|
|
113
|
+
if (isMissing('pageViews', pageViews)) throw new Error(`[MetaExecutor] Missing required root 'pageViews' for ${c.name}`);
|
|
114
|
+
if (isMissing('watchlist', watchlistMembership)) throw new Error(`[MetaExecutor] Missing required root 'watchlist' for ${c.name}`);
|
|
115
|
+
if (isMissing('alerts', alertHistory)) throw new Error(`[MetaExecutor] Missing required root 'alerts' for ${c.name}`);
|
|
126
116
|
}
|
|
127
117
|
}
|
|
128
118
|
}
|
|
@@ -133,7 +123,6 @@ class MetaExecutor {
|
|
|
133
123
|
|
|
134
124
|
// 1. Identify requirements from manifests
|
|
135
125
|
calcs.forEach(c => {
|
|
136
|
-
// Check for Root Data Series
|
|
137
126
|
if (c.rootDataSeries) {
|
|
138
127
|
Object.entries(c.rootDataSeries).forEach(([type, conf]) => {
|
|
139
128
|
const days = typeof conf === 'object' ? conf.lookback : conf;
|
|
@@ -142,7 +131,6 @@ class MetaExecutor {
|
|
|
142
131
|
}
|
|
143
132
|
});
|
|
144
133
|
}
|
|
145
|
-
// Check for Computation Result Series
|
|
146
134
|
if (c.dependencySeries) {
|
|
147
135
|
Object.entries(c.dependencySeries).forEach(([depName, conf]) => {
|
|
148
136
|
const days = typeof conf === 'object' ? conf.lookback : conf;
|
|
@@ -163,13 +151,13 @@ class MetaExecutor {
|
|
|
163
151
|
else if (type === 'insights') loaderMethod = 'loadInsights';
|
|
164
152
|
else if (type === 'ratings') loaderMethod = 'loadRatings';
|
|
165
153
|
else if (type === 'watchlist') loaderMethod = 'loadWatchlistMembership';
|
|
166
|
-
// Add
|
|
154
|
+
// [CRITICAL UPDATE] Add rankings support for Meta lookbacks
|
|
155
|
+
else if (type === 'rankings') loaderMethod = 'loadRankings';
|
|
167
156
|
|
|
168
157
|
if (loaderMethod) {
|
|
169
158
|
logger.log('INFO', `[MetaExecutor] Loading ${days}-day series for Root Data '${type}'...`);
|
|
170
|
-
// Assume CachedDataLoader has loadSeries method (added in previous step)
|
|
171
159
|
const series = await loader.loadSeries(loaderMethod, dStr, days);
|
|
172
|
-
seriesData.root[type] = series.data;
|
|
160
|
+
seriesData.root[type] = series.data;
|
|
173
161
|
}
|
|
174
162
|
}
|
|
175
163
|
|
|
@@ -179,11 +167,10 @@ class MetaExecutor {
|
|
|
179
167
|
const maxDays = Math.max(...Object.values(dependencySeriesRequests));
|
|
180
168
|
logger.log('INFO', `[MetaExecutor] Loading up to ${maxDays}-day series for Dependencies: ${calcNamesToFetch.join(', ')}`);
|
|
181
169
|
|
|
182
|
-
//
|
|
183
|
-
const resultsSeries = await fetchResultSeries(dStr, calcNamesToFetch,
|
|
184
|
-
seriesData.results = resultsSeries;
|
|
170
|
+
// Pass manifestLookup to fetcher
|
|
171
|
+
const resultsSeries = await fetchResultSeries(dStr, calcNamesToFetch, manifestLookup, config, deps, maxDays);
|
|
172
|
+
seriesData.results = resultsSeries;
|
|
185
173
|
}
|
|
186
|
-
// ---------------------------------------------
|
|
187
174
|
|
|
188
175
|
const state = {};
|
|
189
176
|
for (const c of calcs) {
|
|
@@ -200,21 +187,17 @@ class MetaExecutor {
|
|
|
200
187
|
previousComputedDependencies: previousFetchedDeps,
|
|
201
188
|
config, deps,
|
|
202
189
|
allRankings: rankings,
|
|
203
|
-
allRankingsYesterday: rankingsYesterday,
|
|
190
|
+
allRankingsYesterday: rankingsYesterday,
|
|
204
191
|
allVerifications: verifications,
|
|
205
|
-
// [NEW] Pass New Root Data Types for Profile Metrics
|
|
206
192
|
ratings: ratings || {},
|
|
207
193
|
pageViews: pageViews || {},
|
|
208
194
|
watchlistMembership: watchlistMembership || {},
|
|
209
195
|
alertHistory: alertHistory || {},
|
|
210
|
-
// [NEW] Pass Series Data
|
|
211
196
|
seriesData
|
|
212
197
|
});
|
|
213
198
|
|
|
214
199
|
try {
|
|
215
200
|
const result = await inst.process(context);
|
|
216
|
-
// Meta results are usually wrapped in a global key or just the result object
|
|
217
|
-
// The structure below implies we store it under the date key
|
|
218
201
|
inst.results = { [dStr]: { global: result } };
|
|
219
202
|
state[c.name] = inst;
|
|
220
203
|
} catch (e) {
|
|
@@ -222,7 +205,8 @@ class MetaExecutor {
|
|
|
222
205
|
}
|
|
223
206
|
}
|
|
224
207
|
|
|
225
|
-
|
|
208
|
+
// CRITICAL FIX: Pass 'isInitialWrite: true' to ensure proper cleanup of old meta data
|
|
209
|
+
return await commitResults(state, dStr, passName, config, deps, false, { isInitialWrite: true });
|
|
226
210
|
}
|
|
227
211
|
|
|
228
212
|
static async executeOncePerDay(calcInstance, metadata, dateStr, computedDeps, prevDeps, config, deps, loader) {
|
|
@@ -233,7 +217,6 @@ class MetaExecutor {
|
|
|
233
217
|
const insights = metadata.rootDataDependencies?.includes('insights') ? { today: await loader.loadInsights(dateStr) } : null;
|
|
234
218
|
const social = metadata.rootDataDependencies?.includes('social') ? { today: await loader.loadSocial(dateStr) } : null;
|
|
235
219
|
|
|
236
|
-
// [FIX] Historical support for Batch/OncePerDay execution
|
|
237
220
|
let rankingsYesterday = null;
|
|
238
221
|
if (metadata.isHistorical) {
|
|
239
222
|
const prevDate = new Date(dateStr);
|
|
@@ -242,67 +225,37 @@ class MetaExecutor {
|
|
|
242
225
|
rankingsYesterday = await loader.loadRankings(prevStr);
|
|
243
226
|
}
|
|
244
227
|
|
|
245
|
-
// Load current rankings (often needed for ContextFactory.buildMetaContext)
|
|
246
228
|
const rankings = await loader.loadRankings(dateStr);
|
|
247
|
-
|
|
248
|
-
// [NEW] Load New Root Data Types for Profile Metrics
|
|
249
|
-
// [FIX] Enforce canHaveMissingRoots
|
|
250
229
|
const allowMissing = metadata.canHaveMissingRoots === true;
|
|
251
230
|
|
|
252
231
|
let ratings = null;
|
|
253
232
|
if (metadata.rootDataDependencies?.includes('ratings')) {
|
|
254
|
-
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'ratings' failed to load for ${metadata.name}: ${e.message}`);
|
|
258
|
-
ratings = null;
|
|
259
|
-
}
|
|
260
|
-
if (!ratings && !allowMissing) {
|
|
261
|
-
throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'ratings' is missing for ${metadata.name}`);
|
|
262
|
-
}
|
|
233
|
+
try { ratings = await loader.loadRatings(dateStr); }
|
|
234
|
+
catch (e) { if (!allowMissing) throw e; ratings = null; }
|
|
235
|
+
if (!ratings && !allowMissing) throw new Error(`Missing ratings`);
|
|
263
236
|
}
|
|
264
237
|
|
|
265
238
|
let pageViews = null;
|
|
266
239
|
if (metadata.rootDataDependencies?.includes('pageViews')) {
|
|
267
|
-
try {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'pageViews' failed to load for ${metadata.name}: ${e.message}`);
|
|
271
|
-
pageViews = null;
|
|
272
|
-
}
|
|
273
|
-
if (!pageViews && !allowMissing) {
|
|
274
|
-
throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'pageViews' is missing for ${metadata.name}`);
|
|
275
|
-
}
|
|
240
|
+
try { pageViews = await loader.loadPageViews(dateStr); }
|
|
241
|
+
catch (e) { if (!allowMissing) throw e; pageViews = null; }
|
|
242
|
+
if (!pageViews && !allowMissing) throw new Error(`Missing pageViews`);
|
|
276
243
|
}
|
|
277
244
|
|
|
278
245
|
let watchlistMembership = null;
|
|
279
246
|
if (metadata.rootDataDependencies?.includes('watchlist')) {
|
|
280
|
-
try {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'watchlist' failed to load for ${metadata.name}: ${e.message}`);
|
|
284
|
-
watchlistMembership = null;
|
|
285
|
-
}
|
|
286
|
-
if (!watchlistMembership && !allowMissing) {
|
|
287
|
-
throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'watchlist' is missing for ${metadata.name}`);
|
|
288
|
-
}
|
|
247
|
+
try { watchlistMembership = await loader.loadWatchlistMembership(dateStr); }
|
|
248
|
+
catch (e) { if (!allowMissing) throw e; watchlistMembership = null; }
|
|
249
|
+
if (!watchlistMembership && !allowMissing) throw new Error(`Missing watchlist`);
|
|
289
250
|
}
|
|
290
251
|
|
|
291
252
|
let alertHistory = null;
|
|
292
253
|
if (metadata.rootDataDependencies?.includes('alerts')) {
|
|
293
|
-
try {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (!allowMissing) throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'alerts' failed to load for ${metadata.name}: ${e.message}`);
|
|
297
|
-
alertHistory = null;
|
|
298
|
-
}
|
|
299
|
-
if (!alertHistory && !allowMissing) {
|
|
300
|
-
throw new Error(`[MetaExecutor.executeOncePerDay] Required root 'alerts' is missing for ${metadata.name}`);
|
|
301
|
-
}
|
|
254
|
+
try { alertHistory = await loader.loadAlertHistory(dateStr); }
|
|
255
|
+
catch (e) { if (!allowMissing) throw e; alertHistory = null; }
|
|
256
|
+
if (!alertHistory && !allowMissing) throw new Error(`Missing alerts`);
|
|
302
257
|
}
|
|
303
258
|
|
|
304
|
-
// [NOTE] "executeOncePerDay" is typically for sharded price/batch jobs.
|
|
305
|
-
// We initialize empty series data to maintain compatibility.
|
|
306
259
|
const seriesData = { root: {}, results: {} };
|
|
307
260
|
|
|
308
261
|
if (metadata.rootDataDependencies?.includes('price')) {
|
|
@@ -319,12 +272,10 @@ class MetaExecutor {
|
|
|
319
272
|
previousComputedDependencies: prevDeps, config, deps,
|
|
320
273
|
allRankings: rankings,
|
|
321
274
|
allRankingsYesterday: rankingsYesterday,
|
|
322
|
-
// [NEW] Pass New Root Data Types for Profile Metrics
|
|
323
275
|
ratings: ratings || {},
|
|
324
276
|
pageViews: pageViews || {},
|
|
325
277
|
watchlistMembership: watchlistMembership || {},
|
|
326
278
|
alertHistory: alertHistory || {},
|
|
327
|
-
// [NEW] Pass Series Data
|
|
328
279
|
seriesData
|
|
329
280
|
});
|
|
330
281
|
|
|
@@ -346,12 +297,10 @@ class MetaExecutor {
|
|
|
346
297
|
previousComputedDependencies: prevDeps, config, deps,
|
|
347
298
|
allRankings: rankings,
|
|
348
299
|
allRankingsYesterday: rankingsYesterday,
|
|
349
|
-
// [NEW] Pass New Root Data Types for Profile Metrics
|
|
350
300
|
ratings: ratings || {},
|
|
351
301
|
pageViews: pageViews || {},
|
|
352
302
|
watchlistMembership: watchlistMembership || {},
|
|
353
303
|
alertHistory: alertHistory || {},
|
|
354
|
-
// [NEW] Pass Series Data
|
|
355
304
|
seriesData
|
|
356
305
|
});
|
|
357
306
|
const res = await calcInstance.process(context);
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {
|
|
3
|
-
* type: uploaded file
|
|
4
|
-
* fileName: computation-system/executors/StandardExecutor.js
|
|
5
|
-
* }
|
|
6
|
-
*/
|
|
7
1
|
const { normalizeName, getEarliestDataDates } = require('../utils/utils');
|
|
8
2
|
const { streamPortfolioData, streamHistoryData, getPortfolioPartRefs, getHistoryPartRefs } = require('../utils/data_loader');
|
|
9
3
|
const { CachedDataLoader } = require('../data/CachedDataLoader');
|
|
10
4
|
const { ContextFactory } = require('../context/ContextFactory');
|
|
11
5
|
const { commitResults } = require('../persistence/ResultCommitter');
|
|
12
|
-
// [NEW] Import series fetcher for computation results
|
|
13
6
|
const { fetchResultSeries } = require('../data/DependencyFetcher');
|
|
7
|
+
const { getManifest } = require('../topology/ManifestLoader');
|
|
14
8
|
const mathLayer = require('../layers/index');
|
|
15
9
|
const { performance } = require('perf_hooks');
|
|
16
10
|
const v8 = require('v8');
|
|
@@ -78,6 +72,11 @@ class StandardExecutor {
|
|
|
78
72
|
|
|
79
73
|
if (streamingCalcs.length === 0) return { successUpdates: {}, failureReport: [] };
|
|
80
74
|
|
|
75
|
+
// --- 0. [NEW] Build Global Manifest Lookup ---
|
|
76
|
+
const allManifests = getManifest(config.productLines, config.calculationsDirectory, deps);
|
|
77
|
+
const manifestLookup = {};
|
|
78
|
+
allManifests.forEach(m => { manifestLookup[normalizeName(m.name)] = m.category; });
|
|
79
|
+
|
|
81
80
|
// --- 1. Resolve and Filter Portfolio Refs (Today) ---
|
|
82
81
|
let effectivePortfolioRefs = portfolioRefs;
|
|
83
82
|
if (!effectivePortfolioRefs) {
|
|
@@ -167,7 +166,8 @@ class StandardExecutor {
|
|
|
167
166
|
else if (type === 'insights') loaderMethod = 'loadInsights';
|
|
168
167
|
else if (type === 'ratings') loaderMethod = 'loadRatings';
|
|
169
168
|
else if (type === 'watchlist') loaderMethod = 'loadWatchlistMembership';
|
|
170
|
-
// Add
|
|
169
|
+
// [CRITICAL UPDATE] Add rankings support for AUM lookback
|
|
170
|
+
else if (type === 'rankings') loaderMethod = 'loadRankings';
|
|
171
171
|
|
|
172
172
|
if (loaderMethod) {
|
|
173
173
|
logger.log('INFO', `[StandardExecutor] Loading ${days}-day series for Root Data '${type}'...`);
|
|
@@ -183,22 +183,8 @@ class StandardExecutor {
|
|
|
183
183
|
const maxDays = Math.max(...Object.values(dependencySeriesRequests));
|
|
184
184
|
logger.log('INFO', `[StandardExecutor] Loading up to ${maxDays}-day series for Dependencies: ${calcNamesToFetch.join(', ')}`);
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
const resultsSeries = await fetchResultSeries(dateStr, calcNamesToFetch,
|
|
188
|
-
|
|
189
|
-
// [NEW] Validate Series Availability
|
|
190
|
-
streamingCalcs.forEach(c => {
|
|
191
|
-
if (c.manifest.dependencySeries && !c.manifest.canHaveMissingSeries) {
|
|
192
|
-
Object.keys(c.manifest.dependencySeries).forEach(depName => {
|
|
193
|
-
const normDep = normalizeName(depName);
|
|
194
|
-
// If series data is completely missing for a required dependency, verify logic
|
|
195
|
-
if (!resultsSeries[normDep] || Object.keys(resultsSeries[normDep]).length === 0) {
|
|
196
|
-
logger.log('WARN', `[StandardExecutor] Missing dependency series '${depName}' for '${c.name}' (and canHaveMissingSeries=false). This may cause calculation errors.`);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
186
|
+
// Pass manifestLookup to fetcher
|
|
187
|
+
const resultsSeries = await fetchResultSeries(dateStr, calcNamesToFetch, manifestLookup, config, deps, maxDays);
|
|
202
188
|
seriesData.results = resultsSeries;
|
|
203
189
|
}
|
|
204
190
|
// ---------------------------------------------
|
|
@@ -487,4 +473,4 @@ class StandardExecutor {
|
|
|
487
473
|
}
|
|
488
474
|
}
|
|
489
475
|
|
|
490
|
-
module.exports = { StandardExecutor };
|
|
476
|
+
module.exports = { StandardExecutor };
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* UPDATED: Added support for 'isPage' mode to store per-user data in subcollections.
|
|
5
5
|
* UPDATED: Implemented TTL retention policy. Defaults to 90 days from the computation date.
|
|
6
6
|
* UPDATED: Fixed issue where switching to 'isPage' mode didn't clean up old sharded/raw data.
|
|
7
|
+
* CRITICAL FIX: Fixed sharding logic to prevent wiping existing shards during INTERMEDIATE flushes.
|
|
7
8
|
*/
|
|
8
9
|
const { commitBatchInChunks, generateDataHash, FieldValue } = require('../utils/utils')
|
|
9
10
|
const { updateComputationStatus } = require('./StatusRepository');
|
|
@@ -138,8 +139,6 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
138
139
|
continue;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
// [NEW] Page Computation Logic (Fan-Out) with TTL
|
|
142
|
-
// Bypasses standard compression/sharding to write per-user documents
|
|
143
142
|
// [NEW] Page Computation Logic (Fan-Out) with TTL
|
|
144
143
|
// Bypasses standard compression/sharding to write per-user documents
|
|
145
144
|
if (isPageComputation && !isEmpty) {
|
|
@@ -429,7 +428,9 @@ async function writeSingleResult(result, docRef, name, dateContext, logger, conf
|
|
|
429
428
|
let finalStats = { totalSize: 0, isSharded: false, shardCount: 1, nextShardIndex: startShardIndex };
|
|
430
429
|
let rootMergeOption = !isInitialWrite;
|
|
431
430
|
|
|
432
|
-
|
|
431
|
+
// CRITICAL FIX: Only wipe existing shards if this is the INITIAL write for this batch run.
|
|
432
|
+
// If we are flushing intermediate chunks, we should NOT wipe the shards created by previous chunks!
|
|
433
|
+
let shouldWipeShards = wasSharded && isInitialWrite;
|
|
433
434
|
|
|
434
435
|
for (let attempt = 0; attempt < strategies.length; attempt++) {
|
|
435
436
|
if (committed) break;
|