ranjithkumar111's picture
Upload 6 files
daab2d3 verified
Raw
History Blame Contribute Delete
7.92 kB
# File: app.py
# Purpose: FastAPI server — recruiter API + Bland.ai call management
from typing import Optional
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
from fastapi.responses import Response
from pydantic import BaseModel
from sqlalchemy.orm import Session
import time
from src.database import init_db, get_db, Candidate, InterviewSession, ConversationTurn, SessionLocal
from src.resume_rag import build_resume_index, extract_skills_from_resume
from src.call_manager import (
initiate_call, get_call_status, get_call_transcript,
)
from src.evaluator import evaluate_candidate, format_report
app = FastAPI(title="AI Interview Screening Caller", version="1.0.0")
@app.on_event("startup")
def on_startup():
init_db()
class InitiateCallRequest(BaseModel):
candidate_name: str
candidate_phone: str
candidate_email: Optional[str] = None
resume_text: Optional[str] = None
@app.post("/api/initiate-call")
async def api_initiate_call(
req: InitiateCallRequest,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
):
candidate = Candidate(
name=req.candidate_name,
phone=req.candidate_phone,
email=req.candidate_email,
resume_text=req.resume_text or "",
)
db.add(candidate)
db.commit()
db.refresh(candidate)
session = InterviewSession(candidate_id=candidate.id, status="initiated")
db.add(session)
db.commit()
db.refresh(session)
skills = []
if req.resume_text:
skills = extract_skills_from_resume(req.resume_text)
build_resume_index(session.id, req.resume_text)
session.extracted_skills = skills
db.commit()
call_id = initiate_call(
candidate_phone=req.candidate_phone,
session_id=session.id,
candidate_name=req.candidate_name,
resume_skills=skills,
)
session.call_sid = call_id
session.status = "active"
db.commit()
background_tasks.add_task(
poll_call_completion,
call_id=call_id,
session_id=session.id,
candidate_name=req.candidate_name,
candidate_phone=req.candidate_phone,
)
return {"session_id": session.id, "call_id": call_id, "status": "call_initiated"}
@app.post("/api/session/{session_id}/fetch-report")
def api_fetch_report(session_id: int, db: Session = Depends(get_db)):
"""Manually trigger report generation for a session."""
session = db.query(InterviewSession).filter(InterviewSession.id == session_id).first()
if not session:
raise HTTPException(status_code=404, detail="Session not found")
call_id = session.call_sid
if not call_id:
raise HTTPException(status_code=400, detail="No call ID found for this session")
# Fetch status from Bland.ai
data = get_call_status(call_id)
bland_status = data.get("status", "")
transcript = get_call_transcript(call_id)
if not transcript:
return {"message": f"Call status: {bland_status}. No transcript yet."}
# Run evaluation
evaluation = evaluate_candidate(session.candidate.name, transcript)
report_text = format_report(session.candidate.name, session.candidate.phone, evaluation)
session.communication_score = evaluation["communication_score"]
session.technical_score = evaluation["technical_score"]
session.confidence_score = evaluation["confidence_score"]
session.overall_score = evaluation["overall_score"]
session.recommendation = evaluation["recommendation"]
session.report_text = report_text
session.extracted_skills = evaluation.get("skills", [])
session.status = "completed"
db.commit()
# Save transcript
existing = db.query(ConversationTurn).filter(ConversationTurn.session_id == session_id).count()
if existing == 0:
for i, turn in enumerate(transcript):
t = ConversationTurn(
session_id=session_id,
turn_index=i,
speaker=turn["speaker"],
text=turn["text"],
)
db.add(t)
db.commit()
return {"message": "Report generated successfully", "overall_score": evaluation["overall_score"]}
@app.get("/api/session/{session_id}/report")
def api_get_report(session_id: int, db: Session = Depends(get_db)):
session = db.query(InterviewSession).filter(InterviewSession.id == session_id).first()
if not session:
raise HTTPException(status_code=404, detail="Session not found")
return {
"candidate": session.candidate.name,
"status": session.status,
"call_sid": session.call_sid,
"communication_score": session.communication_score,
"technical_score": session.technical_score,
"confidence_score": session.confidence_score,
"overall_score": session.overall_score,
"recommendation": session.recommendation,
"skills": session.extracted_skills,
"report": session.report_text,
}
@app.get("/api/sessions")
def api_list_sessions(db: Session = Depends(get_db)):
sessions = db.query(InterviewSession).order_by(InterviewSession.id.desc()).limit(50).all()
return [
{
"id": s.id,
"candidate": s.candidate.name,
"status": s.status,
"overall_score": s.overall_score,
"recommendation": s.recommendation,
}
for s in sessions
]
@app.get("/api/session/{session_id}/transcript")
def api_get_transcript(session_id: int, db: Session = Depends(get_db)):
turns = db.query(ConversationTurn).filter(
ConversationTurn.session_id == session_id
).order_by(ConversationTurn.turn_index).all()
return [{"speaker": t.speaker, "text": t.text} for t in turns]
def poll_call_completion(call_id, session_id, candidate_name, candidate_phone):
db = SessionLocal()
try:
for _ in range(40):
time.sleep(30)
data = get_call_status(call_id)
status = data.get("status", "")
if status in ("complete", "completed"):
transcript = get_call_transcript(call_id)
if transcript:
evaluation = evaluate_candidate(candidate_name, transcript)
report_text = format_report(candidate_name, candidate_phone, evaluation)
session = db.query(InterviewSession).filter(InterviewSession.id == session_id).first()
if session:
session.communication_score = evaluation["communication_score"]
session.technical_score = evaluation["technical_score"]
session.confidence_score = evaluation["confidence_score"]
session.overall_score = evaluation["overall_score"]
session.recommendation = evaluation["recommendation"]
session.report_text = report_text
session.extracted_skills = evaluation.get("skills", [])
session.status = "completed"
db.commit()
for i, turn in enumerate(transcript):
t = ConversationTurn(session_id=session_id, turn_index=i, speaker=turn["speaker"], text=turn["text"])
db.add(t)
db.commit()
break
elif status in ("failed", "error", "busy", "no-answer"):
session = db.query(InterviewSession).filter(InterviewSession.id == session_id).first()
if session:
session.status = status
db.commit()
break
finally:
db.close()