Explorar o código

fix:卷子分析时增加返回全量知识点掌握度数据 current_master

yemeishu hai 1 semana
pai
achega
f54a4d8d09

+ 1 - 1
app/Http/Controllers/Api/PaperSubmitAnalysisController.php

@@ -150,7 +150,7 @@ class PaperSubmitAnalysisController extends Controller
                     'total_questions' => count($questionsData),
                     'correct_count' => $this->countCorrectQuestions($questionsData),
                     'incorrect_count' => $mistakesAdded,
-                    'analysis_error' => $analysisError, // 如果学情分析失败,返回错误信息
+                    'analysis_error' => $analysisError,
                 ],
                 'message' => "分析完成,新增 {$mistakesAdded} 条错题记录" . ($analysisError ? "(学情分析暂不可用)" : "")
             ]);

+ 53 - 0
app/Models/KnowledgePointMasterySnapshot.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use App\Models\Student;
+
+class KnowledgePointMasterySnapshot extends Model
+{
+    use HasFactory;
+
+    protected $table = 'knowledge_point_mastery_snapshots';
+    protected $primaryKey = 'snapshot_id';
+    public $incrementing = false;
+    protected $keyType = 'string';
+
+    protected $fillable = [
+        'snapshot_id',
+        'student_id',
+        'paper_id',
+        'answer_record_id',
+        'analysis_id',
+        'mastery_data',
+        'current_mastery',
+        'overall_mastery',
+        'weak_knowledge_points_count',
+        'strong_knowledge_points_count',
+        'snapshot_time',
+        'created_at',
+        'updated_at',
+    ];
+
+    protected $casts = [
+        'mastery_data' => 'array',
+        'current_mastery' => 'array',
+        'snapshot_time' => 'datetime',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'overall_mastery' => 'decimal:4',
+    ];
+
+    public function student(): BelongsTo
+    {
+        return $this->belongsTo(Student::class, 'student_id', 'student_id');
+    }
+
+    public function scopeForStudent($query, string $studentId)
+    {
+        return $query->where('student_id', $studentId);
+    }
+}

+ 19 - 10
app/Models/StudentKnowledgeMastery.php

@@ -151,9 +151,9 @@ class StudentKnowledgeMastery extends Model
      * 判断所有知识点是否达标(跳过没有题目的知识点)
      * 用于章节摸底后的知识点学习流程
      *
-     * @param int $studentId 学生ID
-     * @param array $kpCodes 知识点编码列表
-     * @param float $threshold 达标阈值(默认0.9)
+     * @param  int  $studentId  学生ID
+     * @param  array  $kpCodes  知识点编码列表
+     * @param  float  $threshold  达标阈值(默认0.9)
      * @return bool 是否全部达标
      */
     public static function allAtLeastSkipNoQuestions(int $studentId, array $kpCodes, float $threshold = 0.9): bool
