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.
This commit is contained in:
2026-09-18 11:49:49 +02:00
parent 9cbc525cf5
commit 288b8bdcd1
4 changed files with 177 additions and 15 deletions
+26
View File
@@ -200,6 +200,22 @@ const GERMAN_OPTIONAL_RE =
const GERMAN_REQUIRED_RE =
/\b(?:required|require[sd]?|must|mandatory|essential|necessary|fluen\w*|proficien\w*|excellent|strong|very\s+good|good\s+(?:knowledge|command|level)|native|mother\s*tongue|business\s+level|negotiat\w*|B2|C1|C2|verhandlungssicher\w*|fliessend\w*|fließend\w*|muttersprach\w*|sehr\s+gut\w*|gut\w*\s+Deutschkenntnisse|Deutschkenntnisse|voraussetzung|zwingend|erforderlich)\b/i;
/**
* "German is <verdict>" / "proficiency in German is considered <verdict>" —
* the copula directly after the mention names the mention's own predicate,
* which should decide the case outright rather than compete on raw distance
* with unrelated markers elsewhere in the window. Swisscom's stock line
* "Fluency in English is required; proficiency in German is considered an
* advantage" put a required-marker word ("proficiency") right next to
* "German" and an optional-marker word ("advantage") further away — nearest()
* picked "proficiency" and wrongly called it required. The predicate right
* after the mention ("is considered an advantage") is what actually decides.
* Only fires when a copula immediately follows; "Work proficiency in German,
* English and French" (no copula after "German") correctly falls through to
* the general nearest-marker logic below.
*/
const GERMAN_LOCAL_PREDICATE_RE = /^\s*,?\s*(?:is|are)\s+(?:considered\s+|deemed\s+)?/i;
/**
* Spec-sheet style language line ("Languages: German & English"), common on
* contractor postings. There is no adjective for the nearest-marker logic to
@@ -261,6 +277,16 @@ export function requiresGerman(text: string): boolean {
for (const m of flat.matchAll(GERMAN_MENTION_RE)) {
const i = m.index ?? 0;
// The mention's own predicate ("German is <verdict>") overrides distance.
const after = flat.slice(i + m[0].length, i + m[0].length + 60);
const predicateM = after.match(GERMAN_LOCAL_PREDICATE_RE);
if (predicateM) {
const rest = after.slice(predicateM[0].length);
if (new RegExp(`^${GERMAN_OPTIONAL_RE.source}`, "i").test(rest)) continue;
if (new RegExp(`^${GERMAN_REQUIRED_RE.source}`, "i").test(rest)) return true;
}
const from = Math.max(0, i - 110);
const window = flat.slice(from, i + 110);
const at = i - from; // mention's offset inside the window