bulltrackers-module 1.0.329 โ 1.0.331
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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* FILENAME: computation-system/helpers/computation_dispatcher.js
|
|
3
3
|
* PURPOSE: Sequential Cursor-Based Dispatcher.
|
|
4
4
|
* BEHAVIOR: Dispatch -> Wait ETA -> Next Date.
|
|
5
|
-
* UPDATED: Added "
|
|
5
|
+
* UPDATED: Added "Sweep" Protocol for OOM recovery & High-Mem Verification.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const { getExpectedDateStrings, getEarliestDataDates, normalizeName, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils.js');
|
|
@@ -15,16 +15,14 @@ const crypto = require('crypto');
|
|
|
15
15
|
const OOM_THRESHOLD_MB = 1500;
|
|
16
16
|
const BASE_SECONDS_PER_WEIGHT_UNIT = 3;
|
|
17
17
|
const SESSION_CACHE_DURATION_MS = 1000 * 60 * 30; // 30 Minutes
|
|
18
|
-
|
|
19
|
-
// [NEW] Zombie Timeout: Max Cloud Function run is 9m (540s).
|
|
20
|
-
// If no heartbeat/start within 15m, it's definitely dead.
|
|
21
18
|
const STALE_LOCK_THRESHOLD_MS = 1000 * 60 * 15;
|
|
22
19
|
|
|
23
20
|
// =============================================================================
|
|
24
|
-
// HELPER: Ledger Awareness
|
|
21
|
+
// HELPER: Ledger Awareness
|
|
25
22
|
// =============================================================================
|
|
26
|
-
async function filterActiveTasks(db, date, pass, tasks, logger) {
|
|
23
|
+
async function filterActiveTasks(db, date, pass, tasks, logger, forceRun = false) {
|
|
27
24
|
if (!tasks || tasks.length === 0) return [];
|
|
25
|
+
if (forceRun) return tasks; // Bypass check for Sweep Mode
|
|
28
26
|
|
|
29
27
|
const checkPromises = tasks.map(async (t) => {
|
|
30
28
|
const taskName = normalizeName(t.name);
|
|
@@ -37,7 +35,6 @@ async function filterActiveTasks(db, date, pass, tasks, logger) {
|
|
|
37
35
|
|
|
38
36
|
// 1. ZOMBIE CHECK (Recover Stale Locks)
|
|
39
37
|
if (isActive) {
|
|
40
|
-
// Prefer heartbeat, fall back to start time
|
|
41
38
|
const lastActivityTime = data.telemetry?.lastHeartbeat
|
|
42
39
|
? new Date(data.telemetry.lastHeartbeat).getTime()
|
|
43
40
|
: (data.startedAt ? new Date(data.startedAt).getTime() : 0);
|
|
@@ -45,25 +42,18 @@ async function filterActiveTasks(db, date, pass, tasks, logger) {
|
|
|
45
42
|
const timeSinceActive = Date.now() - lastActivityTime;
|
|
46
43
|
|
|
47
44
|
if (timeSinceActive > STALE_LOCK_THRESHOLD_MS) {
|
|
48
|
-
if (logger) {
|
|
49
|
-
logger.log('WARN', `[Dispatcher] ๐ง Breaking stale lock for ${taskName}. Inactive for ${(timeSinceActive/60000).toFixed(1)} mins.`);
|
|
50
|
-
}
|
|
51
|
-
// Return task (Re-dispatching it will overwrite the old lock in Firestore)
|
|
45
|
+
if (logger) logger.log('WARN', `[Dispatcher] ๐ง Breaking stale lock for ${taskName}.`);
|
|
52
46
|
return t;
|
|
53
47
|
}
|
|
54
|
-
|
|
55
|
-
// If distinct and recent, filter it out (let it run)
|
|
56
48
|
return null;
|
|
57
49
|
}
|
|
58
50
|
|
|
59
51
|
// 2. GHOST CHECK (Debounce immediate re-runs)
|
|
60
|
-
// If it finished less than 1 minute ago, don't re-dispatch immediately
|
|
61
|
-
// (prevents double-tap if latency is high)
|
|
62
52
|
const isJustFinished = data.status === 'COMPLETED' &&
|
|
63
53
|
data.completedAt &&
|
|
64
54
|
(Date.now() - new Date(data.completedAt).getTime() < 60 * 1000);
|
|
65
55
|
|
|
66
|
-
if (isJustFinished) return null;
|
|
56
|
+
if (isJustFinished) return null;
|
|
67
57
|
}
|
|
68
58
|
return t;
|
|
69
59
|
});
|
|
@@ -73,7 +63,7 @@ async function filterActiveTasks(db, date, pass, tasks, logger) {
|
|
|
73
63
|
}
|
|
74
64
|
|
|
75
65
|
// =============================================================================
|
|
76
|
-
// HELPER: SimHash Stability
|
|
66
|
+
// HELPER: SimHash Stability
|
|
77
67
|
// =============================================================================
|
|
78
68
|
async function attemptSimHashResolution(dependencies, date, tasks, dailyStatus, manifestMap) {
|
|
79
69
|
const { db, logger } = dependencies;
|
|
@@ -120,15 +110,12 @@ async function attemptSimHashResolution(dependencies, date, tasks, dailyStatus,
|
|
|
120
110
|
};
|
|
121
111
|
});
|
|
122
112
|
await db.collection('computation_status').doc(date).set(updatePayload, { merge: true });
|
|
123
|
-
logger.log('INFO', `[SimHash] โฉ Fast-forwarded ${resolvedTasks.length} tasks for ${date}
|
|
113
|
+
logger.log('INFO', `[SimHash] โฉ Fast-forwarded ${resolvedTasks.length} tasks for ${date}.`);
|
|
124
114
|
}
|
|
125
115
|
|
|
126
116
|
return remainingTasks;
|
|
127
117
|
}
|
|
128
118
|
|
|
129
|
-
// =============================================================================
|
|
130
|
-
// HELPER: Stable Session Management (Solves Cursor Shifting)
|
|
131
|
-
// =============================================================================
|
|
132
119
|
async function getStableDateSession(config, dependencies, passToRun, dateLimitStr, forceRebuild) {
|
|
133
120
|
const { db, logger } = dependencies;
|
|
134
121
|
const sessionId = `pass_${passToRun}_${dateLimitStr.replace(/-/g, '')}`;
|
|
@@ -153,29 +140,164 @@ async function getStableDateSession(config, dependencies, passToRun, dateLimitSt
|
|
|
153
140
|
}
|
|
154
141
|
|
|
155
142
|
// =============================================================================
|
|
156
|
-
// MAIN
|
|
143
|
+
// MAIN ENTRY POINT
|
|
157
144
|
// =============================================================================
|
|
158
145
|
async function dispatchComputationPass(config, dependencies, computationManifest, reqBody = {}) {
|
|
146
|
+
const action = reqBody.action || 'DISPATCH'; // DISPATCH, VERIFY, SWEEP
|
|
147
|
+
|
|
148
|
+
if (action === 'VERIFY') {
|
|
149
|
+
return handlePassVerification(config, dependencies, computationManifest, reqBody);
|
|
150
|
+
}
|
|
151
|
+
else if (action === 'SWEEP') {
|
|
152
|
+
return handleSweepDispatch(config, dependencies, computationManifest, reqBody);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return handleStandardDispatch(config, dependencies, computationManifest, reqBody);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// LOGIC: Verify Pass Completion
|
|
160
|
+
// =============================================================================
|
|
161
|
+
async function handlePassVerification(config, dependencies, computationManifest, reqBody) {
|
|
162
|
+
const { logger } = dependencies;
|
|
163
|
+
const passToRun = String(reqBody.pass || "1");
|
|
164
|
+
const dateLimitStr = reqBody.date || "2025-01-01";
|
|
165
|
+
|
|
166
|
+
logger.log('INFO', `[Verify] ๐งน Sweeping Pass ${passToRun} for unfinished work...`);
|
|
167
|
+
|
|
168
|
+
const sessionDates = await getStableDateSession(config, dependencies, passToRun, dateLimitStr, false);
|
|
169
|
+
const passes = groupByPass(computationManifest);
|
|
170
|
+
const calcsInPass = passes[passToRun] || [];
|
|
171
|
+
const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
|
|
172
|
+
const weightMap = new Map(computationManifest.map(c => [normalizeName(c.name), c.weight || 1.0]));
|
|
173
|
+
|
|
174
|
+
const missingTasks = [];
|
|
175
|
+
|
|
176
|
+
// Optimize: Batch fetch statuses if possible, but for now loop is safer for memory
|
|
177
|
+
// In production, we might want p-limit here.
|
|
178
|
+
for (const date of sessionDates) {
|
|
179
|
+
const [dailyStatus, availability] = await Promise.all([
|
|
180
|
+
fetchComputationStatus(date, config, dependencies),
|
|
181
|
+
checkRootDataAvailability(date, config, dependencies, DEFINITIVE_EARLIEST_DATES)
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
// Need previous status for historical calcs
|
|
185
|
+
let prevDailyStatus = null;
|
|
186
|
+
if (calcsInPass.some(c => c.isHistorical)) {
|
|
187
|
+
const prevD = new Date(date + 'T00:00:00Z');
|
|
188
|
+
prevD.setUTCDate(prevD.getUTCDate() - 1);
|
|
189
|
+
prevDailyStatus = await fetchComputationStatus(prevD.toISOString().slice(0, 10), config, dependencies);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const report = analyzeDateExecution(date, calcsInPass, availability ? availability.status : {}, dailyStatus, manifestMap, prevDailyStatus);
|
|
193
|
+
|
|
194
|
+
// We only care about Runnable (New) or ReRuns (Changed/Failed)
|
|
195
|
+
// We ignore Blocked (impossible to run) and Impossible (permanent fail)
|
|
196
|
+
const pending = [...report.runnable, ...report.reRuns];
|
|
197
|
+
|
|
198
|
+
if (pending.length > 0) {
|
|
199
|
+
// Calculate ETA
|
|
200
|
+
const totalWeight = pending.reduce((sum, t) => sum + (weightMap.get(normalizeName(t.name)) || 1.0), 0);
|
|
201
|
+
const eta = Math.max(30, Math.ceil(totalWeight * BASE_SECONDS_PER_WEIGHT_UNIT));
|
|
202
|
+
|
|
203
|
+
missingTasks.push({
|
|
204
|
+
date: date,
|
|
205
|
+
taskCount: pending.length,
|
|
206
|
+
eta: eta
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
logger.log('INFO', `[Verify] Found ${missingTasks.length} dates with pending work.`);
|
|
212
|
+
return { missingTasks };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// =============================================================================
|
|
216
|
+
// LOGIC: Sweep Dispatch (Forced High-Mem)
|
|
217
|
+
// =============================================================================
|
|
218
|
+
async function handleSweepDispatch(config, dependencies, computationManifest, reqBody) {
|
|
159
219
|
const { logger, db } = dependencies;
|
|
160
|
-
const pubsubUtils
|
|
220
|
+
const pubsubUtils = new PubSubUtils(dependencies);
|
|
221
|
+
const passToRun = String(reqBody.pass || "1");
|
|
222
|
+
const date = reqBody.date;
|
|
223
|
+
|
|
224
|
+
if (!date) throw new Error('Sweep dispatch requires date');
|
|
225
|
+
|
|
226
|
+
const passes = groupByPass(computationManifest);
|
|
227
|
+
const calcsInPass = passes[passToRun] || [];
|
|
228
|
+
const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
|
|
229
|
+
|
|
230
|
+
// 1. Analyze specific date
|
|
231
|
+
const [dailyStatus, availability] = await Promise.all([
|
|
232
|
+
fetchComputationStatus(date, config, dependencies),
|
|
233
|
+
checkRootDataAvailability(date, config, dependencies, DEFINITIVE_EARLIEST_DATES)
|
|
234
|
+
]);
|
|
161
235
|
|
|
162
|
-
|
|
236
|
+
// Previous Status Fetch (simplified for brevity, assume historical dependency check works or fails safe)
|
|
237
|
+
let prevDailyStatus = null;
|
|
238
|
+
if (calcsInPass.some(c => c.isHistorical)) {
|
|
239
|
+
const prevD = new Date(date + 'T00:00:00Z');
|
|
240
|
+
prevD.setUTCDate(prevD.getUTCDate() - 1);
|
|
241
|
+
prevDailyStatus = await fetchComputationStatus(prevD.toISOString().slice(0, 10), config, dependencies);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const report = analyzeDateExecution(date, calcsInPass, availability ? availability.status : {}, dailyStatus, manifestMap, prevDailyStatus);
|
|
245
|
+
const pending = [...report.runnable, ...report.reRuns];
|
|
246
|
+
|
|
247
|
+
if (pending.length === 0) {
|
|
248
|
+
logger.log('INFO', `[Sweep] ${date} is clean. No dispatch.`);
|
|
249
|
+
return { dispatched: 0 };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 2. FORCE High Mem & Skip Zombie Check
|
|
253
|
+
// We do NOT filterActiveTasks because we assume they might be OOM'd zombies
|
|
254
|
+
// We do NOT check resource tier from previous runs, we FORCE 'high-mem'
|
|
255
|
+
const currentDispatchId = crypto.randomUUID();
|
|
256
|
+
|
|
257
|
+
const tasksPayload = pending.map(t => ({
|
|
258
|
+
...t,
|
|
259
|
+
action: 'RUN_COMPUTATION_DATE',
|
|
260
|
+
computation: t.name,
|
|
261
|
+
date: date,
|
|
262
|
+
pass: passToRun,
|
|
263
|
+
dispatchId: currentDispatchId,
|
|
264
|
+
triggerReason: 'SWEEP_RECOVERY',
|
|
265
|
+
resources: 'high-mem' // FORCE
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
logger.log('WARN', `[Sweep] ๐งน Forcing ${tasksPayload.length} tasks to HIGH-MEM for ${date}.`);
|
|
269
|
+
|
|
270
|
+
await pubsubUtils.batchPublishTasks(dependencies, {
|
|
271
|
+
topicName: config.computationTopicHighMem || 'computation-tasks-highmem',
|
|
272
|
+
tasks: tasksPayload,
|
|
273
|
+
taskType: `pass-${passToRun}-sweep`
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return { dispatched: tasksPayload.length };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// =============================================================================
|
|
280
|
+
// LOGIC: Standard Dispatch (Original)
|
|
281
|
+
// =============================================================================
|
|
282
|
+
async function handleStandardDispatch(config, dependencies, computationManifest, reqBody) {
|
|
283
|
+
const { logger, db } = dependencies;
|
|
284
|
+
const pubsubUtils = new PubSubUtils(dependencies);
|
|
285
|
+
|
|
286
|
+
const passToRun = String(reqBody.pass || "1");
|
|
163
287
|
const targetCursorN = parseInt(reqBody.cursorIndex || 1);
|
|
164
|
-
const dateLimitStr
|
|
165
|
-
const forceRebuild
|
|
288
|
+
const dateLimitStr = reqBody.date || "2025-01-01";
|
|
289
|
+
const forceRebuild = reqBody.forceRebuild === true;
|
|
166
290
|
|
|
167
|
-
const manifestMap
|
|
168
|
-
const passes
|
|
291
|
+
const manifestMap = new Map(computationManifest.map(c => [normalizeName(c.name), c]));
|
|
292
|
+
const passes = groupByPass(computationManifest);
|
|
169
293
|
const calcsInThisPass = passes[passToRun] || [];
|
|
170
294
|
const manifestWeightMap = new Map(computationManifest.map(c => [normalizeName(c.name), c.weight || 1.0]));
|
|
171
295
|
|
|
172
296
|
if (!calcsInThisPass.length) return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0 };
|
|
173
297
|
|
|
174
|
-
// 1. Get Stable Date List
|
|
175
298
|
const sessionDates = await getStableDateSession(config, dependencies, passToRun, dateLimitStr, forceRebuild);
|
|
176
299
|
if (!sessionDates || sessionDates.length === 0) return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0 };
|
|
177
300
|
|
|
178
|
-
// 2. Select Date
|
|
179
301
|
let selectedDate = null;
|
|
180
302
|
let selectedTasks = [];
|
|
181
303
|
|
|
@@ -185,13 +307,10 @@ async function dispatchComputationPass(config, dependencies, computationManifest
|
|
|
185
307
|
return { status: 'MOVE_TO_NEXT_PASS', dispatched: 0 };
|
|
186
308
|
}
|
|
187
309
|
|
|
188
|
-
// 3. Analyze SPECIFIC Date
|
|
189
310
|
if (selectedDate) {
|
|
190
311
|
const earliestDates = await getEarliestDataDates(config, dependencies);
|
|
191
|
-
const needsHistory = calcsInThisPass.some(c => c.isHistorical);
|
|
192
|
-
|
|
193
312
|
let prevDailyStatusPromise = Promise.resolve(null);
|
|
194
|
-
if (
|
|
313
|
+
if (calcsInThisPass.some(c => c.isHistorical)) {
|
|
195
314
|
const prevD = new Date(selectedDate + 'T00:00:00Z');
|
|
196
315
|
prevD.setUTCDate(prevD.getUTCDate() - 1);
|
|
197
316
|
if (prevD >= earliestDates.absoluteEarliest) {
|
|
@@ -211,22 +330,16 @@ async function dispatchComputationPass(config, dependencies, computationManifest
|
|
|
211
330
|
|
|
212
331
|
if (rawTasks.length > 0) {
|
|
213
332
|
rawTasks = await attemptSimHashResolution(dependencies, selectedDate, rawTasks, dailyStatus, manifestMap);
|
|
214
|
-
|
|
215
|
-
// [UPDATED] Pass logger to filterActiveTasks for zombie warnings
|
|
216
333
|
selectedTasks = await filterActiveTasks(db, selectedDate, passToRun, rawTasks, logger);
|
|
217
334
|
}
|
|
218
335
|
|
|
219
|
-
// OOM / High-Mem Reroute Check
|
|
220
336
|
if (selectedTasks.length > 0) {
|
|
221
337
|
const reroutes = await getHighMemReroutes(db, selectedDate, passToRun, selectedTasks);
|
|
222
|
-
if (reroutes.length > 0)
|
|
223
|
-
selectedTasks = reroutes;
|
|
224
|
-
}
|
|
338
|
+
if (reroutes.length > 0) selectedTasks = reroutes;
|
|
225
339
|
}
|
|
226
340
|
}
|
|
227
341
|
}
|
|
228
342
|
|
|
229
|
-
// 4. Dispatch Logic
|
|
230
343
|
if (selectedTasks.length === 0) {
|
|
231
344
|
return {
|
|
232
345
|
status: 'CONTINUE_PASS',
|
|
@@ -238,31 +351,18 @@ async function dispatchComputationPass(config, dependencies, computationManifest
|
|
|
238
351
|
};
|
|
239
352
|
}
|
|
240
353
|
|
|
241
|
-
// 5. Send Tasks
|
|
242
354
|
const totalweight = selectedTasks.reduce((sum, t) => sum + (manifestWeightMap.get(normalizeName(t.name)) || 1.0), 0);
|
|
243
355
|
const currentDispatchId = crypto.randomUUID();
|
|
244
356
|
const etaSeconds = Math.max(20, Math.ceil(totalweight * BASE_SECONDS_PER_WEIGHT_UNIT));
|
|
245
357
|
|
|
246
358
|
const taskDetails = selectedTasks.map(t => `${t.name} (${t.reason})`);
|
|
247
359
|
logger.log('INFO', `[Dispatcher] โ
Dispatching ${selectedTasks.length} tasks for ${selectedDate}.`, {
|
|
248
|
-
date: selectedDate,
|
|
249
|
-
pass: passToRun,
|
|
250
|
-
dispatchedCount: selectedTasks.length,
|
|
251
|
-
cursor: targetCursorN,
|
|
252
|
-
etaSeconds: etaSeconds,
|
|
253
|
-
dispatchId: currentDispatchId,
|
|
254
|
-
tasks: taskDetails
|
|
360
|
+
date: selectedDate, pass: passToRun, dispatchedCount: selectedTasks.length, etaSeconds, dispatchId: currentDispatchId, details: taskDetails
|
|
255
361
|
});
|
|
256
362
|
|
|
257
363
|
const mapToTaskPayload = (t) => ({
|
|
258
|
-
...t,
|
|
259
|
-
|
|
260
|
-
computation: t.name,
|
|
261
|
-
date: selectedDate,
|
|
262
|
-
pass: passToRun,
|
|
263
|
-
dispatchId: currentDispatchId,
|
|
264
|
-
triggerReason: t.reason,
|
|
265
|
-
resources: t.resources || 'standard'
|
|
364
|
+
...t, action: 'RUN_COMPUTATION_DATE', computation: t.name, date: selectedDate, pass: passToRun,
|
|
365
|
+
dispatchId: currentDispatchId, triggerReason: t.reason, resources: t.resources || 'standard'
|
|
266
366
|
});
|
|
267
367
|
|
|
268
368
|
const standardTasks = selectedTasks.filter(t => t.resources !== 'high-mem').map(mapToTaskPayload);
|
|
@@ -301,15 +401,12 @@ async function getHighMemReroutes(db, date, pass, tasks) {
|
|
|
301
401
|
const name = normalizeName(task.name);
|
|
302
402
|
const ledgerPath = `computation_audit_ledger/${date}/passes/${pass}/tasks/${name}`;
|
|
303
403
|
const doc = await db.doc(ledgerPath).get();
|
|
304
|
-
|
|
305
404
|
if (doc.exists) {
|
|
306
405
|
const data = doc.data();
|
|
307
406
|
const isOOM = (data.status === 'FAILED' || data.status === 'CRASH') &&
|
|
308
407
|
(data.resourceTier !== 'high-mem') &&
|
|
309
408
|
((data.peakMemoryMB > OOM_THRESHOLD_MB) || (data.error && /memory/i.test(data.error.message)));
|
|
310
|
-
if (isOOM) {
|
|
311
|
-
reroutes.push({ ...task, resources: 'high-mem' });
|
|
312
|
-
}
|
|
409
|
+
if (isOOM) reroutes.push({ ...task, resources: 'high-mem' });
|
|
313
410
|
}
|
|
314
411
|
}
|
|
315
412
|
return reroutes;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Handles saving computation results with observability and Smart Cleanup.
|
|
3
3
|
* UPDATED: Tracks specific Firestore Ops (Writes/Deletes) for cost analysis.
|
|
4
|
+
* UPDATED: Added Dynamic Circuit Breaker Bypass for Price-Only calculations.
|
|
4
5
|
*/
|
|
5
6
|
const { commitBatchInChunks, generateDataHash } = require('../utils/utils');
|
|
6
7
|
const { updateComputationStatus } = require('./StatusRepository');
|
|
@@ -54,12 +55,23 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
54
55
|
try {
|
|
55
56
|
const result = await calc.getResult();
|
|
56
57
|
const configOverrides = validationOverrides[calc.manifest.name] || {};
|
|
58
|
+
|
|
59
|
+
// --- [DYNAMIC OVERRIDE LOGIC START] ---
|
|
57
60
|
const dataDeps = calc.manifest.rootDataDependencies || [];
|
|
58
61
|
const isPriceOnly = (dataDeps.length === 1 && dataDeps[0] === 'price');
|
|
59
62
|
let effectiveOverrides = { ...configOverrides };
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
|
|
64
|
+
// If a calculation relies solely on price, we strictly bypass the circuit breaker.
|
|
65
|
+
// This handles Holidays (Weekday 0s), Weekends (Scheduler Gaps), and Crypto/Stock mix.
|
|
66
|
+
if (isPriceOnly) {
|
|
67
|
+
effectiveOverrides.maxZeroPct = 100;
|
|
68
|
+
effectiveOverrides.maxFlatlinePct = 100;
|
|
69
|
+
effectiveOverrides.maxNullPct = 100;
|
|
70
|
+
effectiveOverrides.maxNanPct = 100;
|
|
71
|
+
// Remove specific weekend config to ensure these permissive global limits apply 7 days/week
|
|
72
|
+
delete effectiveOverrides.weekend;
|
|
62
73
|
}
|
|
74
|
+
// --- [DYNAMIC OVERRIDE LOGIC END] ---
|
|
63
75
|
|
|
64
76
|
const contract = contractMap[name];
|
|
65
77
|
if (contract) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Cloud Workflows: Precision Cursor-Based Orchestrator
|
|
2
2
|
# SIMPLE MODE: Dispatch -> Wait ETA -> Next Date
|
|
3
|
+
# UPGRADED: Added "Sweep & Verify" Step for High-Mem Recovery
|
|
3
4
|
|
|
4
5
|
main:
|
|
5
6
|
params: [input]
|
|
@@ -49,7 +50,7 @@ main:
|
|
|
49
50
|
- log_satiation:
|
|
50
51
|
call: sys.log
|
|
51
52
|
args:
|
|
52
|
-
text: '${"Pass " + pass_id + " - โ
Pass satiated (0 remaining).
|
|
53
|
+
text: '${"Pass " + pass_id + " - โ
Pass satiated (0 remaining). Starting Verification Sweep."}'
|
|
53
54
|
- mark_complete:
|
|
54
55
|
assign:
|
|
55
56
|
- pass_complete: true
|
|
@@ -80,5 +81,43 @@ main:
|
|
|
80
81
|
- n_cursor: '${if(dispatch_res.body.n_cursor_ignored, n_cursor, n_cursor + 1)}'
|
|
81
82
|
- next_loop_retry:
|
|
82
83
|
next: sequential_date_loop
|
|
84
|
+
|
|
85
|
+
# --- NEW STEP: VERIFICATION & SWEEP ---
|
|
86
|
+
- verify_pass_completion:
|
|
87
|
+
call: http.post
|
|
88
|
+
args:
|
|
89
|
+
url: '${"https://europe-west1-" + project + ".cloudfunctions.net/computation-pass-" + pass_id}'
|
|
90
|
+
body:
|
|
91
|
+
action: 'VERIFY'
|
|
92
|
+
pass: '${pass_id}'
|
|
93
|
+
date: '${date_to_run}'
|
|
94
|
+
auth: { type: OIDC }
|
|
95
|
+
result: verify_res
|
|
96
|
+
|
|
97
|
+
- process_sweep_tasks:
|
|
98
|
+
for:
|
|
99
|
+
value: sweep_task
|
|
100
|
+
in: '${verify_res.body.missingTasks}'
|
|
101
|
+
steps:
|
|
102
|
+
- log_sweep:
|
|
103
|
+
call: sys.log
|
|
104
|
+
args:
|
|
105
|
+
text: '${"๐งน SWEEP: Disposing " + sweep_task.taskCount + " high-mem tasks for " + sweep_task.date + ". Wait: " + sweep_task.eta + "s"}'
|
|
106
|
+
|
|
107
|
+
- dispatch_force_sweep:
|
|
108
|
+
call: http.post
|
|
109
|
+
args:
|
|
110
|
+
url: '${"https://europe-west1-" + project + ".cloudfunctions.net/computation-pass-" + pass_id}'
|
|
111
|
+
body:
|
|
112
|
+
action: 'SWEEP'
|
|
113
|
+
pass: '${pass_id}'
|
|
114
|
+
date: '${sweep_task.date}'
|
|
115
|
+
auth: { type: OIDC }
|
|
116
|
+
|
|
117
|
+
- wait_sweep_completion:
|
|
118
|
+
call: sys.sleep
|
|
119
|
+
args:
|
|
120
|
+
seconds: '${int(sweep_task.eta)}'
|
|
121
|
+
|
|
83
122
|
- finish:
|
|
84
|
-
return: "Pipeline Execution Satiated and
|
|
123
|
+
return: "Pipeline Execution Satiated and Verified"
|