Adds the one-off scripts accumulated across recent sessions, removes temp scripts whose searches now live in config.ts as role-family sweeps, and updates fetch-other.ts plus the LinkedIn post drafts.
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
#!/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<string>();
|
|
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<string>();
|
|
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));
|