Spaces:
Configuration error
Configuration error
| """ORM models for Phase 0-3. | |
| Every tenant-scoped table carries `tenant_id` as the first filter column and is | |
| indexed on it. Content-addressed uniqueness on `(tenant_id, sha256)` is what | |
| makes the same resume arriving through three channels resolve to one document. | |
| """ | |
| from __future__ import annotations | |
| import uuid | |
| from datetime import datetime | |
| from decimal import Decimal | |
| from pgvector.sqlalchemy import HALFVEC, Vector | |
| from sqlalchemy import ( | |
| ARRAY, | |
| BigInteger, | |
| Boolean, | |
| Date, | |
| DateTime, | |
| Float, | |
| ForeignKey, | |
| Index, | |
| Integer, | |
| Numeric, | |
| String, | |
| Text, | |
| UniqueConstraint, | |
| func, | |
| ) | |
| from sqlalchemy.dialects.postgresql import JSONB, TSVECTOR | |
| from sqlalchemy.dialects.postgresql import UUID as PG_UUID | |
| from sqlalchemy.orm import Mapped, mapped_column, relationship | |
| from app.db import Base | |
| class TimestampMixin: | |
| """Adds server-side creation and update timestamps.""" | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), nullable=False | |
| ) | |
| updated_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), | |
| server_default=func.now(), | |
| onupdate=func.now(), | |
| nullable=False, | |
| ) | |
| class ResumeDocument(TimestampMixin, Base): | |
| """An uploaded resume file, deduplicated by content hash within a tenant.""" | |
| __tablename__ = "resume_documents" | |
| __table_args__ = ( | |
| UniqueConstraint("tenant_id", "sha256", name="uq_resume_documents_tenant_sha256"), | |
| Index("ix_resume_documents_tenant_created", "tenant_id", "created_at"), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| uploaded_by: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), nullable=False) | |
| storage_key: Mapped[str] = mapped_column(String(512), nullable=False) | |
| filename_sanitized: Mapped[str] = mapped_column(String(255), nullable=False) | |
| media_type: Mapped[str] = mapped_column(String(128), nullable=False) | |
| size_bytes: Mapped[int] = mapped_column(Integer, nullable=False) | |
| sha256: Mapped[str] = mapped_column(String(64), nullable=False) | |
| page_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| parse_status: Mapped[str] = mapped_column(String(32), nullable=False, default="ok") | |
| parse_error: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| parser_version: Mapped[str] = mapped_column(String(64), nullable=False) | |
| needs_ocr: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) | |
| versions: Mapped[list[ResumeVersion]] = relationship( | |
| back_populates="document", cascade="all, delete-orphan", lazy="noload" | |
| ) | |
| class ResumeVersion(TimestampMixin, Base): | |
| """Extracted text for one parse of one document. | |
| Kept separate from the document so a re-parse under a newer parser version | |
| is a new row rather than a destructive update — prior evidence offsets stay | |
| attributable to the parse that produced them. | |
| """ | |
| __tablename__ = "resume_versions" | |
| __table_args__ = ( | |
| UniqueConstraint( | |
| "document_id", "version", name="uq_resume_versions_document_version" | |
| ), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| document_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("resume_documents.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| version: Mapped[int] = mapped_column(Integer, nullable=False, default=1) | |
| extracted_text: Mapped[str] = mapped_column(Text, nullable=False) | |
| text_sha256: Mapped[str] = mapped_column(String(64), nullable=False) | |
| page_offsets: Mapped[list[dict[str, object]]] = mapped_column( | |
| JSONB, nullable=False, default=list | |
| ) | |
| parser_version: Mapped[str] = mapped_column(String(64), nullable=False) | |
| # Sanitization provenance. Stored per version because a re-parse under a | |
| # newer sanitizer must not overwrite what an earlier decision was based on. | |
| sanitization_report: Mapped[dict[str, object]] = mapped_column( | |
| JSONB, nullable=False, default=dict | |
| ) | |
| injection_risk_score: Mapped[float] = mapped_column( | |
| Float, nullable=False, default=0.0 | |
| ) | |
| quarantined: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) | |
| document: Mapped[ResumeDocument] = relationship(back_populates="versions") | |
| class ResumeChunk(TimestampMixin, Base): | |
| """A chunk of resume text with its embedding vector. | |
| Parent-child chunking: small children (~180 words) for embedding precision, | |
| larger parents (~700 words) fed to the LLM for context. Retrieve on children, | |
| expand to parents. | |
| ``embedding_version`` on the row is what makes an embedding-model upgrade a | |
| background backfill instead of a flag day. | |
| """ | |
| __tablename__ = "resume_chunks" | |
| # Every index the migration creates is declared here, including the two | |
| # PostgreSQL-specific ones. Leaving them out of the metadata does not make | |
| # them optional — it makes `alembic revision --autogenerate` emit a | |
| # `drop_index` for each on the next run, quietly removing the indexes both | |
| # halves of hybrid search depend on. | |
| __table_args__ = ( | |
| Index("ix_resume_chunks_tenant_document", "tenant_id", "document_id"), | |
| Index("ix_resume_chunks_tenant_id", "tenant_id"), | |
| Index("ix_resume_chunks_content_tsv", "content_tsv", postgresql_using="gin"), | |
| Index( | |
| "ix_resume_chunks_embedding_hnsw", | |
| "embedding", | |
| postgresql_using="hnsw", | |
| postgresql_with={"m": 16, "ef_construction": 64}, | |
| postgresql_ops={"embedding": "vector_cosine_ops"}, | |
| ), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), nullable=False) | |
| resume_version_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("resume_versions.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| document_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("resume_documents.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| chunk_index: Mapped[int] = mapped_column(Integer, nullable=False) | |
| parent_chunk_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| section: Mapped[str] = mapped_column( | |
| String(64), nullable=False, default="other" | |
| ) | |
| content: Mapped[str] = mapped_column(Text, nullable=False) | |
| # content_tsv is a PostgreSQL tsvector GENERATED column managed by the | |
| # database. The column exists for GIN indexing and ts_rank_cd queries. | |
| # We declare it here so SQLAlchemy knows about it but never write to it | |
| # from Python. | |
| content_tsv: Mapped[str | None] = mapped_column( | |
| TSVECTOR, nullable=True, server_default=None | |
| ) | |
| page_from: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| page_to: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| start_char: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| end_char: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| token_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| # pgvector embedding — stored as vector(1024). The HNSW index is created | |
| # in the Alembic migration rather than here because SQLAlchemy's Index() | |
| # does not support the pgvector-specific USING/WITH clauses. | |
| embedding: Mapped[list[float] | None] = mapped_column( | |
| Vector(1024), nullable=True | |
| ) | |
| embedding_model: Mapped[str] = mapped_column(String(128), nullable=False, default="") | |
| embedding_version: Mapped[str] = mapped_column(String(64), nullable=False, default="") | |
| is_parent: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) | |
| class Job(TimestampMixin, Base): | |
| """A job description, either pasted as text or extracted from an upload.""" | |
| __tablename__ = "jobs" | |
| __table_args__ = (Index("ix_jobs_tenant_created", "tenant_id", "created_at"),) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| created_by: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), nullable=False) | |
| title: Mapped[str] = mapped_column(String(255), nullable=False) | |
| department: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| location: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| employment_type: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| seniority: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| description_raw: Mapped[str] = mapped_column(Text, nullable=False) | |
| source: Mapped[str] = mapped_column(String(32), nullable=False, default="manual") | |
| status: Mapped[str] = mapped_column(String(32), nullable=False, default="draft") | |
| blind_mode: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) | |
| class RubricVersion(TimestampMixin, Base): | |
| """One immutable-once-approved snapshot of the criteria for a job. | |
| A rubric is the artifact a human signs off on, and every score is attributed | |
| to the version it was computed against. Editing an approved rubric mints | |
| version N+1 and leaves prior scores attributable to the criteria that | |
| produced them, so `unique(job_id, version)` is what keeps "which criteria | |
| was this candidate judged by" answerable. | |
| ``content_hash`` fingerprints the full requirement set and is part of every | |
| verdict cache key. It stays NULL while the rubric is a draft — a draft has | |
| no frozen criteria to fingerprint. | |
| """ | |
| __tablename__ = "rubric_versions" | |
| __table_args__ = ( | |
| UniqueConstraint("job_id", "version", name="uq_rubric_versions_job_version"), | |
| Index("ix_rubric_versions_tenant_job", "tenant_id", "job_id"), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| job_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("jobs.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| version: Mapped[int] = mapped_column(Integer, nullable=False, default=1) | |
| status: Mapped[str] = mapped_column(String(32), nullable=False, default="draft") | |
| content_hash: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| # A candidate failing a must-have cannot score above this, no matter how | |
| # strong the rest of the match is. Stored per version so tightening the cap | |
| # does not silently re-rank candidates scored under the old one. | |
| must_have_fail_cap: Mapped[int] = mapped_column(Integer, nullable=False, default=40) | |
| aggregation_formula_version: Mapped[str] = mapped_column( | |
| String(32), nullable=False, default="v1" | |
| ) | |
| source: Mapped[str] = mapped_column(String(32), nullable=False, default="manual") | |
| approved_by: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| approved_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| requirements: Mapped[list[Requirement]] = relationship( | |
| back_populates="rubric_version", | |
| cascade="all, delete-orphan", | |
| lazy="noload", | |
| ) | |
| class Requirement(TimestampMixin, Base): | |
| """One weighted criterion within a rubric version. | |
| ``weight`` is ``numeric(5,4)`` rather than a float because normalized | |
| weights must sum to exactly 1.0. Binary floats cannot represent tenths | |
| exactly, so a float column would make that invariant unverifiable. | |
| ``skill_id`` is nullable: the ESCO skill taxonomy it would point at is not | |
| built yet, and requiring it would make every requirement uninsertable. | |
| """ | |
| __tablename__ = "requirements" | |
| __table_args__ = ( | |
| Index("ix_requirements_tenant_version", "tenant_id", "rubric_version_id"), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| rubric_version_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("rubric_versions.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| ordinal: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| text: Mapped[str] = mapped_column(Text, nullable=False) | |
| category: Mapped[str] = mapped_column(String(32), nullable=False, default="skill") | |
| is_must_have: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) | |
| weight: Mapped[Decimal] = mapped_column(Numeric(5, 4), nullable=False) | |
| min_years: Mapped[Decimal | None] = mapped_column(Numeric(4, 1), nullable=True) | |
| min_seniority: Mapped[str | None] = mapped_column(String(32), nullable=True) | |
| # Reserved for taxonomy linking; the ESCO tables do not exist yet. | |
| skill_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| # Half precision because requirement counts are small and the extra recall | |
| # from full precision is not worth doubling the index footprint. The HNSW | |
| # index is created in the Alembic migration, not here. | |
| embedding: Mapped[list[float] | None] = mapped_column(HALFVEC(1024), nullable=True) | |
| rubric_version: Mapped[RubricVersion] = relationship(back_populates="requirements") | |
| class ScreeningRun(TimestampMixin, Base): | |
| """One execution of the scoring pipeline over a job's candidate pool. | |
| ``rubric_version_id`` is RESTRICT rather than CASCADE: a run is the record | |
| of which criteria produced a ranking, so the rubric it cites must not be | |
| deletable out from under it. The job itself CASCADEs — deleting a job is a | |
| deliberate purge of everything scoped to it. | |
| ``triggered_by`` is a plain uuid, not a foreign key: the ``users`` table | |
| described in ARCHITECTURE.md §6.4 does not exist in this repo yet, and | |
| declaring the constraint would make the migration unrunnable. | |
| """ | |
| __tablename__ = "screening_runs" | |
| __table_args__ = (Index("ix_screening_runs_tenant_job", "tenant_id", "job_id"),) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| job_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("jobs.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| rubric_version_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("rubric_versions.id", ondelete="RESTRICT"), | |
| nullable=False, | |
| ) | |
| status: Mapped[str] = mapped_column(String(32), nullable=False, default="queued") | |
| mode: Mapped[str] = mapped_column(String(32), nullable=False, default="interactive") | |
| candidate_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| # Per-stage survivor counts for the retrieval funnel, so a shortlist can be | |
| # explained as "N of M candidates reached the judge" without re-running it. | |
| funnel_stage_counts: Mapped[dict[str, object] | None] = mapped_column( | |
| JSONB, nullable=True | |
| ) | |
| started_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| completed_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| workflow_id: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| triggered_by: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| cost_usd: Mapped[Decimal] = mapped_column( | |
| Numeric(10, 4), nullable=False, default=Decimal("0") | |
| ) | |
| total_input_tokens: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) | |
| total_output_tokens: Mapped[int] = mapped_column( | |
| BigInteger, nullable=False, default=0 | |
| ) | |
| cache_read_tokens: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) | |
| cache_write_tokens: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) | |
| scores: Mapped[list[CandidateScore]] = relationship( | |
| back_populates="run", | |
| cascade="all, delete-orphan", | |
| lazy="noload", | |
| ) | |
| class CandidateScore(TimestampMixin, Base): | |
| """One candidate's aggregated result within a run. | |
| ``raw_weighted`` keeps four decimal places while ``overall_score`` keeps | |
| two: the weighted sum is the input to capping and rounding, and storing it | |
| pre-rounding is what makes a score replayable. ``cap_applied`` is NULL when | |
| no must-have cap fired, so "was this candidate capped" is answerable without | |
| recomputing the rubric. | |
| ``candidate_id`` and ``profile_id`` are plain uuids rather than foreign | |
| keys: the ``candidates`` and ``candidate_profiles`` tables described in | |
| ARCHITECTURE.md §6.3 are not built in this repo, and declaring the | |
| constraints would make the migration unrunnable. | |
| """ | |
| __tablename__ = "candidate_scores" | |
| __table_args__ = ( | |
| UniqueConstraint( | |
| "run_id", "candidate_id", name="uq_candidate_scores_run_candidate" | |
| ), | |
| Index("ix_candidate_scores_run_rank", "run_id", "rank"), | |
| Index("ix_candidate_scores_tenant_run", "tenant_id", "run_id"), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| run_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("screening_runs.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| candidate_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False | |
| ) | |
| profile_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| overall_score: Mapped[Decimal] = mapped_column(Numeric(5, 2), nullable=False) | |
| raw_weighted: Mapped[Decimal] = mapped_column(Numeric(5, 4), nullable=False) | |
| cap_applied: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| # Assigned in a second pass once every candidate in the run is scored, so a | |
| # partially-completed run has scores without ranks rather than wrong ranks. | |
| rank: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| retrieval_score: Mapped[float | None] = mapped_column(Float, nullable=True) | |
| rerank_score: Mapped[float | None] = mapped_column(Float, nullable=True) | |
| recommendation: Mapped[str | None] = mapped_column(String(32), nullable=True) | |
| recommendation_confidence: Mapped[float | None] = mapped_column(Float, nullable=True) | |
| summary: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| # Provenance for replay: which aggregation formula produced this number. | |
| aggregation_formula_version: Mapped[str] = mapped_column( | |
| String(32), nullable=False, default="v1" | |
| ) | |
| run: Mapped[ScreeningRun] = relationship(back_populates="scores") | |
| verdicts: Mapped[list[RequirementVerdict]] = relationship( | |
| back_populates="score", | |
| cascade="all, delete-orphan", | |
| lazy="noload", | |
| ) | |
| class RequirementVerdict(TimestampMixin, Base): | |
| """The judge's call on one requirement for one candidate. | |
| ``weight_at_scoring`` and ``contribution`` are stored rather than derived: | |
| the rubric can mint a new version with different weights, and a verdict must | |
| stay explainable against the weights that actually produced it. | |
| ``retrieved_chunk_ids`` is the exposure record — exactly what the judge saw. | |
| Without it a "missing" verdict is indistinguishable from a retrieval failure. | |
| ``requirement_id`` is RESTRICT: a verdict cites a criterion, so the | |
| criterion must outlive it. Override columns are all nullable and set | |
| together when a human corrects the judge. | |
| """ | |
| __tablename__ = "requirement_verdicts" | |
| __table_args__ = ( | |
| UniqueConstraint( | |
| "score_id", "requirement_id", name="uq_requirement_verdicts_score_req" | |
| ), | |
| Index("ix_requirement_verdicts_tenant_score", "tenant_id", "score_id"), | |
| Index("ix_requirement_verdicts_cache_key", "result_cache_key"), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| score_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("candidate_scores.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| requirement_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("requirements.id", ondelete="RESTRICT"), | |
| nullable=False, | |
| ) | |
| verdict: Mapped[str] = mapped_column(String(16), nullable=False) | |
| confidence: Mapped[float] = mapped_column(Float, nullable=False) | |
| weight_at_scoring: Mapped[Decimal] = mapped_column(Numeric(5, 4), nullable=False) | |
| contribution: Mapped[Decimal] = mapped_column(Numeric(6, 4), nullable=False) | |
| reasoning: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| judge_model: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| judge_prompt_version: Mapped[str | None] = mapped_column(String(32), nullable=True) | |
| judge_effort: Mapped[str | None] = mapped_column(String(32), nullable=True) | |
| cache_hit: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) | |
| input_tokens: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| output_tokens: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| latency_ms: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| retrieved_chunk_ids: Mapped[list[uuid.UUID] | None] = mapped_column( | |
| ARRAY(PG_UUID(as_uuid=True)), nullable=True | |
| ) | |
| result_cache_key: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| overridden_by: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| override_verdict: Mapped[str | None] = mapped_column(String(16), nullable=True) | |
| override_reason: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| overridden_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| score: Mapped[CandidateScore] = relationship(back_populates="verdicts") | |
| evidence_spans: Mapped[list[EvidenceSpanRecord]] = relationship( | |
| back_populates="verdict", | |
| cascade="all, delete-orphan", | |
| lazy="noload", | |
| ) | |
| class EvidenceSpanRecord(TimestampMixin, Base): | |
| """A verbatim quote from a resume backing one verdict. | |
| Named ``EvidenceSpanRecord`` rather than ``EvidenceSpan`` because | |
| ``app.services.search`` already exposes an ``EvidenceSpan`` dataclass for | |
| in-flight search results; this is the persisted form. | |
| ``verbatim_verified`` is set by the automated check that re-slices the | |
| resume text at ``[start_char, end_char)`` and compares it to | |
| ``quoted_text`` — the anti-hallucination gate. A row with it False is a | |
| quote the judge produced that the source does not contain. | |
| ``chunk_id`` is SET NULL on delete: re-indexing a resume replaces its | |
| chunks, and the citation survives that because ``resume_version_id`` plus | |
| the character offsets locate the quote independently of any chunk row. | |
| """ | |
| __tablename__ = "evidence_spans" | |
| __table_args__ = ( | |
| Index("ix_evidence_spans_tenant_verdict", "tenant_id", "verdict_id"), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| # Nullable so a span can be recorded while its verdict is still being built. | |
| verdict_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("requirement_verdicts.id", ondelete="CASCADE"), | |
| nullable=True, | |
| ) | |
| resume_version_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("resume_versions.id", ondelete="RESTRICT"), | |
| nullable=False, | |
| ) | |
| chunk_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("resume_chunks.id", ondelete="SET NULL"), | |
| nullable=True, | |
| ) | |
| page: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| start_char: Mapped[int] = mapped_column(Integer, nullable=False) | |
| end_char: Mapped[int] = mapped_column(Integer, nullable=False) | |
| quoted_text: Mapped[str] = mapped_column(Text, nullable=False) | |
| verbatim_verified: Mapped[bool] = mapped_column( | |
| Boolean, nullable=False, default=False | |
| ) | |
| relevance: Mapped[float | None] = mapped_column(Float, nullable=True) | |
| verdict: Mapped[RequirementVerdict | None] = relationship( | |
| back_populates="evidence_spans" | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Revised-stack tables (ARCHITECTURE-AGENTS.md §10) | |
| # --------------------------------------------------------------------------- | |
| class RunTask(TimestampMixin, Base): | |
| """One unit of work in the screening pipeline queue. | |
| Replaces a Celery/Arq broker. Claimed via ``SELECT ... FOR UPDATE | |
| SKIP LOCKED``. A rate-limit reschedule sets ``not_before`` and resets | |
| ``status='pending'`` without consuming a retry attempt — distinct | |
| from a genuine failure (status='failed', attempt incremented). | |
| """ | |
| __tablename__ = "run_tasks" | |
| __table_args__ = ( | |
| Index("ix_run_tasks_run_status", "run_id", "status", "not_before"), | |
| ) | |
| id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| run_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("screening_runs.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| stage: Mapped[str] = mapped_column(String(64), nullable=False) | |
| agent_name: Mapped[str] = mapped_column( | |
| String(64), nullable=False, default="" | |
| ) | |
| candidate_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| requirement_group: Mapped[dict[str, object] | None] = mapped_column( | |
| JSONB, nullable=True | |
| ) | |
| status: Mapped[str] = mapped_column(String(32), nullable=False, default="pending") | |
| claimed_by: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| claimed_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| not_before: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| attempt: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| payload: Mapped[dict[str, object] | None] = mapped_column(JSONB, nullable=True) | |
| result: Mapped[dict[str, object] | None] = mapped_column(JSONB, nullable=True) | |
| error: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| class AgentResultCache(TimestampMixin, Base): | |
| """Durable agent result cache — the reproducibility mechanism. | |
| A cache hit skips an LLM call entirely, so a re-run with an unchanged | |
| rubric costs ~zero tokens and produces an identical score. | |
| """ | |
| __tablename__ = "agent_result_cache" | |
| __table_args__ = ( | |
| Index("ix_agent_result_cache_tenant_agent", "tenant_id", "agent_name"), | |
| ) | |
| cache_key: Mapped[str] = mapped_column(String(64), primary_key=True) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False | |
| ) | |
| agent_name: Mapped[str] = mapped_column(String(64), nullable=False) | |
| agent_version: Mapped[str] = mapped_column(String(32), nullable=False) | |
| output: Mapped[dict[str, object]] = mapped_column(JSONB, nullable=False) | |
| expires_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| class RateLimitBucket(Base): | |
| """Per-provider token-bucket state. | |
| UNLOGGED — losing it on crash is an accepted degradation (bucket | |
| resets, worst case: burst of provider 429s absorbed by the rate-limit | |
| classifier). | |
| """ | |
| __tablename__ = "rate_limit_buckets" | |
| __table_args__ = ( | |
| Index( | |
| "ix_rate_limit_buckets_lookup", | |
| "provider", | |
| "model", | |
| "api_key_hash", | |
| "window", | |
| "window_start", | |
| unique=True, | |
| ), | |
| {"prefixes": ["UNLOGGED"]}, | |
| ) | |
| provider: Mapped[str] = mapped_column(String(64), primary_key=True) | |
| model: Mapped[str] = mapped_column(String(128), primary_key=True) | |
| api_key_hash: Mapped[str] = mapped_column(String(64), primary_key=True) | |
| window: Mapped[str] = mapped_column( | |
| String(16), primary_key=True | |
| ) # 'rpm' | 'tpm' | 'rpd' | |
| window_start: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), primary_key=True | |
| ) | |
| used: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) | |
| cap: Mapped[int] = mapped_column(BigInteger, nullable=False) | |
| class RunCheckpoint(TimestampMixin, Base): | |
| """Heartbeat for resume-after-restart. One row per run.""" | |
| __tablename__ = "run_checkpoints" | |
| run_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("screening_runs.id", ondelete="CASCADE"), | |
| primary_key=True, | |
| ) | |
| last_stage: Mapped[str] = mapped_column(String(64), nullable=False, default="") | |
| heartbeat_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), nullable=False, default=func.now | |
| ) | |
| resumed_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| class AtsComplianceReport(TimestampMixin, Base): | |
| """ATS keyword/format compliance sidecar — never blended into overall_score.""" | |
| __tablename__ = "ats_compliance_reports" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| resume_version_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False | |
| ) | |
| rubric_version_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| keyword_coverage: Mapped[float] = mapped_column(Float, nullable=False) | |
| matched_keywords: Mapped[list[str] | None] = mapped_column( | |
| ARRAY(String), nullable=True | |
| ) | |
| missing_keywords: Mapped[list[str] | None] = mapped_column( | |
| ARRAY(String), nullable=True | |
| ) | |
| format_flags: Mapped[dict[str, object] | None] = mapped_column( | |
| JSONB, nullable=True | |
| ) | |
| compliance_score: Mapped[float] = mapped_column(Float, nullable=False) | |
| is_ats_safe: Mapped[bool] = mapped_column(Boolean, nullable=False) | |
| computed_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), nullable=False, default=func.now | |
| ) | |
| class FraudFlag(TimestampMixin, Base): | |
| """Deception signals — distinct from verdicts, never scored.""" | |
| __tablename__ = "fraud_flags" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| score_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("candidate_scores.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| signal_type: Mapped[str] = mapped_column(String(64), nullable=False) | |
| description: Mapped[str] = mapped_column(Text, nullable=False) | |
| confidence: Mapped[float] = mapped_column(Float, nullable=False) | |
| related_requirement_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| judge_model: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| prompt_version: Mapped[str | None] = mapped_column(String(32), nullable=True) | |
| class BiasFlag(TimestampMixin, Base): | |
| """Language-bias findings in generated text — distinct from verdicts.""" | |
| __tablename__ = "bias_flags" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| score_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("candidate_scores.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| source: Mapped[str] = mapped_column(String(32), nullable=False) | |
| text_excerpt: Mapped[str] = mapped_column(Text, nullable=False) | |
| bias_category: Mapped[str] = mapped_column(String(64), nullable=False) | |
| severity: Mapped[str] = mapped_column(String(16), nullable=False) | |
| rationale: Mapped[str] = mapped_column(Text, nullable=False) | |
| judge_model: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| prompt_version: Mapped[str | None] = mapped_column(String(32), nullable=True) | |
| # --------------------------------------------------------------------------- | |
| # Phase 0 foundation tables — tenants, users, candidates, audit | |
| # --------------------------------------------------------------------------- | |
| class Tenant(TimestampMixin, Base): | |
| """One customer account. Not tenant-scoped — this IS the tenant.""" | |
| __tablename__ = "tenants" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) | |
| slug: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) | |
| plan: Mapped[str] = mapped_column(String(32), nullable=False, default="free") | |
| retention_days: Mapped[int] = mapped_column(Integer, nullable=False, default=365) | |
| is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) | |
| class User(TimestampMixin, Base): | |
| """A human user. Cross-tenant — membership is via user_roles.""" | |
| __tablename__ = "users" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| email: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) | |
| display_name: Mapped[str] = mapped_column(String(255), nullable=False) | |
| is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) | |
| class UserRole(TimestampMixin, Base): | |
| """Per-tenant role assignment. Composite PK: (user_id, tenant_id, role).""" | |
| __tablename__ = "user_roles" | |
| __table_args__ = ( | |
| Index("ix_user_roles_tenant", "tenant_id"), | |
| ) | |
| user_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("users.id", ondelete="CASCADE"), | |
| primary_key=True, | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("tenants.id", ondelete="CASCADE"), | |
| primary_key=True, | |
| ) | |
| role: Mapped[str] = mapped_column(String(32), nullable=False, primary_key=True) | |
| class ApiKey(TimestampMixin, Base): | |
| """Programmatic access key per tenant.""" | |
| __tablename__ = "api_keys" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("tenants.id", ondelete="CASCADE"), | |
| nullable=False, index=True, | |
| ) | |
| key_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) | |
| key_prefix: Mapped[str] = mapped_column(String(12), nullable=False) | |
| name: Mapped[str] = mapped_column(String(255), nullable=False) | |
| is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) | |
| last_used_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| class AuditEvent(TimestampMixin, Base): | |
| """Tamper-evident audit log. Hash-chained.""" | |
| __tablename__ = "audit_events" | |
| __table_args__ = ( | |
| Index("ix_audit_events_tenant_time", "tenant_id", "occurred_at"), | |
| Index("ix_audit_events_resource", "resource_type", "resource_id"), | |
| ) | |
| id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| user_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| action: Mapped[str] = mapped_column(String(64), nullable=False) | |
| resource_type: Mapped[str] = mapped_column(String(64), nullable=False) | |
| resource_id: Mapped[str] = mapped_column(String(64), nullable=False) | |
| details: Mapped[dict[str, object] | None] = mapped_column(JSONB, nullable=True) | |
| ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) | |
| request_id: Mapped[str | None] = mapped_column(String(36), nullable=True) | |
| occurred_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), nullable=False, default=func.now | |
| ) | |
| prev_hash: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| chain_hash: Mapped[str] = mapped_column(String(64), nullable=False) | |
| class Candidate(TimestampMixin, Base): | |
| """A job applicant within a tenant.""" | |
| __tablename__ = "candidates" | |
| __table_args__ = ( | |
| UniqueConstraint("tenant_id", "email", name="uq_candidates_tenant_email"), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| external_id: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| name: Mapped[str] = mapped_column(String(255), nullable=False) | |
| email: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| phone: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| consent_purpose: Mapped[str] = mapped_column( | |
| String(64), nullable=False, default="candidate_screening" | |
| ) | |
| consent_granted_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| class CandidateProfile(TimestampMixin, Base): | |
| """Structured extraction from a resume version.""" | |
| __tablename__ = "candidate_profiles" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| candidate_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("candidates.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| resume_version_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("resume_versions.id", ondelete="SET NULL"), | |
| nullable=True, | |
| ) | |
| extraction_status: Mapped[str] = mapped_column( | |
| String(32), nullable=False, default="pending" | |
| ) | |
| total_experience_months: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| skills: Mapped[dict[str, object] | None] = mapped_column(JSONB, nullable=True) | |
| education: Mapped[dict[str, object] | None] = mapped_column(JSONB, nullable=True) | |
| languages: Mapped[dict[str, object] | None] = mapped_column(JSONB, nullable=True) | |
| latest_role: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| extraction_model: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| extraction_prompt_version: Mapped[str | None] = mapped_column( | |
| String(32), nullable=True | |
| ) | |
| class SkillGap(TimestampMixin, Base): | |
| """One identified skill gap for a candidate.""" | |
| __tablename__ = "skill_gaps" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| score_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("candidate_scores.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| requirement_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False | |
| ) | |
| severity: Mapped[str] = mapped_column(String(16), nullable=False) | |
| gap_type: Mapped[str] = mapped_column(String(32), nullable=False) | |
| suggested_probe: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| weight: Mapped[float] = mapped_column(Float, nullable=False) | |
| class InterviewKit(TimestampMixin, Base): | |
| """One set of interview questions for a scored candidate.""" | |
| __tablename__ = "interview_kits" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| score_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("candidate_scores.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| class InterviewQuestion(TimestampMixin, Base): | |
| """One question in an interview kit.""" | |
| __tablename__ = "interview_questions" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| ordinal: Mapped[int] = mapped_column(Integer, nullable=False) | |
| question: Mapped[str] = mapped_column(Text, nullable=False) | |
| category: Mapped[str] = mapped_column(String(32), nullable=False) | |
| targets_requirement_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| difficulty: Mapped[str] = mapped_column(String(16), nullable=False) | |
| rationale: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| expected_signal: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| follow_ups: Mapped[dict[str, object] | None] = mapped_column( | |
| JSONB, nullable=True | |
| ) | |
| kit_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("interview_kits.id", ondelete="CASCADE"), | |
| nullable=False, | |
| ) | |
| class Decision(TimestampMixin, Base): | |
| """A human recruiter's advance/reject decision with mandatory reason.""" | |
| __tablename__ = "decisions" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| score_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("candidate_scores.id", ondelete="RESTRICT"), | |
| nullable=False, | |
| ) | |
| decided_by: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("users.id", ondelete="RESTRICT"), | |
| nullable=False, | |
| ) | |
| decision: Mapped[str] = mapped_column(String(16), nullable=False) | |
| reason: Mapped[str] = mapped_column(Text, nullable=False) | |
| agreed_with_ai: Mapped[bool] = mapped_column(Boolean, nullable=False) | |
| decided_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), nullable=False, default=func.now | |
| ) | |
| class FairnessSnapshot(TimestampMixin, Base): | |
| """Nightly fairness metrics for one tenant.""" | |
| __tablename__ = "fairness_snapshots" | |
| __table_args__ = ( | |
| UniqueConstraint( | |
| "tenant_id", "computed_for_date", | |
| name="uq_fairness_snapshots_tenant_date", | |
| ), | |
| ) | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| computed_for_date: Mapped[datetime.date] = mapped_column( | |
| Date, nullable=False | |
| ) | |
| metrics: Mapped[dict[str, object]] = mapped_column(JSONB, nullable=False) | |
| computed_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), nullable=False, default=func.now | |
| ) | |
| class ChatSession(TimestampMixin, Base): | |
| """One recruiter chat session (multi-turn RAG).""" | |
| __tablename__ = "chat_sessions" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| tenant_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=False, index=True | |
| ) | |
| job_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("jobs.id", ondelete="CASCADE"), | |
| nullable=True, | |
| ) | |
| candidate_id: Mapped[uuid.UUID | None] = mapped_column( | |
| PG_UUID(as_uuid=True), nullable=True | |
| ) | |
| created_by: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("users.id", ondelete="RESTRICT"), | |
| nullable=False, | |
| ) | |
| last_active_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) | |
| class ChatMessage(TimestampMixin, Base): | |
| """One message in a chat session.""" | |
| __tablename__ = "chat_messages" | |
| id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) | |
| session_id: Mapped[uuid.UUID] = mapped_column( | |
| PG_UUID(as_uuid=True), | |
| ForeignKey("chat_sessions.id", ondelete="CASCADE"), | |
| nullable=False, index=True, | |
| ) | |
| role: Mapped[str] = mapped_column(String(16), nullable=False) | |
| content: Mapped[str] = mapped_column(Text, nullable=False) | |
| citations: Mapped[dict[str, object] | None] = mapped_column( | |
| JSONB, nullable=True | |
| ) | |
| tool_calls: Mapped[dict[str, object] | None] = mapped_column( | |
| JSONB, nullable=True | |
| ) | |