Spaces:
Running
Running
| """Author RAG Chatbot SaaS β Analytics Models.""" | |
| import datetime | |
| from sqlalchemy import Boolean, Date, Float, ForeignKey, Integer, String, UniqueConstraint | |
| from sqlalchemy.orm import Mapped, mapped_column | |
| from app.models.base import Base, TimestampMixin, generate_uuid | |
| class AnalyticsEvent(Base): | |
| """Raw analytics event per chat turn β aggregated into AnalyticsDaily.""" | |
| __tablename__ = "analytics_events" | |
| id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid) | |
| session_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("chat_sessions.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| author_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| book_id: Mapped[str | None] = mapped_column( | |
| String(36), ForeignKey("books.id", ondelete="SET NULL"), nullable=True, index=True | |
| ) | |
| timestamp: Mapped[datetime.datetime] = mapped_column(nullable=False, index=True) | |
| turn_number: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| intent: Mapped[str | None] = mapped_column(String(50), nullable=True) | |
| intent_confidence: Mapped[float | None] = mapped_column(Float, nullable=True) | |
| faithfulness_score: Mapped[float | None] = mapped_column(Float, nullable=True) | |
| hallucination_detected: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) | |
| boundary_triggered: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) | |
| prompt_tokens: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| completion_tokens: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| response_ms: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| upsell_strategy: Mapped[str | None] = mapped_column(String(50), nullable=True) | |
| link_shown: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) | |
| link_clicked: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) | |
| visitor_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False, default="") | |
| class AnalyticsDaily(Base): | |
| """Pre-aggregated daily analytics β populated by Celery task hourly.""" | |
| __tablename__ = "analytics_daily" | |
| # DESIGN-2 fix: add the unique constraint required by the ON CONFLICT upsert | |
| # in aggregator.py. Without this, create_all creates the table without the | |
| # constraint and the upsert silently fails, creating duplicate rows. | |
| __table_args__ = ( | |
| UniqueConstraint("author_id", "date", name="uq_analytics_daily_author_date"), | |
| ) | |
| id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid) | |
| author_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| date: Mapped[datetime.date] = mapped_column(Date, nullable=False, index=True) | |
| total_chats: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| unique_visitors: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| total_tokens_used: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| total_link_clicks: Mapped[int] = mapped_column(Integer, default=0, nullable=False) | |
| avg_session_turns: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) | |
| avg_faithfulness_score: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) | |