|
|
@@ -794,38 +794,22 @@ class ExamAnswerAnalysisService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 【公司要求】计算并更新父节点掌握度(支持多级递归)
|
|
|
- *
|
|
|
- * 公司要求:
|
|
|
- * 1. 查询结构:通过 knowledge_points 表获取知识点的层级关系(parent_kp_code)
|
|
|
- * 2. 平均值计算:如果一个知识点是父节点,它的掌握度不再从数据库直接读,
|
|
|
- * 而是实时计算其下所有子节点掌握度的算术平均数
|
|
|
- * 3. 多级递归:支持多级结构,能够从最底层逐级向上求平均
|
|
|
- *
|
|
|
- * 递归计算流程:
|
|
|
- * - 从最底层节点开始,逐级向上递归计算父节点掌握度
|
|
|
- * - 父节点掌握度 = 所有子节点掌握度的算术平均数
|
|
|
- * - 支持任意层级结构,递归深度限制为10级
|
|
|
- *
|
|
|
- * @param string $studentId 学生ID
|
|
|
- * @param array $childKpCodes 当前层的子节点编码列表
|
|
|
- * @param int $maxDepth 最大递归深度(防止无限递归,默认10级)
|
|
|
- * @return array 返回所有层级的父节点掌握度数据
|
|
|
+ * 更新父节点掌握度(优化版:批量查询+批量upsert)
|
|
|
+ * 父节点掌握度 = 所有子节点掌握度的算术平均数,支持多级递归
|
|
|
*/
|
|
|
private function updateParentMasteryLevels(string $studentId, array $childKpCodes, int $maxDepth = 2): array
|
|
|
{
|
|
|
$allParentMasteryData = [];
|
|
|
$currentLevelChildCodes = $childKpCodes;
|
|
|
- $processedNodes = new \ArrayObject(); // 防止重复处理
|
|
|
+ $processedNodes = new \ArrayObject();
|
|
|
+ $now = now();
|
|
|
|
|
|
try {
|
|
|
- // 【公司要求】多级递归:逐级向上递归计算,支持多级结构
|
|
|
for ($level = 0; $level < $maxDepth; $level++) {
|
|
|
if (empty($currentLevelChildCodes)) {
|
|
|
- break; // 没有更多父节点,退出循环
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
- // 【公司要求】查询结构:通过 knowledge_points 表获取知识点的层级关系(parent_kp_code)
|
|
|
// 获取当前层子节点的直接父节点
|
|
|
$parentKpCodes = DB::connection('mysql')
|
|
|
->table('knowledge_points')
|
|
|
@@ -836,49 +820,54 @@ class ExamAnswerAnalysisService
|
|
|
->toArray();
|
|
|
|
|
|
if (empty($parentKpCodes)) {
|
|
|
- Log::debug("第{$level}层没有更多父节点,递归结束");
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- $currentLevelParentData = [];
|
|
|
-
|
|
|
- foreach ($parentKpCodes as $parentKpCode) {
|
|
|
- // 防止重复处理同一节点
|
|
|
- if (isset($processedNodes[$parentKpCode])) {
|
|
|
- continue;
|
|
|
+ // 过滤已处理的节点
|
|
|
+ $parentKpCodesToProcess = [];
|
|
|
+ foreach ($parentKpCodes as $code) {
|
|
|
+ if (!isset($processedNodes[$code])) {
|
|
|
+ $processedNodes[$code] = true;
|
|
|
+ $parentKpCodesToProcess[] = $code;
|
|
|
}
|
|
|
- $processedNodes[$parentKpCode] = true;
|
|
|
+ }
|
|
|
|
|
|
- // 【公司要求】平均值计算:使用MasteryCalculator计算父节点掌握度(实时计算)
|
|
|
- // 父节点掌握度 = 所有子节点掌握度的算术平均数
|
|
|
- $parentMastery = $this->masteryCalculator->calculateParentMastery($studentId, $parentKpCode);
|
|
|
+ if (empty($parentKpCodesToProcess)) {
|
|
|
+ $currentLevelChildCodes = $parentKpCodes;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- // 获取父节点历史数据
|
|
|
- $historyParentMastery = DB::connection('mysql')
|
|
|
- ->table('student_knowledge_mastery')
|
|
|
- ->where('student_id', $studentId)
|
|
|
- ->where('kp_code', $parentKpCode)
|
|
|
- ->first();
|
|
|
+ // 【优化】批量查询历史掌握度(1次查询代替N次)
|
|
|
+ $historyRecords = DB::connection('mysql')
|
|
|
+ ->table('student_knowledge_mastery')
|
|
|
+ ->where('student_id', $studentId)
|
|
|
+ ->whereIn('kp_code', $parentKpCodesToProcess)
|
|
|
+ ->get()
|
|
|
+ ->keyBy('kp_code');
|
|
|
+
|
|
|
+ // 计算每个父节点的掌握度并收集upsert数据
|
|
|
+ $upsertData = [];
|
|
|
+ $currentLevelParentData = [];
|
|
|
|
|
|
- $previousMastery = $historyParentMastery->mastery_level ?? 0.0;
|
|
|
+ foreach ($parentKpCodesToProcess as $parentKpCode) {
|
|
|
+ // 计算父节点掌握度(这个暂时保留,后续可优化)
|
|
|
+ $parentMastery = $this->masteryCalculator->calculateParentMastery($studentId, $parentKpCode);
|
|
|
|
|
|
- // 【自己要求】保存父节点掌握度到数据库(只保存核心数据)
|
|
|
- DB::connection('mysql')
|
|
|
- ->table('student_knowledge_mastery')
|
|
|
- ->updateOrInsert(
|
|
|
- ['student_id' => $studentId, 'kp_code' => $parentKpCode],
|
|
|
- [
|
|
|
- 'mastery_level' => $parentMastery,
|
|
|
- 'confidence_level' => 0.0, // 不再保存置信度
|
|
|
- 'total_attempts' => 1, // 父节点不记录答题次数
|
|
|
- 'correct_attempts' => 0, // 父节点不记录正确次数
|
|
|
- 'mastery_trend' => 'stable', // 统一设为stable
|
|
|
- 'last_mastery_update' => now(),
|
|
|
- 'updated_at' => now(),
|
|
|
- ]
|
|
|
- );
|
|
|
+ $historyMastery = $historyRecords->get($parentKpCode);
|
|
|
+ $previousMastery = $historyMastery->mastery_level ?? 0.0;
|
|
|
+
|
|
|
+ $upsertData[] = [
|
|
|
+ 'student_id' => $studentId,
|
|
|
+ 'kp_code' => $parentKpCode,
|
|
|
+ 'mastery_level' => $parentMastery,
|
|
|
+ 'confidence_level' => 0.0,
|
|
|
+ 'total_attempts' => 1,
|
|
|
+ 'correct_attempts' => 0,
|
|
|
+ 'mastery_trend' => 'stable',
|
|
|
+ 'last_mastery_update' => $now,
|
|
|
+ 'updated_at' => $now,
|
|
|
+ ];
|
|
|
|
|
|
- // 【公司要求】记录当前层级数据(移除置信度)
|
|
|
$currentLevelParentData[$parentKpCode] = [
|
|
|
'kp_id' => $parentKpCode,
|
|
|
'current_mastery' => $parentMastery,
|
|
|
@@ -886,48 +875,37 @@ class ExamAnswerAnalysisService
|
|
|
'change' => $parentMastery - $previousMastery,
|
|
|
'weight' => 1,
|
|
|
'is_parent' => true,
|
|
|
- 'level' => $level + 1 // 记录层级深度
|
|
|
+ 'level' => $level + 1
|
|
|
];
|
|
|
+ }
|
|
|
|
|
|
- Log::debug("第" . ($level + 1) . "层父节点掌握度已更新", [
|
|
|
- 'student_id' => $studentId,
|
|
|
- 'parent_kp_code' => $parentKpCode,
|
|
|
- 'parent_mastery' => $parentMastery,
|
|
|
- 'child_nodes_count' => count($currentLevelChildCodes)
|
|
|
- ]);
|
|
|
+ // 【优化】批量upsert(1次查询代替N次updateOrInsert)
|
|
|
+ if (!empty($upsertData)) {
|
|
|
+ DB::connection('mysql')
|
|
|
+ ->table('student_knowledge_mastery')
|
|
|
+ ->upsert(
|
|
|
+ $upsertData,
|
|
|
+ ['student_id', 'kp_code'],
|
|
|
+ ['mastery_level', 'confidence_level', 'total_attempts', 'correct_attempts', 'mastery_trend', 'last_mastery_update', 'updated_at']
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- // 合并到总结果中
|
|
|
$allParentMasteryData = array_merge($allParentMasteryData, $currentLevelParentData);
|
|
|
-
|
|
|
- // 【公司要求】多级递归:准备下一轮递归,从最底层逐级向上求平均
|
|
|
- // 当前层的父节点作为下一层的子节点
|
|
|
$currentLevelChildCodes = $parentKpCodes;
|
|
|
}
|
|
|
|
|
|
if (!empty($allParentMasteryData)) {
|
|
|
- $totalParentCount = count($allParentMasteryData);
|
|
|
- $maxLevel = 0;
|
|
|
- foreach ($allParentMasteryData as $data) {
|
|
|
- $maxLevel = max($maxLevel, $data['level']);
|
|
|
- }
|
|
|
-
|
|
|
Log::info('多级父节点掌握度计算完成', [
|
|
|
'student_id' => $studentId,
|
|
|
'child_count' => count($childKpCodes),
|
|
|
- 'total_parent_count' => $totalParentCount,
|
|
|
- 'max_level' => $maxLevel,
|
|
|
- 'all_parent_kp_codes' => array_keys($allParentMasteryData)
|
|
|
+ 'total_parent_count' => count($allParentMasteryData),
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('计算多级父节点掌握度失败', [
|
|
|
'student_id' => $studentId,
|
|
|
- 'child_kp_codes' => $childKpCodes,
|
|
|
- 'current_level' => $currentLevelChildCodes,
|
|
|
'error' => $e->getMessage(),
|
|
|
- 'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
}
|
|
|
|