|
@@ -1123,11 +1123,33 @@ class LearningAnalyticsService
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 获取学生薄弱点列表
|
|
* 获取学生薄弱点列表
|
|
|
|
|
+ * 策略:MySQL作为权威数据源,LearningAnalytics API仅作为辅助/缓存
|
|
|
*/
|
|
*/
|
|
|
public function getStudentWeaknesses(string $studentId, int $limit = 10): array
|
|
public function getStudentWeaknesses(string $studentId, int $limit = 10): array
|
|
|
{
|
|
{
|
|
|
try {
|
|
try {
|
|
|
- // 使用正确的API路径:/api/v1/student/{student_id}/weak-points
|
|
|
|
|
|
|
+ // 首先从本地MySQL(权威数据源)获取数据
|
|
|
|
|
+ Log::info('从MySQL权威数据源获取学生薄弱点', [
|
|
|
|
|
+ 'student_id' => $studentId,
|
|
|
|
|
+ 'source' => 'mysql'
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $localData = $this->getStudentWeaknessesFromMySQL($studentId, $limit);
|
|
|
|
|
+
|
|
|
|
|
+ if (!empty($localData)) {
|
|
|
|
|
+ Log::info('从MySQL权威数据源获取到薄弱点数据', [
|
|
|
|
|
+ 'student_id' => $studentId,
|
|
|
|
|
+ 'count' => count($localData)
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return $localData;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 本地无数据时,尝试从LearningAnalytics API获取(作为辅助/缓存)
|
|
|
|
|
+ Log::warning('MySQL中无该学生薄弱点数据,尝试从LearningAnalytics API获取', [
|
|
|
|
|
+ 'student_id' => $studentId,
|
|
|
|
|
+ 'api_url' => $this->baseUrl . "/api/v1/student/{$studentId}/weak-points"
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
$response = Http::timeout($this->timeout)
|
|
$response = Http::timeout($this->timeout)
|
|
|
->get($this->baseUrl . "/api/v1/student/{$studentId}/weak-points");
|
|
->get($this->baseUrl . "/api/v1/student/{$studentId}/weak-points");
|
|
|
|
|
|
|
@@ -1135,36 +1157,42 @@ class LearningAnalyticsService
|
|
|
$data = $response->json('data', []);
|
|
$data = $response->json('data', []);
|
|
|
$weakPoints = $data['weak_points'] ?? [];
|
|
$weakPoints = $data['weak_points'] ?? [];
|
|
|
|
|
|
|
|
- // 转换为统一的格式
|
|
|
|
|
- return array_map(function ($item) use ($studentId) {
|
|
|
|
|
- return [
|
|
|
|
|
- 'kp_code' => $item['kp'] ?? '',
|
|
|
|
|
- 'kp_name' => $item['kp'] ?? '',
|
|
|
|
|
- 'mastery' => $item['mastery_level'] ?? 0,
|
|
|
|
|
- 'stability' => 0.5, // 默认稳定性
|
|
|
|
|
- 'weakness_level' => 1.0 - ($item['mastery_level'] ?? 0.5),
|
|
|
|
|
- 'practice_count' => $item['practice_count'] ?? 0,
|
|
|
|
|
- 'success_rate' => $item['success_rate'] ?? 0,
|
|
|
|
|
- 'priority' => $item['priority'] ?? '中',
|
|
|
|
|
- 'suggested_questions' => $item['suggested_questions'] ?? 0
|
|
|
|
|
- ];
|
|
|
|
|
- }, $weakPoints);
|
|
|
|
|
|
|
+ if (!empty($weakPoints)) {
|
|
|
|
|
+ Log::info('从LearningAnalytics API辅助获取到薄弱点数据', [
|
|
|
|
|
+ 'student_id' => $studentId,
|
|
|
|
|
+ 'count' => count($weakPoints)
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return array_map(function ($item) use ($studentId) {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'kp_code' => $item['kp'] ?? '',
|
|
|
|
|
+ 'kp_name' => $item['kp'] ?? '',
|
|
|
|
|
+ 'mastery' => $item['mastery_level'] ?? 0,
|
|
|
|
|
+ 'stability' => 0.5, // 默认稳定性
|
|
|
|
|
+ 'weakness_level' => 1.0 - ($item['mastery_level'] ?? 0.5),
|
|
|
|
|
+ 'practice_count' => $item['practice_count'] ?? 0,
|
|
|
|
|
+ 'success_rate' => $item['success_rate'] ?? 0,
|
|
|
|
|
+ 'priority' => $item['priority'] ?? '中',
|
|
|
|
|
+ 'suggested_questions' => $item['suggested_questions'] ?? 0
|
|
|
|
|
+ ];
|
|
|
|
|
+ }, $weakPoints);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Log::warning('LearningAnalytics weaknesses API失败,使用本地MySQL数据', [
|
|
|
|
|
|
|
+ Log::warning('所有数据源均无该学生薄弱点数据', [
|
|
|
'student_id' => $studentId,
|
|
'student_id' => $studentId,
|
|
|
- 'status' => $response->status()
|
|
|
|
|
|
|
+ 'mysql_count' => count($localData),
|
|
|
|
|
+ 'api_status' => $response->status() ?? 'timeout'
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
- // API失败时,从MySQL直接查询
|
|
|
|
|
- return $this->getStudentWeaknessesFromMySQL($studentId, $limit);
|
|
|
|
|
|
|
+ return [];
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
|
Log::error('Get Student Weaknesses Error', [
|
|
Log::error('Get Student Weaknesses Error', [
|
|
|
'student_id' => $studentId,
|
|
'student_id' => $studentId,
|
|
|
'error' => $e->getMessage()
|
|
'error' => $e->getMessage()
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
- // 发生异常时,返回空数组,让前端可以继续使用默认值
|
|
|
|
|
|
|
+ // 发生异常时,返回空数组
|
|
|
return [];
|
|
return [];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1175,34 +1203,108 @@ class LearningAnalyticsService
|
|
|
private function getStudentWeaknessesFromMySQL(string $studentId, int $limit = 10): array
|
|
private function getStudentWeaknessesFromMySQL(string $studentId, int $limit = 10): array
|
|
|
{
|
|
{
|
|
|
try {
|
|
try {
|
|
|
- $weaknesses = DB::table('student_mastery as sm')
|
|
|
|
|
- ->join('knowledge_points as kp', 'sm.kp', '=', 'kp.kp')
|
|
|
|
|
- ->where('sm.student_id', $studentId)
|
|
|
|
|
- ->where('sm.mastery', '<', 0.7) // 掌握度低于70%视为薄弱点
|
|
|
|
|
- ->orderBy('sm.mastery', 'asc')
|
|
|
|
|
|
|
+ // 优先从 student_knowledge_mastery 表读取(更完整的掌握度数据)
|
|
|
|
|
+ $weaknesses = DB::table('student_knowledge_mastery as skm')
|
|
|
|
|
+ ->where('skm.student_id', $studentId)
|
|
|
|
|
+ ->where('skm.mastery_level', '<', 0.7) // 掌握度低于70%视为薄弱点
|
|
|
|
|
+ ->orderBy('skm.mastery_level', 'asc')
|
|
|
->limit($limit)
|
|
->limit($limit)
|
|
|
->select([
|
|
->select([
|
|
|
- 'sm.kp as kp_code',
|
|
|
|
|
- 'kp.cn_name as kp_name',
|
|
|
|
|
- 'sm.mastery',
|
|
|
|
|
- 'sm.stability'
|
|
|
|
|
|
|
+ 'skm.kp_code',
|
|
|
|
|
+ 'skm.mastery_level',
|
|
|
|
|
+ 'skm.total_attempts',
|
|
|
|
|
+ 'skm.correct_attempts',
|
|
|
|
|
+ 'skm.incorrect_attempts',
|
|
|
|
|
+ 'skm.confidence_level',
|
|
|
|
|
+ 'skm.mastery_trend'
|
|
|
])
|
|
])
|
|
|
->get()
|
|
->get()
|
|
|
->toArray();
|
|
->toArray();
|
|
|
|
|
|
|
|
|
|
+ // 如果student_knowledge_mastery表没有数据,尝试从student_mastery表读取
|
|
|
|
|
+ if (empty($weaknesses)) {
|
|
|
|
|
+ Log::info('student_knowledge_mastery表无数据,尝试从student_mastery表读取', [
|
|
|
|
|
+ 'student_id' => $studentId
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $weaknesses = DB::table('student_mastery as sm')
|
|
|
|
|
+ ->leftJoin('knowledge_points as kp', 'sm.kp', '=', 'kp.kp')
|
|
|
|
|
+ ->where('sm.student_id', $studentId)
|
|
|
|
|
+ ->where('sm.mastery', '<', 0.7) // 掌握度低于70%视为薄弱点
|
|
|
|
|
+ ->orderBy('sm.mastery', 'asc')
|
|
|
|
|
+ ->limit($limit)
|
|
|
|
|
+ ->select([
|
|
|
|
|
+ 'sm.kp as kp_code',
|
|
|
|
|
+ 'kp.cn_name as kp_name',
|
|
|
|
|
+ 'sm.mastery',
|
|
|
|
|
+ 'sm.attempts',
|
|
|
|
|
+ 'sm.correct'
|
|
|
|
|
+ ])
|
|
|
|
|
+ ->get()
|
|
|
|
|
+ ->toArray();
|
|
|
|
|
+
|
|
|
|
|
+ // 转换为统一格式
|
|
|
|
|
+ return array_map(function ($item) {
|
|
|
|
|
+ $mastery = (float) ($item->mastery ?? 0);
|
|
|
|
|
+ $attempts = (int) ($item->attempts ?? 0);
|
|
|
|
|
+ $correct = (int) ($item->correct ?? 0);
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'kp_code' => $item->kp_code,
|
|
|
|
|
+ 'kp_name' => $item->kp_name ?? $item->kp_code,
|
|
|
|
|
+ 'mastery' => $mastery,
|
|
|
|
|
+ 'stability' => 0.5, // 默认稳定性
|
|
|
|
|
+ 'weakness_level' => 1.0 - $mastery, // 薄弱程度
|
|
|
|
|
+ 'practice_count' => $attempts,
|
|
|
|
|
+ 'success_rate' => $attempts > 0 ? ($correct / $attempts) : 0,
|
|
|
|
|
+ 'priority' => $mastery < 0.3 ? '高' : ($mastery < 0.5 ? '中' : '低'),
|
|
|
|
|
+ 'suggested_questions' => max(5, (int)((0.7 - $mastery) * 20)) // 掌握度越低,建议题目越多
|
|
|
|
|
+ ];
|
|
|
|
|
+ }, $weaknesses);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 转换student_knowledge_mastery表的数据格式
|
|
|
return array_map(function ($item) {
|
|
return array_map(function ($item) {
|
|
|
|
|
+ $mastery = (float) ($item->mastery_level ?? 0);
|
|
|
|
|
+ $totalAttempts = (int) ($item->total_attempts ?? 0);
|
|
|
|
|
+ $correctAttempts = (int) ($item->correct_attempts ?? 0);
|
|
|
|
|
+ $incorrectAttempts = (int) ($item->incorrect_attempts ?? 0);
|
|
|
|
|
+ $confidence = (float) ($item->confidence_level ?? 0.5);
|
|
|
|
|
+ $trend = $item->mastery_trend ?? 'stable';
|
|
|
|
|
+
|
|
|
|
|
+ // 计算成功率
|
|
|
|
|
+ $successRate = $totalAttempts > 0 ? ($correctAttempts / $totalAttempts) : 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 确定优先级
|
|
|
|
|
+ $priority = '中';
|
|
|
|
|
+ if ($mastery < 0.3) {
|
|
|
|
|
+ $priority = '高';
|
|
|
|
|
+ } elseif ($mastery < 0.5) {
|
|
|
|
|
+ $priority = '中';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $priority = '低';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return [
|
|
return [
|
|
|
'kp_code' => $item->kp_code,
|
|
'kp_code' => $item->kp_code,
|
|
|
- 'kp_name' => $item->kp_name,
|
|
|
|
|
- 'mastery' => (float) $item->mastery,
|
|
|
|
|
- 'stability' => (float) $item->stability,
|
|
|
|
|
- 'weakness_level' => 1.0 - (float) $item->mastery // 薄弱程度
|
|
|
|
|
|
|
+ 'kp_name' => $item->kp_code, // 如果没有中文名,使用代码作为名称
|
|
|
|
|
+ 'mastery' => $mastery,
|
|
|
|
|
+ 'stability' => $confidence,
|
|
|
|
|
+ 'weakness_level' => 1.0 - $mastery, // 薄弱程度
|
|
|
|
|
+ 'practice_count' => $totalAttempts,
|
|
|
|
|
+ 'success_rate' => $successRate,
|
|
|
|
|
+ 'priority' => $priority,
|
|
|
|
|
+ 'suggested_questions' => max(5, (int)((0.7 - $mastery) * 20)), // 掌握度越低,建议题目越多
|
|
|
|
|
+ 'trend' => $trend,
|
|
|
|
|
+ 'correct_attempts' => $correctAttempts,
|
|
|
|
|
+ 'incorrect_attempts' => $incorrectAttempts
|
|
|
];
|
|
];
|
|
|
}, $weaknesses);
|
|
}, $weaknesses);
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
|
Log::error('Get Student Weaknesses From MySQL Error', [
|
|
Log::error('Get Student Weaknesses From MySQL Error', [
|
|
|
'student_id' => $studentId,
|
|
'student_id' => $studentId,
|
|
|
- 'error' => $e->getMessage()
|
|
|
|
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
|
|
+ 'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
]);
|
|
|
return [];
|
|
return [];
|
|
|
}
|
|
}
|