组卷验证案例与流程说明.md 7.7 KB

组卷验证案例与流程说明

一、验证案例:教材组卷(assemble_type=3)

1.1 前置条件

  • 存在学生、教师、教材及章节数据
  • 学生已有作答历史(用于验证「排除已做题目」生效)
  • 选中章节的题目数量少于 total_questions(用于验证「智能补充」生效)

1.2 请求示例

接口POST /api/intelligent-exams

请求体(JSON)- 以七年级下册为例:

{
  "student_id": "1764913638",
  "teacher_id": "1764913637",
  "student_name": "张三",
  "teacher_name": "李老师",
  "grade": 7,
  "assemble_type": 3,
  "series_id": 1,
  "semester_code": 2,
  "chapter_id_list": [244, 248],
  "total_questions": 20,
  "difficulty_category": 1,
  "paper_name": "七年级下册第1-2章测试"
}

说明semester_code: 2 表示下学期;chapter_id_list: [244, 248] 为教材 textbook_id=2(七年级下册)的前 2 个章节节点 ID。

参数说明

参数 必填 说明
student_id 学生ID
teacher_id 教师ID
student_name 学生姓名
teacher_name 教师姓名
grade 年级(如 7=初一)
assemble_type 3=教材组卷,默认为 4(通用)
series_id 教材组卷必填 教材系列ID
semester_code 教材组卷必填 1=上册,2=下册(当前学期示例用 2)
chapter_id_list 教材组卷推荐 章节节点ID 列表;不传则自动选该教材下有题目的章节
total_questions 题目数量,默认 20
difficulty_category 难度类别 1-4
paper_name 试卷名称

说明:API 通过 series_id + semester_code + grade 解析出 textbook_id。内部调用(如 Filament 页面)可直接传 textbook_id

1.3 预期行为

  1. 不会出现学生已做过的题目(去重生效)
  2. 选中章节题目不足时,会从同教材前章节补充(未学章节不补)
  3. 日志中出现 getSupplementaryQuestionsForGrade排除学生已做过的题目限制为前章节 等字样

二、流程说明:从请求到生效的每一步

Step 0:入口

POST /api/intelligent-exams
  → IntelligentExamController::store()
  → 校验参数,resolveTextbookId(若用 series_id)
  → $params 传入 LearningAnalyticsService::generateIntelligentExam()

Step 1:组卷类型策略(ExamTypeStrategy)

generateIntelligentExam($params)
  → $params['assemble_type'] = 3(教材组卷)
  → ExamTypeStrategy::buildParams($params, 3)
  → buildTextbookAssembleParams($params)

