""" FastAPI backend — optional REST API alongside Gradio UI. Run: uvicorn main:app --host 0.0.0.0 --port 8000 --reload """ import os import uuid import io from dotenv import load_dotenv load_dotenv() import pandas as pd from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.middleware.cors import CORSMiddleware from app.models.schemas import EvaluationRequest, EvaluationResponse, Candidate from app.services.evaluation_service import perform_hybrid_evaluation app = FastAPI( title="AI Recruitment Engine", description="Hybrid 5-stage candidate evaluation pipeline", version="1.0.0", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # Simple in-memory cache (use Redis/DB in production) _cache: dict = {} @app.get("/health") async def health(): return {"status": "ok", "service": "AI Recruitment Engine"} @app.post("/upload-csv") async def upload_csv(file: UploadFile = File(...)): if not (file.filename or "").endswith(".csv"): raise HTTPException(status_code=400, detail="Please upload a .csv file.") try: content = await file.read() df = pd.read_csv(io.BytesIO(content)).fillna("") candidates = [] for _, row in df.iterrows(): candidates.append(Candidate( id=str(uuid.uuid4()), name=str(row.get("name", "Unknown")), email=str(row.get("email", "")), skills=str(row.get("skills", row.get("parsed_skills", ""))), experience=str(row.get("experience", row.get("parsed_work_experience", ""))), projects=str(row.get("projects", "")), education=str(row.get("education", row.get("parsed_metadata_education", ""))), resume_text=str(row.get("resume_text", row.get("parsed_summary", ""))), )) return {"count": len(candidates), "candidates": candidates} except Exception as e: raise HTTPException(status_code=500, detail=f"CSV parse error: {e}") @app.post("/evaluate", response_model=EvaluationResponse) async def evaluate(request: EvaluationRequest): if not request.jd: raise HTTPException(status_code=400, detail="Job Description is required.") if not request.candidates: raise HTTPException(status_code=400, detail="At least one candidate is required.") response = await perform_hybrid_evaluation(request.jd, request.candidates) for rank in response.shortlist: _cache[rank.candidate_id] = rank.model_dump() _cache.update(response.details) return response @app.get("/candidate/{candidate_id}") async def get_candidate(candidate_id: str): if candidate_id not in _cache: raise HTTPException(status_code=404, detail="Candidate report not found.") return _cache[candidate_id] if __name__ == "__main__": import uvicorn uvicorn.run( app, host=os.getenv("APP_HOST", "0.0.0.0"), port=int(os.getenv("APP_PORT", "8000")), )