englishjobsearch.ch listed the same posting twice under different
canton pages, once as 'Swiss AviationSoftware' and once as 'Swiss-AS' --
different clickout links and slightly different company text for the
same opening, so it showed up twice in one digest. Matched jobs are now
deduped on normalized title+company, plus a title-only pass for titles
specific enough (5+ words) to make a same-source coincidental match
implausible.
Jobs rejected for German/French content never reach jobs.csv, so daily.ts
had no way to see they existed — the digest silently under-reported how
much the run actually looked at.
index.ts now counts them across both the LinkedIn and extra-source paths
and writes results/.lang-dropped.json; daily.ts reads it back, keyed on
the digest date, and appends a count-only category. Count-only because
there is no Row to list — these were dropped before ever being parsed.
fetch.ts already parses each job card's own location, but index.ts
discarded it and stored only the search-area label in the CSV, so
daily.ts's telegram grouping used the query that found a job instead
of where it actually is (e.g. a Bern search surfacing a Lausanne job).
Now the location is written to a new CSV column and used for grouping,
with a one-time migration for existing rows.
telegram.ts
- Bot API wrapper: HTML escaping, 4096-char chunking, rate-limit
spacing, and --setup to discover the chat ID from the bot's inbox.
Reads TELEGRAM_BOT (or TELEGRAM_TOKEN) and TELEGRAM_CHAT_ID.
profile.ts
- matchesProfile() scores a title against cv/polo.md. Hard rejects for
Microsoft stack, Java/C++/SAP, aggregators (Jobgether, ALLPS),
non-software functions and life-science domains; then weighted
signals for the core stack, platform/DevOps, data, AI/ML, cloud and
seniority. Threshold is 4 so short bullseye titles ("Data Engineer",
"DevOps Engineer") pass, and forward-deployed is weighted to clear it
on its own.
daily.ts
- Runs the fetch, diffs jobs.csv, keeps only profile matches, groups by
area and pushes to Telegram. results/sent.txt records every link
actually sent, written only after a successful send, so a failure
retries instead of losing jobs and an ad is never sent twice.
- --dry-run previews, --resend --date= re-sends a past day.
- A failed fetch reports itself rather than going quiet.
Scheduled at 08:00 and 18:00 by a launchd agent
(com.rafapolo.linkme-daily). launchd runs missed jobs on wake, and the
digest is diff-based, so downtime delays delivery without losing jobs.