chore: reorganize repo structure and update docs

- move temp scripts to scripts/, cv files to cv/, jobs.csv to results/
- update index.ts and recheck.ts CSV path to results/jobs.csv
- fix imports in scripts/ to use ../config.ts and ../fetch.ts
- update CLAUDE.md and README.md to reflect new structure
- add plans/ with LinkedIn post drafts
- update .gitignore: ignore results/, jobs/, cv binaries, .DS_Store
This commit is contained in:
2026-06-03 16:01:32 +02:00
parent bcb6ae5ea3
commit fb67c38edd
40 changed files with 1472 additions and 806 deletions
+13 -3
View File
@@ -8,6 +8,7 @@ export interface Job {
postedAt: string;
link: string;
search: string;
source: string;
}
const WORK_TYPE_MAP: Record<WorkType, string> = {
@@ -54,7 +55,7 @@ const SENIORITY_MAP: Record<Seniority, string> = {
// - gender markers: (H/F), (F/H), (h/f)
// - French-only accented chars: é è ê ë à â ç î ï ô ù û ÿ œ æ
// - French-only vocabulary
const NON_ENGLISH_RE = new RegExp(
export const NON_ENGLISH_RE = new RegExp(
[
// German gender markers (w = weiblich; must not match m/f/d)
String.raw`\(m\/w[^)]*\)`,
@@ -87,7 +88,9 @@ const HEADERS = {
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
};
const JUNIOR_TITLE_RE = /\b(junior|jr\.?|entry.?level|graduate|intern(?:ship)?|trainee|apprentice|werkstudent)\b/i;
export const JUNIOR_TITLE_RE = /\b(junior|jr\.?|entry.?level|graduate|intern(?:ship)?|trainee|apprentice|werkstudent)\b/i;
export const JAVA_TITLE_RE = /\bjava\b/i;
export const SOFTWARE_TITLE_RE = /\b(software|developer|architect|devops|platform|data|backend|frontend|fullstack|full.stack|tech\s*lead|technical|typescript|python|rails|node\.?js|ai\b|machine\s*learning|cloud|security|analytics|sre|reliability|solutions?|consultant|applications?\s*engineer|system[\s-]*engineer|informatics|director|product\s*manager|engineering\s*manager|it\s*manager|it\s*project|staff\s*engineer|principal\s*engineer|cto|vp\s*eng)\b/i;
@@ -166,11 +169,17 @@ function parseJobs(html: string, label: string): Job[] {
const companyM = card.match(/<h4 class="base-search-card__subtitle">[\s\S]{0,300}?<a[^>]*>\s*([\s\S]*?)\s*<\/a>/);
const timeM = card.match(/<time[^>]+datetime="([^"]+)"/);
const locM = card.match(/job-search-card__location[^>]*>\s*([^<]+)/);
const jobLocation = locM ? locM[1].trim() : "";
// Drop jobs not physically in Switzerland — LinkedIn ignores geoId without auth
if (jobLocation && !jobLocation.includes("Switzerland")) continue;
const title = clean(titleM[1]);
const company = companyM ? clean(companyM[1]) : "";
const postedAt = timeM ? timeM[1] : "";
jobs.push({ id, title, company, location: label, postedAt, link: `https://www.linkedin.com/jobs/view/${id}/`, search: label });
jobs.push({ id, title, company, location: label, postedAt, link: `https://www.linkedin.com/jobs/view/${id}/`, search: label, source: "linkedin.com" });
}
return jobs;
@@ -196,6 +205,7 @@ export async function fetchJobs(config: SearchConfig, { raw = false } = {}): Pro
const jobs = parseJobs(html, config.label)
.filter((j) => !NON_ENGLISH_RE.test(j.title))
.filter((j) => !JUNIOR_TITLE_RE.test(j.title))
.filter((j) => !JAVA_TITLE_RE.test(j.title))
.filter((j) => raw || SOFTWARE_TITLE_RE.test(j.title));
let newOnPage = 0;