from __future__ import annotations from datetime import datetime from typing import TYPE_CHECKING from sqlalchemy import DateTime, ForeignKey, Integer, JSON, String, Text, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base from app.utils.ids import prefixed_id if TYPE_CHECKING: from app.models.document_chunk import DocumentChunk class Document(Base): __tablename__ = "documents" id: Mapped[str] = mapped_column( String(40), primary_key=True, default=lambda: prefixed_id("doc"), ) user_id: Mapped[str] = mapped_column( String(40), ForeignKey("users.id"), index=True, nullable=False, ) title: Mapped[str] = mapped_column(String(255), nullable=False) file_name: Mapped[str] = mapped_column(String(255), nullable=False) file_type: Mapped[str] = mapped_column(String(80), nullable=False) file_path: Mapped[str] = mapped_column(String(500), nullable=False) subject: Mapped[str | None] = mapped_column(String(120), nullable=True) chapter: Mapped[str | None] = mapped_column(String(160), nullable=True) syllabus: Mapped[str | None] = mapped_column(String(120), nullable=True) status: Mapped[str] = mapped_column(String(30), default="uploaded", nullable=False) extracted_text: Mapped[str | None] = mapped_column(Text, nullable=True) extraction_error: Mapped[str | None] = mapped_column(Text, nullable=True) chunk_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) source_type: Mapped[str] = mapped_column( String(40), default="pdf", nullable=False, ) # Semantic material type set by the source classifier on upload. # Possible values: notes | chapter | textbook | question_paper | # handwritten_notes | syllabus | resume_or_personal_doc | unknown material_type: Mapped[str] = mapped_column( String(40), default="unknown", nullable=False, ) education_extraction_status: Mapped[str] = mapped_column( String(30), default="uploaded", nullable=False, ) education_extraction_error: Mapped[str | None] = mapped_column(Text, nullable=True) education_warnings_json: Mapped[list[str]] = mapped_column( JSON, default=list, nullable=False, ) syllabus_items_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) pyq_questions_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) pyq_years_json: Mapped[list[int]] = mapped_column(JSON, default=list, nullable=False) extracted_text_length: Mapped[int | None] = mapped_column(Integer, nullable=True) processing_started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) processing_completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False, ) chunks: Mapped[list["DocumentChunk"]] = relationship( "DocumentChunk", back_populates="document", cascade="all, delete-orphan", order_by="DocumentChunk.chunk_index", )