Commit Graph
9 Commits
Author SHA1 Message Date
rodado 288b8bdcd1 feat: add sciCORE and Swisscom as job sources
sciCORE now runs inside bun fetch/daily via fetchSciCore instead of its
own standalone launchd watcher. Swisscom's Workday career site is added
as a new source (fetchSwisscom), pulled via its public CXS JSON API.

Also fixes a false positive in requiresGerman(): Swisscom's stock line
"proficiency in German is considered an advantage" was being flagged as
requiring German because the noun "proficiency" sat closer to the
mention than the real optional marker "advantage". A local-predicate
override now lets the words immediately following "German is/are ..."
decide the verdict instead of losing to raw character distance.
2026-09-18 11:49:49 +02:00
rodado 0249800278 fix: stop letting unreadable or truncated descriptions slip past the German filter
isViableContent/isViableUrl defaulted to keep a job when its description
couldn't be fetched at all -- that's what let a fully-German HRM
Systems/Consult & Pepper posting ("In Deutsch bist du verhandlungssicher")
through on 2026-09-10. They now report retry:true instead, and index.ts
leaves the job out of the CSV so a fresh fetch judges it next run rather
than defaulting to keep.

job-room.ch's search API also returns a different keyword-anchored teaser
per hit for the same ad, and only the last one was kept -- a genuinely
German posting could slip through whenever the last keyword's teaser
happened to read as English. Every teaser variant is now collected and
checked together.
2026-09-14 12:12:43 +02:00
rodado 7cd1f9254d fix: lower German-teaser threshold for short job-room descriptions
job-room's search API returns a short keyword-anchored teaser (always
< 500 chars), not the full posting. The >=6 DE/FR-word threshold was
calibrated against full page text and is unreachable on a couple of
sentences, so a genuinely German teaser (Consult & Pepper's "Senior
Fullstack Software Engineer") could clear it on word count alone despite
jobs.ch itself showing "Deutsch (Fliessend)" on the full posting. Use a
lower threshold for short text; the full-page >=6 threshold stays as-is
for longer descriptions, where it was tuned to avoid false positives.
2026-09-10 10:39:54 +02:00
rodado 2b7bf3b5f7 fix: catch German descriptions by umlaut, not just DE word count
The >=6 DE_CONTENT_RE word threshold needs a reasonably long snippet to
trip. An Expleo (Bern) posting slipped through because a concurrent fetch
under load returned a truncated description with too few matches, even
though the full text (72 matches) was entirely German.

A bare umlaut or eszett is a near-certain German signal and survives
truncation — one German sentence almost always carries one. Zurich/
Zurcher is excluded as the one umlaut word that shows up constantly in
genuinely English-language Swiss postings, as a place name.

Applied in isViableContent, isEnglishContent, isViableUrl and the
fetch-other.ts sources, so every description path gets the same check.
2026-09-05 18:04:22 +02:00
rodado b2e5c7865a fix: catch job-room.ch's structured German-fluency requirement
languageSkills[] (e.g. {languageIsoCode: "de", spokenLevel: "PROFICIENT"})
sits outside jobDescriptions[].description, so requiresGerman()/
isViableDescription() never saw it — the UI renders this as
"Deutsch (Fliessend)", which is exactly what kept reaching Telegram.
Measured: 69 of 1843 ads required PROFICIENT German, prose check caught
only 3.
2026-09-04 11:18:15 +02:00
rodado d5e6605176 feat: revive datacareer.ch and integrate basel-jobs.ch
datacareer.ch had been returning 403 on every page for months. The cause
was our own User-Agent: its nginx inverts the usual bot rule and blocks
browser-like UAs while serving honest ones (no UA, curl, Googlebot).
Give it PLAIN_HEADERS instead of the shared spoofed-Chrome HEADERS.
Basel has 8 jobs on a single page with no pagination, so page 2 returning
0 items is correct rather than truncation.

basel-jobs.ch was documented as un-integrable because DataDome blocks
plain curl. Only half of that was true. Measured: plain fetch 403s on any
UA, headless Chrome over CDP 403s, Playwright's own launch 403s even
headful — but a real Chrome launched normally with --remote-debugging-port
and attached over CDP afterwards gets 200. Clearance then lives entirely
in a datadome cookie that replays fine over plain fetch with a matching
UA, so a browser is touched only to mint, never to scrape. The earlier
"cookie can't be extracted" note was true of the browser extension, not
of CDP.

Minting needs a visible window, so launchd runs can't do it; a stale
cookie makes the source log and yield nothing instead of failing the run.
Refresh by hand with `bun run mint-datadome`, or set LINKME_NO_BROWSER=1.

Three passes, all needed — they cover different sets. The informatik
category is a curated taxonomy, not the whole board, and the two search
modes query different indexes: websearch=1 finds role words and postings
filed outside IT (+147 over the category), while the bare search is the
only mode matching stack terms — react, frontend and backend return zero
under websearch=1. Language checks run against div.plain-content only;
against the whole page the German site chrome alone clears the DE/FR
threshold and drops every job, English ones included.

Also fix a pre-existing double-wrapped Promise in the extraSources type.
2026-09-01 12:23:59 +02:00
rodado d195d67c2a feat: add jobbasel.ch and englishjobsearch.ch job sources
jobbasel.ch's search route (/job/suche-{slug}) is server-rendered with
a window.__PRELOADED_STATE__ JSON blob, no headless browser needed.
englishjobsearch.ch works the same way via its ?format=markdown param.
Both swept with the same fixed keyword list job-room.ch already uses.

basel-jobs.ch was evaluated too but left out: it's DataDome-protected,
which blocks plain curl outright, and this project's fetchers are
intentionally browser-free.

While wiring these in, found that isEnglishDescription (now
isViableDescription) only ever ran the DE/FR word-count check against
inline descriptions, never requiresGerman() - so an English-written
posting demanding fluent German (e.g. PRODYNA's "you can communicate
fluently in both German and English") sailed straight through. That
gap existed in itjobs.ch and job-room.ch too, not just the two new
sources, so the fix applies to all four.
2026-08-30 13:07:16 +02:00
rodado b4957025b1 chore: update search scripts, extra sources and post drafts
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.
2026-08-27 09:03:05 +02:00
rodado fb67c38edd 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
2026-06-03 16:01:32 +02:00