Files
VacatureRadar/apps/jobs/services/relevance.py
T
Jens 029df89265
deploy / deploy (push) Canceled after 0s
feat: ship premium IT-focused vacancy radar
2026-07-22 15:16:15 +02:00

181 lines
4.7 KiB
Python

from __future__ import annotations
import re
from dataclasses import dataclass
from django.db.models import Q
from apps.profiles.models import SearchProfile
from .normalization import normalize_token
# Deliberately title-led: employer pages regularly mention digital systems in otherwise
# non-IT vacancies. A description hit alone must therefore never make a vacancy relevant.
IT_TITLE_TERMS = (
"it",
"ict",
"informatica",
"software",
"development engineer",
"devops",
"secops",
"cloud",
"data engineer",
"data scientist",
"data steward",
"database",
"dba",
"business intelligence",
"bi analyst",
"system engineer",
"systems engineer",
"systeembeheer",
"infrastructure engineer",
"infrastructuur engineer",
"network engineer",
"network architect",
"netwerkbeheer",
"netwerk engineer",
"cybersecurity",
"cyber security",
"security engineer",
"security architect",
"security analyst",
"information security",
"informatiebeveiliging",
"workplace engineer",
"modern workplace",
"digital workplace",
"service desk",
"servicedesk",
"helpdesk",
"support engineer",
"support specialist",
"application lead",
"application engineer",
"application manager",
"application specialist",
"applicatiebeheer",
"platform engineer",
"platform expert",
"solution architect",
"solutions architect",
"technical architect",
"technisch architect",
"functional analyst",
"functioneel analist",
"business analyst",
"product owner",
"web developer",
"mobile developer",
"low-code developer",
"low code developer",
"api developer",
"idm developer",
"power platform",
"forgerock",
"embedded",
"c#",
"front end",
"frontend",
"back end",
"backend",
"full stack",
"fullstack",
"php",
".net",
"java",
"python",
"powershell",
"azure",
"aws",
"microsoft 365",
"linux",
"windows server",
"kubernetes",
"docker",
"terraform",
"erp",
"sap",
"machine learning",
"ai engineer",
"artificial intelligence",
"image processing",
"computer vision",
"qa engineer",
"test automation",
)
# These phrases can contain IT vocabulary while describing commercial, recruitment,
# or educational-design work. They therefore override positive title signals.
NON_IT_TITLE_TERMS = (
"business developer",
"business development",
"it recruitment",
"it recruiter",
"learning designer",
)
IT_PROFILE_TERMS = (*IT_TITLE_TERMS, "networking", "security", "active directory", "intune")
def _term_pattern(term: str) -> str:
escaped = re.escape(term).replace(r"\ ", r"[\s/_-]+")
return rf"(?<!\w){escaped}(?!\w)"
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) + ")"
_IT_TITLE_RE = re.compile(IT_TITLE_PATTERN, re.IGNORECASE)
_NON_IT_TITLE_RE = re.compile(NON_IT_TITLE_PATTERN, re.IGNORECASE)
@dataclass(frozen=True)
class ItRelevanceAssessment:
relevant: bool
signals: tuple[str, ...]
reason: str
def assess_it_relevance(title: str) -> ItRelevanceAssessment:
normalized_title = normalize_token(title)
blocked_signal = _NON_IT_TITLE_RE.search(normalized_title)
if blocked_signal:
return ItRelevanceAssessment(
relevant=False,
signals=(),
reason=f"Niet-technische titelcontext: {blocked_signal.group(0)}.",
)
signals = tuple(
term
for term in IT_TITLE_TERMS
if re.search(_term_pattern(normalize_token(term)), normalized_title, re.IGNORECASE)
)
if signals:
return ItRelevanceAssessment(
relevant=True,
signals=signals[:4],
reason="IT-signaal in functietitel: " + ", ".join(signals[:4]),
)
return ItRelevanceAssessment(
relevant=False,
signals=(),
reason="Geen aantoonbaar IT-signaal in de functietitel.",
)
def profile_requires_it_focus(profile: SearchProfile) -> bool:
configured = " ".join([profile.name, *profile.desired_titles, *profile.desired_skills])
normalized = normalize_token(configured)
return any(
re.search(_term_pattern(normalize_token(term)), normalized, re.IGNORECASE)
for term in IT_PROFILE_TERMS
)
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(
**{f"{prefix}original_title__iregex": NON_IT_TITLE_PATTERN}
)