""" 课程相关数据库模型 """ from enum import Enum from typing import List, Optional from datetime import datetime from sqlalchemy import ( String, Text, Integer, Boolean, ForeignKey, Enum as SQLEnum, Float, JSON, DateTime, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import BaseModel, SoftDeleteMixin, AuditMixin class CourseStatus(str, Enum): """课程状态枚举""" DRAFT = "draft" # 草稿 PUBLISHED = "published" # 已发布 ARCHIVED = "archived" # 已归档 class CourseCategory(str, Enum): """课程分类枚举""" TECHNOLOGY = "technology" # 技术 MANAGEMENT = "management" # 管理 BUSINESS = "business" # 业务 GENERAL = "general" # 通用 class Course(BaseModel, SoftDeleteMixin, AuditMixin): """ 课程表 """ __tablename__ = "courses" # 基本信息 name: Mapped[str] = mapped_column(String(200), nullable=False, comment="课程名称") description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="课程描述" ) category: Mapped[CourseCategory] = mapped_column( SQLEnum( CourseCategory, values_callable=lambda enum_cls: [e.value for e in enum_cls], validate_strings=True, ), default=CourseCategory.GENERAL, nullable=False, comment="课程分类", ) status: Mapped[CourseStatus] = mapped_column( SQLEnum( CourseStatus, values_callable=lambda enum_cls: [e.value for e in enum_cls], validate_strings=True, ), default=CourseStatus.DRAFT, nullable=False, comment="课程状态", ) # 课程详情 cover_image: Mapped[Optional[str]] = mapped_column( String(500), nullable=True, comment="封面图片URL" ) duration_hours: Mapped[Optional[float]] = mapped_column( Float, nullable=True, comment="课程时长(小时)" ) difficulty_level: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, comment="难度等级(1-5)" ) tags: Mapped[Optional[List[str]]] = mapped_column( JSON, nullable=True, comment="标签列表" ) # 发布信息 published_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="发布时间" ) publisher_id: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, comment="发布人ID" ) # 播课信息 # 播课功能(Coze工作流直接写数据库) broadcast_audio_url: Mapped[Optional[str]] = mapped_column( String(500), nullable=True, comment="播课音频URL" ) broadcast_generated_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="播课生成时间" ) # 排序和权重 sort_order: Mapped[int] = mapped_column( Integer, default=0, nullable=False, comment="排序顺序" ) is_featured: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, comment="是否推荐" ) # 统计信息 student_count: Mapped[int] = mapped_column( Integer, default=0, nullable=False, comment="学习人数" ) is_new: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, comment="是否新课程" ) # 资料下载设置 allow_download: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, comment="是否允许下载资料" ) # 关联关系 materials: Mapped[List["CourseMaterial"]] = relationship( "CourseMaterial", back_populates="course" ) knowledge_points: Mapped[List["KnowledgePoint"]] = relationship( "KnowledgePoint", back_populates="course" ) # 岗位分配关系(通过关联表) position_assignments = relationship("PositionCourse", back_populates="course", cascade="all, delete-orphan") exams = relationship("Exam", back_populates="course") questions = relationship("Question", back_populates="course") class CourseMaterial(BaseModel, SoftDeleteMixin, AuditMixin): """ 课程资料表 """ __tablename__ = "course_materials" course_id: Mapped[int] = mapped_column( Integer, ForeignKey("courses.id", ondelete="CASCADE"), nullable=False, comment="课程ID", ) name: Mapped[str] = mapped_column(String(200), nullable=False, comment="资料名称") description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="资料描述" ) file_url: Mapped[str] = mapped_column(String(500), nullable=False, comment="文件URL") file_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="文件类型") file_size: Mapped[int] = mapped_column(Integer, nullable=False, comment="文件大小(字节)") # 排序 sort_order: Mapped[int] = mapped_column( Integer, default=0, nullable=False, comment="排序顺序" ) # 关联关系 course: Mapped["Course"] = relationship("Course", back_populates="materials") # 关联的知识点(直接关联) knowledge_points: Mapped[List["KnowledgePoint"]] = relationship( "KnowledgePoint", back_populates="material" ) class KnowledgePoint(BaseModel, SoftDeleteMixin, AuditMixin): """ 知识点表 """ __tablename__ = "knowledge_points" course_id: Mapped[int] = mapped_column( Integer, ForeignKey("courses.id", ondelete="CASCADE"), nullable=False, comment="课程ID", ) material_id: Mapped[int] = mapped_column( Integer, ForeignKey("course_materials.id", ondelete="CASCADE"), nullable=False, comment="关联资料ID", ) name: Mapped[str] = mapped_column(String(200), nullable=False, comment="知识点名称") description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="知识点描述" ) type: Mapped[str] = mapped_column( String(50), default="概念定义", nullable=False, comment="知识点类型" ) source: Mapped[int] = mapped_column( Integer, default=0, nullable=False, comment="来源:0=手动,1=AI分析" ) topic_relation: Mapped[Optional[str]] = mapped_column( String(200), nullable=True, comment="与主题的关系描述" ) # 关联关系 course: Mapped["Course"] = relationship("Course", back_populates="knowledge_points") material: Mapped["CourseMaterial"] = relationship("CourseMaterial") class GrowthPath(BaseModel, SoftDeleteMixin): """ 成长路径表 """ __tablename__ = "growth_paths" name: Mapped[str] = mapped_column(String(200), nullable=False, comment="路径名称") description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="路径描述" ) target_role: Mapped[Optional[str]] = mapped_column( String(100), nullable=True, comment="目标角色" ) # 路径配置 courses: Mapped[Optional[List[dict]]] = mapped_column( JSON, nullable=True, comment="课程列表[{course_id, order, is_required}]" ) # 预计时长 estimated_duration_days: Mapped[Optional[int]] = mapped_column( Integer, nullable=True, comment="预计完成天数" ) # 状态 is_active: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, comment="是否启用" ) sort_order: Mapped[int] = mapped_column( Integer, default=0, nullable=False, comment="排序顺序" ) class MaterialKnowledgePoint(BaseModel, SoftDeleteMixin): """ 资料知识点关联表 """ __tablename__ = "material_knowledge_points" material_id: Mapped[int] = mapped_column( Integer, ForeignKey("course_materials.id", ondelete="CASCADE"), nullable=False, comment="资料ID", ) knowledge_point_id: Mapped[int] = mapped_column( Integer, ForeignKey("knowledge_points.id", ondelete="CASCADE"), nullable=False, comment="知识点ID", ) sort_order: Mapped[int] = mapped_column( Integer, default=0, nullable=False, comment="排序顺序" ) is_ai_generated: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, comment="是否AI生成" )