""" lib/title_scoring.py — PRINCIPAL-LEVEL UPGRADE The original system only had a binary bad_title penalty. This module provides: 1. A continuous title_relevance score [0.0, 1.0] 2. A seniority level score [0.0, 1.0] 3. Evidence strings for reasoning Design rationale: - The JD's "ideal candidate" is a "Senior AI Engineer" — title is a primary signal - NDCG@10 (50% of score) depends on getting the top titles right - Staff/Principal titles indicate higher capability than "Senior" - Generic "Software Engineer" with ML experience should score lower than "ML Engineer" - Non-engineering titles should be near-zero (but handled separately by disqualifier) """ from __future__ import annotations # Title → relevance score patterns # Ordered from most specific/relevant to least TITLE_PATTERNS = [ # Perfect matches: directly describes the JD role (1.00, [ "senior ai engineer", "staff ai engineer", "principal ai engineer", "lead ai engineer", "head of ai", "senior nlp engineer", "staff nlp engineer", "lead nlp engineer", "senior ml engineer", "staff machine learning engineer", "senior machine learning engineer", "principal machine learning engineer", "search engineer", "senior search engineer", "staff search engineer", "recommendation systems engineer", "recommendation engineer", "retrieval engineer", "ranking engineer", "senior applied scientist", "staff applied scientist", "applied ml engineer", "senior applied ml engineer", "ml platform engineer", "ml infrastructure engineer", "ai platform engineer", "ai infrastructure engineer", ]), # Strong matches: ML/AI roles with implied seniority (0.85, [ "ai engineer", "ml engineer", "machine learning engineer", "nlp engineer", "natural language engineer", "deep learning engineer", "applied scientist", "data scientist", "senior data scientist", "research scientist", "research engineer", "ml ops engineer", "mlops engineer", ]), # Moderate matches: engineering roles with possible ML context (0.60, [ "data engineer", "senior data engineer", "backend engineer", "senior backend engineer", "software engineer", "senior software engineer", "full stack engineer", "fullstack engineer", "platform engineer", "senior platform engineer", "systems engineer", "infrastructure engineer", ]), # Weak matches: adjacent but not directly relevant (0.30, [ "devops engineer", "sre engineer", "site reliability", "qa engineer", "test engineer", "product manager", "technical program manager", ]), # Irrelevant (handled by disqualifier_penalty, but score low here too) (0.05, [ "customer support", "customer success", "marketing manager", "content writer", "hr manager", "human resources", "graphic designer", "ui designer", "ux designer", "sales manager", "account manager", "civil engineer", "mechanical engineer", "electrical engineer", "accountant", "recruiter", "talent acquisition", "operations manager", "project manager", "android developer", "ios developer", "mobile developer", "seo specialist", "social media manager", "business analyst", ]), ] # Seniority patterns (independent of relevance) SENIORITY_PATTERNS = [ (1.00, ["principal", "distinguished", "fellow", "vp of", "head of", "chief"]), (0.90, ["staff", "lead", "director", "group lead"]), (0.80, ["senior", "sr.", "sr "]), (0.60, []), # default — no seniority indicator (0.40, ["junior", "jr.", "jr ", "intern", "associate"]), (0.30, ["fresher", "trainee", "entry level"]), ] def title_relevance_score(title: str) -> tuple[float, str]: """ Returns (score, matched_pattern_description). Score in [0.0, 1.0] based on how closely the title matches the JD role. """ if not title: return (0.0, "no title") t = title.lower().strip() for score, patterns in TITLE_PATTERNS: for p in patterns: if p in t: return (score, p) return (0.40, "unrecognized title") # neutral default for unknown titles def seniority_score(title: str) -> tuple[float, str]: """ Returns (score, level_description). Score in [0.30, 1.00] based on seniority indicators in the title. """ if not title: return (0.60, "unknown") t = title.lower().strip() for score, patterns in SENIORITY_PATTERNS: if not patterns: return (score, "mid-level") # no indicator found → default for p in patterns: if p in t: label = p.strip() if label in ("sr.", "sr"): label = "senior" if label in ("jr.", "jr"): label = "junior" return (score, label) return (0.60, "mid-level")