feat: capture external ATS URLs + analyze_ats.mjs to rank platforms by job count
This commit is contained in:
87
analyze_ats.mjs
Normal file
87
analyze_ats.mjs
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* analyze_ats.mjs — ATS platform analysis
|
||||
* Reads the applications log and ranks external ATS platforms by job count
|
||||
* Run: node analyze_ats.mjs
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
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;
|
||||
|
||||
// From applications log (processed jobs)
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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';
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -170,12 +170,15 @@ async function handleResult(job, result, results, settings) {
|
||||
results.skipped_recruiter++;
|
||||
break;
|
||||
|
||||
case 'skipped_external_unsupported':
|
||||
console.log(` ⏭️ Skipped — external ATS (not yet supported)`);
|
||||
updateJobStatus(job.id, 'skipped_external_unsupported', { title, company });
|
||||
appendLog({ ...job, title, company, status: 'skipped_external_unsupported' });
|
||||
case 'skipped_external_unsupported': {
|
||||
const atsUrl = result.externalUrl || '';
|
||||
const atsDomain = atsUrl ? (new URL(atsUrl).hostname.replace('www.', '').split('.')[0]) : 'unknown';
|
||||
console.log(` ⏭️ Skipped — external ATS: ${atsDomain || 'unknown'}`);
|
||||
updateJobStatus(job.id, 'skipped_external_unsupported', { title, company, ats_url: atsUrl, ats_platform: atsDomain });
|
||||
appendLog({ ...job, title, company, status: 'skipped_external_unsupported', ats_url: atsUrl, ats_platform: atsDomain });
|
||||
results.skipped_external++;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'skipped_easy_apply_unsupported':
|
||||
case 'no_easy_apply':
|
||||
|
||||
@@ -102,7 +102,17 @@ export async function applyLinkedIn(page, job, formFiller) {
|
||||
const interestedBtn = await page.$('button[aria-label*="interested"], button:has-text("I\'m interested")');
|
||||
|
||||
if (!eaBtn && interestedBtn) return { status: 'skipped_recruiter_only', meta };
|
||||
if (!eaBtn && externalBtn) return { status: 'skipped_external_unsupported', meta };
|
||||
if (!eaBtn && externalBtn) {
|
||||
// Capture the external apply URL for ATS analysis
|
||||
const externalUrl = await externalBtn.evaluate(el => el.getAttribute('href') || el.dataset?.href || '')
|
||||
.catch(() => '');
|
||||
// Also check for redirect links in the page
|
||||
const applyLink = await page.evaluate(() => {
|
||||
const a = document.querySelector('a[href*="greenhouse"], a[href*="lever"], a[href*="workday"], a[href*="ashby"], a[href*="jobvite"], a[href*="smartrecruiters"], a[href*="icims"], a[href*="taleo"]');
|
||||
return a?.href || '';
|
||||
}).catch(() => '');
|
||||
return { status: 'skipped_external_unsupported', meta, externalUrl: applyLink || externalUrl };
|
||||
}
|
||||
if (!eaBtn) return { status: 'skipped_easy_apply_unsupported', meta };
|
||||
|
||||
// Click Easy Apply
|
||||
|
||||
Reference in New Issue
Block a user