RM / app\models\library.py
Bromeo777's picture
Add app\models\library.py
67c0130 verified
from datetime import datetime
from typing import Optional, TYPE_CHECKING, List, Any
from sqlalchemy import (
Integer,
DateTime,
ForeignKey,
Text,
Index,
UniqueConstraint,
JSON
)
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
from sqlalchemy.sql import func
from app.models.base import Base
if TYPE_CHECKING:
from app.models.user import User
from app.models.paper import Paper
class LibraryItem(Base):
"""
Represents a paper saved to a user's personal knowledge base.
System Role
-----------
- Powers the Phase 4 Saved Library dashboard.
- Acts as a curated dataset for Phase 8 ProposAI (Grant Generation).
"""
__tablename__ = "library_items"
# ------------------------------------------------------------------
# Identifiers & Ownership
# ------------------------------------------------------------------
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
)
paper_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("papers.id", ondelete="CASCADE"),
nullable=False,
)
# ------------------------------------------------------------------
# Curation Metadata (FIX: Reviewer 1 #31 & #70)
# ------------------------------------------------------------------
tags: Mapped[Optional[List[str]]] = mapped_column(
JSON,
nullable=True,
default=list,
server_default="[]",
comment="Native JSON list of researcher-defined categories"
)
notes: Mapped[Optional[str]] = mapped_column(
Text,
nullable=True,
comment="Markdown-supported personal annotations"
)
# ------------------------------------------------------------------
# Validation Logic (FIX: Reviewer 1 #70)
# ------------------------------------------------------------------
@validates('tags')
def validate_tags(self, key: str, tags: Any) -> List[str]:
"""
Enforces a hard limit of 20 tags per library item.
Prevents metadata bloat and ensures frontend layout stability.
"""
if tags is not None and len(tags) > 20:
raise ValueError("Maximum 20 tags allowed per research artifact.")
return tags
# ------------------------------------------------------------------
# Audit Fields
# ------------------------------------------------------------------
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
)
# ------------------------------------------------------------------
# ORM Relationships
# ------------------------------------------------------------------
user: Mapped["User"] = relationship(
"User",
back_populates="library_items",
lazy="select",
)
paper: Mapped["Paper"] = relationship(
"Paper",
lazy="joined", # Fetch metadata in a single query for library views
)
# ------------------------------------------------------------------
# Constraints & Performance
# ------------------------------------------------------------------
__table_args__ = (
UniqueConstraint(
"user_id",
"paper_id",
name="uq_user_library_paper",
),
Index(
"idx_library_user_created",
"user_id",
"created_at",
),
)
def __repr__(self) -> str:
return f"<LibraryItem(user={self.user_id}, paper={self.paper_id})>"