| import datetime |
| from dateutil import parser |
| from sklearn.metrics.pairwise import cosine_similarity |
|
|
| def is_killed(candidate: dict, jd: dict, tfidf) -> tuple[bool, str]: |
| """Applies the 8 hard filtering rules in order, short-circuiting on first match. |
| |
| Supports both nested (actual dataset) and flat (test fixture) candidate structures. |
| """ |
| signals = candidate.get("redrob_signals") or {} |
| profile = candidate.get("profile") or {} |
| |
| |
| profile_completeness = signals.get("profile_completeness_score") or candidate.get("profile_completeness_score", 0) |
| if profile_completeness < 30: |
| return True, f"profile_completeness_score {profile_completeness} < 30" |
| |
| |
| verified_email = signals.get("verified_email") |
| if verified_email is None: |
| verified_email = candidate.get("verified_email", False) |
| if verified_email is False: |
| return True, "verified_email == False" |
| |
| |
| interview_completion_rate = signals.get("interview_completion_rate") |
| if interview_completion_rate is None: |
| interview_completion_rate = candidate.get("interview_completion_rate", 0.0) |
| if interview_completion_rate < 0.20: |
| return True, f"interview_completion_rate {interview_completion_rate} < 0.20" |
| |
| |
| last_active_date_str = signals.get("last_active_date") or candidate.get("last_active_date") |
| if not last_active_date_str: |
| return True, "Missing last_active_date" |
| try: |
| last_active_date = parser.parse(last_active_date_str) |
| if last_active_date.tzinfo is not None: |
| today = datetime.datetime.now(datetime.timezone.utc) |
| else: |
| today = datetime.datetime.now() |
| days_inactive = (today - last_active_date).days |
| if days_inactive > 180: |
| return True, f"Inactive for {days_inactive} days (> 180 days)" |
| except Exception as e: |
| return True, f"Failed to parse last_active_date: {str(e)}" |
| |
| |
| open_to_work = signals.get("open_to_work_flag") |
| if open_to_work is None: |
| open_to_work = candidate.get("open_to_work_flag", False) |
| if open_to_work is False: |
| return True, "open_to_work_flag == False" |
| |
| |
| cand_industries = set() |
| |
| prof_ind = profile.get("current_industry") |
| if prof_ind: |
| cand_industries.add(prof_ind.strip().lower()) |
| |
| history = candidate.get("career_history") or candidate.get("experience") or candidate.get("work_experience") or [] |
| for job in history: |
| if isinstance(job, dict) and job.get("industry"): |
| cand_industries.add(job["industry"].strip().lower()) |
| |
| for ind in (candidate.get("industries") or []): |
| if ind: |
| cand_industries.add(ind.strip().lower()) |
| |
| jd_industries = {ind.strip().lower() for ind in (jd.get("target_industries") or []) if ind} |
| if not cand_industries.intersection(jd_industries): |
| return True, f"Zero industry overlap (Candidate: {cand_industries}, JD: {jd_industries})" |
| |
| |
| cand_title = profile.get("current_title") or candidate.get("current_title", "") |
| jd_title = jd.get("title", "") |
| if not cand_title or not jd_title: |
| sim = 0.0 |
| else: |
| try: |
| cand_tfidf = tfidf.transform([cand_title]) |
| jd_tfidf = tfidf.transform([jd_title]) |
| sim = cosine_similarity(cand_tfidf, jd_tfidf)[0][0] |
| except Exception: |
| sim = 0.0 |
| if sim < 0.05: |
| return True, f"Title similarity {sim:.4f} < 0.05 (Candidate: '{cand_title}', JD: '{jd_title}')" |
| |
| |
| github_score = signals.get("github_activity_score") |
| if github_score is None: |
| github_score = candidate.get("github_activity_score", 0) |
| |
| skill_assessments = signals.get("skill_assessment_scores") |
| if skill_assessments is None: |
| skill_assessments = candidate.get("skill_assessment_scores") or {} |
| |
| endorsements = signals.get("endorsements_received") |
| if endorsements is None: |
| endorsements = candidate.get("endorsements_received", 0) |
| |
| connections = signals.get("connection_count") |
| if connections is None: |
| connections = candidate.get("connection_count", 0) |
| |
| if (github_score == -1 and |
| not skill_assessments and |
| endorsements == 0 and |
| connections < 5): |
| return True, f"Honeypot detected (GitHub: {github_score}, Assessments: {skill_assessments}, Endorsements: {endorsements}, Connections: {connections})" |
| |
| return False, "" |
|
|
| def apply_hard_filter(candidates: list[dict], jd: dict, tfidf) -> tuple[list[dict], list[dict]]: |
| """Applies the hard filter rules to a list of candidates. |
| |
| Returns (survivors_list, killed_list). |
| """ |
| survivors = [] |
| killed = [] |
| for cand in candidates: |
| killed_flag, reason = is_killed(cand, jd, tfidf) |
| if killed_flag: |
| killed.append({ |
| "candidate_id": cand.get("candidate_id") or cand.get("id"), |
| "candidate": cand, |
| "reason": reason |
| }) |
| else: |
| survivors.append(cand) |
| return survivors, killed |
|
|