feat: personalize scoring and add skills radar

This commit is contained in:
Jens
2026-07-22 19:33:29 +02:00
parent 029df89265
commit a30ecbc553
33 changed files with 1529 additions and 114 deletions
+82 -2
View File
@@ -29,10 +29,13 @@ IT_TITLE_TERMS = (
"bi analyst",
"system engineer",
"systems engineer",
"system network engineer",
"system administrator",
"systeembeheer",
"infrastructure engineer",
"infrastructuur engineer",
"network engineer",
"network administrator",
"network architect",
"netwerkbeheer",
"netwerk engineer",
@@ -44,6 +47,10 @@ IT_TITLE_TERMS = (
"information security",
"informatiebeveiliging",
"workplace engineer",
"endpoint engineer",
"microsoft 365 engineer",
"m365 engineer",
"intune engineer",
"modern workplace",
"digital workplace",
"service desk",
@@ -51,6 +58,8 @@ IT_TITLE_TERMS = (
"helpdesk",
"support engineer",
"support specialist",
"it technician",
"pc technician",
"application lead",
"application engineer",
"application manager",
@@ -106,6 +115,48 @@ IT_TITLE_TERMS = (
"test automation",
)
# These engineering titles occur both in IT and in unrelated technical sectors. They are
# accepted only when the title supplies the role context and the description independently
# supplies a strong IT signal. A description signal by itself remains insufficient.
CONTEXTUAL_IT_TITLE_TERMS = (
"field engineer",
"field service engineer",
"implementation engineer",
"implementation consultant",
"service engineer",
)
IT_DESCRIPTION_TERMS = (
"microsoft 365",
"m365",
"windows server",
"active directory",
"entra id",
"intune",
"autopilot",
"exchange online",
"sharepoint",
"vmware",
"proxmox",
"hyper-v",
"networking",
"netwerkbeheer",
"tcp/ip",
"vlan",
"vpn",
"dhcp",
"dns",
"firewall",
"switches",
"routers",
"workstations",
"desktops",
"laptops",
"voip",
"3cx",
"powershell",
)
# These phrases can contain IT vocabulary while describing commercial, recruitment,
# or educational-design work. They therefore override positive title signals.
NON_IT_TITLE_TERMS = (
@@ -126,8 +177,16 @@ def _term_pattern(term: str) -> str:
IT_TITLE_PATTERN = "(?:" + "|".join(_term_pattern(term) for term in IT_TITLE_TERMS) + ")"
NON_IT_TITLE_PATTERN = "(?:" + "|".join(_term_pattern(term) for term in NON_IT_TITLE_TERMS) + ")"
CONTEXTUAL_IT_TITLE_PATTERN = (
"(?:" + "|".join(_term_pattern(term) for term in CONTEXTUAL_IT_TITLE_TERMS) + ")"
)
IT_DESCRIPTION_PATTERN = (
"(?:" + "|".join(_term_pattern(term) for term in IT_DESCRIPTION_TERMS) + ")"
)
_IT_TITLE_RE = re.compile(IT_TITLE_PATTERN, re.IGNORECASE)
_NON_IT_TITLE_RE = re.compile(NON_IT_TITLE_PATTERN, re.IGNORECASE)
_CONTEXTUAL_IT_TITLE_RE = re.compile(CONTEXTUAL_IT_TITLE_PATTERN, re.IGNORECASE)
_IT_DESCRIPTION_RE = re.compile(IT_DESCRIPTION_PATTERN, re.IGNORECASE)
@dataclass(frozen=True)
@@ -137,7 +196,7 @@ class ItRelevanceAssessment:
reason: str
def assess_it_relevance(title: str) -> ItRelevanceAssessment:
def assess_it_relevance(title: str, description: str = "") -> ItRelevanceAssessment:
normalized_title = normalize_token(title)
blocked_signal = _NON_IT_TITLE_RE.search(normalized_title)
if blocked_signal:
@@ -157,6 +216,23 @@ def assess_it_relevance(title: str) -> ItRelevanceAssessment:
signals=signals[:4],
reason="IT-signaal in functietitel: " + ", ".join(signals[:4]),
)
contextual_title = _CONTEXTUAL_IT_TITLE_RE.search(normalized_title)
if contextual_title:
description_signal = _IT_DESCRIPTION_RE.search(normalize_token(description))
if description_signal:
return ItRelevanceAssessment(
relevant=True,
signals=(contextual_title.group(0), description_signal.group(0)),
reason=(
"IT-context bevestigd via ambigue functietitel en vacaturetekst: "
f"{contextual_title.group(0)}, {description_signal.group(0)}."
),
)
return ItRelevanceAssessment(
relevant=False,
signals=(),
reason="Ambigue technische functietitel zonder aantoonbare IT-context.",
)
return ItRelevanceAssessment(
relevant=False,
signals=(),
@@ -175,6 +251,10 @@ def profile_requires_it_focus(profile: SearchProfile) -> bool:
def it_relevance_query(prefix: str = "") -> Q:
"""Return the database equivalent of the conservative title-led classifier."""
return Q(**{f"{prefix}original_title__iregex": IT_TITLE_PATTERN}) & ~Q(
clear_title = Q(**{f"{prefix}original_title__iregex": IT_TITLE_PATTERN})
contextual_title = Q(**{f"{prefix}original_title__iregex": CONTEXTUAL_IT_TITLE_PATTERN}) & Q(
**{f"{prefix}description_text__iregex": IT_DESCRIPTION_PATTERN}
)
return (clear_title | contextual_title) & ~Q(
**{f"{prefix}original_title__iregex": NON_IT_TITLE_PATTERN}
)