Files
linkme/bern-langcheck.ts
T
rodado bcb6ae5ea3 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
2026-05-12 13:42:27 +02:00

76 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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}/`);
}