Spaces:
Sleeping
Sleeping
| """ | |
| Simple FastAPI Resume-JD Scorer | |
| Accepts PDF resumes and job description, returns scored results | |
| """ | |
| from fastapi import FastAPI, File, UploadFile, Form, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from typing import List | |
| import uvicorn | |
| import os | |
| import tempfile | |
| import shutil | |
| # Import scoring utilities | |
| from utils.scorer import ResumeScorer | |
| from utils.pdf_processor import extract_text_from_pdf | |
| # Initialize FastAPI app | |
| app = FastAPI() | |
| # Enable CORS for frontend | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Global scorer instance | |
| scorer = None | |
| async def startup_event(): | |
| """Load ML model on startup""" | |
| global scorer | |
| print("βοΈ Loading InstructorXL model (this may take 2-3 minutes)...") | |
| scorer = ResumeScorer() | |
| print("β Model loaded successfully!") | |
| async def health(): | |
| """Health check endpoint""" | |
| return { | |
| "status": "healthy", | |
| "model_loaded": scorer is not None | |
| } | |
| async def score_resumes( | |
| job_description: str = Form(...), | |
| resumes: List[UploadFile] = File(...) | |
| ): | |
| """ | |
| Score resumes against job description | |
| Parameters: | |
| - job_description: Text of the job description | |
| - resumes: List of PDF files | |
| Returns: | |
| - Ranked list of resumes with scores | |
| """ | |
| if not scorer: | |
| raise HTTPException(status_code=503, detail="Model not loaded yet") | |
| if not resumes: | |
| raise HTTPException(status_code=400, detail="No resumes provided") | |
| if len(job_description.strip()) < 50: | |
| raise HTTPException(status_code=400, detail="Job description too short") | |
| results = [] | |
| temp_dir = tempfile.mkdtemp() | |
| try: | |
| print(f"π Processing {len(resumes)} resumes...") | |
| for idx, resume_file in enumerate(resumes, 1): | |
| print(f" [{idx}/{len(resumes)}] Processing: {resume_file.filename}") | |
| # Validate PDF | |
| if not resume_file.filename.lower().endswith('.pdf'): | |
| results.append({ | |
| "resume_name": resume_file.filename, | |
| "error": "Only PDF files supported", | |
| "skills_score": 0.0, | |
| "projects_score": 0.0, | |
| "experience_score": 0.0, | |
| "final_score": 0.0 | |
| }) | |
| continue | |
| try: | |
| # Save and extract PDF | |
| temp_path = os.path.join(temp_dir, resume_file.filename) | |
| content = await resume_file.read() | |
| with open(temp_path, 'wb') as f: | |
| f.write(content) | |
| resume_text = extract_text_from_pdf(temp_path) | |
| if not resume_text or len(resume_text.strip()) < 100: | |
| results.append({ | |
| "resume_name": resume_file.filename, | |
| "error": "Could not extract text from PDF", | |
| "skills_score": 0.0, | |
| "projects_score": 0.0, | |
| "experience_score": 0.0, | |
| "final_score": 0.0 | |
| }) | |
| continue | |
| # Score resume | |
| score_result = scorer.score_resume(job_description, resume_text) | |
| score_result["resume_name"] = resume_file.filename | |
| results.append(score_result) | |
| print(f" β Scored: {resume_file.filename} (final_score: {score_result['final_score']:.3f})") | |
| except Exception as e: | |
| print(f"β Error processing {resume_file.filename}: {str(e)}") | |
| results.append({ | |
| "resume_name": resume_file.filename, | |
| "error": str(e), | |
| "skills_score": 0.0, | |
| "projects_score": 0.0, | |
| "experience_score": 0.0, | |
| "final_score": 0.0 | |
| }) | |
| # Sort by final score | |
| results.sort(key=lambda x: x.get("final_score", 0), reverse=True) | |
| print(f"β Scoring complete!") | |
| return { | |
| "success": True, | |
| "total": len(resumes), | |
| "processed": len([r for r in results if "error" not in r]), | |
| "results": results | |
| } | |
| finally: | |
| # Cleanup | |
| shutil.rmtree(temp_dir, ignore_errors=True) | |
| if __name__ == "__main__": | |
| uvicorn.run( | |
| "main:app", | |
| host="0.0.0.0", | |
| port=8000, | |
| reload=True | |
| ) | |