"""Phase 1 — the floor-free + coverage-aware invariants (the v1-collapse guardrails). This is the test that would have caught the old engine: every factor is float-or-`None` (no constant floors), and a sparse profile must NOT collapse all jobs to one neutral score. """ from app.engine import rank as R _JOBS = [ {"title": "Product Manager", "seniority_level": "senior", "location_city": "New Delhi", "location_country": "India", "salary_lpa": 30, "details": {"skills": ["Product Management", "SQL"], "industry": "Fintech", "description": "product manager roadmap stakeholders"}}, {"title": "Sales Lead", "seniority_level": "lead", "location_city": "Mumbai", "location_country": "India", "salary_lpa": None, "details": {"skills": ["Sales", "CRM"], "industry": "SaaS", "description": "sales quota targets"}}, {"title": "Product Manager", "seniority_level": "mid", "location_mode": "remote", "location_city": "Remote", "details": {"skills": ["Product Management"], "description": "product manager"}}, {"title": "Designer", "seniority_level": None, "location_city": None, "salary_lpa": None, "details": {}}, ] def test_every_factor_returns_float_or_none(): p_full = R.profile_from({"title": "Product Manager", "location": ["New Delhi · India"], "industry": ["Fintech"], "experience": ["Senior"], "years": 9, "role": ["Product"], "targetComp": "₹25–30L"}) p_sparse = R.profile_from({"title": "Product Manager"}) for p in (p_full, p_sparse): for job in _JOBS: for name, fn in R.FACTORS.items(): v = fn(p, job) assert v is None or (isinstance(v, float) and 0.0 <= v <= 1.0), f"{name} → {v!r}" def test_sparse_profile_does_not_collapse(): # The v1 bug: sparse profile → everyone ~60. Assert real score variance across varied jobs. p = R.profile_from({"title": "Product Manager", "location": ["New Delhi · India"]}) us = [u for u in (R.utility(p, j)[0] for j in _JOBS) if u is not None] assert len(us) >= 2 and (max(us) - min(us)) > 0.1, f"collapsed to a flat band: {us}" def test_absent_factor_redistributes_weight_not_floors(): # A job missing salary/industry/skills must still score on what IS present (no 60 injected). p = R.profile_from({"title": "Product Manager", "location": ["New Delhi · India"]}) bare = {"title": "Product Manager", "location_city": "New Delhi", "location_country": "India", "details": {"description": "product manager"}} U, factors = R.utility(p, bare) assert U is not None assert factors["salary"] is None and factors["skill"] is None # absent assert factors["location"] == 1.0 # present, real