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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user