From 7cd1f9254dd69203440e0f85195c2aabef0d573a Mon Sep 17 00:00:00 2001 From: rafapolo Date: Thu, 10 Sep 2026 10:39:54 +0200 Subject: [PATCH] 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. --- fetch-other.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/fetch-other.ts b/fetch-other.ts index cd87ccc..cf1ed2f 100644 --- a/fetch-other.ts +++ b/fetch-other.ts @@ -43,9 +43,18 @@ function applyFilters(title: string): boolean { const MS_STACK_RE = /\.net\b|\bc#|sharepoint|dynamics 365|power platform|\bsccm\b|intune|successfactors|\bsap\b/i; // DE/FR indicator words, counted against a description we already have in hand. -// Mirrors the ≥ 6 threshold isEnglishContent uses on fetched pages. +// Mirrors the ≥ 6 threshold isEnglishContent uses on fetched pages — but see +// the adaptive threshold below for why short snippets need a lower bar. +// du/dein*/dich/dir/einem/einer/eines/der/dem added 2026-09-08: job-room's +// search API "Senior Fullstack Software Engineer" (Consult & Pepper) teaser — +// "Du arbeitest in einem agilen Umfeld nach Scrum mit folgendem Tech-Stack: +// ... TypeScript, Angular, HTML, CSS, PostgreSQL, ..." — is entirely German +// but has zero umlauts and only one hit ("mit") on the original list, so it +// sailed through as English despite jobs.ch itself showing "Deutsch +// (Fliessend)" on the full posting. These are unambiguous German/French +// function words with no common English collision. const DE_FR_WORDS_RE = - /\b(und|oder|wir|Sie|Ihre|Ihrer|Kenntnisse|Erfahrung|Aufgaben|Anforderungen|Stelle|Unternehmen|Mitarbeiter|Bereich|nous|vous|votre|pour|avec|des|les)\b/g; + /\b(und|oder|wir|Sie|Ihre|Ihrer|Kenntnisse|Erfahrung|Aufgaben|Anforderungen|Stelle|Unternehmen|Mitarbeiter|Bereich|nous|vous|votre|pour|avec|des|les|du|dein\w*|dich|dir|einem|einer|eines|der|dem)\b/g; // English-language AND not demanding German above B1 — mirrors isViableContent/ // isViableUrl in fetch.ts, but against a description we already have in hand @@ -61,7 +70,14 @@ function isViableDescription(description?: string): { ok: boolean; reason?: stri if (!description) return { ok: true }; // nothing to judge — let the URL check in index.ts decide const text = description.replace(/<[^>]+>/g, " ").replace(/&[a-z]+;/g, " "); if (hasNonEnglishUmlaut(text)) return { ok: false, reason: "non-English (umlaut)" }; - if ((text.match(DE_FR_WORDS_RE) ?? []).length >= 6) return { ok: false, reason: "non-English" }; + // job-room's search API returns a short keyword-anchored teaser (measured: + // always < 500 chars), not the full posting — ≥6 is calibrated against full + // page text and is unreachable on a couple of sentences, so a genuinely + // German teaser can clear it on word count alone. Lower the bar for short + // text; leave the full-page threshold (≥6) alone for longer descriptions, + // where it was tuned to avoid false positives on English text. + const threshold = text.length < 500 ? 3 : 6; + if ((text.match(DE_FR_WORDS_RE) ?? []).length >= threshold) return { ok: false, reason: "non-English" }; if (requiresGerman(text)) return { ok: false, reason: "requires German > B1" }; return { ok: true }; }