ilyass yani
feat: password reset (endpoints forgot/reset, email service, migration colonnes)
61bb0c4
Raw
History Blame Contribute Delete
11.8 kB
from sqlalchemy import Column, Integer, String, Text, DateTime, Float, ForeignKey, Enum, Boolean
from sqlalchemy.orm import relationship
from datetime import datetime
import enum
from app.core.database import Base
class UserRole(str, enum.Enum):
admin = "admin"
recruiter = "recruiter"
candidate = "candidate"
class ModerationStatus(str, enum.Enum):
pending = "pending"
approved = "approved"
rejected = "rejected"
class SkillCategory(str, enum.Enum):
tech = "tech"
soft = "soft"
language = "language"
class ProficiencyLevel(str, enum.Enum):
beginner = "beginner"
intermediate = "intermediate"
advanced = "advanced"
expert = "expert"
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
full_name = Column(String, nullable=False)
role = Column(Enum(UserRole), default=UserRole.recruiter, nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
reset_password_token = Column(String, nullable=True, index=True)
reset_password_token_expires = Column(DateTime, nullable=True)
# Relationships
job_criteria = relationship("JobCriteria", back_populates="recruiter")
favorites = relationship("Favorite", back_populates="recruiter")
candidate = relationship("Candidate", back_populates="user", uselist=False, foreign_keys="Candidate.user_id")
class Candidate(Base):
__tablename__ = "candidates"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True, unique=True)
recruiter_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True)
full_name = Column(String, nullable=False)
email = Column(String, unique=True, index=True, nullable=False)
phone = Column(String, nullable=True)
linkedin_url = Column(String, nullable=True)
github_url = Column(String, nullable=True)
cv_path = Column(String, nullable=True)
raw_text = Column(Text, nullable=True)
# Visibility: who deposited ("candidate" | "recruiter") and whether recruiters can see it
owner_role = Column(String, nullable=True) # "candidate" or "recruiter"
is_visible = Column(Boolean, default=False, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=True)
# NER Extraction Fields (Étape 5-6 optimization)
extracted_name = Column(String, nullable=True) # From NER
extracted_emails = Column(Text, nullable=True) # JSON list: ["email1", "email2"]
extracted_phones = Column(Text, nullable=True) # JSON list: ["+33612345"]
extracted_job_titles = Column(Text, nullable=True) # JSON list with confidence
extracted_companies = Column(Text, nullable=True) # JSON list with confidence
extracted_education = Column(Text, nullable=True) # JSON list
extraction_quality_score = Column(Float, default=0.0) # 0-100 quality metric
ner_extraction_data = Column(Text, nullable=True) # Full JSON from NER
is_fully_extracted = Column(Boolean, default=False) # Flag: quality > 70%
# Relationships
user = relationship("User", back_populates="candidate", foreign_keys=[user_id])
# cascade="all, delete-orphan" ensures child rows are deleted (not nullified) when
# the candidate is deleted. Without this, SQLAlchemy tries SET candidate_id=NULL
# which violates the NOT NULL constraint on every child table.
candidate_skills = relationship("CandidateSkill", back_populates="candidate", cascade="all, delete-orphan")
experiences = relationship("Experience", back_populates="candidate", cascade="all, delete-orphan")
educations = relationship("Education", back_populates="candidate", cascade="all, delete-orphan")
match_results = relationship("MatchResult", back_populates="candidate", cascade="all, delete-orphan")
favorites = relationship("Favorite", back_populates="candidate", cascade="all, delete-orphan")
class Skill(Base):
__tablename__ = "skills"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True, nullable=False)
category = Column(Enum(SkillCategory), nullable=False)
synonyms = Column(Text, nullable=True) # Stored as comma-separated values
# Relationships
candidate_skills = relationship("CandidateSkill", back_populates="skill")
criteria_skills = relationship("CriteriaSkill", back_populates="skill")
class CandidateSkill(Base):
__tablename__ = "candidate_skills"
id = Column(Integer, primary_key=True, index=True)
candidate_id = Column(Integer, ForeignKey("candidates.id"), nullable=False)
skill_id = Column(Integer, ForeignKey("skills.id"), nullable=False)
proficiency_level = Column(Enum(ProficiencyLevel), nullable=False)
source = Column(String, nullable=True) # e.g., "CV", "LinkedIn", "AI extraction"
# Relationships
candidate = relationship("Candidate", back_populates="candidate_skills")
skill = relationship("Skill", back_populates="candidate_skills")
class Experience(Base):
__tablename__ = "experiences"
id = Column(Integer, primary_key=True, index=True)
candidate_id = Column(Integer, ForeignKey("candidates.id"), nullable=False)
title = Column(String, nullable=False)
company = Column(String, nullable=False)
duration_months = Column(Integer, nullable=False)
description = Column(Text, nullable=True)
# Relationships
candidate = relationship("Candidate", back_populates="experiences")
class Education(Base):
__tablename__ = "educations"
id = Column(Integer, primary_key=True, index=True)
candidate_id = Column(Integer, ForeignKey("candidates.id"), nullable=False)
degree = Column(String, nullable=False)
institution = Column(String, nullable=False)
field = Column(String, nullable=False)
year = Column(Integer, nullable=True)
# Relationships
candidate = relationship("Candidate", back_populates="educations")
class JobCriteria(Base):
__tablename__ = "job_criteria"
id = Column(Integer, primary_key=True, index=True)
recruiter_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String, nullable=False)
description = Column(Text, nullable=True)
moderation_status = Column(Enum(ModerationStatus), default=ModerationStatus.pending, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
recruiter = relationship("User", back_populates="job_criteria")
criteria_skills = relationship("CriteriaSkill", back_populates="criteria")
match_results = relationship("MatchResult", back_populates="criteria")
class CriteriaSkill(Base):
__tablename__ = "criteria_skills"
id = Column(Integer, primary_key=True, index=True)
criteria_id = Column(Integer, ForeignKey("job_criteria.id"), nullable=False)
skill_id = Column(Integer, ForeignKey("skills.id"), nullable=False)
weight = Column(Integer, nullable=False) # 0-100
# Relationships
criteria = relationship("JobCriteria", back_populates="criteria_skills")
skill = relationship("Skill", back_populates="criteria_skills")
class MatchResult(Base):
__tablename__ = "match_results"
id = Column(Integer, primary_key=True, index=True)
criteria_id = Column(Integer, ForeignKey("job_criteria.id"), nullable=False)
candidate_id = Column(Integer, ForeignKey("candidates.id"), nullable=False)
score = Column(Float, nullable=False) # 0.0-1.0
explanation = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
criteria = relationship("JobCriteria", back_populates="match_results")
candidate = relationship("Candidate", back_populates="match_results")
class Favorite(Base):
__tablename__ = "favorites"
id = Column(Integer, primary_key=True, index=True)
recruiter_id = Column(Integer, ForeignKey("users.id"), nullable=False)
candidate_id = Column(Integer, ForeignKey("candidates.id"), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
recruiter = relationship("User", back_populates="favorites")
candidate = relationship("Candidate", back_populates="favorites")
class SystemSetting(Base):
"""Admin use case: 'Configurer les paramètres du pipeline IA'.
Key/value store (value is JSON-encoded) for runtime configuration such as
the matching pipeline weights and decision thresholds.
"""
__tablename__ = "system_settings"
id = Column(Integer, primary_key=True, index=True)
key = Column(String, unique=True, index=True, nullable=False)
value = Column(Text, nullable=True) # JSON-encoded payload
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
updated_by = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
class ActivityLog(Base):
"""Admin use case: 'Superviser les logs et performances'.
Lightweight audit trail of meaningful actions (auth, user management,
configuration changes) used to power the admin monitoring view.
"""
__tablename__ = "activity_logs"
id = Column(Integer, primary_key=True, index=True)
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
level = Column(String, default="INFO", nullable=False) # INFO | WARNING | ERROR
action = Column(String, nullable=False, index=True) # e.g. "user.create", "auth.login"
user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
detail = Column(Text, nullable=True)
user = relationship("User", foreign_keys=[user_id])
class RecruiterFeedback(Base):
"""Phase 3: Capture recruiter decisions vs model predictions for continuous learning."""
__tablename__ = "recruiter_feedback"
id = Column(Integer, primary_key=True, index=True)
criteria_id = Column(Integer, ForeignKey("job_criteria.id"), nullable=False)
candidate_id = Column(Integer, ForeignKey("candidates.id"), nullable=False)
recruiter_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Model prediction at time of decision
model_predicted_score = Column(Float, nullable=False) # 0.0-100.0
model_predicted_decision = Column(String, nullable=False) # "accepted" | "review" | "rejected"
# Recruiter decision (can override model)
recruiter_decision = Column(String, nullable=False) # "accepted" | "rejected" | "no_action"
recruiter_score_override = Column(Float, nullable=True) # If recruiter gave explicit score
# Context/reason for decision
feedback_reason = Column(Text, nullable=True) # Why recruiter accepted/rejected
is_override = Column(Boolean, default=False) # True if recruiter decision != model prediction
# Outcome tracking
hire_outcome = Column(String, nullable=True) # "hired" | "rejected_later" | "unknown"
hire_date = Column(DateTime, nullable=True)
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
criteria = relationship("JobCriteria", foreign_keys=[criteria_id])
candidate = relationship("Candidate", foreign_keys=[candidate_id])
recruiter = relationship("User", foreign_keys=[recruiter_id])