QuestionPayloadMapper.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. <?php
  2. namespace App\Services;
  3. use App\Models\Question;
  4. class QuestionPayloadMapper
  5. {
  6. /**
  7. * @param array<int, string> $fallbackKpCodes
  8. * @return array<string, mixed>
  9. */
  10. public function fromArray(array $question, array $fallbackKpCodes = []): array
  11. {
  12. $type = $this->normalizeQuestionType(
  13. (string) ($question['question_type'] ?? $question['type'] ?? '')
  14. ) ?? $this->guessType($question);
  15. $kpCode = (string) ($question['kp_code']
  16. ?? $question['kp']
  17. ?? $question['knowledge_point']
  18. ?? $question['knowledge_point_code']
  19. ?? ($fallbackKpCodes[0] ?? ''));
  20. $id = $question['id'] ?? $question['question_id'] ?? $question['question_bank_id'] ?? null;
  21. return [
  22. 'id' => $id,
  23. 'question_id' => $question['question_id'] ?? $id,
  24. 'question_type' => $type,
  25. 'stem' => $question['stem'] ?? $question['content'] ?? ($question['question_text'] ?? ''),
  26. 'content' => $question['content'] ?? $question['stem'] ?? '',
  27. 'options' => $question['options'] ?? ($question['choices'] ?? []),
  28. 'answer' => $question['answer'] ?? $question['correct_answer'] ?? '',
  29. 'solution' => $question['solution'] ?? '',
  30. 'difficulty' => $this->normalizeDifficulty($question['difficulty'] ?? null),
  31. 'score' => $question['score'] ?? $this->defaultScore($type),
  32. 'estimated_time' => $question['estimated_time'] ?? 300,
  33. 'kp' => $kpCode,
  34. 'kp_code' => $kpCode,
  35. ];
  36. }
  37. /**
  38. * @return array<string, mixed>
  39. */
  40. public function fromModel(Question $question): array
  41. {
  42. return $this->fromArray([
  43. 'id' => $question->id,
  44. 'question_id' => $question->id,
  45. 'question_type' => $question->question_type,
  46. 'stem' => $question->stem,
  47. 'content' => $question->stem,
  48. 'options' => $question->options ?? [],
  49. 'answer' => $question->answer,
  50. 'solution' => $question->solution,
  51. 'difficulty' => $question->difficulty,
  52. 'kp_code' => $question->kp_code,
  53. ]);
  54. }
  55. public function normalizeQuestionType(?string $type): ?string
  56. {
  57. $type = strtolower(trim((string) $type));
  58. if (in_array($type, ['choice', 'single_choice', 'multiple_choice', '选择题', '单选', '多选'], true)) {
  59. return 'choice';
  60. }
  61. if (in_array($type, ['fill', 'fill_in_the_blank', 'blank', '填空题', '填空'], true)) {
  62. return 'fill';
  63. }
  64. if (in_array($type, ['answer', 'calculation', 'word_problem', 'proof', '解答题', '计算题'], true)) {
  65. return 'answer';
  66. }
  67. return null;
  68. }
  69. public function normalizeDifficulty(mixed $difficulty): float
  70. {
  71. if ($difficulty === null || $difficulty === '') {
  72. return 0.5;
  73. }
  74. $value = (float) $difficulty;
  75. if ($value > 1) {
  76. $value = $value / 5;
  77. }
  78. return max(0.0, min(1.0, $value));
  79. }
  80. private function guessType(array $question): string
  81. {
  82. if (! empty($question['options']) && is_array($question['options'])) {
  83. return 'choice';
  84. }
  85. $content = $question['stem'] ?? $question['content'] ?? '';
  86. if (is_string($content) && (str_contains($content, '____') || str_contains($content, '()'))) {
  87. return 'fill';
  88. }
  89. return 'answer';
  90. }
  91. private function defaultScore(string $type): int
  92. {
  93. return match ($type) {
  94. 'choice', 'fill' => 5,
  95. default => 10,
  96. };
  97. }
  98. }