Spaces:
Running
Running
| """ | |
| main.py — CLI entry point for SmartHire AI | |
| Usage: | |
| python main.py --demo | |
| python main.py --resume resume.pdf --jd job.txt | |
| """ | |
| import argparse | |
| import logging | |
| import sys | |
| import time | |
| from pathlib import Path | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", handlers=[logging.StreamHandler(sys.stdout)]) | |
| logger = logging.getLogger("SmartHireAI") | |
| def run_demo() -> None: | |
| sample_dir = Path("datasets") | |
| if not sample_dir.exists(): | |
| logger.error("datasets/ directory not found. Run from the SmartHireAI/ root.") | |
| sys.exit(1) | |
| resume_files = list(sample_dir.glob("*.txt")) | |
| jd_file = sample_dir / "sample_jd.txt" | |
| if not jd_file.exists(): | |
| logger.error(f"Sample JD not found: {jd_file}") | |
| sys.exit(1) | |
| jd_text = jd_file.read_text(encoding="utf-8") | |
| logger.info(f"Job Description loaded: {jd_file.name}") | |
| resume_candidates = [] | |
| for rf in resume_files: | |
| if rf.name == "sample_jd.txt": | |
| continue | |
| text = rf.read_text(encoding="utf-8") | |
| resume_candidates.append({"name": rf.stem, "raw_text": text}) | |
| logger.info(f"Loaded {len(resume_candidates)} resume(s)") | |
| _run_pipeline(resume_candidates, jd_text) | |
| def run_custom(resume_paths: list, jd_path: str) -> None: | |
| from src.parser import parse_resume, parse_job_description | |
| jd_path = Path(jd_path) | |
| jd_raw = jd_path.read_bytes() | |
| jd_text = parse_job_description(jd_raw, filename=jd_path.name) | |
| resume_candidates = [] | |
| for rp in resume_paths: | |
| rp = Path(rp) | |
| raw = rp.read_bytes() | |
| text = parse_resume(raw, filename=rp.name) | |
| resume_candidates.append({"name": rp.stem, "raw_text": text}) | |
| _run_pipeline(resume_candidates, jd_text) | |
| def _run_pipeline(resume_candidates: list, jd_text: str) -> None: | |
| from src.preprocess import preprocess_text | |
| from src.model import get_model | |
| from src.similarity import batch_similarity | |
| from src.ranking import rank_candidates, summarize_rankings | |
| import torch | |
| logger.info("Step 1/4: Preprocessing text...") | |
| jd_clean = preprocess_text(jd_text) | |
| for c in resume_candidates: | |
| c["clean_text"] = preprocess_text(c["raw_text"]) | |
| logger.info("Step 2/4: Loading DistilBERT model and encoding texts...") | |
| t0 = time.time() | |
| model = get_model() | |
| resume_texts = [c["clean_text"] for c in resume_candidates] | |
| resume_embeddings = model.encode(resume_texts, show_progress=True) | |
| jd_embedding = model.encode_single(jd_clean) | |
| elapsed = time.time() - t0 | |
| logger.info(f"Encoding complete in {elapsed:.2f}s for {len(resume_candidates)} resume(s).") | |
| logger.info("Step 3/4: Computing cosine similarities...") | |
| scores = batch_similarity(resume_embeddings, jd_embedding) | |
| for c, score in zip(resume_candidates, scores): | |
| c["score"] = score | |
| logger.info("Step 4/4: Ranking candidates...") | |
| candidates_input = [{"name": c["name"], "text": c["clean_text"], "score": c["score"]} for c in resume_candidates] | |
| results = rank_candidates(candidates_input, jd_clean) | |
| print("\n" + "=" * 65) | |
| print(" SmartHire AI — Candidate Ranking Results") | |
| print("=" * 65) | |
| for rank, result in enumerate(results, start=1): | |
| print(f"\nRank #{rank}: {result.name}") | |
| print(f" Match Score : {result.score_pct:.1f}%") | |
| print(f" Recommendation : {result.recommendation}") | |
| print(f" Skill Coverage : {result.skill_coverage_pct:.1f}%") | |
| if result.matching_skills: | |
| print(f" Matching Skills: {', '.join(result.matching_skills[:8])}") | |
| if result.critical_missing: | |
| print(f" Critical Missing: {', '.join(result.critical_missing)}") | |
| summary = summarize_rankings(results) | |
| print("\n" + "-" * 65) | |
| print(f"Summary: {summary['total_candidates']} candidates | Avg: {summary['average_score']}% | Top: {summary['highest_score']}%") | |
| print("=" * 65 + "\n") | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="SmartHire AI — Transformer-Based Resume & Job Matching System") | |
| parser.add_argument("--demo", action="store_true", help="Run with bundled sample dataset") | |
| parser.add_argument("--resume", nargs="+", metavar="FILE", help="Path(s) to resume file(s)") | |
| parser.add_argument("--jd", metavar="FILE", help="Path to job description file") | |
| args = parser.parse_args() | |
| if args.demo: | |
| run_demo() | |
| elif args.resume and args.jd: | |
| run_custom(args.resume, args.jd) | |
| else: | |
| parser.print_help() | |
| print("\nError: Provide --demo or both --resume and --jd flags.") | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() | |