"""Persistence utilities, ORM models, and repository implementations.""" from datetime import datetime, timezone from pgvector.sqlalchemy import Vector from sqlalchemy import ( CheckConstraint, DateTime, Float, ForeignKey, ForeignKeyConstraint, Index, Integer, String, Text, ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from src.storage.db import Base class Candidate(Base): """Data model for candidate values.""" __tablename__ = "candidates" id: Mapped[int] = mapped_column(Integer, primary_key=True) external_id: Mapped[str | None] = mapped_column(String(128), index=True, nullable=True) name: Mapped[str | None] = mapped_column(String(255), nullable=True) email: Mapped[str | None] = mapped_column(String(255), nullable=True) phone: Mapped[str | None] = mapped_column(String(64), nullable=True) links: Mapped[list[str] | None] = mapped_column(JSONB, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) resumes: Mapped[list["Resume"]] = relationship(back_populates="candidate") class Resume(Base): """Data model for resume values.""" __tablename__ = "resumes" id: Mapped[int] = mapped_column(Integer, primary_key=True) candidate_id: Mapped[int] = mapped_column(ForeignKey("candidates.id"), nullable=False) source_file: Mapped[str] = mapped_column(String(512), nullable=False) content_hash: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) raw_text: Mapped[str] = mapped_column(Text, nullable=False) parsed_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True) signals_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True) language: Mapped[str | None] = mapped_column(String(16), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) candidate: Mapped[Candidate] = relationship(back_populates="resumes") sections: Mapped[list["ResumeSection"]] = relationship(back_populates="resume") class ResumeSection(Base): """Data model for resumesection values.""" __tablename__ = "resume_sections" id: Mapped[int] = mapped_column(Integer, primary_key=True) resume_id: Mapped[int] = mapped_column(ForeignKey("resumes.id"), nullable=False) section_type: Mapped[str] = mapped_column(String(64), nullable=False, index=True) content: Mapped[str] = mapped_column(Text, nullable=False) tokens: Mapped[int | None] = mapped_column(Integer, nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True) resume: Mapped[Resume] = relationship(back_populates="sections") class JobPosting(Base): """Data model for jobposting values.""" __tablename__ = "job_postings" id: Mapped[int] = mapped_column(Integer, primary_key=True) title: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) requirements_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) class Embedding(Base): """Data model for embedding values.""" __tablename__ = "embeddings" id: Mapped[int] = mapped_column(Integer, primary_key=True) owner_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) model: Mapped[str] = mapped_column(String(128), nullable=False) dimensions: Mapped[int] = mapped_column(Integer, nullable=False) vector: Mapped[list[float]] = mapped_column(Vector(), nullable=False) text_hash: Mapped[str] = mapped_column(String(64), nullable=False, index=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) __table_args__ = ( CheckConstraint("dimensions > 0", name="ck_embeddings_dimensions_positive"), CheckConstraint( "dimensions = vector_dims(vector)", name="ck_embeddings_dimensions_match_vector" ), ForeignKeyConstraint( ["model", "dimensions"], ["embedding_models.model", "embedding_models.dimensions"], name="fk_embeddings_model_dimensions", ), Index("ix_embeddings_owner", "owner_id"), Index("ix_embeddings_model_dimensions_owner", "model", "dimensions", "owner_id"), ) class EmbeddingModel(Base): """Data model for embeddingmodel values.""" __tablename__ = "embedding_models" model: Mapped[str] = mapped_column(String(128), primary_key=True) dimensions: Mapped[int] = mapped_column(Integer, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) __table_args__ = ( CheckConstraint("dimensions > 0", name="ck_embedding_models_dimensions_positive"), Index("ux_embedding_models_model_dimensions", "model", "dimensions", unique=True), ) class Match(Base): """Data model for match values.""" __tablename__ = "matches" id: Mapped[int] = mapped_column(Integer, primary_key=True) job_id: Mapped[int] = mapped_column(ForeignKey("job_postings.id"), nullable=False) candidate_id: Mapped[int] = mapped_column(ForeignKey("candidates.id"), nullable=False) retrieval_score: Mapped[float | None] = mapped_column(Float, nullable=True) rerank_score: Mapped[float | None] = mapped_column(Float, nullable=True) final_score: Mapped[float | None] = mapped_column(Float, nullable=True) reasons_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True) interview_pack_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) candidate: Mapped["Candidate"] = relationship() class AsyncTask(Base): """Data model for background task tracking.""" __tablename__ = "async_tasks" id: Mapped[int] = mapped_column(Integer, primary_key=True) task_type: Mapped[str] = mapped_column(String(128), nullable=False, index=True) status: Mapped[str] = mapped_column(String(64), nullable=False, default="PENDING", index=True) input_payload: Mapped[dict | None] = mapped_column(JSONB, nullable=True) output_payload: Mapped[dict | None] = mapped_column(JSONB, nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False, )