salary-benchmark/app/models.py
DJP e9b9c66423 Add login (JWT + local admin user) and deploy script for optical-dev
- Backend: users table + admin seed (004), /api/auth endpoints, JWT auth
  dep gating benchmarks + research routes
- Frontend: AuthContext, LoginPage, ProtectedRoute, subpath-aware via
  VITE_BASE / import.meta.env.BASE_URL so same build works at /opt/
- deploy/: Dockerfile.prod, docker-compose.prod.yml, Apache vhost
  fragment template, and idempotent deploy.sh (port scan, rsync, env
  generation, Apache Include + reload)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 19:34:15 -04:00

116 lines
4.3 KiB
Python

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")