Browse Source

move report messages to config and refine difficulty messaging

yemeishu 3 tuần trước cách đây
mục cha
commit
960dbcbbc4
2 tập tin đã thay đổi với 243 bổ sung70 xóa
  1. 100 70
      app/Services/ExamPdfExportService.php
  2. 143 0
      config/exam.php

+ 100 - 70
app/Services/ExamPdfExportService.php

@@ -818,14 +818,16 @@ class ExamPdfExportService
             }
         }
 
-        $explain = '暂无足够数据评估难度匹配。';
-        if ($matchStatus === '匹配') {
-            $explain = '本次题目整体难度与学案目标难度基本一致,结果可直接反映当前掌握水平。';
-        } elseif ($matchStatus === '偏难') {
-            $explain = '本次题目整体偏难,错误率偏高有客观因素,建议先补齐同模块中档题再冲高档。';
-        } elseif ($matchStatus === '偏易') {
-            $explain = '本次题目整体偏易,若得分高不代表上限已到,建议补充更高一档难度验证稳定性。';
-        }
+        $scoreRate = $this->resolveCurrentScoreRateForDifficultyInsight($templateData);
+        $scoreBand = $this->resolveScoreRateBandForDifficultyInsight($scoreRate);
+        $seed = sprintf(
+            '%s|%s|%s|%s',
+            (string) ($paper['id'] ?? ''),
+            (string) (($templateData['student'] ?? [])['id'] ?? ''),
+            $matchStatus,
+            $scoreBand
+        );
+        $explain = $this->buildDifficultyExplainByContext($matchStatus, $scoreBand, $seed);
 
         return [
             'target_category_raw' => $rawCategory,
@@ -838,10 +840,90 @@ class ExamPdfExportService
             'deviation' => $deviation !== null ? round($deviation, 4) : null,
             'status' => $matchStatus,
             'question_count' => count($difficulties),
+            'score_rate' => $scoreRate !== null ? round($scoreRate, 4) : null,
+            'score_band' => $scoreBand,
             'explain' => $explain,
         ];
     }
 
+    private function resolveCurrentScoreRateForDifficultyInsight(array $templateData): ?float
+    {
+        $rawOverall = (array) (($templateData['analysis_data'] ?? [])['overall_summary'] ?? []);
+        if (isset($rawOverall['score_rate']) && is_numeric($rawOverall['score_rate'])) {
+            $v = (float) $rawOverall['score_rate'];
+            if ($v >= 0.0 && $v <= 1.0) {
+                return $v;
+            }
+            if ($v > 1.0 && $v <= 100.0) {
+                return $v / 100.0;
+            }
+        }
+
+        $questions = $templateData['questions'] ?? [];
+        $total = 0.0;
+        $obtained = 0.0;
+        foreach ($questions as $q) {
+            $max = $q['score'] ?? $q['max_score'] ?? null;
+            $got = $q['score_obtained'] ?? null;
+            if (! is_numeric($max) || ! is_numeric($got)) {
+                continue;
+            }
+            $maxVal = (float) $max;
+            if ($maxVal <= 0) {
+                continue;
+            }
+            $gotVal = max(0.0, min((float) $got, $maxVal));
+            $total += $maxVal;
+            $obtained += $gotVal;
+        }
+
+        return $total > 0 ? ($obtained / $total) : null;
+    }
+
+    private function resolveScoreRateBandForDifficultyInsight(?float $scoreRate): string
+    {
+        if ($scoreRate === null) {
+            return 'unknown';
+        }
+        if ($scoreRate >= 0.8) {
+            return 'high';
+        }
+        if ($scoreRate >= 0.6) {
+            return 'mid';
+        }
+
+        return 'low';
+    }
+
+    private function buildDifficultyExplainByContext(string $matchStatus, string $scoreBand, string $seed): string
+    {
+        $messageMap = config('exam.analysis_report_v3.difficulty_explain_messages', []);
+        if (! is_array($messageMap) || empty($messageMap)) {
+            $messageMap = [
+                '暂无' => [
+                    'unknown' => [
+                        '暂无足够数据评估难度匹配。',
+                    ],
+                ],
+            ];
+        }
+
+        $statusMap = $messageMap[$matchStatus] ?? $messageMap['暂无'];
+        $candidates = $statusMap[$scoreBand] ?? $statusMap['unknown'] ?? $messageMap['暂无']['unknown'];
+
+        return $this->pickStableVariantMessage($candidates, $seed);
+    }
+
+    private function pickStableVariantMessage(array $messages, string $seed): string
+    {
+        if (empty($messages)) {
+            return '暂无足够数据评估难度匹配。';
+        }
+        $idx = abs(crc32($seed)) % count($messages);
+
+        return (string) $messages[$idx];
+    }
+
     private function buildV3ComparisonInsight(array $templateData, ?float $currentScoreRate, ?float $currentMastery): array
     {
         $paper = $templateData['paper'] ?? [];
@@ -1218,68 +1300,16 @@ class ExamPdfExportService
             default => 'E',
         };
 
