""" Unit tests for feature_engineering.py. Runs on 50 sample candidates with no artifacts (stubs for semantic/BM25). Checks: correct vector length, no NaN/Inf, value ranges, spot-checks. """ import json import sys from pathlib import Path import numpy as np sys.path.insert(0, str(Path(__file__).parent.parent)) from src.feature_engineering import ( FEATURE_NAMES, compute_features, compute_features_dict, load_precomputed, ) from src.consistency_checks import check_consistency SAMPLE_PATH = Path(__file__).parent.parent / "dataset" / "sample_candidates.json" ARTIFACTS_DIR = Path(__file__).parent.parent / "artifacts" def main(): with open(SAMPLE_PATH, encoding="utf-8") as f: candidates = json.load(f) # Load precomputed (empty — no artifacts yet) precomputed = load_precomputed(ARTIFACTS_DIR) if ARTIFACTS_DIR.exists() else {"feature_names": FEATURE_NAMES} errors = [] all_vecs = [] for c in candidates: cid = c["candidate_id"] is_hp, n_viol, _ = check_consistency(c) try: vec = compute_features(c, precomputed, violation_count=n_viol, is_honeypot=is_hp) except Exception as e: errors.append(f"[{cid}] compute_features raised: {e}") continue # Length check if len(vec) != len(FEATURE_NAMES): errors.append(f"[{cid}] vector length {len(vec)} != {len(FEATURE_NAMES)}") # NaN / Inf check if not np.all(np.isfinite(vec)): bad = [FEATURE_NAMES[i] for i, v in enumerate(vec) if not np.isfinite(v)] errors.append(f"[{cid}] non-finite values in: {bad}") # Range checks on known-bounded features fdict = dict(zip(FEATURE_NAMES, vec.tolist())) range_checks = { "skill_evidence_ratio": (0.0, 1.0), "product_company_ratio": (0.0, 1.0), "notice_penalty": (0.0, 1.0), "recency_score": (0.0, 1.0), "behavioral_composite": (0.0, 1.1), "profile_completeness": (0.0, 1.0), "recruiter_response_rate": (0.0, 1.0), "interview_completion_rate":(0.0, 1.0), "work_mode_match": (0.5, 1.0), "best_edu_tier": (1.0, 4.0), "is_honeypot": (0.0, 1.0), } for feat, (lo, hi) in range_checks.items(): v = fdict[feat] if not (lo - 1e-6 <= v <= hi + 1e-6): errors.append(f"[{cid}] {feat}={v:.4f} out of [{lo}, {hi}]") all_vecs.append(vec) # Summary stats mat = np.array(all_vecs) print(f"\n{'='*65}") print(f"Feature Engineering Test — {len(candidates)} candidates, {len(FEATURE_NAMES)} features") print(f"{'='*65}") if errors: print(f"\nERRORS ({len(errors)}):") for e in errors: print(f" {e}") else: print("\nAll checks passed.") print(f"\nFeature stats (non-stub rows only):") print(f" {'Feature':<35} {'min':>7} {'mean':>7} {'max':>7} {'nonzero%':>9}") print(f" {'-'*65}") for i, name in enumerate(FEATURE_NAMES): col = mat[:, i] nz_pct = 100.0 * np.mean(col != 0) print(f" {name:<35} {col.min():>7.3f} {col.mean():>7.3f} {col.max():>7.3f} {nz_pct:>8.0f}%") print() if __name__ == "__main__": main()