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", "system network engineer", "system administrator", "systeembeheer", "infrastructure engineer", "infrastructuur engineer", "network engineer", "network administrator", "network architect", "netwerkbeheer", "netwerk engineer", "cybersecurity", "cyber security", "security engineer", "security architect", "security analyst", "information security", "informatiebeveiliging", "workplace engineer", "endpoint engineer", "microsoft 365 engineer", "m365 engineer", "intune engineer", "modern workplace", "digital workplace", "service desk", "servicedesk", "helpdesk", "support engineer", "support specialist", "it technician", "pc technician", "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 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 = ( "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"(? 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]), ) 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=(), 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.""" 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} )