-        $messagesByBucket = [
-            'A' => [
-                '这次开局很稳,说明你的基础和状态都在线。',
-                '第一次就拿到高分,后续保持节奏会更强。',
-                '你的学习方法是有效的,继续按这个路径推进。',
-                '这是一个很好的起点,接下来可以适度挑战难题。',
-                '成绩很亮眼,说明你已经具备较强的掌握能力。',
-                '你的投入有明显回报,继续保持就会持续领先。',
-                '开局高分值得肯定,下一步重点是稳定输出。',
-                '这次表现优秀,后续可以往“又快又准”再升级。',
-                '你已经在高水平区间,继续打磨细节会更出色。',
-                '这是非常有竞争力的起步,继续冲就对了。',
-            ],
-            'B' => [
-                '这个分数是很不错的起点,方向完全正确。',
-                '你已经进入良好区间,再补几处薄弱点就能上台阶。',
-                '开局表现可圈可点,继续练会更稳定。',
-                '说明你有扎实基础,后续提升空间也很清晰。',
-                '这次成绩不错,下一步就是把失分点逐个清掉。',
-                '起步良好,继续保持专注,进步会很快。',
-                '你已经具备不错的能力,差的是一点点细节打磨。',
-                '这个起点很健康,后续很有机会冲到更高档。',
-                '成绩说明你在正轨上,继续按计划推进就行。',
-                '这次发挥稳定,接下来把短板补齐会很明显。',
-            ],
-            'C' => [
-                '这是正常且可提升的起点,先稳住基础最关键。',
-                '你已经有一定掌握度,接下来重点是补薄弱模块。',
-                '这个分数段提升通常很快,方向对了就会涨。',
-                '开局在中位区间,不焦虑,持续练习就会突破。',
-                '先把常错题型吃透,你的分数会明显上来。',
-                '这次结果能帮我们精准定位问题,价值很大。',
-                '起点清晰、空间也清晰,后续提升可期待。',
-                '你的基础在,下一步要把稳定性做出来。',
-                '这个阶段最怕放弃,最值得坚持。',
-                '继续按节奏推进,很快就能看到上升曲线。',
-            ],
-            'D' => [
-                '第一次这个分数不代表上限,只代表当前起点。',
-                '现在最重要的是先建立信心,再逐步提分。',
-                '这次结果很有价值,能帮你更精准地补基础。',
-                '先把核心概念补牢,分数会先稳再升。',
-                '这个阶段提升潜力很大,方法对了进步会很快。',
-                '不用和别人比,先和昨天的自己比就很好。',
-                '先做对“会做的题”,再攻“有难度的题”。',
-                '你现在需要的是节奏和耐心,不是否定自己。',
-                '起步偏低很常见,持续练习就会逐渐反转。',
-                '只要不放弃,这个分段通常最容易拉开增幅。',
-            ],
-            'E' => [
-                '第一次分数偏低很正常,先把学习路径走顺。',
-                '这不是结论,只是起点,我们从基础一点点重建。',
-                '先把会做题做稳,信心会先回来。',
-                '现在最关键的是“稳基础、慢提速”。',
-                '低分并不定义能力,持续训练才会定义结果。',
-                '先把核心知识补齐,后续提升会很明显。',
-                '今天看到的是起点,不是终点。',
-                '你需要的是清晰步骤,不是压力。',
-                '每次进步一点点,累计起来会很惊人。',
-                '从现在开始,踏实走每一步,结果一定会变。',
-            ],
-        ];
+        $messagesByBucket = config('exam.analysis_report_v3.first_exam_messages_by_bucket', []);
+        if (! is_array($messagesByBucket) || empty($messagesByBucket)) {
+            $messagesByBucket = [
+                'A' => ['这次开局很稳,说明你的基础和状态都在线。'],
+                'B' => ['这个分数是很不错的起点,方向完全正确。'],
+                'C' => ['这是正常且可提升的起点,先稳住基础最关键。'],
+                'D' => ['第一次这个分数不代表上限,只代表当前起点。'],
+                'E' => ['第一次分数偏低很正常,先把学习路径走顺。'],
+            ];
+        }
 
         $messages = $messagesByBucket[$bucket] ?? $messagesByBucket['C'];
         $idx = random_int(0, count($messages) - 1);

