diff --git a/analyze_ats.mjs b/analyze_ats.mjs index d45803b..5c93476 100644 --- a/analyze_ats.mjs +++ b/analyze_ats.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** - * analyze_ats.mjs — ATS platform analysis - * Reads the applications log and ranks external ATS platforms by job count + * analyze_ats.mjs — Full job pipeline breakdown + * Shows Easy Apply vs external ATS platforms vs other skips * Run: node analyze_ats.mjs */ import { readFileSync, existsSync } from 'fs'; @@ -12,76 +12,90 @@ const __dir = dirname(fileURLToPath(import.meta.url)); const logPath = resolve(__dir, 'data/applications_log.json'); const queuePath = resolve(__dir, 'data/jobs_queue.json'); -function analyze() { - const atsCounts = {}; - const atsExamples = {}; - let totalExternal = 0; - let unknownCount = 0; +function hostname(url) { + try { return new URL(url).hostname.replace('www.', '').split('.')[0]; } catch { return 'unknown'; } +} - // From applications log (processed jobs) +function analyze() { + const all = []; + + if (existsSync(queuePath)) { + all.push(...JSON.parse(readFileSync(queuePath, 'utf8'))); + } if (existsSync(logPath)) { - const log = JSON.parse(readFileSync(logPath, 'utf8')); - for (const entry of log) { - if (entry.status === 'skipped_external_unsupported') { - totalExternal++; - const platform = entry.ats_platform || 'unknown'; - atsCounts[platform] = (atsCounts[platform] || 0) + 1; - if (!atsExamples[platform]) atsExamples[platform] = []; - if (atsExamples[platform].length < 3) { - atsExamples[platform].push({ title: entry.title, company: entry.company, url: entry.ats_url }); - } - if (platform === 'unknown') unknownCount++; - } - } + all.push(...JSON.parse(readFileSync(logPath, 'utf8'))); } - // From queue (not yet processed) - if (existsSync(queuePath)) { - const queue = JSON.parse(readFileSync(queuePath, 'utf8')); - for (const job of queue) { - if (job.status === 'skipped_external_unsupported') { - totalExternal++; - const platform = job.ats_platform || 'unknown'; + if (all.length === 0) { + console.log('No data yet. Run the searcher first.'); + return; + } + + // Counters + let easyApply = 0, applied = 0, failed = 0, needsAnswer = 0; + let recruiterOnly = 0, noEasyApply = 0; + const atsCounts = {}; + const atsExamples = {}; + let newJobs = 0; + + for (const job of all) { + switch (job.status) { + case 'new': newJobs++; break; + case 'applied': applied++; break; + case 'failed': failed++; break; + case 'needs_answer': needsAnswer++; break; + case 'skipped_recruiter_only': recruiterOnly++; break; + case 'skipped_easy_apply_unsupported': noEasyApply++; break; + case 'skipped_external_unsupported': { + const platform = job.ats_platform || (job.ats_url ? hostname(job.ats_url) : 'unknown'); atsCounts[platform] = (atsCounts[platform] || 0) + 1; if (!atsExamples[platform]) atsExamples[platform] = []; if (atsExamples[platform].length < 3) { atsExamples[platform].push({ title: job.title, company: job.company, url: job.ats_url }); } + break; + } + default: + // Count easy apply eligible (status=new on linkedin with easy apply available) + if (job.status === 'new' && job.easy_apply) easyApply++; + } + } + + const totalExternal = Object.values(atsCounts).reduce((a, b) => a + b, 0); + const total = all.length; + + console.log(`\nšŸ“Š Job Pipeline Breakdown — ${total} total jobs\n`); + console.log(` āœ… Applied: ${applied}`); + console.log(` šŸ”„ Pending (new): ${newJobs}`); + console.log(` šŸ’¬ Needs answer: ${needsAnswer}`); + console.log(` āŒ Failed: ${failed}`); + console.log(` 🚫 Recruiter-only: ${recruiterOnly}`); + console.log(` ā­ļø No Easy Apply btn: ${noEasyApply}`); + console.log(` 🌐 External ATS: ${totalExternal}`); + console.log(''); + + if (totalExternal > 0) { + const sorted = Object.entries(atsCounts).sort((a, b) => b[1] - a[1]); + console.log(' External ATS breakdown:'); + for (const [platform, count] of sorted) { + const pct = ((count / totalExternal) * 100).toFixed(0); + const bar = 'ā–ˆ'.repeat(Math.max(1, Math.round(count / totalExternal * 15))); + console.log(` ${platform.padEnd(18)} ${String(count).padEnd(5)} (${pct}%) ${bar}`); + } + + console.log('\n Top ATS — examples:'); + for (const [platform, count] of sorted.slice(0, 4)) { + console.log(`\n ${platform} (${count} jobs):`); + for (const ex of (atsExamples[platform] || [])) { + console.log(` • ${ex.title || '?'} @ ${ex.company || '?'}`); + if (ex.url) console.log(` ${ex.url}`); } } + + console.log(`\nšŸ’” Build support for: ${sorted.slice(0, 3).map(([p]) => p).join(', ')} first.`); + } else { + console.log(' No external ATS data yet — run the applier to classify jobs.'); } - - if (totalExternal === 0) { - console.log('No external ATS jobs found yet. Run the applier first.'); - return; - } - - const sorted = Object.entries(atsCounts).sort((a, b) => b[1] - a[1]); - - console.log(`\nšŸ“Š ATS Platform Analysis — ${totalExternal} external jobs\n`); - console.log('Platform Count % of external Build next?'); - console.log('─'.repeat(60)); - - for (const [platform, count] of sorted) { - const pct = ((count / totalExternal) * 100).toFixed(1); - const bar = 'ā–ˆ'.repeat(Math.round(count / totalExternal * 20)); - console.log(`${platform.padEnd(16)} ${String(count).padEnd(7)} ${pct.padEnd(15)}% ${bar}`); - } - - console.log('\nTop platforms with examples:'); - for (const [platform, count] of sorted.slice(0, 5)) { - console.log(`\n ${platform} (${count} jobs):`); - for (const ex of (atsExamples[platform] || [])) { - console.log(` • ${ex.title} @ ${ex.company}`); - if (ex.url) console.log(` ${ex.url}`); - } - } - - if (unknownCount > 0) { - console.log(`\nāš ļø ${unknownCount} jobs with unknown ATS (URL capture may need improvement)`); - } - - console.log('\nšŸ’” Recommendation: Build support for the top 2-3 platforms first.'); } analyze();