feat: granular skip statuses — recruiter_only, external_unsupported, easy_apply_unsupported

Queue preserves all jobs for future reprocessing as capabilities expand.
Applier summary breaks down skips by type.
This commit is contained in:
2026-03-05 23:44:19 +00:00
parent 11cb80c942
commit 675697b990
4 changed files with 36 additions and 9 deletions

View File

@@ -50,7 +50,10 @@ async function main() {
return;
}
const results = { submitted: 0, skipped: 0, failed: 0, needs_answer: 0, total: jobs.length };
const results = {
submitted: 0, failed: 0, needs_answer: 0, total: jobs.length,
skipped_recruiter: 0, skipped_external: 0, skipped_no_easy_apply: 0
};
// Group by platform
const liJobs = jobs.filter(j => j.platform === 'linkedin');
@@ -154,12 +157,27 @@ async function handleResult(job, result, results, settings) {
results.skipped++;
break;
case 'skipped_recruiter_only':
console.log(` ⏭️ Skipped — recruiter-only ("I'm interested")`);
updateJobStatus(job.id, 'skipped_recruiter_only', { title, company });
appendLog({ ...job, title, company, status: 'skipped_recruiter_only' });
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' });
results.skipped_external++;
break;
case 'skipped_easy_apply_unsupported':
case 'no_easy_apply':
case 'no_button':
console.log(` ⏭️ Skipped — no apply button`);
updateJobStatus(job.id, 'skipped', { notes: status, title, company });
appendLog({ ...job, title, company, status: 'skipped', notes: status });
results.skipped++;
console.log(` ⏭️ Skipped — no Easy Apply`);
updateJobStatus(job.id, 'skipped_easy_apply_unsupported', { title, company });
appendLog({ ...job, title, company, status: 'skipped_easy_apply_unsupported' });
results.skipped_no_easy_apply++;
break;
default:

View File

@@ -93,9 +93,14 @@ export async function applyLinkedIn(page, job, formFiller) {
company: document.querySelector('.job-details-jobs-unified-top-card__company-name a, .jobs-unified-top-card__company-name a')?.textContent?.trim(),
}));
// Find Easy Apply button
// Detect apply type
const eaBtn = await page.$('button.jobs-apply-button[aria-label*="Easy Apply"]');
if (!eaBtn) return { status: 'no_easy_apply', meta };
const externalBtn = await page.$('button.jobs-apply-button:not([aria-label*="Easy Apply"])');
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) return { status: 'skipped_easy_apply_unsupported', meta };
// Click Easy Apply
await page.click('button.jobs-apply-button', { timeout: 5000 }).catch(() => {});

View File

@@ -34,13 +34,16 @@ export function formatSearchSummary(added, skipped, platforms) {
}
export function formatApplySummary(results) {
const { submitted, skipped, failed, needs_answer, total } = results;
const { submitted, failed, needs_answer, total,
skipped_recruiter, skipped_external, skipped_no_easy_apply } = results;
const lines = [
`✅ *Apply Run Complete*`,
`Applied: ${submitted} | Skipped: ${skipped} | Failed: ${failed} | Needs answer: ${needs_answer}`,
`Applied: ${submitted} | Failed: ${failed} | Needs answer: ${needs_answer}`,
`Skipped: ${skipped_recruiter} recruiter-only | ${skipped_external} external ATS | ${skipped_no_easy_apply} no Easy Apply`,
`Total processed: ${total}`,
];
if (needs_answer > 0) lines.push(`\n💬 Check messages — I sent questions that need your answers`);
if (skipped_external > 0) lines.push(`\n🔜 ${skipped_external} external ATS jobs saved for when Greenhouse/Lever support lands`);
return lines.join('\n');
}

1
node_modules Symbolic link
View File

@@ -0,0 +1 @@
/home/ubuntu/.openclaw/workspace/node_modules