|
@@ -157,6 +157,7 @@ class ExamAnswerAnalysisService
|
|
|
private function getQuestionKnowledgeMappings(array $questions): array
|
|
private function getQuestionKnowledgeMappings(array $questions): array
|
|
|
{
|
|
{
|
|
|
$mappings = [];
|
|
$mappings = [];
|
|
|
|
|
+ $failedQuestions = [];
|
|
|
|
|
|
|
|
// 直接从题目数据中提取知识点信息(不再调用外部服务)
|
|
// 直接从题目数据中提取知识点信息(不再调用外部服务)
|
|
|
foreach ($questions as $question) {
|
|
foreach ($questions as $question) {
|
|
@@ -172,19 +173,34 @@ class ExamAnswerAnalysisService
|
|
|
?? $question['kp_code']
|
|
?? $question['kp_code']
|
|
|
?? null;
|
|
?? null;
|
|
|
|
|
|
|
|
- if (!empty($kpCode)) {
|
|
|
|
|
|
|
+ // 如果请求中没有知识点信息,从数据库自动获取
|
|
|
|
|
+ if (empty($kpCode)) {
|
|
|
|
|
+ $dbKpInfo = $this->getQuestionKnowledgePointFromDb($questionId);
|
|
|
|
|
+ if ($dbKpInfo) {
|
|
|
|
|
+ $kpMapping[] = [
|
|
|
|
|
+ 'kp_id' => $dbKpInfo['kp_id'],
|
|
|
|
|
+ 'kp_name' => $dbKpInfo['kp_name'],
|
|
|
|
|
+ 'weight' => 1.0
|
|
|
|
|
+ ];
|
|
|
|
|
+ Log::debug('从数据库获取题目知识点', [
|
|
|
|
|
+ 'question_id' => $questionId,
|
|
|
|
|
+ 'kp_id' => $dbKpInfo['kp_id']
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
$kpMapping[] = [
|
|
$kpMapping[] = [
|
|
|
'kp_id' => $kpCode,
|
|
'kp_id' => $kpCode,
|
|
|
'kp_name' => $question['kp_name'] ?? $kpCode,
|
|
'kp_name' => $question['kp_name'] ?? $kpCode,
|
|
|
'weight' => 1.0
|
|
'weight' => 1.0
|
|
|
];
|
|
];
|
|
|
- } else {
|
|
|
|
|
- // 【修复】不允许使用默认知识点,必须明确指定
|
|
|
|
|
- Log::warning('ExamAnswerAnalysisService: 题目缺少知识点信息', [
|
|
|
|
|
- 'question_id' => $questionId,
|
|
|
|
|
- 'question' => $question
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果仍然没有知识点信息,跳过该题目
|
|
|
|
|
+ if (empty($kpMapping)) {
|
|
|
|
|
+ $failedQuestions[] = $questionId;
|
|
|
|
|
+ Log::warning('跳过无法获取知识点的题目', [
|
|
|
|
|
+ 'question_id' => $questionId
|
|
|
]);
|
|
]);
|
|
|
- // 不创建默认映射,让后续处理明确报错
|
|
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -194,14 +210,98 @@ class ExamAnswerAnalysisService
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (!empty($failedQuestions)) {
|
|
|
|
|
+ Log::warning('部分题目无法获取知识点信息', [
|
|
|
|
|
+ 'failed_questions' => $failedQuestions,
|
|
|
|
|
+ 'total_questions' => count($questions),
|
|
|
|
|
+ 'mapped_questions' => count($mappings)
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Log::info('题目知识点映射构建完成', [
|
|
Log::info('题目知识点映射构建完成', [
|
|
|
'total_questions' => count($questions),
|
|
'total_questions' => count($questions),
|
|
|
'mapped_questions' => count($mappings),
|
|
'mapped_questions' => count($mappings),
|
|
|
|
|
+ 'failed_questions' => count($failedQuestions)
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
return $mappings;
|
|
return $mappings;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从数据库获取题目的知识点信息
|
|
|
|
|
+ */
|
|
|
|
|
+ private function getQuestionKnowledgePointFromDb(string $questionId): ?array
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 优先从 MySQL 的 questions 表获取
|
|
|
|
|
+ $question = DB::connection('mysql')
|
|
|
|
|
+ ->table('questions')
|
|
|
|
|
+ ->where('id', $questionId)
|
|
|
|
|
+ ->orWhere('question_code', $questionId)
|
|
|
|
|
+ ->first();
|
|
|
|
|
+
|
|
|
|
|
+ if ($question) {
|
|
|
|
|
+ // 尝试从 kp_code 或 knowledge_point 字段获取
|
|
|
|
|
+ $kpCode = $question->kp_code
|
|
|
|
|
+ ?? $question->knowledge_point
|
|
|
|
|
+ ?? $question->knowledge_points
|
|
|
|
|
+ ?? null;
|
|
|
|
|
+
|
|
|
|
|
+ if ($kpCode) {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'kp_id' => $kpCode,
|
|
|
|
|
+ 'kp_name' => $kpCode
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 备选:从 QuestionBank API 获取知识点
|
|
|
|
|
+ $questionBankInfo = $this->getQuestionFromQuestionBank($questionId);
|
|
|
|
|
+ if ($questionBankInfo && !empty($questionBankInfo['knowledge_points'])) {
|
|
|
|
|
+ $kpCode = $questionBankInfo['knowledge_points'][0]['code'] ?? null;
|
|
|
|
|
+ if ($kpCode) {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'kp_id' => $kpCode,
|
|
|
|
|
+ 'kp_name' => $questionBankInfo['knowledge_points'][0]['name'] ?? $kpCode
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ Log::warning('从数据库获取题目知识点失败', [
|
|
|
|
|
+ 'question_id' => $questionId,
|
|
|
|
|
+ 'error' => $e->getMessage()
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 QuestionBank API 获取题目信息
|
|
|
|
|
+ */
|
|
|
|
|
+ private function getQuestionFromQuestionBank(string $questionId): ?array
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ $baseUrl = config('services.question_bank_api.base_url', 'http://question-bank-api:5015');
|
|
|
|
|
+ $response = Http::timeout(10)->get($baseUrl . '/questions/' . $questionId);
|
|
|
|
|
+
|
|
|
|
|
+ if ($response->successful()) {
|
|
|
|
|
+ $data = $response->json();
|
|
|
|
|
+ return $data['data'] ?? null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ Log::warning('从QuestionBank获取题目信息失败', [
|
|
|
|
|
+ 'question_id' => $questionId,
|
|
|
|
|
+ 'error' => $e->getMessage()
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 计算知识点掌握度向量
|
|
* 计算知识点掌握度向量
|
|
|
* 【修复】集成MasteryCalculator的BKT模型进行精确计算
|
|
* 【修复】集成MasteryCalculator的BKT模型进行精确计算
|
|
@@ -299,13 +399,16 @@ class ExamAnswerAnalysisService
|
|
|
foreach ($knowledgeAttempts as $kpId => $data) {
|
|
foreach ($knowledgeAttempts as $kpId => $data) {
|
|
|
$attempts = $data['attempts'];
|
|
$attempts = $data['attempts'];
|
|
|
|
|
|
|
|
|
|
+ // 如果没有学案基准难度,使用默认值2(提分)
|
|
|
|
|
+ $baseDifficulty = $examBaseDifficulty ?? 2;
|
|
|
|
|
+
|
|
|
// 调用MasteryCalculator的核心算法(传入学案基准难度)
|
|
// 调用MasteryCalculator的核心算法(传入学案基准难度)
|
|
|
// 该算法包含:正确率、难度加权、时间效率、技能熟练度、遗忘曲线衰减
|
|
// 该算法包含:正确率、难度加权、时间效率、技能熟练度、遗忘曲线衰减
|
|
|
$masteryResult = $this->masteryCalculator->calculateMasteryLevel(
|
|
$masteryResult = $this->masteryCalculator->calculateMasteryLevel(
|
|
|
$studentId ?? '', // 传递学生ID,用于保存掌握度到数据库
|
|
$studentId ?? '', // 传递学生ID,用于保存掌握度到数据库
|
|
|
$kpId,
|
|
$kpId,
|
|
|
$attempts,
|
|
$attempts,
|
|
|
- $examBaseDifficulty
|
|
|
|
|
|
|
+ $baseDifficulty
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
$masteryVector[$kpId] = [
|
|
$masteryVector[$kpId] = [
|