Richer search and filter summaries
Search: show per-track breakdown (found/added per track name) Filter: show top 5 scoring jobs with score, title, company and cost Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ process.stderr.write = (chunk, ...args) => { logStream.write(chunk); return orig
|
||||
|
||||
import { getJobsByStatus, updateJobStatus, loadConfig, loadQueue, saveQueue, dedupeAfterFilter } from './lib/queue.mjs';
|
||||
import { loadProfile, submitBatches, checkBatch, downloadResults } from './lib/filter.mjs';
|
||||
import { sendTelegram } from './lib/notify.mjs';
|
||||
import { sendTelegram, formatFilterSummary } from './lib/notify.mjs';
|
||||
import { DEFAULT_FILTER_MODEL, DEFAULT_FILTER_MIN_SCORE } from './lib/constants.mjs';
|
||||
|
||||
const isStats = process.argv.includes('--stats');
|
||||
@@ -187,12 +187,17 @@ async function collect(state, settings) {
|
||||
});
|
||||
writeFileSync(runsPath, JSON.stringify(runs, null, 2));
|
||||
|
||||
const summary = `✅ Filter complete — ${passed} passed, ${filtered} filtered, ${errors} errors (est. cost: $${totalCost.toFixed(2)})`;
|
||||
console.log(`\n${summary}`);
|
||||
// Collect top-scoring jobs for summary
|
||||
const freshQueue = loadQueue();
|
||||
const topJobs = freshQueue
|
||||
.filter(j => resultMap[j.id] && j.filter_score >= (searchConfig.filter_min_score ?? DEFAULT_FILTER_MIN_SCORE))
|
||||
.sort((a, b) => (b.filter_score || 0) - (a.filter_score || 0))
|
||||
.slice(0, 5)
|
||||
.map(j => ({ score: j.filter_score, title: j.title, company: j.company }));
|
||||
|
||||
await sendTelegram(settings,
|
||||
`🔍 *AI Filter complete*\n✅ Passed: ${passed}\n🚫 Filtered: ${filtered}\n⚠️ Errors: ${errors}`
|
||||
).catch(() => {});
|
||||
const summary = formatFilterSummary({ passed, filtered, errors, cost: totalCost, topJobs });
|
||||
console.log(`\n${summary.replace(/\*/g, '')}`);
|
||||
await sendTelegram(settings, summary).catch(() => {});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -38,6 +38,7 @@ async function main() {
|
||||
|
||||
let totalAdded = 0, totalSeen = 0;
|
||||
const platformsRun = [];
|
||||
const trackCounts = {}; // { trackName: { found, added } }
|
||||
const startedAt = Date.now();
|
||||
|
||||
const settings = loadConfig(resolve(__dir, 'config/settings.json'));
|
||||
@@ -70,7 +71,7 @@ async function main() {
|
||||
console.log(' Writing partial results to last-run file...');
|
||||
writeLastRun(false);
|
||||
if (totalAdded > 0) {
|
||||
const summary = formatSearchSummary(totalAdded, totalSeen - totalAdded, platformsRun.length ? platformsRun : ['LinkedIn']);
|
||||
const summary = formatSearchSummary(totalAdded, totalSeen - totalAdded, platformsRun.length ? platformsRun : ['LinkedIn'], trackCounts);
|
||||
await sendTelegram(settings, summary + '\n_(partial run — interrupted)_').catch(() => {});
|
||||
}
|
||||
});
|
||||
@@ -178,6 +179,8 @@ async function main() {
|
||||
});
|
||||
console.log(`\r [${search.name}] ${queryFound} found, ${queryAdded} new`);
|
||||
markComplete('linkedin', search.name, { found: queryFound, added: queryAdded });
|
||||
const tc = trackCounts[search.name] || (trackCounts[search.name] = { found: 0, added: 0 });
|
||||
tc.found += queryFound; tc.added += queryAdded;
|
||||
}
|
||||
|
||||
platformsRun.push('LinkedIn');
|
||||
@@ -220,6 +223,8 @@ async function main() {
|
||||
});
|
||||
console.log(`\r [${search.name}] ${queryFound} found, ${queryAdded} new`);
|
||||
markComplete('wellfound', search.name, { found: queryFound, added: queryAdded });
|
||||
const tc = trackCounts[search.name] || (trackCounts[search.name] = { found: 0, added: 0 });
|
||||
tc.found += queryFound; tc.added += queryAdded;
|
||||
}
|
||||
|
||||
platformsRun.push('Wellfound');
|
||||
@@ -232,7 +237,7 @@ async function main() {
|
||||
}
|
||||
|
||||
// Summary
|
||||
const summary = formatSearchSummary(totalAdded, totalSeen - totalAdded, platformsRun);
|
||||
const summary = formatSearchSummary(totalAdded, totalSeen - totalAdded, platformsRun, trackCounts);
|
||||
console.log(`\n${summary.replace(/\*/g, '')}`);
|
||||
if (totalAdded > 0) await sendTelegram(settings, summary).catch(() => {});
|
||||
|
||||
|
||||
@@ -87,9 +87,20 @@ export async function replyTelegram(botToken, chatId, replyToMessageId, text) {
|
||||
} catch { /* best effort */ }
|
||||
}
|
||||
|
||||
export function formatSearchSummary(added, skipped, platforms) {
|
||||
export function formatSearchSummary(added, skipped, platforms, trackCounts = {}) {
|
||||
if (added === 0) return `🔍 *Job Search Complete*\nNo new jobs found this run.`;
|
||||
return `🔍 *Job Search Complete*\n${added} new job${added !== 1 ? 's' : ''} added to queue (${skipped} already seen)\nPlatforms: ${platforms.join(', ')}`;
|
||||
const lines = [
|
||||
`🔍 *Job Search Complete*`,
|
||||
`${added} new job${added !== 1 ? 's' : ''} added (${skipped} already seen)`,
|
||||
`Platforms: ${platforms.join(', ')}`,
|
||||
];
|
||||
if (Object.keys(trackCounts).length > 0) {
|
||||
lines.push('');
|
||||
for (const [track, counts] of Object.entries(trackCounts)) {
|
||||
lines.push(` • *${track}*: ${counts.added} new / ${counts.found} found`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatApplySummary(results) {
|
||||
@@ -130,6 +141,21 @@ export function formatApplySummary(results) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatFilterSummary({ passed, filtered, errors, cost, topJobs = [] }) {
|
||||
const lines = [
|
||||
`🧠 *AI Filter Complete*`,
|
||||
`✅ Passed: ${passed} | 🚫 Filtered: ${filtered}${errors ? ` | ⚠️ Errors: ${errors}` : ''}`,
|
||||
];
|
||||
if (cost != null) lines.push(`💰 Cost: $${cost.toFixed(2)}`);
|
||||
if (topJobs.length > 0) {
|
||||
lines.push('', '*Top scores:*');
|
||||
for (const j of topJobs.slice(0, 5)) {
|
||||
lines.push(` • ${j.score} — ${j.title} @ ${j.company}`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatUnknownQuestion(job, question) {
|
||||
return `❓ *Unknown question while applying*\n\n*Job:* ${job.title} @ ${job.company}\n*Question:* "${question}"\n\nWhat should I answer? (Reply and I'll save it for all future runs)`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user