""" 成长路径相关数据库模型 """ from enum import Enum from typing import List, Optional, TYPE_CHECKING from datetime import datetime from decimal import Decimal if TYPE_CHECKING: from app.models.course import Course from app.models.user import User from sqlalchemy import ( String, Text, Integer, Boolean, ForeignKey, Enum as SQLEnum, JSON, DateTime, DECIMAL, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import BaseModel, SoftDeleteMixin class GrowthPathStatus(str, Enum): """成长路径学习状态""" NOT_STARTED = "not_started" # 未开始 IN_PROGRESS = "in_progress" # 进行中 COMPLETED = "completed" # 已完成 class NodeStatus(str, Enum): """节点状态""" LOCKED = "locked" # 锁定(前置未完成) UNLOCKED = "unlocked" # 已解锁(可以开始) IN_PROGRESS = "in_progress" # 学习中 COMPLETED = "completed" # 已完成 class GrowthPathNode(BaseModel, SoftDeleteMixin): """ 成长路径节点表 每个节点对应一门课程 """ __tablename__ = "growth_path_nodes" # 关联 growth_path_id: Mapped[int] = mapped_column( Integer, ForeignKey("growth_paths.id", ondelete="CASCADE"), nullable=False, comment="成长路径ID" ) course_id: Mapped[int] = mapped_column( Integer, ForeignKey("courses.id", ondelete="CASCADE"), nullable=False, comment="课程ID" ) # 节点信息 stage_name: Mapped[Optional[str]] = mapped_column( String(100), nullable=True, comment="所属阶段名称" ) title: Mapped[str] = mapped_column( String(200), nullable=False, comment="节点标题" ) description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="节点描述" ) # 配置 order_num: Mapped[int] = mapped_column( Integer, default=0, nullable=False, comment="排序顺序" ) is_required: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, comment="是否必修" ) prerequisites: Mapped[Optional[List[int]]] = mapped_column( JSON, nullable=True, comment="前置节点IDs" ) estimated_days: Mapped[int] = mapped_column( Integer, default=7, nullable=False, comment="预计学习天数" ) # 画布位置(用于可视化编辑器) position_x: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, default=0, comment="画布X坐标" ) position_y: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, default=0, comment="画布Y坐标" ) # 关联关系 growth_path: Mapped["GrowthPath"] = relationship( # noqa: F821 "GrowthPath", back_populates="nodes" ) course: Mapped["Course"] = relationship("Course") user_completions: Mapped[List["UserNodeCompletion"]] = relationship( "UserNodeCompletion", back_populates="node" ) class UserGrowthPathProgress(BaseModel): """ 用户成长路径进度表 记录用户在某条成长路径上的整体进度 """ __tablename__ = "user_growth_path_progress" # 关联 user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, comment="用户ID" ) growth_path_id: Mapped[int] = mapped_column( Integer, ForeignKey("growth_paths.id", ondelete="CASCADE"), nullable=False, comment="成长路径ID" ) # 进度信息 current_node_id: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, comment="当前学习节点ID" ) completed_node_ids: Mapped[Optional[List[int]]] = mapped_column( JSON, nullable=True, comment="已完成节点IDs" ) total_progress: Mapped[Decimal] = mapped_column( DECIMAL(5, 2), default=0.00, nullable=False, comment="总进度百分比" ) # 状态 status: Mapped[str] = mapped_column( String(20), default=GrowthPathStatus.NOT_STARTED.value, nullable=False, comment="状态" ) # 时间记录 started_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="开始时间" ) completed_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="完成时间" ) last_activity_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="最后活动时间" ) # 关联关系 user: Mapped["User"] = relationship("User") growth_path: Mapped["GrowthPath"] = relationship("GrowthPath") # noqa: F821 class UserNodeCompletion(BaseModel): """ 用户节点完成记录表 详细记录用户在每个节点上的学习状态 """ __tablename__ = "user_node_completions" # 关联 user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, comment="用户ID" ) growth_path_id: Mapped[int] = mapped_column( Integer, ForeignKey("growth_paths.id", ondelete="CASCADE"), nullable=False, comment="成长路径ID" ) node_id: Mapped[int] = mapped_column( Integer, ForeignKey("growth_path_nodes.id", ondelete="CASCADE"), nullable=False, comment="节点ID" ) # 进度信息 course_progress: Mapped[Decimal] = mapped_column( DECIMAL(5, 2), default=0.00, nullable=False, comment="课程学习进度" ) status: Mapped[str] = mapped_column( String(20), default=NodeStatus.LOCKED.value, nullable=False, comment="状态" ) # 时间记录 unlocked_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="解锁时间" ) started_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="开始学习时间" ) completed_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="完成时间" ) # 关联关系 user: Mapped["User"] = relationship("User") node: Mapped["GrowthPathNode"] = relationship( "GrowthPathNode", back_populates="user_completions" ) growth_path: Mapped["GrowthPath"] = relationship("GrowthPath") # noqa: F821