exam-analysis-report-flow-review-20260311.md 7.5 KB

学情分析报告生成逻辑深度分析(2026-03-11)

1. 分析目标与结论摘要

目标

  • 梳理“学情分析报告(PDF)”完整流程(入口、数据、算法、渲染、存储)。
  • 定位客户反馈问题:上一份学案 20% -> 本次 65%,变化值却显示 80%
  • 给出功能、体验、算法三个维度的可落地优化方案。

核心结论

  1. 当前系统的“变化值”在子知识点父知识点(层级掌握度)上存在口径不一致
  2. 父节点当前值来自“全量子节点平均”,但父节点变化值来自“本次考试命中的子节点平均变化”,导致可出现 当前65%,变化80% 这种反直觉结果。
  3. 报告中的“变化值”实际是绝对变化(百分点),UI 用 % 容易被理解成“相对增长率”,语义也不清晰。
  4. 存在多处“默认 previous=0”的兜底,会在数据缺失时放大变化值。

2. 端到端流程(当前实现)

2.1 入口与任务

  • API 入口:POST /api/exam-analysis/report
  • 代码:app/Http/Controllers/Api/ExamAnalysisApiController.php:24
  • 控制器调用:ExamAnalysisService::generateReport(...)
  • 注意:名义上是“异步任务”,但当前实现是同步执行模拟异步
    • app/Services/ExamAnalysisService.php:49

2.2 服务编排

  • ExamAnalysisService::processReportGeneration(...) 主要步骤:
    1. 组装分析数据 getAnalysisData
    2. 生成 PDF ExamPdfExportService::generateAnalysisReportPdf
    3. 保存 URL 到 student_reports 或 OCR 记录
  • 代码:app/Services/ExamAnalysisService.php:117

2.3 PDF 数据构建核心

  • 主入口:ExamPdfExportService::generateAnalysisReportPdf
  • 代码:app/Services/ExamPdfExportService.php:201
  • 内部调用:buildAnalysisData($paperId, $studentId)

