DiagnosticChapterService.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\Log;
  4. use App\Models\KnowledgePoint;
  5. use App\Models\StudentKnowledgeMastery;
  6. use App\Models\TextbookCatalog;
  7. use App\Models\TextbookChapterKnowledgeRelation;
  8. class DiagnosticChapterService
  9. {
  10. public function getTextbookKnowledgePointsInOrder(int $textbookId): array
  11. {
  12. $chapters = TextbookCatalog::query()
  13. ->where('textbook_id', $textbookId)
  14. ->where('node_type', 'chapter')
  15. ->orderBy('display_no')
  16. ->orderBy('sort_order')
  17. ->orderBy('id')
  18. ->get();
  19. if ($chapters->isEmpty()) {
  20. Log::warning('DiagnosticChapterService: 未找到章节节点', [
  21. 'textbook_id' => $textbookId,
  22. ]);
  23. return [];
  24. }
  25. $orderedCodes = [];
  26. $seen = [];
  27. foreach ($chapters as $chapter) {
  28. $sectionIds = TextbookCatalog::query()
  29. ->where('parent_id', $chapter->id)
  30. ->where('node_type', 'section')
  31. ->orderBy('display_no')
  32. ->orderBy('sort_order')
  33. ->orderBy('id')
  34. ->pluck('id')
  35. ->toArray();
  36. if (empty($sectionIds)) {
  37. continue;
  38. }
  39. $kpCodes = TextbookChapterKnowledgeRelation::query()
  40. ->whereIn('catalog_chapter_id', $sectionIds)
  41. ->pluck('kp_code')
  42. ->filter()
  43. ->unique()
  44. ->values()
  45. ->toArray();
  46. $kpCodes = $this->expandWithChildKnowledgePoints($kpCodes);
  47. foreach ($kpCodes as $kpCode) {
  48. if (isset($seen[$kpCode])) {
  49. continue;
  50. }
  51. $seen[$kpCode] = true;
  52. $orderedCodes[] = $kpCode;
  53. }
  54. }
  55. Log::info('DiagnosticChapterService: 获取教材知识点顺序列表', [
  56. 'textbook_id' => $textbookId,
  57. 'kp_count' => count($orderedCodes),
  58. ]);
  59. return $orderedCodes;
  60. }
  61. public function getInitialChapterKnowledgePoints(int $textbookId): array
  62. {
  63. $chapter = TextbookCatalog::query()
  64. ->where('textbook_id', $textbookId)
  65. ->where('node_type', 'chapter')
  66. ->orderBy('display_no')
  67. ->orderBy('sort_order')
  68. ->orderBy('id')
  69. ->first();
  70. if (!$chapter) {
  71. Log::warning('DiagnosticChapterService: 未找到章节节点', [
  72. 'textbook_id' => $textbookId,
  73. ]);
  74. return [];
  75. }
  76. $sectionIds = TextbookCatalog::query()
  77. ->where('parent_id', $chapter->id)
  78. ->where('node_type', 'section')
  79. ->orderBy('display_no')
  80. ->orderBy('sort_order')
  81. ->orderBy('id')
  82. ->pluck('id')
  83. ->toArray();
  84. if (empty($sectionIds)) {
  85. Log::warning('DiagnosticChapterService: 章节下未找到section节点', [
  86. 'textbook_id' => $textbookId,
  87. 'chapter_id' => $chapter->id,
  88. ]);
  89. return [];
  90. }
  91. $kpCodes = TextbookChapterKnowledgeRelation::query()
  92. ->whereIn('catalog_chapter_id', $sectionIds)
  93. ->pluck('kp_code')
  94. ->filter()
  95. ->unique()
  96. ->values()
  97. ->toArray();
  98. $kpCodes = $this->expandWithChildKnowledgePoints($kpCodes);
  99. Log::info('DiagnosticChapterService: 获取首章知识点', [
  100. 'textbook_id' => $textbookId,
  101. 'chapter_id' => $chapter->id,
  102. 'section_count' => count($sectionIds),
  103. 'kp_count' => count($kpCodes),
  104. ]);
  105. return [
  106. 'chapter_id' => $chapter->id,
  107. 'section_ids' => $sectionIds,
  108. 'kp_codes' => $kpCodes,
  109. ];
  110. }
  111. public function getFirstUnmasteredChapterKnowledgePoints(int $textbookId, int $studentId, float $threshold = 0.9): array
  112. {
  113. $chapters = TextbookCatalog::query()
  114. ->where('textbook_id', $textbookId)
  115. ->where('node_type', 'chapter')
  116. ->orderBy('display_no')
  117. ->orderBy('sort_order')
  118. ->orderBy('id')
  119. ->get();
  120. if ($chapters->isEmpty()) {
  121. Log::warning('DiagnosticChapterService: 未找到章节节点', [
  122. 'textbook_id' => $textbookId,
  123. ]);
  124. return [];
  125. }
  126. foreach ($chapters as $chapter) {
  127. $sectionIds = TextbookCatalog::query()
  128. ->where('parent_id', $chapter->id)
  129. ->where('node_type', 'section')
  130. ->orderBy('display_no')
  131. ->orderBy('sort_order')
  132. ->orderBy('id')
  133. ->pluck('id')
  134. ->toArray();
  135. if (empty($sectionIds)) {
  136. continue;
  137. }
  138. $kpCodes = TextbookChapterKnowledgeRelation::query()
  139. ->whereIn('catalog_chapter_id', $sectionIds)
  140. ->pluck('kp_code')
  141. ->filter()
  142. ->unique()
  143. ->values()
  144. ->toArray();
  145. $kpCodes = $this->expandWithChildKnowledgePoints($kpCodes);
  146. if (empty($kpCodes)) {
  147. continue;
  148. }
  149. $allMastered = StudentKnowledgeMastery::allAtLeast($studentId, $kpCodes, $threshold);
  150. Log::info('DiagnosticChapterService: 章节掌握度评估', [
  151. 'student_id' => $studentId,
  152. 'textbook_id' => $textbookId,
  153. 'chapter_id' => $chapter->id,
  154. 'section_count' => count($sectionIds),
  155. 'kp_count' => count($kpCodes),
  156. 'all_mastered' => $allMastered,
  157. 'threshold' => $threshold,
  158. ]);
  159. if (!$allMastered) {
  160. return [
  161. 'chapter_id' => $chapter->id,
  162. 'section_ids' => $sectionIds,
  163. 'kp_codes' => $kpCodes,
  164. 'all_mastered' => $allMastered,
  165. ];
  166. }
  167. }
  168. Log::info('DiagnosticChapterService: 所有章节均达到掌握度阈值', [
  169. 'student_id' => $studentId,
  170. 'textbook_id' => $textbookId,
  171. 'threshold' => $threshold,
  172. ]);
  173. return [];
  174. }
  175. private function expandWithChildKnowledgePoints(array $kpCodes): array
  176. {
  177. if (empty($kpCodes)) {
  178. return [];
  179. }
  180. $baseCodes = collect($kpCodes)->filter()->values()->all();
  181. $children = KnowledgePoint::query()
  182. ->whereIn('parent_kp_code', $baseCodes)
  183. ->orderBy('kp_code')
  184. ->get(['parent_kp_code', 'kp_code']);
  185. $childrenMap = [];
  186. foreach ($children as $child) {
  187. $childrenMap[$child->parent_kp_code][] = $child->kp_code;
  188. }
  189. $ordered = [];
  190. $seen = [];
  191. foreach ($baseCodes as $kpCode) {
  192. if (!isset($seen[$kpCode])) {
  193. $seen[$kpCode] = true;
  194. $ordered[] = $kpCode;
  195. }
  196. foreach ($childrenMap[$kpCode] ?? [] as $childCode) {
  197. if (isset($seen[$childCode])) {
  198. continue;
  199. }
  200. $seen[$childCode] = true;
  201. $ordered[] = $childCode;
  202. }
  203. }
  204. return $ordered;
  205. }
  206. }