Automated Action 3d6b44a6e6 Implement SkillSync AI-Powered Resume & Job Match Hub backend
- Complete FastAPI backend with SQLite database
- AI-powered resume parsing and job matching using OpenAI
- JWT authentication with role-based access control
- Resume upload, job management, and matching endpoints
- Recruiter dashboard with candidate ranking
- Analytics and skill gap analysis features
- Comprehensive API documentation with OpenAPI
- Alembic database migrations
- File upload support for PDF, DOCX, and TXT resumes
- CORS enabled for frontend integration

🤖 Generated with BackendIM

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-27 14:58:12 +00:00

154 lines
5.0 KiB
Python

from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy import func, desc
from app.core.deps import get_db, get_current_recruiter
from app.models.user import User
from app.models.job import Job
from app.models.match import Match
from app.models.resume import Resume
router = APIRouter()
@router.get("/candidates/{job_id}")
def get_candidates_for_job(
job_id: int,
min_score: float = Query(0, ge=0, le=100),
limit: int = Query(50, ge=1, le=100),
current_user: User = Depends(get_current_recruiter),
db: Session = Depends(get_db)
):
"""Get ranked candidates for a specific job"""
# Verify job belongs to recruiter
job = db.query(Job).filter(
Job.id == job_id,
Job.recruiter_id == current_user.id
).first()
if not job:
raise HTTPException(status_code=404, detail="Job not found")
# Get matches for this job, ordered by score
matches = db.query(Match).join(Resume).join(User).filter(
Match.job_id == job_id,
Match.match_score >= min_score
).order_by(desc(Match.match_score)).limit(limit).all()
# Format response with candidate info
candidates = []
for match in matches:
resume = db.query(Resume).filter(Resume.id == match.resume_id).first()
user = db.query(User).filter(User.id == match.user_id).first()
candidates.append({
"match_id": match.id,
"candidate_name": user.full_name,
"candidate_email": user.email,
"resume_title": resume.title,
"match_score": match.match_score,
"skill_match_score": match.skill_match_score,
"experience_match_score": match.experience_match_score,
"education_match_score": match.education_match_score,
"skills": resume.skills,
"experience_years": resume.experience_years,
"education_level": resume.education_level,
"created_at": match.created_at
})
return {
"job_title": job.title,
"job_company": job.company,
"total_candidates": len(candidates),
"candidates": candidates
}
@router.get("/jobs/stats")
def get_job_stats(
current_user: User = Depends(get_current_recruiter),
db: Session = Depends(get_db)
):
"""Get statistics for recruiter's jobs"""
# Get job stats
job_stats = db.query(
Job.id,
Job.title,
Job.company,
Job.created_at,
func.count(Match.id).label("total_matches"),
func.avg(Match.match_score).label("avg_match_score"),
func.max(Match.match_score).label("best_match_score")
).outerjoin(Match).filter(
Job.recruiter_id == current_user.id
).group_by(Job.id).all()
return [
{
"job_id": stat.id,
"job_title": stat.title,
"company": stat.company,
"created_at": stat.created_at,
"total_matches": stat.total_matches or 0,
"avg_match_score": round(stat.avg_match_score or 0, 2),
"best_match_score": stat.best_match_score or 0
}
for stat in job_stats
]
@router.get("/overview")
def get_dashboard_overview(
current_user: User = Depends(get_current_recruiter),
db: Session = Depends(get_db)
):
"""Get overview statistics for recruiter dashboard"""
# Total jobs
total_jobs = db.query(Job).filter(Job.recruiter_id == current_user.id).count()
# Active jobs
active_jobs = db.query(Job).filter(
Job.recruiter_id == current_user.id,
Job.is_active
).count()
# Total matches across all jobs
total_matches = db.query(Match).join(Job).filter(
Job.recruiter_id == current_user.id
).count()
# High-quality matches (score >= 80)
high_quality_matches = db.query(Match).join(Job).filter(
Job.recruiter_id == current_user.id,
Match.match_score >= 80
).count()
# Recent matches (last 7 days)
from datetime import datetime, timedelta
recent_matches = db.query(Match).join(Job).filter(
Job.recruiter_id == current_user.id,
Match.created_at >= datetime.utcnow() - timedelta(days=7)
).count()
# Top performing job
top_job = db.query(
Job.title,
func.count(Match.id).label("match_count"),
func.avg(Match.match_score).label("avg_score")
).outerjoin(Match).filter(
Job.recruiter_id == current_user.id
).group_by(Job.id).order_by(
desc(func.count(Match.id))
).first()
return {
"total_jobs": total_jobs,
"active_jobs": active_jobs,
"total_matches": total_matches,
"high_quality_matches": high_quality_matches,
"recent_matches": recent_matches,
"top_performing_job": {
"title": top_job.title if top_job else None,
"match_count": top_job.match_count if top_job else 0,
"avg_score": round(top_job.avg_score or 0, 2) if top_job else 0
}
}