feat: configure ready outreach funnel limit
This commit is contained in:
1 parent
22eb32e5bf
commit
e9083281a8
15 files changed
+66
-11
No files matched your search
@@ -50,6 +50,7 @@ export const ConfigSchema = z.object({
|
||||
filterSellers: z.boolean().default(true),
|
||||
prioritizeRecentPosts: z.boolean().default(false),
|
||||
recentWindowMinutes: z.number().positive().default(60),
|
||||
maxReadyCandidates: z.number().int().min(1).max(500).default(25),
|
||||
sellerKeywords: z
|
||||
.array(z.string())
|
||||
.default([
|
||||
|
||||
@@ -191,12 +191,22 @@ export class CandidateQueueRepository {
|
||||
`).run(now, postId, workerId).changes === 1;
|
||||
}
|
||||
|
||||
promoteDueRetries(now: number = Date.now()): number {
|
||||
promoteDueRetries(maxReadyCandidates: number, now: number = Date.now()): number {
|
||||
if (!Number.isInteger(maxReadyCandidates) || maxReadyCandidates < 1) {
|
||||
throw new Error('Maximum ready candidates must be a positive integer');
|
||||
}
|
||||
const capacity = maxReadyCandidates - this.countReady();
|
||||
if (capacity <= 0) return 0;
|
||||
return this.db.prepare(`
|
||||
UPDATE candidate_queue
|
||||
SET status = 'ready', updated_at = ?, assigned_account_id = NULL
|
||||
WHERE status = 'retryable' AND available_at <= ?
|
||||
`).run(now, now).changes;
|
||||
WHERE post_id IN (
|
||||
SELECT post_id FROM candidate_queue
|
||||
WHERE status = 'retryable' AND available_at <= ?
|
||||
ORDER BY priority DESC, discovered_at ASC
|
||||
LIMIT ?
|
||||
)
|
||||
`).run(now, now, capacity).changes;
|
||||
}
|
||||
|
||||
recoverExpiredLeases(now: number = Date.now(), sendingDelayMs = 10 * 60_000): number {
|
||||
|
||||
+2
-3
@@ -429,7 +429,6 @@ async function runConcurrentAccountRuntimes(app: AppContainer): Promise<void> {
|
||||
const workerInstance = `candidate-funnel-${process.pid}-${Date.now()}`;
|
||||
const leaseMs = 5 * 60_000;
|
||||
const maxActiveCandidates = 500;
|
||||
const readyTarget = 100;
|
||||
const recovered = candidateQueueRepo.recoverAllLeases();
|
||||
app.recipientDeduplicator.recoverStaleReservations(Date.now() - 10 * 60_000);
|
||||
logger.info(
|
||||
@@ -527,7 +526,7 @@ async function runConcurrentAccountRuntimes(app: AppContainer): Promise<void> {
|
||||
candidateQueueRepo.expireStale(
|
||||
Date.now() - config.reddit.recentWindowMinutes * 60_000,
|
||||
);
|
||||
if (candidateQueueRepo.countReady() >= readyTarget) {
|
||||
if (candidateQueueRepo.countReady() >= config.reddit.maxReadyCandidates) {
|
||||
await sleep(2_000, controller.signal).catch(() => undefined);
|
||||
continue;
|
||||
}
|
||||
@@ -590,7 +589,7 @@ async function runConcurrentAccountRuntimes(app: AppContainer): Promise<void> {
|
||||
await sleep(1_000, controller.signal).catch(() => undefined);
|
||||
continue;
|
||||
}
|
||||
candidateQueueRepo.promoteDueRetries();
|
||||
candidateQueueRepo.promoteDueRetries(config.reddit.maxReadyCandidates);
|
||||
candidateQueueRepo.recoverExpiredLeases();
|
||||
app.recipientDeduplicator.recoverStaleReservations(Date.now() - 10 * 60_000);
|
||||
const recipients = await availableRecipients();
|
||||
|
||||
@@ -10,6 +10,7 @@ const AdvancedConfigSchema = z.strictObject({
|
||||
subreddits: true,
|
||||
prioritizeRecentPosts: true,
|
||||
recentWindowMinutes: true,
|
||||
maxReadyCandidates: true,
|
||||
}),
|
||||
openrouter: ConfigSchema.shape.openrouter.omit({ apiKey: true }),
|
||||
browser: z.strictObject({ sessionDir: z.string() }),
|
||||
@@ -19,7 +20,13 @@ const AdvancedConfigSchema = z.strictObject({
|
||||
});
|
||||
|
||||
function advancedConfig(ctx: WebContext): z.infer<typeof AdvancedConfigSchema> {
|
||||
const { subreddits: _subreddits, prioritizeRecentPosts: _prioritize, recentWindowMinutes: _window, ...reddit } = ctx.config.reddit;
|
||||
const {
|
||||
subreddits: _subreddits,
|
||||
prioritizeRecentPosts: _prioritize,
|
||||
recentWindowMinutes: _window,
|
||||
maxReadyCandidates: _maxReady,
|
||||
...reddit
|
||||
} = ctx.config.reddit;
|
||||
const { apiKey: _apiKey, ...openrouter } = ctx.config.openrouter;
|
||||
const { dryRun: _dryRun, ...browser } = ctx.config.browser;
|
||||
return {
|
||||
@@ -98,6 +105,12 @@ export function createConfigRouter(ctx: WebContext): Router {
|
||||
if (typeof updates.reddit.recentWindowMinutes === 'number' && updates.reddit.recentWindowMinutes > 0) {
|
||||
ctx.runtimeConfig.reddit.recentWindowMinutes = updates.reddit.recentWindowMinutes;
|
||||
}
|
||||
if (typeof updates.reddit.maxReadyCandidates === 'number'
|
||||
&& Number.isInteger(updates.reddit.maxReadyCandidates)
|
||||
&& updates.reddit.maxReadyCandidates >= 1
|
||||
&& updates.reddit.maxReadyCandidates <= 500) {
|
||||
ctx.runtimeConfig.reddit.maxReadyCandidates = updates.reddit.maxReadyCandidates;
|
||||
}
|
||||
}
|
||||
|
||||
// Mirror into the loaded config so getStats reflects new values.
|
||||
@@ -107,6 +120,7 @@ export function createConfigRouter(ctx: WebContext): Router {
|
||||
ctx.config.browser.dryRun = ctx.runtimeConfig.browser.dryRun;
|
||||
ctx.config.reddit.prioritizeRecentPosts = ctx.runtimeConfig.reddit.prioritizeRecentPosts;
|
||||
ctx.config.reddit.recentWindowMinutes = ctx.runtimeConfig.reddit.recentWindowMinutes;
|
||||
ctx.config.reddit.maxReadyCandidates = ctx.runtimeConfig.reddit.maxReadyCandidates;
|
||||
|
||||
if (parsedSubreddits?.success) {
|
||||
const next: SubredditConfig[] = parsedSubreddits.data;
|
||||
|
||||
@@ -95,6 +95,7 @@ export class WebServer {
|
||||
reddit: {
|
||||
prioritizeRecentPosts: config.reddit.prioritizeRecentPosts,
|
||||
recentWindowMinutes: config.reddit.recentWindowMinutes,
|
||||
maxReadyCandidates: config.reddit.maxReadyCandidates,
|
||||
},
|
||||
};
|
||||
this.app = express();
|
||||
|
||||
@@ -65,5 +65,6 @@ export interface RuntimeConfig {
|
||||
reddit: {
|
||||
prioritizeRecentPosts: boolean;
|
||||
recentWindowMinutes: number;
|
||||
maxReadyCandidates: number;
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user