from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.database import Base # 导入任务/文件项统一状态 JOB_STATUS_QUEUED = "queued" JOB_STATUS_RUNNING = "running" JOB_STATUS_SUCCESS = "success" JOB_STATUS_FAILED = "failed" JOB_STATUS_CANCELLED = "cancelled" JOB_STATUS_RETRYING = "retrying" JOB_STATUSES = ( JOB_STATUS_QUEUED, JOB_STATUS_RUNNING, JOB_STATUS_SUCCESS, JOB_STATUS_FAILED, JOB_STATUS_CANCELLED, JOB_STATUS_RETRYING, ) class Question(Base): __tablename__ = "questions" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) chapter: Mapped[str] = mapped_column(Text, default="") primary_knowledge: Mapped[str] = mapped_column(Text, default="") secondary_knowledge: Mapped[str] = mapped_column(Text, default="") question_type: Mapped[str] = mapped_column(Text, default="") difficulty: Mapped[str] = mapped_column(Text, default="") stem: Mapped[str] = mapped_column(Text) option_a: Mapped[str] = mapped_column(Text, default="") option_b: Mapped[str] = mapped_column(Text, default="") option_c: Mapped[str] = mapped_column(Text, default="") option_d: Mapped[str] = mapped_column(Text, default="") answer: Mapped[str] = mapped_column(Text, default="") explanation: Mapped[str] = mapped_column(Text, default="") notes: Mapped[str] = mapped_column(Text, default="") source_file: Mapped[str] = mapped_column(Text, default="") created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.utcnow, onupdate=datetime.utcnow ) class Category(Base): __tablename__ = "categories" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(Text, nullable=False) parent_id: Mapped[int | None] = mapped_column(ForeignKey("categories.id"), nullable=True) level: Mapped[int] = mapped_column(Integer, default=1) children: Mapped[list["Category"]] = relationship( "Category", backref="parent", remote_side=[id], lazy="joined" ) class ImportHistory(Base): __tablename__ = "import_history" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) filename: Mapped[str] = mapped_column(Text, default="") method: Mapped[str] = mapped_column(Text, default="manual") question_count: Mapped[int] = mapped_column(Integer, default=0) status: Mapped[str] = mapped_column(Text, default="success") created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) class ImportJob(Base): """导入任务(持久化队列项)。""" __tablename__ = "import_jobs" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) status: Mapped[str] = mapped_column(String(32), default=JOB_STATUS_QUEUED, index=True) method: Mapped[str] = mapped_column(String(32), nullable=False) total: Mapped[int] = mapped_column(Integer, default=0) processed: Mapped[int] = mapped_column(Integer, default=0) success_count: Mapped[int] = mapped_column(Integer, default=0) failed_count: Mapped[int] = mapped_column(Integer, default=0) current_index: Mapped[int] = mapped_column(Integer, default=0) current_file: Mapped[str] = mapped_column(Text, default="") error: Mapped[str] = mapped_column(Text, default="") attempt: Mapped[int] = mapped_column(Integer, default=1) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) updated_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.utcnow, onupdate=datetime.utcnow ) items: Mapped[list["ImportJobItem"]] = relationship( "ImportJobItem", back_populates="job", order_by="ImportJobItem.seq", lazy="selectin" ) class ImportJobItem(Base): """导入任务内的单个文件项。""" __tablename__ = "import_job_items" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) job_id: Mapped[int] = mapped_column(ForeignKey("import_jobs.id", ondelete="CASCADE"), nullable=False) seq: Mapped[int] = mapped_column(Integer, nullable=False) filename: Mapped[str] = mapped_column(Text, default="") stored_path: Mapped[str] = mapped_column(Text, default="") status: Mapped[str] = mapped_column(String(32), default=JOB_STATUS_QUEUED) attempt: Mapped[int] = mapped_column(Integer, default=1) error: Mapped[str] = mapped_column(Text, default="") question_count: Mapped[int] = mapped_column(Integer, default=0) started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) job: Mapped["ImportJob"] = relationship("ImportJob", back_populates="items")