buildTextbookAssembleParams 主要逻辑

  1. 解析 chapter_id_list,若无则从教材下自动选有题目的章节
  2. 根据章节获取 kp_codes(知识点)
  3. 排除已做题目getStudentAnsweredQuestionIds($studentId, $kpCodes)
    • paper_questions 查学生试卷
    • question_bank_id(对应 questions.id
    • 作为 exclude_question_ids 写入 params
  4. 补全 grade:若 grade 缺失,从 textbooks 表按 textbook_id 推断
  5. 输出增强后参数:kp_codestextbook_catalog_node_idsexclude_question_idsgradetextbook_id

Step 2:智能出卷主体(LearningAnalyticsService)

generateIntelligentExam 继续
  → 获取 kp_codes、exclude_question_ids 等
  → 若有错题优先获取错题
  → getQuestionsFromBank($kpCodes, ..., $excludeQuestionIds, $textbookCatalogNodeIds, $grade, $textbookId, ...)

Step 3:题库选题(getQuestionsFromBank)

  1. 主查询

    • kp_codesgrade、难度等筛选
    • whereNotIn('id', $excludeQuestionIds),排除已做题目
    • 若配置了 textbook_catalog_node_ids,再按章节节点筛选
  2. 检查题目是否足够

    • count($selectedQuestions) < $totalNeeded$grade !== null
    • 调用智能补充
  3. 智能补充

    getSupplementaryQuestionsForGrade(
       $grade,
       array_column($selectedQuestions, 'kp_code'),  // 排除已选知识点
       $deficit,
       $difficultyCategory,
       $textbookId,
       $excludeQuestionIds,           // ← 排除已做题目
       $textbookCatalogNodeIds        // ← 用于前章节限制
    )
    

Step 4:智能补充(getSupplementaryQuestionsForGrade)

  1. 排除已做题目

    if (!empty($excludeQuestionIds)) {
       $query->whereNotIn('id', $excludeQuestionIds);
    }
    
    • 补充题同样不会包含学生已做过的题
  2. 同年级同教材

    • getGradeKnowledgePoints($grade, $textbookId) 获取该教材知识点
    • whereIn('kp_code', $gradeKpCodes)
  3. 仅从前章节补充(未学章节不补)

    • 若同时传入 $textbookId$textbookCatalogNodeIds
    • 调用 getEarlierChapterNodeIds($textbookId, $textbookCatalogNodeIds)
    • 取选中章节的 max(sort_order),得到「前章节」节点 ID
    • whereIn('textbook_catalog_nodes_id', $allowedNodeIds)
    • 只从当前章节及之前章节补充,不包含后续未学章节
  4. 按难度、题型等筛选后返回补充题目

Step 5:筛选与难度分布

getQuestionsFromBank 返回
  → selectQuestionsByMastery():按掌握度、题型配比筛选
  → applyTypeAwareDifficultyDistribution():题型感知难度分布
  → 返回最终题目列表

Step 6:持久化与返回

  • 创建 Paper、PaperQuestion
  • 生成 PDF、判卷链接等
  • 返回任务 ID 或试卷信息

三、生效点汇总

能力 生效位置 实现方式
排除已做题目 getStudentAnsweredQuestionIds paper_questionsquestion_bank_id,写入 exclude_question_ids
主选题排除 getQuestionsFromBank 主查询 whereNotIn('id', $excludeQuestionIds)
补充时排除 getSupplementaryQuestionsForGrade 传入 excludeQuestionIdswhereNotIn('id', $excludeQuestionIds)
grade 推断 buildTextbookAssembleParams 无 grade 时从 textbookstextbook_id 查 grade
前章节限制 getEarlierChapterNodeIds max(sort_order) 取前章节节点,whereIn('textbook_catalog_nodes_id', ...)

四、如何验证

4.1 日志验证

storage/logs/laravel.log 中可看到类似日志:

ExamTypeStrategy: 教材组卷从教材推断 grade
ExamTypeStrategy: 获取学生已答题目 ... answered_count: N
getQuestionsFromBank: 指定知识点题目不足,尝试智能补充
getSupplementaryQuestionsForGrade: 开始智能补充 ... exclude_count: N, has_chapter_scope: true
getSupplementaryQuestionsForGrade: 应用排除筛选 (或 排除学生已做过的题目)
getSupplementaryQuestionsForGrade: 限制为前章节 ... allowed_node_count: M
getQuestionsFromBank: 智能补充完成 ... supplementary_count: K

4.2 数据验证

  1. 排除已做:对比 paper_questions 中该学生的 question_bank_id,与本次组卷题目 ID,应无交集。
  2. 前章节:补充题目对应的 textbook_catalog_nodes_id,其 sort_order 应 ≤ 选中章节的最大 sort_order

4.3 curl 快速测试

curl -X POST 'http://localhost/api/intelligent-exams' \
  -H 'Content-Type: application/json' \
  -d '{
    "student_id": "1764913638",
    "teacher_id": "1764913637",
    "student_name": "张三",
    "teacher_name": "李老师",
    "grade": 7,
    "assemble_type": 3,
    "series_id": 1,
    "semester_code": 2,
    "chapter_id_list": [244, 248],
    "total_questions": 20,
    "paper_name": "七年级下册教材组卷验证测试"
  }'

student_idteacher_id 替换为环境中的真实数据。series_id=1, semester_code=2, grade=7 对应 七年级下册(textbook_id=2)。