From ac05c54c064daa44cb9e0d22b0f084c3feaa8169 Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Sat, 7 Mar 2026 08:41:21 -0800 Subject: [PATCH] Add next_run.json override for searcher lookback Drop data/next_run.json with {"lookback_days": 90} to force a full re-search. File is consumed after the run completes. Co-Authored-By: Claude Opus 4.6 --- job_searcher.mjs | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/job_searcher.mjs b/job_searcher.mjs index a9bf2f3..c0917a4 100644 --- a/job_searcher.mjs +++ b/job_searcher.mjs @@ -21,7 +21,7 @@ process.stdout.write = (chunk, ...args) => { logStream.write(chunk); return orig process.stderr.write = (chunk, ...args) => { logStream.write(chunk); return origStderrWrite(chunk, ...args); }; import { addJobs, loadQueue, loadConfig, getJobsByStatus, updateJobStatus, initQueue } from './lib/queue.mjs'; -import { writeFileSync, readFileSync, existsSync } from 'fs'; +import { writeFileSync, readFileSync, existsSync, unlinkSync } from 'fs'; import { acquireLock } from './lib/lock.mjs'; import { createBrowser } from './lib/browser.mjs'; import { verifyLogin as liLogin, searchLinkedIn, classifyExternalJobs } from './lib/linkedin.mjs'; @@ -102,19 +102,28 @@ async function main() { const profile = await loadConfig(resolve(__dir, 'config/profile.json')); const anthropicKey = process.env.ANTHROPIC_API_KEY || settings.anthropic_api_key; - // Determine lookback: check for an in-progress run first, then fall back to first-run/normal logic + // Determine lookback: + // 1. data/next_run.json override (consumed after use) + // 2. Resuming in-progress run + // 3. Dynamic: time since last run Ɨ 1.25 const savedProgress = existsSync(resolve(__dir, 'data/search_progress.json')) ? JSON.parse(readFileSync(resolve(__dir, 'data/search_progress.json'), 'utf8')) : null; - const isFirstRun = loadQueue().length === 0; - // Dynamic lookback: time since last run Ɨ 1.25 buffer (min 4h, max first_run_days) + const nextRunPath = resolve(__dir, 'data/next_run.json'); + let nextRunOverride = null; + if (existsSync(nextRunPath)) { + try { + nextRunOverride = JSON.parse(readFileSync(nextRunPath, 'utf8')); + } catch {} + } + function dynamicLookbackDays() { const lastRunPath = resolve(__dir, 'data/searcher_last_run.json'); - if (!existsSync(lastRunPath)) return searchConfig.posted_within_days || 2; + if (!existsSync(lastRunPath)) return searchConfig.first_run_days || DEFAULT_FIRST_RUN_DAYS; const lastRun = JSON.parse(readFileSync(lastRunPath, 'utf8')); const lastRanAt = lastRun.started_at || lastRun.finished_at; - if (!lastRanAt) return searchConfig.posted_within_days || 2; + if (!lastRanAt) return searchConfig.first_run_days || DEFAULT_FIRST_RUN_DAYS; const hoursSince = (Date.now() - new Date(lastRanAt).getTime()) / (1000 * 60 * 60); const buffered = hoursSince * 1.25; const minHours = 4; @@ -122,16 +131,17 @@ async function main() { return Math.min(Math.max(buffered / 24, minHours / 24), maxDays); } - const lookbackDays = savedProgress?.lookback_days - || (isFirstRun ? (searchConfig.first_run_days || DEFAULT_FIRST_RUN_DAYS) : dynamicLookbackDays()); - - if (savedProgress?.lookback_days) { + let lookbackDays; + if (nextRunOverride?.lookback_days) { + lookbackDays = nextRunOverride.lookback_days; + console.log(`šŸ“‹ Override from next_run.json — looking back ${lookbackDays} days\n`); + } else if (savedProgress?.lookback_days) { + lookbackDays = savedProgress.lookback_days; console.log(`šŸ” Resuming ${lookbackDays.toFixed(2)}-day search run\n`); - } else if (isFirstRun) { - console.log(`šŸ“… First run — looking back ${lookbackDays} days\n`); } else { + lookbackDays = dynamicLookbackDays(); const hours = (lookbackDays * 24).toFixed(1); - console.log(`ā±ļø Dynamic lookback: ${hours}h (time since last run Ɨ 1.25)\n`); + console.log(`ā±ļø Lookback: ${hours}h (time since last run Ɨ 1.25)\n`); } // Init progress tracking — enables resume on restart @@ -332,6 +342,11 @@ async function main() { } clearProgress(); // run finished cleanly — next run starts fresh with new keywords + // Consume override file so next cron run uses dynamic lookback + if (existsSync(nextRunPath)) { + try { unlinkSync(nextRunPath); } catch {} + } + console.log(`\nāœ… Search complete at ${new Date().toISOString()}`); return { added: totalAdded, seen: totalSeen }; }