Replaces the freeform briefing-profile note with a DB-backed user_profiles table. Users can edit job/industry/expertise/response preferences/interests/ work schedule via a new Settings → Profile tab. The LLM appends nightly observations; at 14+ entries they are auto-consolidated into a learned_summary. Profile context is injected into both briefing and chat system prompts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
56 lines
2.5 KiB
Python
56 lines
2.5 KiB
Python
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
|
|
),
|
|
}
|