231 lines
6.9 KiB
TypeScript
231 lines
6.9 KiB
TypeScript
export type WorkType = "onsite" | "remote" | "hybrid";
|
|
export type JobType = "full-time" | "part-time" | "contract" | "temporary" | "internship";
|
|
export type TimePosted = "day" | "week" | "month";
|
|
export type Seniority = "internship" | "entry" | "associate" | "mid-senior" | "director" | "executive";
|
|
|
|
export interface SearchConfig {
|
|
label: string;
|
|
/** Use a raw LinkedIn URL instead of building one from filters. */
|
|
url?: string;
|
|
keywords?: string;
|
|
geoId?: string;
|
|
location?: string;
|
|
workType?: WorkType[];
|
|
jobType?: JobType[];
|
|
timePosted?: TimePosted;
|
|
seniority?: Seniority[];
|
|
}
|
|
|
|
export const KEYWORDS = [
|
|
"Software Engineer",
|
|
"Solutions Engineer",
|
|
"Full Stack Engineer",
|
|
"Backend Engineer",
|
|
"Data Engineer",
|
|
"AI Engineer",
|
|
"Agentic",
|
|
"Applications Engineer",
|
|
"Platform Engineer",
|
|
"DevOps Engineer",
|
|
"Staff Engineer",
|
|
"Principal Engineer",
|
|
"Tech Lead",
|
|
"Developer",
|
|
"Ruby on Rails",
|
|
"Python",
|
|
"Node.js",
|
|
"TypeScript",
|
|
"Artificial Intelligence",
|
|
"Machine Learning",
|
|
"Data Science",
|
|
"AI Consultant",
|
|
"ML Engineer",
|
|
"Director of Engineering",
|
|
"Engineering Manager",
|
|
"Lead Developer",
|
|
"System Engineer",
|
|
"Solution Engineer",
|
|
].join(" OR ");
|
|
|
|
// --- Role families beyond the core engineer/developer titles ---
|
|
// Kept as separate OR-strings rather than folded into KEYWORDS: LinkedIn ranks
|
|
// a focused query far better than one 50-term disjunction, and each family
|
|
// needs a different set of areas (research is Basel-heavy, field/remote is not).
|
|
|
|
/** Platform, reliability, cloud and security — the DevOps/Swisscom lane. */
|
|
export const PLATFORM_KEYWORDS = [
|
|
"Solutions Architect",
|
|
"Cloud Architect",
|
|
"Site Reliability Engineer",
|
|
"Infrastructure Engineer",
|
|
"Cloud Engineer",
|
|
"Security Engineer",
|
|
"DevSecOps",
|
|
"Application Security Engineer",
|
|
"Cloud Security Engineer",
|
|
"MLOps Engineer",
|
|
"AI Platform Engineer",
|
|
"ML Platform Engineer",
|
|
"AI Infrastructure",
|
|
].join(" OR ");
|
|
|
|
/** Research software engineering — Basel pharma/university cluster.
|
|
* Deliberately no bioinformatics/computational-biology terms: those are
|
|
* domain-science roles, filtered out by BIO_DOMAIN_RE in fetch.ts. */
|
|
export const RESEARCH_KEYWORDS = [
|
|
"Research Software Engineer",
|
|
"Scientific Software Engineer",
|
|
"Scientific Programmer",
|
|
"Scientific Computing",
|
|
"Research Engineer",
|
|
"HPC Engineer",
|
|
"Data Platform Engineer",
|
|
].join(" OR ");
|
|
|
|
/** Customer-facing and early-stage product roles — the consulting lane. */
|
|
export const FIELD_KEYWORDS = [
|
|
"Forward Deployed Engineer",
|
|
"Palantir Foundry",
|
|
"Field Engineer",
|
|
"Customer Engineer",
|
|
"Sales Engineer",
|
|
"Pre-Sales Engineer",
|
|
"Integration Engineer",
|
|
"API Engineer",
|
|
"Analytics Engineer",
|
|
"Founding Engineer",
|
|
"Product Engineer",
|
|
].join(" OR ");
|
|
|
|
// --- Define your searches here ---
|
|
export const searches: SearchConfig[] = [
|
|
{
|
|
label: "Basel (city)",
|
|
keywords: KEYWORDS,
|
|
geoId: "103383283",
|
|
location: "Basel",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
label: "Basel Metropolitan Area",
|
|
keywords: KEYWORDS,
|
|
geoId: "90009885",
|
|
location: "Basel Metropolitan Area",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
label: "Basel-Country",
|
|
keywords: KEYWORDS,
|
|
geoId: "107117530",
|
|
location: "Basel-Country",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
// Name-only, no geoId — deliberately alongside the three geoId searches
|
|
// above, not instead of them. LinkedIn's guest search serves a *different*
|
|
// slice per query shape: measured 2026-09-05, the Basel-city geoId query
|
|
// exhausts at 76 unique cards and never returns some genuinely Basel-Stadt
|
|
// postings that a name-resolved query does. The PAS01 "Full-Stack Software
|
|
// Engineer (60-80%)" in Basel (4461764782) was invisible to all six Basel
|
|
// searches and reached the digest only because the *Bern* query — also
|
|
// name-only — happened to return it. Overlap with the geoId slice is high
|
|
// but not total, so this is cheap insurance against exactly that miss.
|
|
label: "Basel (broad)",
|
|
keywords: KEYWORDS,
|
|
location: "Basel, Switzerland",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
label: "Bern",
|
|
keywords: KEYWORDS,
|
|
location: "Bern, Switzerland",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
label: "Zürich",
|
|
keywords: KEYWORDS,
|
|
geoId: "102436504",
|
|
location: "Zürich, Switzerland",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
label: "Zürich Metropolitan Area",
|
|
keywords: KEYWORDS,
|
|
geoId: "107814425",
|
|
location: "Zürich Metropolitan Area",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
label: "Zürich Area",
|
|
keywords: KEYWORDS,
|
|
geoId: "90009888",
|
|
location: "Zürich Area",
|
|
workType: ["onsite", "hybrid"],
|
|
timePosted: "month",
|
|
},
|
|
{
|
|
// Core KEYWORDS previously only ran onsite/hybrid against the three named
|
|
// cities — a manual "Fullstack Developer" remote-CH search on 2026-09-10
|
|
// turned up 21 viable postings (PAS01, xorlab, Proton, SCOR...) that no
|
|
// existing search covered, since Platform/Field are the only families with
|
|
// a Switzerland-wide remote sweep. Adding the same sweep for core KEYWORDS.
|
|
label: "Switzerland Remote",
|
|
keywords: KEYWORDS,
|
|
location: "Switzerland",
|
|
workType: ["remote"],
|
|
timePosted: "month",
|
|
},
|
|
];
|
|
|
|
// --- Role-family sweeps ---------------------------------------------------
|
|
// Same filters as the core searches, run across a narrower set of areas.
|
|
// Research is Basel/Zürich only (that's where the institutes are); platform
|
|
// and field roles also sweep Switzerland-wide remote.
|
|
|
|
const CORE_AREAS: { label: string; geoId?: string; location: string }[] = [
|
|
{ label: "Basel Metropolitan Area", geoId: "90009885", location: "Basel Metropolitan Area" },
|
|
{ label: "Zürich Metropolitan Area", geoId: "107814425", location: "Zürich Metropolitan Area" },
|
|
{ label: "Bern", location: "Bern, Switzerland" },
|
|
];
|
|
|
|
const RESEARCH_AREAS = CORE_AREAS.filter((a) => a.label !== "Bern");
|
|
|
|
function sweep(
|
|
family: string,
|
|
keywords: string,
|
|
areas: typeof CORE_AREAS,
|
|
opts: { remote?: boolean } = {}
|
|
): SearchConfig[] {
|
|
const out: SearchConfig[] = areas.map((a) => ({
|
|
label: `${family} — ${a.label}`,
|
|
keywords,
|
|
geoId: a.geoId,
|
|
location: a.location,
|
|
workType: ["onsite", "hybrid"] as WorkType[],
|
|
timePosted: "month" as TimePosted,
|
|
}));
|
|
if (opts.remote) {
|
|
out.push({
|
|
label: `${family} — Switzerland Remote`,
|
|
keywords,
|
|
location: "Switzerland",
|
|
workType: ["remote"],
|
|
timePosted: "month",
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
searches.push(
|
|
...sweep("Platform", PLATFORM_KEYWORDS, CORE_AREAS, { remote: true }),
|
|
...sweep("Research", RESEARCH_KEYWORDS, RESEARCH_AREAS),
|
|
...sweep("Field", FIELD_KEYWORDS, CORE_AREAS, { remote: true })
|
|
);
|