2026-03-05 11:50:15 +08:00
|
|
|
from datetime import datetime
|
|
|
|
|
|
2026-03-06 15:52:34 +08:00
|
|
|
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text
|
2026-03-05 11:50:15 +08:00
|
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
|
|
|
|
|
|
from backend.database import Base
|
|
|
|
|
|
|
|
|
|
|
2026-03-06 15:52:34 +08:00
|
|
|
# 导入任务/文件项统一状态
|
|
|
|
|
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,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-03-05 11:50:15 +08:00
|
|
|
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)
|
2026-03-06 15:52:34 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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")
|