""" lib/hiring_intent.py — V6 Hiring Intent Engine Infers WHY the company is hiring, not just WHAT skills they need. A hiring manager doesn't think "I want Python" — they think "I need someone who can own production systems" or "I need a founding engineer." The intent drives downstream weight adjustments, feature emphasis, and reasoning quality. Outputs a HiringIntent dataclass with: - philosophy: list of hiring philosophy tags - ownership_expectation: how much ownership is expected - shipping_culture: scrappy vs methodical - depth_requirement: specialist vs generalist - scale_requirement: startup vs large scale - team_context: founding vs established - independence: how independently they expect work - mentorship: whether they expect mentoring - primary_need: the core thing this hire must deliver """ from __future__ import annotations import re from dataclasses import dataclass, field from lib.jd_parser import get_jd, JDUnderstanding @dataclass class HiringIntent: """Structured inference of WHY the company is hiring.""" # Hiring philosophy tags (multiple can apply) philosophy: list[str] = field(default_factory=list) # Primary thing this hire must deliver primary_need: str = "general" # How much ownership is expected (0.0-1.0) ownership_expectation: float = 0.5 # Shipping culture shipping_culture: str = "balanced" # scrappy, methodical, balanced # Depth vs breadth depth_requirement: str = "generalist" # specialist, generalist, t_shaped # Scale context scale_requirement: str = "mid_scale" # startup_scale, mid_scale, large_scale # Team context team_context: str = "established" # founding, early, growing, established # Independence expected (0.0-1.0) independence: float = 0.5 # Whether they expect mentoring juniors mentorship: bool = False # Raw signals extracted from JD raw_signals: dict = field(default_factory=dict) # --------------------------------------------------------------------------- # Intent signal detectors # --------------------------------------------------------------------------- _PHILOSOPHY_PATTERNS = { "startup_builder": [ r"founding", r"first\s+hire", r"early\s+stage", r"build\s+from\s+scratch", r"ship\s+a\s+working", r"scrappy", r"0\s+to\s+1", r"ground\s+up", ], "hands_on_ic": [ r"hands\s+on", r"write\s+code", r"coding", r"implement", r"no\s+pure\s+research", r"not\s+a\s+research", r"product.engineering", ], "fast_shipper": [ r"ship\s+in\s+a\s+week", r"fast", r"quickly", r"rapid", r"iterate", r"agile", r"move\s+fast", ], "research_heavy": [ r"research", r"paper", r"publication", r"novel", r"state.of.the.art", r"cutting.edge", r"advance\s+the\s+field", ], "platform_engineer": [ r"platform", r"infrastructure", r"scalable", r"system\s+design", r"architecture", r"end.to.end", ], "specialist_depth": [ r"deep\s+technical\s+depth", r"expertise\s+in", r"specialist", r"deep\s+knowledge", r"domain\s+expert", ], "team_leader": [ r"mentor", r"lead", r"senior", r"guide", r"coach", r"team\s+of", r"manage\s+engineers", ], "customer_facing": [ r"customer", r"user.facing", r"production", r"real\s+users", r"live\s+traffic", r"on.call", ], "scale_focused": [ r"at\s+scale", r"large.scale", r"millions?\s+of\s+users", r"high\s+throughput", r"low\s+latency", r"distributed", ], } _OWNERSHIP_PATTERNS = { "founding": [r"founding", r"first\s+hire", r"build\s+the", r"own\s+the"], "senior_ic": [r"senior", r"staff", r"lead", r"own", r"independent"], "team_lead": [r"lead\s+a\s+team", r"manage", r"mentor", r"guide"], "contributor": [r"contribute", r"part\s+of", r"join", r"work\s+with"], } _SHIPPING_PATTERNS = { "scrappy": [r"ship\s+in\s+a\s+week", r"scrappy", r"just\s+ship\s+it", r"willing\s+to\s+ship"], "methodical": [r"rigorous", r"systematic", r"process", r"methodology", r"thorough"], "balanced": [], # default } _DEPTH_PATTERNS = { "specialist": [r"deep\s+technical\s+depth", r"specialist", r"expert\s+in", r"deep\s+dive"], "generalist": [r"full\s+stack", r"generalist", r"versatile", r"broad"], "t_shaped": [r"deep\s+in\s+one", r"broad\s+knowledge", r"t.shaped", r"depth.*breadth"], } _SCALE_PATTERNS = { "startup_scale": [r"startup", r"early\s+stage", r"small\s+team", r"series\s+[ab]"], "mid_scale": [r"scale", r"growing", r"product.company", r"thousands"], "large_scale": [r"millions", r"enterprise", r"global", r"large.scale"], } _TEAM_PATTERNS = { "founding": [r"founding\s+team", r"first\s+engineer", r"employee\s+#"], "early": [r"early\s+team", r"small\s+team", r"core\s+team"], "growing": [r"growing\s+team", r"scaling\s+team", r"hiring\s+team"], "established": [r"team\s+of\s+\d+", r"large\s+team", r"department"], } def _detect_signals(jd_text: str) -> dict[str, list[str]]: """Detect all intent signals from JD text.""" signals = {} # Philosophy signals["philosophy"] = [] for tag, patterns in _PHILOSOPHY_PATTERNS.items(): if any(re.search(p, jd_text, re.IGNORECASE) for p in patterns): signals["philosophy"].append(tag) # Ownership signals["ownership"] = [] for level, patterns in _OWNERSHIP_PATTERNS.items(): if any(re.search(p, jd_text, re.IGNORECASE) for p in patterns): signals["ownership"].append(level) # Shipping culture signals["shipping"] = [] for culture, patterns in _SHIPPING_PATTERNS.items(): if culture == "balanced": continue if any(re.search(p, jd_text, re.IGNORECASE) for p in patterns): signals["shipping"].append(culture) # Depth signals["depth"] = [] for depth, patterns in _DEPTH_PATTERNS.items(): if any(re.search(p, jd_text, re.IGNORECASE) for p in patterns): signals["depth"].append(depth) # Scale signals["scale"] = [] for scale, patterns in _SCALE_PATTERNS.items(): if any(re.search(p, jd_text, re.IGNORECASE) for p in patterns): signals["scale"].append(scale) # Team context signals["team"] = [] for ctx, patterns in _TEAM_PATTERNS.items(): if any(re.search(p, jd_text, re.IGNORECASE) for p in patterns): signals["team"].append(ctx) return signals def _compute_ownership_expectation(signals: dict) -> float: """Compute ownership expectation level from signals.""" levels = signals.get("ownership", []) if "founding" in levels: return 0.95 if "senior_ic" in levels: return 0.80 if "team_lead" in levels: return 0.70 return 0.50 def _determine_primary_need(jd: JDUnderstanding, signals: dict) -> str: """Determine the primary thing this hire must deliver.""" # Check for explicit production ownership if "customer_facing" in signals.get("philosophy", []): return "production_systems" if "platform_engineer" in signals.get("philosophy", []): return "platform_engineering" if "research_heavy" in signals.get("philosophy", []): return "research" if "startup_builder" in signals.get("philosophy", []): return "build_from_scratch" if "specialist_depth" in signals.get("philosophy", []): return "specialist_contribution" return "general" def _compute_independence(jd_text: str, signals: dict) -> float: """How independently is this person expected to work?""" score = 0.5 # baseline indie_patterns = [ (r"independently", 0.15), (r"own\s+initiative", 0.10), (r"async", 0.08), (r"self.directed", 0.10), (r"decides?\s+quickly", 0.08), (r"disagrees?\s+openly", 0.05), (r"minimal\s+supervision", 0.10), (r"autonom", 0.10), ] for pattern, bonus in indie_patterns: if re.search(pattern, jd_text, re.IGNORECASE): score = min(1.0, score + bonus) return score # --------------------------------------------------------------------------- # Main API # --------------------------------------------------------------------------- def analyze(jd: JDUnderstanding | None = None) -> HiringIntent: """ Analyze a JD to infer hiring intent. Returns a HiringIntent dataclass that drives downstream weight adjustments, feature emphasis, and reasoning. """ if jd is None: jd = get_jd() jd_text = jd.raw_text.lower() signals = _detect_signals(jd_text) # Build the intent philosophy = signals.get("philosophy", []) if not philosophy: philosophy = ["general"] ownership_levels = signals.get("ownership", []) ownership_expectation = _compute_ownership_expectation(signals) shipping_cultures = signals.get("shipping", []) shipping_culture = shipping_cultures[0] if shipping_cultures else "balanced" depths = signals.get("depth", []) depth_requirement = depths[0] if depths else "generalist" scales = signals.get("scale", []) scale_requirement = scales[0] if scales else "mid_scale" teams = signals.get("team", []) team_context = teams[0] if teams else "established" independence = _compute_independence(jd_text, signals) mentorship = bool( re.search(r"mentor|guide|coach|senior\s+to|teach", jd_text, re.IGNORECASE) ) primary_need = _determine_primary_need(jd, signals) return HiringIntent( philosophy=philosophy, primary_need=primary_need, ownership_expectation=ownership_expectation, shipping_culture=shipping_culture, depth_requirement=depth_requirement, scale_requirement=scale_requirement, team_context=team_context, independence=independence, mentorship=mentorship, raw_signals=signals, ) # Singleton _intent_cache: HiringIntent | None = None def get_intent() -> HiringIntent: """Get the hiring intent (cached after first analysis).""" global _intent_cache if _intent_cache is None: _intent_cache = analyze() return _intent_cache