This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from difflib import SequenceMatcher
|
||||
from typing import Any, Iterable
|
||||
from typing import Any
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from apps.jobs.models import JobPosting, ScoreRun
|
||||
from apps.jobs.services.ai import AiAnalysis, AiAnalysisCache, analyze_job_text
|
||||
from apps.jobs.services.distance import estimate_commute, haversine_km
|
||||
from apps.jobs.services.geocoding import resolve_cached_location
|
||||
from apps.jobs.models import JobPosting, ScoreRun
|
||||
from apps.profiles.models import SearchProfile
|
||||
|
||||
from .normalization import normalize_token
|
||||
@@ -106,7 +107,9 @@ def _profile_reference(profile: SearchProfile) -> _GeoReference | None:
|
||||
def _title_fit(job: JobPosting, profile: SearchProfile) -> float:
|
||||
if not profile.desired_titles:
|
||||
return 0.65
|
||||
return max(_similarity(job.normalized_title, desired_title) for desired_title in profile.desired_titles)
|
||||
return max(
|
||||
_similarity(job.normalized_title, desired_title) for desired_title in profile.desired_titles
|
||||
)
|
||||
|
||||
|
||||
def _skill_fit(job: JobPosting, profile: SearchProfile) -> tuple[float, list[str], list[str]]:
|
||||
@@ -191,9 +194,15 @@ def _hard_exclusions(
|
||||
):
|
||||
reasons.append(f"Uitgesloten regio: {job.region or job.municipality}")
|
||||
|
||||
if distance.exact_distance_km is not None and distance.exact_distance_km > distance_limit:
|
||||
if job.workplace_type != "remote":
|
||||
reasons.append(f"Afstand {distance.exact_distance_km:.0f} km boven maximum {profile.max_distance_km} km")
|
||||
if (
|
||||
distance.exact_distance_km is not None
|
||||
and distance.exact_distance_km > distance_limit
|
||||
and job.workplace_type != "remote"
|
||||
):
|
||||
reasons.append(
|
||||
f"Afstand {distance.exact_distance_km:.0f} km boven maximum "
|
||||
f"{profile.max_distance_km} km"
|
||||
)
|
||||
|
||||
max_commute_minutes = profile.hard_rules.get("max_commute_minutes")
|
||||
try:
|
||||
@@ -206,7 +215,8 @@ def _hard_exclusions(
|
||||
and distance.commute_minutes > max_commute_limit
|
||||
):
|
||||
reasons.append(
|
||||
f"Geschatte reistijd {distance.commute_minutes} minuten boven limiet van {max_commute_limit}"
|
||||
f"Geschatte reistijd {distance.commute_minutes} minuten boven limiet van "
|
||||
f"{max_commute_limit}"
|
||||
)
|
||||
|
||||
excluded_skills = {
|
||||
@@ -244,7 +254,9 @@ def _ai_feature_score(features: dict[str, Any]) -> float:
|
||||
"unknown": 0.03,
|
||||
"": 0.03,
|
||||
}.get(seniority, 0.05)
|
||||
score = 0.5 * (1.0 - support_ratio) + 0.25 * (1.0 - consultancy_ratio) + 0.15 * (1.0 - travel_ratio)
|
||||
score = (
|
||||
0.5 * (1.0 - support_ratio) + 0.25 * (1.0 - consultancy_ratio) + 0.15 * (1.0 - travel_ratio)
|
||||
)
|
||||
return max(0.0, min(1.0, score + seniority_boost))
|
||||
|
||||
|
||||
@@ -373,7 +385,10 @@ def calculate_score(job: JobPosting, profile: SearchProfile) -> ScoreResult:
|
||||
positives.append("Herkenbare skills: " + ", ".join(present_skills[:6]))
|
||||
if job.direct_employer and not job.recruiter:
|
||||
positives.append("Rechtstreekse werkgeversbron.")
|
||||
if distance.exact_distance_km is not None and distance.exact_distance_km <= profile.max_distance_km:
|
||||
if (
|
||||
distance.exact_distance_km is not None
|
||||
and distance.exact_distance_km <= profile.max_distance_km
|
||||
):
|
||||
positives.append(f"Binnen de ingestelde afstand ({distance.exact_distance_km:.0f} km).")
|
||||
if (
|
||||
distance.exact_distance_km is None
|
||||
@@ -382,13 +397,18 @@ def calculate_score(job: JobPosting, profile: SearchProfile) -> ScoreResult:
|
||||
):
|
||||
estimate_label = "geschatte" if distance.commute_estimate else "ingeschatte"
|
||||
concerns.append(
|
||||
f"Schatting: {estimate_label} reistijd ca. {distance.commute_minutes} min (conservatief)."
|
||||
f"Schatting: {estimate_label} reistijd ca. {distance.commute_minutes} min "
|
||||
"(conservatief)."
|
||||
)
|
||||
if support_ratio >= 0.5:
|
||||
concerns.append("Vacature bevat sterke first-line/helpdesksignalen.")
|
||||
if missing_skills:
|
||||
concerns.append("Niet duidelijk teruggevonden: " + ", ".join(missing_skills[:6]))
|
||||
if distance.exact_distance_km is None and distance.has_distance_data and job.workplace_type != "remote":
|
||||
if (
|
||||
distance.exact_distance_km is None
|
||||
and distance.has_distance_data
|
||||
and job.workplace_type != "remote"
|
||||
):
|
||||
concerns.append("Afstand kon nog niet exact betrouwbaar worden berekend.")
|
||||
if not job.compensation:
|
||||
concerns.append("Salaris of barema is niet vermeld.")
|
||||
@@ -436,7 +456,9 @@ def calculate_score(job: JobPosting, profile: SearchProfile) -> ScoreResult:
|
||||
"warnings": ai_analysis.warnings,
|
||||
"features": ai_analysis.features,
|
||||
"weight_requested": float(profile.weights.get("ai", 0) or 0),
|
||||
"weight_applied": ai_weight if ai_analysis.status == AiAnalysisCache.Status.OK else 0.0,
|
||||
"weight_applied": ai_weight
|
||||
if ai_analysis.status == AiAnalysisCache.Status.OK
|
||||
else 0.0,
|
||||
},
|
||||
},
|
||||
model_version=ai_analysis.model,
|
||||
@@ -451,9 +473,7 @@ def _iter_active_profiles(profile_id: int | None):
|
||||
return profiles
|
||||
|
||||
|
||||
def rescore_jobs_with_profiles(
|
||||
jobs: Iterable[JobPosting], *, profile_id: int | None = None
|
||||
) -> int:
|
||||
def rescore_jobs_with_profiles(jobs: Iterable[JobPosting], *, profile_id: int | None = None) -> int:
|
||||
count = 0
|
||||
for profile in _iter_active_profiles(profile_id):
|
||||
for job in jobs:
|
||||
|
||||
Reference in New Issue
Block a user