mochirank / tests /test_features.py
DawnOfJustize's picture
Initial HF Spaces deployment (orphan — no history)
7a9cfa1
Raw
History Blame Contribute Delete
3.39 kB
"""
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()