Vettly / src /run_fast_pipeline.py
MrugajaJ's picture
Upload 11 files
cd3d2c2 verified
Raw
History Blame Contribute Delete
6.05 kB
import os
import json
import time
import numpy as np
import faiss
import polars as pl
from pathlib import Path
from sentence_transformers import SentenceTransformer
from sklearn.feature_extraction.text import TfidfVectorizer
from src.hard_filter import apply_hard_filter, is_killed
from src.score_career import compute_A, compute_keyword_max
from src.score_skills import compute_B
from src.score_embed import compute_C_all
from src.availability import apply_multipliers
from src.output import write_submission, generate_reasoning
from src.precompute import build_candidate_text, build_jd_text
def run_fast_pipeline():
start_time = time.perf_counter()
print("=" * 60)
print("RUNNING HIGH-PERFORMANCE PIPELINE ON 100,000 CANDIDATES")
print("=" * 60)
# Paths
jd_path = Path("data/job_description.json")
candidates_path = Path("data/candidates.jsonl")
output_path = Path("submission.csv")
if not jd_path.exists():
raise FileNotFoundError(f"Job description not found at {jd_path}")
if not candidates_path.exists():
raise FileNotFoundError(f"Candidates dataset not found at {candidates_path}")
# 1. Load Job Description
print("Loading Job Description...")
with open(jd_path, "r", encoding="utf-8") as f:
jd = json.load(f)
# ── PASS 1: Stream titles only to fit TF-IDF (low memory) ──
print("Pass 1: Streaming 100,000 candidate titles for TF-IDF fit...")
titles = []
with open(candidates_path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
cand = json.loads(line)
title = cand.get("profile", {}).get("current_title") or cand.get("current_title") or ""
titles.append(title)
total_count = len(titles)
print(f" Collected {total_count} titles.")
# 2. Fit TF-IDF on all candidate titles for the hard filter
print("Fitting TF-IDF Vectorizer on all candidate titles...")
tfidf = TfidfVectorizer(max_features=30000, ngram_range=(1, 2))
tfidf.fit(titles)
del titles # free memory immediately
# ── PASS 2: Stream again β€” hard-filter inline and collect survivors ──
print("\n--- STAGE 1: Applying Hard Filters (streaming pass 2) ---")
survivors = []
killed_count = 0
with open(candidates_path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
cand = json.loads(line)
killed_flag, reason = is_killed(cand, jd, tfidf)
if killed_flag:
killed_count += 1
else:
survivors.append(cand)
print(f"Killed: {killed_count} candidates.")
print(f"Surviving: {len(survivors)} candidates.")
if not survivors:
print("No candidates survived. Exiting.")
return
# 4. Generate Embeddings for ONLY the survivors + JD (Massive speedup!)
print("\n--- STAGE 2: Embedding Survivors & Job Description ---")
print("Loading SentenceTransformer model 'all-MiniLM-L6-v2'...")
model = SentenceTransformer("all-MiniLM-L6-v2")
print("Embedding Job Description...")
jd_text = build_jd_text(jd)
jd_vec = model.encode(jd_text, normalize_embeddings=True).astype("float32")
print(f"Embedding {len(survivors)} surviving candidates...")
survivor_texts = [build_candidate_text(s) for s in survivors]
cand_vecs = model.encode(survivor_texts, batch_size=256, normalize_embeddings=True).astype("float32")
# 5. Compute Stage 2C Embedding Similarity
print("Computing embedding similarities (Stage 2C)...")
C_scores = compute_C_all(jd_vec, cand_vecs)
C_map = {str(s.get("candidate_id") or s.get("id")): float(score) for s, score in zip(survivors, C_scores)}
# 6. Fit TF-IDF on survivor texts for career scoring
print("Fitting TF-IDF Vectorizer on survivor texts...")
tfidf_surv = TfidfVectorizer(max_features=30000, ngram_range=(1, 2))
tfidf_surv.fit(survivor_texts)
# 7. Compute Stage 2A & 2B scores
print("Computing career and skill scores...")
keyword_max = compute_keyword_max(survivors, jd, tfidf_surv)
raw_scored = []
for cand in survivors:
cand_id = str(cand.get("candidate_id") or cand.get("id"))
A_res = compute_A(cand, jd, tfidf_surv, keyword_max)
B_res = compute_B(cand, jd)
C = C_map.get(cand_id, 0.0)
A = A_res["A"]
B = B_res["B"]
raw_score = round(0.40 * A + 0.35 * B + 0.25 * C, 4)
raw_scored.append({
"candidate_id": cand_id,
"candidate": cand,
"A": A,
"title_sim": A_res["title_sim"],
"industry_match": A_res["industry_match"],
"prod_keyword_density": A_res["prod_keyword_density"],
"yoe_score": A_res["yoe_score"],
"B": B,
"must_have_coverage": B_res["must_have_coverage"],
"nice_coverage": B_res["nice_coverage"],
"cert_bonus": B_res["cert_bonus"],
"C": C,
"raw_score": raw_score
})
# 8. Apply Behavioral Multipliers (Stage 3)
print("\n--- STAGE 3: Applying Behavioral Multipliers ---")
final_scored = apply_multipliers(raw_scored, jd)
# 9. Output ranked results (Stage 4)
print("\n--- STAGE 4: Generating Output ---")
df_final = write_submission(final_scored, jd, str(output_path))
elapsed_time = time.perf_counter() - start_time
print("=" * 60)
print(f"PIPELINE EXECUTED IN {elapsed_time:.2f} SECONDS")
print("=" * 60)
print("\nTOP 10 CANDIDATES:")
print(df_final.head(10))
if __name__ == "__main__":
run_fast_pipeline()