from datetime import datetime from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, JSON, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base from app.models.mixins import TimestampMixin BIGINT_PK = BigInteger().with_variant(Integer, "sqlite") class TrainingSession(TimestampMixin, Base): """训练会话表:保存一次训练的运行状态、Django 用户隔离信息和短期 memory key。""" __tablename__ = "training_session" id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="训练会话ID") session_code: Mapped[str] = mapped_column(String(64), nullable=False, unique=True, index=True, comment="会话编码") user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="Django用户中心ID") tenant_id: Mapped[str | None] = mapped_column(String(128), comment="租户或项目ID") class_id: Mapped[str | None] = mapped_column(String(128), comment="班级或课程ID") entry_scene: Mapped[str | None] = mapped_column(String(64), comment="入口场景") case_id: Mapped[int] = mapped_column(ForeignKey("case_base.id"), nullable=False, index=True, comment="病例ID") training_type: Mapped[str] = mapped_column("case_type", String(30), nullable=False, comment="病例/训练类型") mode: Mapped[str] = mapped_column("training_mode", String(50), nullable=False, index=True, comment="训练模式") score_type: Mapped[str] = mapped_column(String(20), default="percentage", comment="分数输出类型") status: Mapped[str] = mapped_column(String(30), default="created", index=True, comment="会话状态") started_at: Mapped[datetime | None] = mapped_column(DateTime, comment="开始时间") inquiry_completed_at: Mapped[datetime | None] = mapped_column(DateTime, comment="问诊完成时间") completed_at: Mapped[datetime | None] = mapped_column(DateTime, comment="完成时间") memory_key: Mapped[str | None] = mapped_column(String(128), comment="短期memory key") metadata_: Mapped[dict | None] = mapped_column("metadata", JSON, comment="扩展数据") case = relationship("CaseBase") orders = relationship("SessionOrder", back_populates="session", cascade="all, delete-orphan") submission = relationship("SessionSubmission", back_populates="session", uselist=False, cascade="all, delete-orphan") __table_args__ = {"comment": "训练会话表"} class SessionOrder(Base): """训练检查申请表:记录用户在一次训练中申请过的检查/检验项目和固定结果。""" __tablename__ = "training_order" id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="检查申请ID") session_id: Mapped[int] = mapped_column(ForeignKey("training_session.id"), nullable=False, index=True, comment="训练会话ID") user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="Django用户中心ID") case_id: Mapped[int] = mapped_column(ForeignKey("case_base.id"), nullable=False, index=True, comment="病例ID") case_exam_item_id: Mapped[int] = mapped_column("exam_item_id", ForeignKey("case_exam_item.id"), nullable=False, comment="检查项目ID") item_code: Mapped[str] = mapped_column(String(64), nullable=False, comment="项目编码") item_name: Mapped[str] = mapped_column(String(128), nullable=False, comment="项目名称") item_type: Mapped[str] = mapped_column(String(32), nullable=False, comment="项目类型") result_text: Mapped[str] = mapped_column(Text, nullable=False, comment="检查结果文本") result_structured: Mapped[dict | None] = mapped_column(JSON, comment="结构化检查结果") is_key: Mapped[bool] = mapped_column(default=False, comment="是否关键检查") is_abnormal: Mapped[bool] = mapped_column(default=False, comment="是否异常结果") ordered_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="申请时间") session = relationship("TrainingSession", back_populates="orders") case = relationship("CaseBase") exam_item = relationship("CaseExamItem") __table_args__ = ( UniqueConstraint("session_id", "item_code", name="uk_training_order_session_item"), {"comment": "训练检查申请表"}, ) class SessionSubmission(TimestampMixin, Base): """训练诊断治疗提交表:保存用户最终提交的诊断、治疗、沟通和随访内容。""" __tablename__ = "training_submission" id: Mapped[int] = mapped_column(BIGINT_PK, primary_key=True, autoincrement=True, comment="提交记录ID") session_id: Mapped[int] = mapped_column(ForeignKey("training_session.id"), nullable=False, unique=True, comment="训练会话ID") user_id: Mapped[str] = mapped_column("external_user_id", String(128), nullable=False, index=True, comment="Django用户中心ID") primary_diagnosis: Mapped[str | None] = mapped_column(Text, comment="主要诊断") differential_diagnoses: Mapped[list | None] = mapped_column(JSON, comment="鉴别诊断") diagnosis_basis: Mapped[str | None] = mapped_column(Text, comment="诊断依据") treatment_principle: Mapped[str | None] = mapped_column(Text, comment="治疗原则") treatment_measures: Mapped[str | None] = mapped_column(Text, comment="治疗措施") risk_plan: Mapped[str | None] = mapped_column(Text, comment="风险预案") communication: Mapped[str | None] = mapped_column(Text, comment="医患沟通") follow_up: Mapped[str | None] = mapped_column(Text, comment="随访安排") diagnosis_submitted_at: Mapped[datetime | None] = mapped_column(DateTime, comment="诊断提交时间") treatment_submitted_at: Mapped[datetime | None] = mapped_column(DateTime, comment="治疗提交时间") session = relationship("TrainingSession", back_populates="submission") __table_args__ = {"comment": "训练诊断治疗提交表"}