RunQuestionQualityCheckCommand.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Services\QuestionQualityCheckService;
  4. use Illuminate\Console\Command;
  5. use Illuminate\Support\Facades\DB;
  6. use Illuminate\Support\Facades\Schema;
  7. class RunQuestionQualityCheckCommand extends Command
  8. {
  9. protected $signature = 'question:qc
  10. {--table=questions_tem : 待质检题目表名}
  11. {--kp= : 指定知识点,不传则按下学期题少 KP 筛选}
  12. {--limit=100 : 质检题目数量上限}
  13. {--textbook= : 教材 ID}
  14. {--semester=2 : 学期 1=上 2=下}
  15. {--dry-run : 仅输出筛选结果,不执行质检}';
  16. protected $description = '题目自动质检:从 questions_tem 按下学期题少 KP 筛选题目并执行校验';
  17. public function handle(QuestionQualityCheckService $qcService): int
  18. {
  19. $table = $this->option('table');
  20. if (! Schema::hasTable($table)) {
  21. $this->error("表 {$table} 不存在,请先创建或指定正确表名");
  22. return 1;
  23. }
  24. $kp = $this->option('kp');
  25. $limit = (int) $this->option('limit');
  26. $dryRun = $this->option('dry-run');
  27. if ($kp) {
  28. $questions = DB::table($table)
  29. ->where('kp_code', $kp)
  30. ->limit($limit)
  31. ->get();
  32. } else {
  33. $kps = $qcService->getKpsWithFewQuestions(
  34. $this->option('textbook') ? (int) $this->option('textbook') : null,
  35. (int) $this->option('semester'),
  36. 20
  37. );
  38. if (empty($kps)) {
  39. $this->warn('未找到题少的 KP,请检查 textbooks、textbook_chapter_knowledge_relation 数据');
  40. return 0;
  41. }
  42. $this->info('按下学期题少 KP 筛选,前 5 个:');
  43. foreach (array_slice($kps, 0, 5) as $r) {
  44. $this->line(" {$r['kp_code']}: {$r['question_count']} 题");
  45. }
  46. $kpCodes = array_column($kps, 'kp_code');
  47. $questions = DB::table($table)
  48. ->whereIn('kp_code', $kpCodes)
  49. ->limit($limit)
  50. ->get();
  51. }
  52. $total = $questions->count();
  53. $this->info("待质检题目数: {$total}");
  54. if ($dryRun) {
  55. $this->info('[dry-run] 不执行质检');
  56. return 0;
  57. }
  58. $passed = 0;
  59. $failed = 0;
  60. $bar = $this->output->createProgressBar($total);
  61. $bar->start();
  62. foreach ($questions as $q) {
  63. $row = (array) $q;
  64. $mapped = $this->mapQuestionRow($row);
  65. $result = $qcService->runAutoCheck(
  66. $mapped,
  67. $row['id'] ?? null,
  68. null
  69. );
  70. if ($result['passed']) {
  71. $passed++;
  72. } else {
  73. $failed++;
  74. $this->newLine();
  75. $this->warn(" [{$row['id']}] " . implode(', ', $result['errors']));
  76. }
  77. $bar->advance();
  78. }
  79. $bar->finish();
  80. $this->newLine(2);
  81. $this->info("质检完成: 通过 {$passed},未通过 {$failed}");
  82. return 0;
  83. }
  84. /**
  85. * 将 questions_tem 行映射为质检服务所需格式
  86. */
  87. private function mapQuestionRow(array $row): array
  88. {
  89. return [
  90. 'stem' => $row['stem'] ?? $row['content'] ?? '',
  91. 'answer' => $row['answer'] ?? $row['correct_answer'] ?? '',
  92. 'solution' => $row['solution'] ?? '',
  93. 'question_type' => $row['question_type'] ?? $row['tags'] ?? '',
  94. 'options' => is_string($row['options'] ?? null)
  95. ? json_decode($row['options'], true)
  96. : ($row['options'] ?? null),
  97. ];
  98. }
  99. }