#!/usr/bin/env bun import type { SearchConfig } from "../config.ts"; import { fetchJobs, isEnglishContent, JUNIOR_TITLE_RE, JAVA_TITLE_RE } from "../fetch.ts"; const CSV = "./results/jobs.csv"; // Titles the engineer-centric KEYWORDS list never searches for. const BROAD_KEYWORDS = [ "Implementation Manager", "Implementation Consultant", "Implementation Specialist", "Solutions Consultant", "Solution Architect", "Technical Consultant", "Technical Account Manager", "Technical Program Manager", "Technical Project Manager", "Delivery Manager", "Integration Engineer", "Integration Specialist", "Customer Success Engineer", "Sales Engineer", "Presales Engineer", "Digital Transformation", "Product Owner", "Scientific Software", "Research Software", "Automation Engineer", "Systems Integration", ].join(" OR "); // Microsoft stack = hard no (see cv/polo.md). const MICROSOFT_RE = /\b(\.net|dotnet|c#|azure|sharepoint|dynamics|power\s*(platform|bi|apps|automate)|sccm|intune|windows\s*server|m365|office\s*365|uipath)\b/i; const SAP_RE = /\bsap\b|\bs\/4hana\b|\bsalesforce\b|\bservicenow\b/i; const AREAS: SearchConfig[] = [ { label: "Basel (city)", keywords: BROAD_KEYWORDS, geoId: "103383283", location: "Basel", workType: ["onsite", "hybrid"], timePosted: "week" }, { label: "Basel Metropolitan Area", keywords: BROAD_KEYWORDS, geoId: "90009885", location: "Basel Metropolitan Area", workType: ["onsite", "hybrid"], timePosted: "week" }, { label: "Basel-Country", keywords: BROAD_KEYWORDS, geoId: "107117530", location: "Basel-Country", workType: ["onsite", "hybrid"], timePosted: "week" }, ]; const existingIds = new Set(); try { const text = await Bun.file(CSV).text(); for (const line of text.split("\n").slice(1)) { const id = line.match(/\/jobs\/view\/(\d+)\//)?.[1]; if (id) existingIds.add(id); } } catch { /* first run */ } type Row = { id: string; title: string; company: string; location: string; link: string; posted: string; isNew: boolean }; const seen = new Set(); const rows: Row[] = []; for (const config of AREAS) { // raw: true — bypass SOFTWARE_TITLE_RE so non-engineer titles survive const jobs = await fetchJobs(config, { raw: true }); console.error(`[${config.label}] ${jobs.length} jobs`); for (const job of jobs) { if (seen.has(job.id)) continue; seen.add(job.id); const t = job.title; if (JUNIOR_TITLE_RE.test(t)) { console.error(` junior: ${t}`); continue; } if (JAVA_TITLE_RE.test(t)) { console.error(` java: ${t}`); continue; } if (MICROSOFT_RE.test(t)) { console.error(` MS stack: ${t}`); continue; } if (SAP_RE.test(t)) { console.error(` SAP/SF/SNOW: ${t}`); continue; } rows.push({ id: job.id, title: t, company: job.company, location: (job as any).location ?? "", link: job.link, posted: (job as any).postedAt ?? "", isNew: !existingIds.has(job.id), }); } } const survivors: Row[] = []; for (const r of rows) { if (await isEnglishContent(r.id)) survivors.push(r); else console.error(` DE/FR content dropped: ${r.title} — ${r.company}`); } console.error(`\n${survivors.length} survivors (${survivors.filter((r) => r.isNew).length} not in CSV)`); console.log(JSON.stringify(survivors, null, 2));