Spaces:
Running
Running
| """ | |
| GraphoLab Backend — Project and Analysis ORM models. | |
| A Project (= "Perizia") groups one or more Documents and their Analyses. | |
| """ | |
| from __future__ import annotations | |
| import enum | |
| from datetime import datetime | |
| from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text, func | |
| from sqlalchemy.orm import Mapped, mapped_column, relationship | |
| from backend.database import Base | |
| class ProjectStatus(str, enum.Enum): | |
| draft = "draft" | |
| in_progress = "in_progress" | |
| completed = "completed" | |
| archived = "archived" | |
| class AnalysisType(str, enum.Enum): | |
| htr = "htr" | |
| signature_detection = "signature_detection" | |
| signature_verification = "signature_verification" | |
| ner = "ner" | |
| writer_identification = "writer_identification" | |
| graphology = "graphology" | |
| pipeline = "pipeline" | |
| dating = "dating" | |
| rag = "rag" | |
| class Project(Base): | |
| __tablename__ = "projects" | |
| id: Mapped[int] = mapped_column(primary_key=True, index=True) | |
| title: Mapped[str] = mapped_column(String(256), nullable=False) | |
| description: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| status: Mapped[ProjectStatus] = mapped_column( | |
| Enum(ProjectStatus), default=ProjectStatus.draft, nullable=False | |
| ) | |
| owner_id: Mapped[int] = mapped_column( | |
| ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| owner: Mapped["User"] = relationship("User", back_populates="projects") | |
| 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() | |
| ) | |
| documents: Mapped[list[Document]] = relationship( | |
| "Document", back_populates="project", cascade="all, delete-orphan" | |
| ) | |
| analyses: Mapped[list[Analysis]] = relationship( | |
| "Analysis", back_populates="project", cascade="all, delete-orphan" | |
| ) | |
| agent_chats: Mapped[list[AgentChat]] = relationship( | |
| "AgentChat", back_populates="project", cascade="all, delete-orphan" | |
| ) | |
| class Document(Base): | |
| """A file uploaded to a project (stored in MinIO).""" | |
| __tablename__ = "documents" | |
| id: Mapped[int] = mapped_column(primary_key=True, index=True) | |
| filename: Mapped[str] = mapped_column(String(512), nullable=False) | |
| content_type: Mapped[str] = mapped_column(String(128), nullable=False) | |
| storage_key: Mapped[str] = mapped_column(String(512), nullable=False) # MinIO object key | |
| size_bytes: Mapped[int] = mapped_column(Integer, default=0) | |
| project_id: Mapped[int] = mapped_column( | |
| ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| project: Mapped[Project] = relationship("Project", back_populates="documents") | |
| uploaded_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now() | |
| ) | |
| class Analysis(Base): | |
| """Result of one AI analysis step on a project document.""" | |
| __tablename__ = "analyses" | |
| id: Mapped[int] = mapped_column(primary_key=True, index=True) | |
| analysis_type: Mapped[AnalysisType] = mapped_column(Enum(AnalysisType), nullable=False) | |
| result_text: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| result_storage_key: Mapped[str | None] = mapped_column( | |
| String(512), nullable=True # MinIO key for image/PDF result | |
| ) | |
| document_id: Mapped[int | None] = mapped_column( | |
| ForeignKey("documents.id", ondelete="SET NULL"), nullable=True | |
| ) | |
| project_id: Mapped[int] = mapped_column( | |
| ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| project: Mapped[Project] = relationship("Project", back_populates="analyses") | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now() | |
| ) | |
| class AgentChat(Base): | |
| """A persistent chat session inside an agent project.""" | |
| __tablename__ = "agent_chats" | |
| id: Mapped[int] = mapped_column(primary_key=True, index=True) | |
| title: Mapped[str] = mapped_column(String(256), nullable=False, default="Nuova chat") | |
| project_id: Mapped[int] = mapped_column( | |
| ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| project: Mapped[Project] = relationship("Project", back_populates="agent_chats") | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now() | |
| ) | |
| messages: Mapped[list[AgentMessage]] = relationship( | |
| "AgentMessage", back_populates="chat", cascade="all, delete-orphan", | |
| order_by="AgentMessage.created_at" | |
| ) | |
| class AgentMessage(Base): | |
| """A single message in an AgentChat.""" | |
| __tablename__ = "agent_messages" | |
| id: Mapped[int] = mapped_column(primary_key=True, index=True) | |
| chat_id: Mapped[int] = mapped_column( | |
| ForeignKey("agent_chats.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| chat: Mapped[AgentChat] = relationship("AgentChat", back_populates="messages") | |
| role: Mapped[str] = mapped_column(String(16), nullable=False) # "user" | "assistant" | |
| content: Mapped[str] = mapped_column(Text, nullable=False) | |
| # JSON-encoded list of Document.id attached by the user (user messages only) | |
| file_ids: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now() | |
| ) | |