+ 143 - 0
config/exam.php

@@ -90,4 +90,147 @@ return [
             'blank_is_wrong' => true,
         ],
     ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | 学情报告 V3 话术配置
+    |--------------------------------------------------------------------------
+    |
+    | difficulty_explain_messages: 难度说明话术池
+    | 第一层 key: 难度状态(匹配/偏难/偏易/暂无)
+    | 第二层 key: 得分率档位(high/mid/low/unknown)
+    |
+    */
+    'analysis_report_v3' => [
+        'first_exam_messages_by_bucket' => [
+            // 分档:A(90-100), B(75-89), C(60-74), D(40-59), E(0-39)
+            'A' => [
+                '这次开局很稳,说明你的基础和状态都在线。',
+                '第一次就拿到高分,后续保持节奏会更强。',
+                '你的学习方法是有效的,继续按这个路径推进。',
+                '这是一个很好的起点,接下来可以适度挑战难题。',
+                '成绩很亮眼,说明你已经具备较强的掌握能力。',
+                '你的投入有明显回报,继续保持就会持续领先。',
+                '开局高分值得肯定,下一步重点是稳定输出。',
+                '这次表现优秀,后续可以往“又快又准”再升级。',
+                '你已经在高水平区间,继续打磨细节会更出色。',
+                '这是非常有竞争力的起步,继续冲就对了。',
+            ],
+            'B' => [
+                '这个分数是很不错的起点,方向完全正确。',
+                '你已经进入良好区间,再补几处薄弱点就能上台阶。',
+                '开局表现可圈可点,继续练会更稳定。',
+                '说明你有扎实基础,后续提升空间也很清晰。',
+                '这次成绩不错,下一步就是把失分点逐个清掉。',
+                '起步良好,继续保持专注,进步会很快。',
+                '你已经具备不错的能力,差的是一点点细节打磨。',
+                '这个起点很健康,后续很有机会冲到更高档。',
+                '成绩说明你在正轨上,继续按计划推进就行。',
+                '这次发挥稳定,接下来把短板补齐会很明显。',
+            ],
+            'C' => [
+                '这是正常且可提升的起点,先稳住基础最关键。',
+                '你已经有一定掌握度,接下来重点是补薄弱模块。',
+                '这个分数段提升通常很快,方向对了就会涨。',
+                '开局在中位区间,不焦虑,持续练习就会突破。',
+                '先把常错题型吃透,你的分数会明显上来。',
+                '这次结果能帮我们精准定位问题,价值很大。',
+                '起点清晰、空间也清晰,后续提升可期待。',
+                '你的基础在,下一步要把稳定性做出来。',
+                '这个阶段最怕放弃,最值得坚持。',
+                '继续按节奏推进,很快就能看到上升曲线。',
+            ],
+            'D' => [
+                '第一次这个分数不代表上限,只代表当前起点。',
+                '现在最重要的是先建立信心,再逐步提分。',
+                '这次结果很有价值,能帮你更精准地补基础。',
+                '先把核心概念补牢,分数会先稳再升。',
+                '这个阶段提升潜力很大,方法对了进步会很快。',
+                '不用和别人比,先和昨天的自己比就很好。',
+                '先做对“会做的题”,再攻“有难度的题”。',
+                '你现在需要的是节奏和耐心,不是否定自己。',
+                '起步偏低很常见,持续练习就会逐渐反转。',
+                '只要不放弃,这个分段通常最容易拉开增幅。',
+            ],
+            'E' => [
+                '第一次分数偏低很正常,先把学习路径走顺。',
+                '这不是结论,只是起点,我们从基础一点点重建。',
+                '先把会做题做稳,信心会先回来。',
+                '现在最关键的是“稳基础、慢提速”。',
+                '低分并不定义能力,持续训练才会定义结果。',
+                '先把核心知识补齐,后续提升会很明显。',
+                '今天看到的是起点,不是终点。',
+                '你需要的是清晰步骤,不是压力。',
+                '每次进步一点点,累计起来会很惊人。',
+                '从现在开始,踏实走每一步,结果一定会变。',
+            ],
+        ],
+        'difficulty_explain_messages' => [
+            '匹配' => [
+                'high' => [
+                    '本次题目难度与目标基本一致,且得分表现优秀,说明当前掌握质量与稳定性都较好。',
+                    '本卷难度与学案目标贴合,你在该难度下保持了高得分,当前阶段学习效果较扎实。',
+                    '难度匹配且得分率高,结果可信度高,可作为当前能力水平的有效反映。',
+                ],
+                'mid' => [
+                    '本次题目难度与目标一致,当前得分处于可提升区间,建议围绕失分点做定向巩固。',
+                    '难度匹配,成绩能够真实反映现阶段水平;下一步重点是把薄弱题型转为稳定得分。',
+                    '在目标难度下表现中位,说明基础已建立,建议通过专题训练提升稳定性。',
+                ],
+                'low' => [
+                    '本次题目难度与目标一致,当前得分偏低主要反映掌握度不足,建议先补核心基础再提速。',
+                    '难度与目标基本匹配,分数偏低具有诊断价值,优先处理高频错因会更有效。',
+                    '在匹配难度下得分偏低,建议先稳基础模块,再逐步扩大题型覆盖面。',
+                ],
+                'unknown' => [
+                    '本次题目整体难度与学案目标基本一致,结果可直接反映当前掌握水平。',
+                ],
+            ],
+            '偏难' => [
+                'high' => [
+                    '本次题目整体偏难,但你仍保持了高得分,说明在高压难度下也具备较强解题稳定性。',
+                    '虽然本卷难度高于目标区间,你依然取得了优秀得分,体现出明显的能力上限优势。',
+                    '题目偏难且得分率仍高,当前阶段可适度增加高阶题比例,持续验证上限。',
+                ],
+                'mid' => [
+                    '本次题目偏难,当前得分处于中位区间属正常表现,建议先巩固中档再逐步冲高档。',
+                    '在高于目标的难度下取得当前成绩,说明基础可用;下一步可聚焦中高难过渡题。',
+                    '题目整体偏难,分数受客观难度影响,建议优先补齐同模块关键中档题。',
+                ],
+                'low' => [
+                    '本次题目整体偏难,低得分中包含客观难度因素,建议先回到目标难度做稳态提升。',
+                    '由于题目难度超出目标区间,当前分数偏低可理解,建议先补齐基础与中档能力。',
+                    '本卷偏难且得分偏低,建议先通过同模块中档题建立稳定正确率,再冲高难。',
+                ],
+                'unknown' => [
+                    '本次题目整体偏难,错误率偏高有客观因素,建议先补齐同模块中档题再冲高档。',
+                ],
+            ],
+            '偏易' => [
+                'high' => [
+                    '本次题目整体偏易且得分较高,说明基础掌握较稳,建议补充更高一档难度验证上限。',
+                    '在低于目标难度的试卷上保持高得分,建议增加中高难题比例做进一步校准。',
+                    '题目偏易时获得高分符合预期,下一步可通过更高难度题检验真实上限。',
+                ],
+                'mid' => [
+                    '本次题目偏易但得分仍有提升空间,建议先排查基础失分点并提高稳定正确率。',
+                    '在偏易试卷下表现中位,说明基础环节仍有波动,建议先做易中题稳定训练。',
+                    '难度低于目标但得分未显著拉开,建议优先修复粗心与步骤性失分。',
+                ],
+                'low' => [
+                    '本次题目整体偏易,得分偏低提示基础环节存在短板,建议先做基础题稳固训练。',
+                    '在偏易难度下分数仍偏低,建议优先回补核心概念与高频基础题型。',
+                    '题目偏易但得分不理想,需先解决基础正确率问题,再考虑提升难度。',
+                ],
+                'unknown' => [
+                    '本次题目整体偏易,若得分高不代表上限已到,建议补充更高一档难度验证稳定性。',
+                ],
+            ],
+            '暂无' => [
+                'unknown' => [
+                    '暂无足够数据评估难度匹配。',
+                ],
+            ],
+        ],
+    ],
 ];