Add content-level language filter, recheck command, expanded keywords

- isEnglishContent() fetches each job page and drops descriptions with ≥6 German or French indicator words; called for every new job in index.ts before writing to CSV
- New `bun recheck` command (recheck.ts) runs searches in raw mode, lists filtered-out jobs, and prints word-frequency suggestions for SOFTWARE_TITLE_RE
- Expanded KEYWORDS and SOFTWARE_TITLE_RE to cover ML/AI, system/applications engineer, director, informatics, and more title variants
- Added `raw` flag to fetchJobs() to bypass the software-role filter
- Add CLAUDE.md with project workflow docs; add CV files and city temp scripts
This commit is contained in:
2026-05-12 13:42:27 +02:00
parent ffe2b4467a
commit bcb6ae5ea3
19 changed files with 1201 additions and 14 deletions
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bun
const DE_CONTENT_RE = /\b(Sie|Ihre?|Ihnen|Wir|wir|und\s|für|mit\s|von\s|bei\s|das\s|ist\s|sind\s|haben\s|werden\s|können|suchen|bieten|Kenntnisse|Erfahrung|Anforderungen|Aufgaben|Stellenbeschreibung|Stellenangebot|Bewerbung|Ausbildung|Berufserfahrung|Teamarbeit|Quereinsteiger|Vollzeit|Teilzeit|unbefristet|Abschluss|gesucht|Deutsch|Englisch|Schweizer)\b/g;
const FR_CONTENT_RE = /\b(vous|nous|votre|notre|pour\s|avec\s|profil|poste|emploi|entreprise|compétences|expérience|formation|équipe|rejoindre|recherchons|offrons|missions|candidature|CDI|CDD|temps\s+plein|temps\s+partiel)\b/g;
const HEADERS = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
};
const jobs = [
{ id: "4412445330", title: "Senior Software and System Architect", company: "NVIDIA AI" },
{ id: "4406964785", title: "Digital Platform Engineer – EasyGov.swiss", company: "Swiss Federal Administration" },
{ id: "4405193749", title: "Technical Lead Web Portals", company: "Smart Energy Link AG" },
{ id: "4411445039", title: "Senior System Engineer (Cloud Native, Remote-First)", company: "Adfinis" },
{ id: "4411290320", title: "Automation Data Engineer", company: "Takeda Pharmaceuticals" },
{ id: "4411348405", title: "Solutions Architect", company: "RJC Group" },
{ id: "4406980033", title: "Enterprise Architect", company: "Swiss Federal Administration" },
{ id: "4367874399", title: "Software Engineer", company: "Cartier" },
{ id: "4399418573", title: "Data Science Specialist", company: "Nestlé" },
{ id: "4411369976", title: "Data Scientist 80–100%", company: "Fincons Group" },
{ id: "4412464434", title: "Solution Engineer Management Systems", company: "Siemens" },
{ id: "4401638413", title: "Fullstack Developer (Java/React)", company: "soobr" },
{ id: "4399356316", title: "Senior Java Software Engineer", company: "Smart Energy Link AG" },
{ id: "4407778229", title: "Software Engineer", company: "Softcom" },
{ id: "4402476156", title: "Software Developer", company: "SwissPeak Partners" },
// remaining from the 20
{ id: "4404649130", title: "Senior System Engineer (100%)", company: "nanoTRONIC" },
{ id: "4408770074", title: "System Engineer", company: "Infopro AG" },
{ id: "4408864023", title: "System Engineer – Workplace Engineering", company: "Bedag Informatique SA" },
{ id: "4405214433", title: "Cloud Platform Engineer (Kubernetes/Azure/DevOps)", company: "Gramian Consulting" },
];
function detectLanguage(text: string): "de" | "fr" | "en" {
const deMatches = (text.match(DE_CONTENT_RE) ?? []).length;
const frMatches = (text.match(FR_CONTENT_RE) ?? []).length;
if (deMatches >= 6) return "de";
if (frMatches >= 6) return "fr";
return "en";
}
function extractDescription(html: string): string {
const m = html.match(/class="show-more-less-html__markup[^"]*">([\s\S]{0,8000}?)<\/div>/);
if (m) return m[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
// fallback: grab large text blocks
return html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").slice(0, 5000);
}
const passed: typeof jobs = [];
const dropped: { id: string; title: string; company: string; lang: string }[] = [];
for (const job of jobs) {
await Bun.sleep(350);
process.stdout.write(` checking ${job.company} — ${job.title} … `);
try {
const res = await fetch(`https://www.linkedin.com/jobs/view/${job.id}/`, { headers: HEADERS });
const html = await res.text();
const desc = extractDescription(html);
const lang = detectLanguage(desc);
console.log(lang);
if (lang === "en") passed.push(job);
else dropped.push({ ...job, lang });
} catch (e) {
console.log("fetch error, keeping");
passed.push(job);
}
}
console.log(`\n── ${dropped.length} dropped (non-English) ──`);
for (const j of dropped) console.log(` [${j.lang}] ${j.title} @ ${j.company}`);
console.log(`\n── ${passed.length} English jobs ──`);
for (const [i, j] of passed.entries()) {
console.log(` ${i + 1}. ${j.title} — ${j.company} https://www.linkedin.com/jobs/view/${j.id}/`);
}