KronosDP's picture
let there be light
57303cc unverified
Raw
History Blame Contribute Delete
6.45 kB
"""
crud.py — Database CRUD operations.
"""
import json
from sqlalchemy import func
from sqlalchemy.orm import Session
from . import models, schemas
# ---------------------------------------------------------------------------
# Client
# ---------------------------------------------------------------------------
def create_client(db: Session, data: schemas.ClientCreate) -> models.Client:
"""Create a new client profile."""
client = models.Client(**data.model_dump())
db.add(client)
db.commit()
db.refresh(client)
return client
def list_clients(db: Session) -> list[models.Client]:
"""Return all client profiles ordered by creation date (newest first)."""
return db.query(models.Client).order_by(models.Client.created_at.desc()).all()
def get_client(db: Session, client_id: int) -> models.Client | None:
"""Return a single client by ID, or None."""
return db.query(models.Client).filter(models.Client.id == client_id).first()
# ---------------------------------------------------------------------------
# Candidate
# ---------------------------------------------------------------------------
def create_candidate(db: Session, data: schemas.CandidateCreate) -> models.Candidate:
"""Create a new candidate linked to a client."""
candidate = models.Candidate(**data.model_dump())
db.add(candidate)
db.commit()
db.refresh(candidate)
return candidate
def list_candidates(
db: Session, client_id: int | None = None
) -> list[models.Candidate]:
"""Return candidates, optionally filtered by client_id."""
query = db.query(models.Candidate)
if client_id is not None:
query = query.filter(models.Candidate.client_id == client_id)
return query.order_by(models.Candidate.created_at.desc()).all()
def get_candidate(db: Session, candidate_id: int) -> models.Candidate | None:
"""Return a single candidate by ID with eager-loaded score and feedback."""
return (
db.query(models.Candidate)
.filter(models.Candidate.id == candidate_id)
.first()
)
# ---------------------------------------------------------------------------
# Score (with mismatch detection)
# ---------------------------------------------------------------------------
_COMPETENCY_FIELDS = [
("communication", "Communication"),
("adaptability", "Adaptability"),
("collaboration", "Collaboration"),
("problem_solving", "Problem Solving"),
("leadership", "Leadership"),
]
def submit_scores(
db: Session, candidate_id: int, data: schemas.ScoreCreate
) -> models.Score:
"""Score a candidate on the 5 BARS competencies and detect mismatches."""
candidate = get_candidate(db, candidate_id)
if candidate is None:
raise ValueError("Candidate not found")
client = get_client(db, candidate.client_id)
if client is None:
raise ValueError("Client not found")
# Build mismatch list
mismatches: list[str] = []
passing = 0
total = len(_COMPETENCY_FIELDS)
for field, label in _COMPETENCY_FIELDS:
score_val = getattr(data, field)
min_val = getattr(client, f"min_{field}")
if score_val >= min_val:
passing += 1
else:
mismatches.append(
f"{label}: scored {score_val}, minimum required {min_val}"
)
overall_match = passing / total if total > 0 else 0.0
# Check if score already exists for this candidate
existing = (
db.query(models.Score)
.filter(models.Score.candidate_id == candidate_id)
.first()
)
if existing:
# Update existing score
for field, _ in _COMPETENCY_FIELDS:
setattr(existing, field, getattr(data, field))
existing.overall_match = overall_match
existing.mismatches = json.dumps(mismatches)
db.commit()
db.refresh(existing)
return existing
# Create new score
score = models.Score(
candidate_id=candidate_id,
**data.model_dump(),
overall_match=overall_match,
mismatches=json.dumps(mismatches),
)
db.add(score)
db.commit()
db.refresh(score)
return score
# ---------------------------------------------------------------------------
# Feedback
# ---------------------------------------------------------------------------
def submit_feedback(
db: Session, candidate_id: int, data: schemas.FeedbackCreate
) -> models.Feedback:
"""Record post-interview feedback for a candidate."""
# Check if feedback already exists
existing = (
db.query(models.Feedback)
.filter(models.Feedback.candidate_id == candidate_id)
.first()
)
if existing:
existing.outcome = data.outcome
existing.primary_reason = data.primary_reason
existing.client_notes = data.client_notes
db.commit()
db.refresh(existing)
return existing
feedback = models.Feedback(candidate_id=candidate_id, **data.model_dump())
db.add(feedback)
db.commit()
db.refresh(feedback)
return feedback
# ---------------------------------------------------------------------------
# Stats
# ---------------------------------------------------------------------------
def get_stats(db: Session) -> dict:
"""Compute dashboard statistics."""
total_candidates = db.query(func.count(models.Candidate.id)).scalar() or 0
total_with_feedback = (
db.query(func.count(models.Feedback.id)).scalar() or 0
)
accepted_count = (
db.query(func.count(models.Feedback.id))
.filter(models.Feedback.outcome == "accepted")
.scalar()
or 0
)
rejected_count = (
db.query(func.count(models.Feedback.id))
.filter(models.Feedback.outcome == "rejected")
.scalar()
or 0
)
acceptance_rate = (
(accepted_count / total_with_feedback * 100)
if total_with_feedback > 0
else 0.0
)
feedback_compliance_rate = (
(total_with_feedback / total_candidates * 100)
if total_candidates > 0
else 0.0
)
return {
"total_candidates": total_candidates,
"total_with_feedback": total_with_feedback,
"accepted_count": accepted_count,
"rejected_count": rejected_count,
"acceptance_rate": round(acceptance_rate, 1),
"feedback_compliance_rate": round(feedback_compliance_rate, 1),
}