import uuid from datetime import datetime, timezone from sqlalchemy import ( Boolean, DateTime, Float, ForeignKey, Integer, String, UniqueConstraint, ) from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): pass class Location(Base): __tablename__ = "locations" id: Mapped[int] = mapped_column(Integer, primary_key=True) city: Mapped[str] = mapped_column(String(255)) country: Mapped[str] = mapped_column(String(100), default="US") normalized_name: Mapped[str] = mapped_column(String(255), unique=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) ) benchmarks: Mapped[list["Benchmark"]] = relationship(back_populates="location") research_sessions: Mapped[list["ResearchSession"]] = relationship( back_populates="location" ) class Role(Base): __tablename__ = "roles" id: Mapped[int] = mapped_column(Integer, primary_key=True) title: Mapped[str] = mapped_column(String(255)) normalized_title: Mapped[str] = mapped_column(String(255), unique=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) ) benchmarks: Mapped[list["Benchmark"]] = relationship(back_populates="role") research_sessions: Mapped[list["ResearchSession"]] = relationship( back_populates="role" ) class Benchmark(Base): __tablename__ = "benchmarks" __table_args__ = ( UniqueConstraint("role_id", "location_id", "level", name="uq_benchmark"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True) role_id: Mapped[int] = mapped_column(ForeignKey("roles.id")) location_id: Mapped[int] = mapped_column(ForeignKey("locations.id")) level: Mapped[str] = mapped_column(String(20)) salary: Mapped[int] = mapped_column(Integer) source: Mapped[str] = mapped_column(String(50), default="seed") confidence_score: Mapped[float | None] = mapped_column(Float, nullable=True) validated: Mapped[bool] = mapped_column(Boolean, default=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), ) role: Mapped["Role"] = relationship(back_populates="benchmarks") location: Mapped["Location"] = relationship(back_populates="benchmarks") class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True) email: Mapped[str] = mapped_column(String(255), unique=True) password_hash: Mapped[str] = mapped_column(String(255)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) ) class ResearchSession(Base): __tablename__ = "research_sessions" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) role_id: Mapped[int] = mapped_column(ForeignKey("roles.id")) location_id: Mapped[int] = mapped_column(ForeignKey("locations.id")) status: Mapped[str] = mapped_column(String(30), default="searching") serper_results: Mapped[dict | None] = mapped_column(JSONB, nullable=True) firecrawl_results: Mapped[dict | None] = mapped_column(JSONB, nullable=True) cohere_ranked: Mapped[dict | None] = mapped_column(JSONB, nullable=True) claude_analysis: Mapped[dict | None] = mapped_column(JSONB, nullable=True) proposed_benchmarks: Mapped[dict | None] = mapped_column(JSONB, nullable=True) error_message: Mapped[str | None] = mapped_column(String, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) ) completed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) role: Mapped["Role"] = relationship(back_populates="research_sessions") location: Mapped["Location"] = relationship(back_populates="research_sessions")