@@ -181,7 +181,7 @@ class StudentKnowledgeMastery extends Model
 
         foreach ($kpCodes as $kpCode) {
             // 跳过没有题目的知识点
-            if (!in_array($kpCode, $kpCodesWithQuestions)) {
+            if (! in_array($kpCode, $kpCodesWithQuestions)) {
                 continue;
             }
 
@@ -209,9 +209,9 @@ class StudentKnowledgeMastery extends Model
     /**
      * 获取第一个未达标的知识点(跳过没有题目的知识点)
      *
-     * @param int $studentId 学生ID
-     * @param array $kpCodes 知识点编码列表(按顺序)
-     * @param float $threshold 达标阈值(默认0.9)
+     * @param  int  $studentId  学生ID
+     * @param  array  $kpCodes  知识点编码列表(按顺序)
+     * @param  float  $threshold  达标阈值(默认0.9)
      * @return string|null 第一个未达标的知识点编码,如果全部达标返回null
      */
     public static function getFirstUnmasteredKpCode(int $studentId, array $kpCodes, float $threshold = 0.9): ?string
@@ -237,7 +237,7 @@ class StudentKnowledgeMastery extends Model
 
         foreach ($kpCodes as $kpCode) {
             // 跳过没有题目的知识点
-            if (!in_array($kpCode, $kpCodesWithQuestions)) {
+            if (! in_array($kpCode, $kpCodesWithQuestions)) {
                 continue;
             }
 
@@ -262,9 +262,18 @@ class StudentKnowledgeMastery extends Model
     /**
      * 计算掌握度等级
      */
-    public function getMasteryLevelAttribute($value): string
+    public function getMasteryLevelAttribute($value): float
     {
-        if ($value >= 0.85) {
+        return (float) $value;
+    }
+
+    /**
+     * 获取掌握度等级标签
+     */
+    public function getMasteryLevelLabelAttribute(): string
+    {
+        $value = $this->mastery_level;
+        if ($value >= 0.9) {
             return '优秀';
         } elseif ($value >= 0.70) {
             return '良好';

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 213 - 177
app/Services/ExamAnswerAnalysisService.php


+ 119 - 3
app/Services/MistakeBookService.php

@@ -107,6 +107,7 @@ class MistakeBookService
                 ]);
 
                 // 合并知识点到已有记录中
+                $updates = ['updated_at' => now()];
                 if ($kpIds) {
                     $existingKpIds = $existingMistake->kp_ids ?? [];
                     $newKpIds = is_array($kpIds) ? $kpIds : [$kpIds];
@@ -115,9 +116,7 @@ class MistakeBookService
                     $mergedKpIds = array_values(array_unique(array_merge($existingKpIds, $newKpIds)));
 
                     // 更新记录(仅更新 kp_ids)
-                    $existingMistake->update([
-                        'kp_ids' => $mergedKpIds,
-                    ]);
+                    $updates['kp_ids'] = $mergedKpIds;
 
                     Log::info('错题记录知识点合并成功', [
                         'student_id' => $studentId,
@@ -129,6 +128,8 @@ class MistakeBookService
                     ]);
                 }
 
+                $existingMistake->update($updates);
+
                 return [
                     'duplicate' => true,
                     'mistake_id' => $existingMistake->id,
@@ -164,6 +165,121 @@ class MistakeBookService
         });
     }
 
+    /**
+     * 批量新增错题(同一学生/卷子)
+     *
+     * @param array<int, array> $payloads
+     * @return array
+     */
+    public function createMistakesBatch(array $payloads): array
+    {
+        if (empty($payloads)) {
+            return ['created' => 0, 'duplicates' => 0, 'updated' => 0];
+        }
+
+        $studentId = $payloads[0]['student_id'] ?? null;
+        $paperId = $payloads[0]['paper_id'] ?? null;
+        $source = $payloads[0]['source'] ?? MistakeRecord::SOURCE_PRACTICE;
+
+        if (!$studentId) {
+            throw new \InvalidArgumentException('学生ID不能为空');
+        }
+
+        $questionIds = [];
+        foreach ($payloads as $payload) {
+            if (!empty($payload['question_id'])) {
+                $questionIds[] = $payload['question_id'];
+            }
+        }
+
+        $questionIds = array_values(array_unique($questionIds));
+
+        return \DB::transaction(function () use ($payloads, $studentId, $paperId, $source, $questionIds) {
+            $existingMap = [];
+            if (!empty($questionIds)) {
+                $existing = MistakeRecord::where('student_id', $studentId)
+                    ->where('source', $source)
+                    ->when($paperId, fn ($q) => $q->where('paper_id', $paperId))
+                    ->whereIn('question_id', $questionIds)
+                    ->get();
+
+                foreach ($existing as $item) {
+                    $existingMap[$item->question_id] = $item;
+                }
+            }
+
+            $toInsert = [];
+            $created = 0;
+            $duplicates = 0;
+            $updated = 0;
+
+            foreach ($payloads as $payload) {
+                $questionId = $payload['question_id'] ?? null;
+                if (!$questionId) {
+                    continue;
+                }
+
+                $kpIds = $payload['kp_ids'] ?? null;
+                $kpIds = is_array($kpIds) ? $kpIds : ($kpIds ? [$kpIds] : null);
+
+                if (isset($existingMap[$questionId])) {
+                    $duplicates++;
+                    $updates = ['updated_at' => now()];
+                    if ($kpIds) {
+                        $existingKpIds = $existingMap[$questionId]->kp_ids ?? [];
+                        $merged = array_values(array_unique(array_merge($existingKpIds, $kpIds)));
+                        if ($merged !== $existingKpIds) {
+                            $updates['kp_ids'] = $merged;
+                            $updated++;
+                        }
+                    }
+                    $existingMap[$questionId]->update($updates);
+                    continue;
+                }
+
+                $toInsert[] = [
+                    'student_id' => $studentId,
+                    'question_id' => $questionId,
+                    'paper_id' => $payload['paper_id'] ?? null,
+                    'student_answer' => $payload['my_answer'] ?? null,
+                    'correct_answer' => $payload['correct_answer'] ?? null,
+                    'question_text' => $payload['question_text'] ?? null,
+                    'knowledge_point' => $payload['knowledge_point'] ?? null,
+                    'explanation' => $payload['explanation'] ?? null,
+                    'kp_ids' => $kpIds,
+                    'source' => $payload['source'] ?? MistakeRecord::SOURCE_PRACTICE,
+                    'created_at' => $payload['happened_at'] ?? now(),
+                    'review_status' => MistakeRecord::REVIEW_STATUS_PENDING,
+                    'review_count' => 0,
+                ];
+            }
+
+            if (!empty($toInsert)) {
+                // MistakeRecord::insert 不会触发模型事件,但速度更快
+                MistakeRecord::insert($toInsert);
+                $created = count($toInsert);
+            }
+
+            // 统一刷新本次题目的 updated_at(无论是否合并)
+            if (!empty($questionIds)) {
+                \DB::table('mistake_records')
+                    ->where('student_id', $studentId)
+                    ->where('source', $source)
+                    ->when($paperId, fn ($q) => $q->where('paper_id', $paperId))
+                    ->whereIn('question_id', $questionIds)
+                    ->update(['updated_at' => now()]);
+            }
+
+            $this->clearCache($studentId);
+
+            return [
+                'created' => $created,
+                'duplicates' => $duplicates,
+                'updated' => $updated,
+            ];
+        });
+    }
+
     /**
      * 获取错题列表
      */

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio