# ============================================================================= # 企微IT智能服务台 — 答题与积分模型 # ============================================================================= # 说明:包含3张表,支撑排队等待期间的答题+积分系统: # 1. quiz_questions: IT知识题库(7类×10题=70题起步) # 2. quiz_answers: 答题记录(每次答题一条记录) # 3. employee_points: 员工积分账户(跨会话累积,5级等级体系) # # 答题双模式: # 模式A(info_locked=false)— 诊断题:与当前问题相关的选择题,答案附加到会话上下文 # 模式B(info_locked=true) — IT知识题:纯教育性质,提升IT素养 # # 插队规则: # queue_priority = min(quiz_answered_count // 3, 2) # 每答3题前移1位,上限2位 # 积分规则: # 答对 +10分,答错不扣分,跨会话累积 # 0-99 IT小白 → 100-299 IT入门 → 300-599 IT达人 → 600-999 IT专家 → 1000+ IT大师 # ============================================================================= import uuid from datetime import datetime from typing import Any, Dict, List, Optional from sqlalchemy import Boolean, DateTime, Index, Integer, JSON, String, Text from sqlalchemy.orm import Mapped, mapped_column from app.database import Base class QuizQuestion(Base): """IT知识题库 — 排队等待期间向员工推送的选择题。 分为两类(通过 type 字段区分): - knowledge: IT知识题(模式B,info_locked=true时推送) - diagnostic: 诊断题(模式A,info_locked=false时推送,答案附加到会话上下文) 诊断题按 problem_category 组织,每类3-5题。 知识题按 category 组织,每类10题。 Attributes: id: 题目唯一标识(UUID) type: 题目类型(knowledge=IT知识题 / diagnostic=诊断题) category: 题目类别(network/vpn/email/system/printer/security/office) problem_category: 诊断题对应的问题类别(仅diagnostic类型有效,如"vpn_disconnect") difficulty: 难度(easy/medium/hard) question: 题目文本 options: 选项数组(JSON,["选项A", "选项B", "选项C", "选项D"]) correct_index: 正确答案索引(0-3) explanation: 答案解析 is_active: 是否启用 created_at: 创建时间 """ __tablename__ = "quiz_questions" id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()) ) type: Mapped[str] = mapped_column( String(20), nullable=False, default="knowledge", comment="题目类型: knowledge(IT知识题) / diagnostic(诊断题)" ) category: Mapped[str] = mapped_column( String(50), nullable=False, comment="题目类别: network/vpn/email/system/printer/security/office" ) problem_category: Mapped[Optional[str]] = mapped_column( String(100), nullable=True, comment="诊断题对应的问题类别(仅diagnostic类型有效)" ) difficulty: Mapped[str] = mapped_column( String(20), nullable=False, default="medium", comment="难度: easy/medium/hard" ) question: Mapped[str] = mapped_column( Text, nullable=False, comment="题目文本" ) options: Mapped[list] = mapped_column( JSON, nullable=False, comment="选项数组: ['选项A', '选项B', ...]" ) correct_index: Mapped[int] = mapped_column( Integer, nullable=False, comment="正确答案索引(0-based)" ) explanation: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="答案解析" ) is_active: Mapped[bool] = mapped_column( Boolean, nullable=False, default=True, comment="是否启用" ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=datetime.now, comment="创建时间" ) __table_args__ = ( Index("idx_quiz_q_type", "type"), Index("idx_quiz_q_category", "category"), Index("idx_quiz_q_active", "is_active"), ) def __repr__(self) -> str: return f"" class QuizAnswer(Base): """答题记录 — 每次答题一条记录。 Attributes: id: 记录ID employee_id: 员工ID conversation_id: 关联会话ID(可空,非排队时答题无会话) question_id: 题目ID selected_index: 员工选择的答案索引 is_correct: 是否答对 points_earned: 获得积分(答对=10,答错=0) created_at: 答题时间 """ __tablename__ = "quiz_answers" id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()) ) employee_id: Mapped[str] = mapped_column( String(64), nullable=False, comment="员工ID" ) conversation_id: Mapped[Optional[str]] = mapped_column( String(36), nullable=True, comment="关联会话ID" ) question_id: Mapped[str] = mapped_column( String(36), nullable=False, comment="题目ID" ) selected_index: Mapped[int] = mapped_column( Integer, nullable=False, comment="选择的答案索引" ) is_correct: Mapped[bool] = mapped_column( Boolean, nullable=False, comment="是否答对" ) points_earned: Mapped[int] = mapped_column( Integer, nullable=False, default=0, comment="获得积分" ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=datetime.now, comment="答题时间" ) __table_args__ = ( Index("idx_quiz_a_employee", "employee_id"), Index("idx_quiz_a_conversation", "conversation_id"), Index("idx_quiz_a_created", "created_at"), ) def __repr__(self) -> str: return f"" class EmployeePoints(Base): """员工积分账户 — 跨会话累积,5级等级体系。 积分规则:答对一题 +10分,答错不扣分。 等级体系: 0-99 IT小白 (灰色) 100-299 IT入门 (蓝色) 300-599 IT达人 (绿色) 600-999 IT专家 (琥珀) 1000+ IT大师 (珊瑚红) Attributes: employee_id: 员工ID(主键) total_points: 累计积分 answered_count: 答题总数 correct_count: 答对总数 level: 当前等级名称 updated_at: 最后更新时间 """ __tablename__ = "employee_points" employee_id: Mapped[str] = mapped_column( String(64), primary_key=True, comment="员工ID" ) total_points: Mapped[int] = mapped_column( Integer, nullable=False, default=0, comment="累计积分" ) answered_count: Mapped[int] = mapped_column( Integer, nullable=False, default=0, comment="答题总数" ) correct_count: Mapped[int] = mapped_column( Integer, nullable=False, default=0, comment="答对总数" ) level: Mapped[str] = mapped_column( String(20), nullable=False, default="IT小白", comment="当前等级" ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now, comment="最后更新时间" ) def __repr__(self) -> str: return f"" @staticmethod def calculate_level(points: int) -> str: """根据积分计算等级名称。 Args: points: 当前累计积分 Returns: 等级名称字符串 """ if points >= 1000: return "IT大师" elif points >= 600: return "IT专家" elif points >= 300: return "IT达人" elif points >= 100: return "IT入门" else: return "IT小白"