from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Integer, Text from sqlalchemy.dialects.postgresql import ARRAY, JSONB from sqlalchemy.orm import Mapped, mapped_column from fabledassistant.models import Base from fabledassistant.models.base import TimestampMixin class UserProfile(Base, TimestampMixin): __tablename__ = "user_profiles" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True ) display_name: Mapped[str | None] = mapped_column(Text, nullable=True) job_title: Mapped[str | None] = mapped_column(Text, nullable=True) industry: Mapped[str | None] = mapped_column(Text, nullable=True) # novice / intermediate / expert — calibrates explanation depth expertise_level: Mapped[str | None] = mapped_column(Text, nullable=True) # concise / balanced / detailed response_style: Mapped[str | None] = mapped_column(Text, nullable=True) # casual / professional / technical tone: Mapped[str | None] = mapped_column(Text, nullable=True) interests: Mapped[list[str] | None] = mapped_column(ARRAY(Text), nullable=True) # {days: ["Mon","Tue",...], start: "09:00", end: "17:00"} work_schedule: Mapped[dict | None] = mapped_column(JSONB, nullable=True) # LLM-consolidated summary of learned preferences learned_summary: Mapped[str | None] = mapped_column(Text, nullable=True) # [{date: "YYYY-MM-DD", bullets: "..."}, ...] observations_raw: Mapped[list | None] = mapped_column(JSONB, nullable=True) observations_updated_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) def to_dict(self) -> dict: return { "display_name": self.display_name or "", "job_title": self.job_title or "", "industry": self.industry or "", "expertise_level": self.expertise_level or "intermediate", "response_style": self.response_style or "balanced", "tone": self.tone or "casual", "interests": self.interests or [], "work_schedule": self.work_schedule or {}, "learned_summary": self.learned_summary or "", "observations_count": len(self.observations_raw or []), "observations_updated_at": ( self.observations_updated_at.isoformat() if self.observations_updated_at else None ), }