buildAnalysisData 数据源优先级

  1. 试卷与学生基础信息(papers / students
  2. 分析结果(exam_analysis_results.analysis_data
  3. 掌握度数据(优先 analysis_data.knowledge_point_analysis,否则走 MasteryCalculator 概览)
  4. 快照表 knowledge_point_mastery_snapshots 用于补充 previous_mastery
  5. 题目列表(paper_questions + 题库详情)

关键代码:

  • app/Services/ExamPdfExportService.php:735-923
  • 报告模板:resources/views/exam-analysis/pdf-report.blade.php

3. “20% -> 65%,变化却80%”问题根因

3.1 现象复现逻辑

报告中“层级掌握度分析”显示的是父节点:

  • 当前掌握度:65%
  • 变化值:↑80%

用户直觉:如果上次是 20%,那这次 65%,变化应约 +45(百分点),而不是 +80

3.2 代码级根因(关键)

根因A:父节点“当前值”和“变化值”来自不同口径

  • 父节点当前掌握度来自:MasteryCalculator::getStudentMasteryOverviewWithHierarchy(全量子节点聚合)
    • app/Services/MasteryCalculator.php:555
  • 父节点变化值来自:只对“本次考试涉及子节点(relevantChildren)”计算平均变化
    • app/Services/ExamPdfExportService.php:815-834

也就是说:

  • mastery_level(parent) = 全兄弟子节点平均
  • mastery_change(parent) = 本次命中子节点平均变化

这两个分母不同,数值天然不可直接对比,导致“当前值不大、变化却很大”的感知错误。

根因B:变化值展示语义不清

模板展示逻辑:

  • changeText = number_format(abs($delta) * 100, 1) . '%'
  • resources/views/exam-analysis/pdf-report.blade.php:111-113, 151-153

这里把 delta(0~1) 直接乘100并显示 %,但未说明是“百分点变化”。 用户容易理解成“相对增幅%”,语义冲突。

根因C:缺失数据时默认 previous=0,放大变化

  • previous_mastery 缺失时被当成 0:
    • app/Services/ExamPdfExportService.php:791, 905

这会把本应“无对比基线”的记录显示成“大幅提升”。


4. 功能、体验、算法层面的改进建议

4.1 功能层(高优先)

  1. 统一父节点变化值口径(P0)
  2. 方案A(推荐):父节点变化值 = 当前父节点掌握度 - 上次父节点掌握度
  3. 即都按“父节点维度”计算,不再用“命中子节点均值变化”替代。
  4. 收益:杜绝 65% +80% 类错觉。

  5. 基线明确化(P0)

  6. 在报告中写明:

    • 对比基线:上一份已完成学案(时间:xxxx-xx-xx xx:xx)
    • 如果无基线:显示 首次分析,无历史对比
  7. 禁止 silently fallback 到 0。

  8. 无基线不显示变化(P1)

  9. previous_mastery 缺失时显示 --,并附 无可比历史数据

  10. 报告数据版本化(P1)

  11. exam_analysis_results 记录 metric_versionbaseline_snapshot_id

  12. 避免后续算法改动后旧报告口径不可追溯。

4.2 体验层(高优先)

  1. 文案改造(P0)
  2. 当前:↑ 80.0%
  3. 建议:↑ 45.0 个百分点(绝对变化)
  4. 若要展示相对增幅,另加一行:较上次提升 225%

  5. 在卡片中显示“三元组”

  6. 上次 20.0% → 本次 65.0%(+45.0pp)

  7. 直接消除认知歧义。

  8. 父节点卡片增加口径说明(P1)

  9. 标注:父节点掌握度=全部子知识点加权/平均

  10. 与“本次命中知识点变化”分开展示。

  11. 异常提示(P1)

  12. |delta_pp| > 60 且题量很少时,标记 样本偏小,变化波动较大

4.3 算法层(高优先)

  1. 父节点变化值重算(P0)
  2. 按父节点历史快照直接对比:
    • parent_delta = parent_current - parent_previous
  3. 不再用 relevantChildren 的平均变化替代。

  4. 子节点变化值稳健化(P1)

  5. 增加最小样本约束(如 attempts>=N)才展示变化。

  6. 否则显示“趋势待观察”。

  7. 变化值双口径并存(P2)

  8. delta_pp(百分点)

  9. delta_ratio(相对变化率)

  10. 前端默认展示 delta_pp,tooltip 展示 delta_ratio


5. 关键风险点清单

  1. 伪异步(同步执行)
  2. generateReport 目前同步执行耗时任务,峰值时可能拖慢接口。
  3. 代码:app/Services/ExamAnalysisService.php:49

  4. fallback 分支基线不稳定

  5. 当没有 knowledge_point_analysis 时,取“最新快照(不按 paper_id)”作为对比基线。

  6. 代码:app/Services/ExamPdfExportService.php:894-899

  7. 风险:跨学案串基线。

  8. previous 缺失默认0

  9. 可能制造虚高变化。

  10. 代码:app/Services/ExamPdfExportService.php:791, 905

  11. 父节点层级计算规则与展示说明不一致

  12. 代码注释写“所有兄弟节点历史数据”,实现却是 relevantChildren

  13. 代码:app/Services/ExamPdfExportService.php:818-833


6. 建议的落地优先级(两周内)

第一优先(本周)

  1. 修正父节点变化值口径统一(P0)
  2. 无基线不显示变化,不再默认 previous=0(P0)
  3. 报告文案改为“百分点变化”(P0)

第二优先(下周)

  1. 报告增加“对比基线时间/来源”(P1)
  2. 加样本量阈值与波动提示(P1)
  3. 异步化真正落地(队列任务)(P1)

7. 对你问题的直接回答

“是对比上一份学案吗?”

当前实现并不稳定地等价于“上一份学案”:

  • 有的路径用“当前分析快照内的 previous_mastery”(接近上一次状态);
  • 有的路径用“学生最新快照”(可能跨学案);
  • 父节点变化还混用了“本次命中子节点变化”。

所以你看到 20% -> 65%,却↑80%代码层面的口径混用问题,不是你的理解问题。