@hed-hog/lms 0.0.358 → 0.0.364

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +65 -0
  2. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
  3. package/dist/bitcode-wallet/bitcode-wallet.service.js +72 -0
  4. package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -1
  5. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts +8 -0
  6. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts.map +1 -0
  7. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js +40 -0
  8. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js.map +1 -0
  9. package/dist/class-group/class-group.controller.d.ts +16 -16
  10. package/dist/class-group/class-group.service.d.ts +12 -12
  11. package/dist/course/course-audio-transcription.service.d.ts +3 -2
  12. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  13. package/dist/course/course-audio-transcription.service.js +49 -8
  14. package/dist/course/course-audio-transcription.service.js.map +1 -1
  15. package/dist/course/course-lesson.controller.d.ts +4 -0
  16. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  17. package/dist/course/course-lesson.controller.js +10 -0
  18. package/dist/course/course-lesson.controller.js.map +1 -1
  19. package/dist/course/course-structure.controller.d.ts +11 -4
  20. package/dist/course/course-structure.controller.d.ts.map +1 -1
  21. package/dist/course/course-structure.controller.js +14 -0
  22. package/dist/course/course-structure.controller.js.map +1 -1
  23. package/dist/course/course-structure.service.d.ts +15 -1
  24. package/dist/course/course-structure.service.d.ts.map +1 -1
  25. package/dist/course/course-structure.service.js +139 -4
  26. package/dist/course/course-structure.service.js.map +1 -1
  27. package/dist/course/course-video-conversion.service.d.ts +8 -0
  28. package/dist/course/course-video-conversion.service.d.ts.map +1 -1
  29. package/dist/course/course-video-conversion.service.js +87 -51
  30. package/dist/course/course-video-conversion.service.js.map +1 -1
  31. package/dist/course/course.controller.d.ts +73 -1
  32. package/dist/course/course.controller.d.ts.map +1 -1
  33. package/dist/course/course.controller.js +27 -3
  34. package/dist/course/course.controller.js.map +1 -1
  35. package/dist/course/course.module.d.ts.map +1 -1
  36. package/dist/course/course.module.js +15 -3
  37. package/dist/course/course.module.js.map +1 -1
  38. package/dist/course/course.service.d.ts +108 -4
  39. package/dist/course/course.service.d.ts.map +1 -1
  40. package/dist/course/course.service.js +631 -30
  41. package/dist/course/course.service.js.map +1 -1
  42. package/dist/course/dto/cleanup-course-storage.dto.d.ts +6 -0
  43. package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -0
  44. package/dist/course/dto/cleanup-course-storage.dto.js +33 -0
  45. package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -0
  46. package/dist/course/dto/cleanup-upload-history.dto.d.ts +9 -0
  47. package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -0
  48. package/dist/course/dto/cleanup-upload-history.dto.js +36 -0
  49. package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -0
  50. package/dist/course/dto/create-course-bulk-job.dto.d.ts +4 -0
  51. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -0
  52. package/dist/course/dto/create-course-bulk-job.dto.js +21 -0
  53. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -0
  54. package/dist/course/lms-bulk-upload-automation.service.d.ts +39 -0
  55. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -0
  56. package/dist/course/lms-bulk-upload-automation.service.js +443 -0
  57. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -0
  58. package/dist/course/lms-bulk-upload-infra.service.d.ts +31 -0
  59. package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -0
  60. package/dist/course/lms-bulk-upload-infra.service.js +277 -0
  61. package/dist/course/lms-bulk-upload-infra.service.js.map +1 -0
  62. package/dist/course/lms-bulk-upload.constants.d.ts +4 -0
  63. package/dist/course/lms-bulk-upload.constants.d.ts.map +1 -0
  64. package/dist/course/lms-bulk-upload.constants.js +7 -0
  65. package/dist/course/lms-bulk-upload.constants.js.map +1 -0
  66. package/dist/course/lms-bulk-upload.controller.d.ts +153 -0
  67. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -0
  68. package/dist/course/lms-bulk-upload.controller.js +129 -0
  69. package/dist/course/lms-bulk-upload.controller.js.map +1 -0
  70. package/dist/course/lms-bulk-upload.service.d.ts +181 -0
  71. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -0
  72. package/dist/course/lms-bulk-upload.service.js +754 -0
  73. package/dist/course/lms-bulk-upload.service.js.map +1 -0
  74. package/dist/course/lms-setting.controller.d.ts +7 -1
  75. package/dist/course/lms-setting.controller.d.ts.map +1 -1
  76. package/dist/course/lms-setting.controller.js +26 -3
  77. package/dist/course/lms-setting.controller.js.map +1 -1
  78. package/dist/enterprise/enterprise.controller.d.ts +20 -20
  79. package/dist/enterprise/enterprise.service.d.ts +20 -20
  80. package/dist/enterprise/training/training-admin.controller.d.ts +11 -11
  81. package/dist/enterprise/training/training-admin.service.d.ts +11 -11
  82. package/dist/enterprise/training/training-instructor.controller.d.ts +2 -2
  83. package/dist/enterprise/training/training-instructor.service.d.ts +2 -2
  84. package/dist/enterprise/training/training-student.controller.d.ts +1 -1
  85. package/dist/enterprise/training/training-student.service.d.ts +1 -1
  86. package/dist/enterprise/training/training-viewer.controller.d.ts +2 -2
  87. package/dist/evaluation/evaluation.controller.d.ts +8 -8
  88. package/dist/evaluation/evaluation.service.d.ts +8 -8
  89. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts +6 -0
  90. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts.map +1 -0
  91. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js +34 -0
  92. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js.map +1 -0
  93. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts +28 -0
  94. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts.map +1 -0
  95. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js +123 -0
  96. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js.map +1 -0
  97. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts +4 -0
  98. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts.map +1 -0
  99. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js +22 -0
  100. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js.map +1 -0
  101. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts +10 -0
  102. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts.map +1 -0
  103. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js +52 -0
  104. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js.map +1 -0
  105. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts +15 -0
  106. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts.map +1 -0
  107. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js +86 -0
  108. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js.map +1 -0
  109. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +26 -0
  110. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -0
  111. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +304 -0
  112. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -0
  113. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts +87 -0
  114. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts.map +1 -0
  115. package/dist/lesson-xp-map/lesson-xp-map.controller.js +185 -0
  116. package/dist/lesson-xp-map/lesson-xp-map.controller.js.map +1 -0
  117. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts +3 -0
  118. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -0
  119. package/dist/lesson-xp-map/lesson-xp-map.module.js +34 -0
  120. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -0
  121. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts +84 -0
  122. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts.map +1 -0
  123. package/dist/lesson-xp-map/lesson-xp-map.service.js +353 -0
  124. package/dist/lesson-xp-map/lesson-xp-map.service.js.map +1 -0
  125. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts +10 -0
  126. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts.map +1 -0
  127. package/dist/lesson-xp-map/lesson-xp-segment.controller.js +63 -0
  128. package/dist/lesson-xp-map/lesson-xp-segment.controller.js.map +1 -0
  129. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts +27 -0
  130. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts.map +1 -0
  131. package/dist/lesson-xp-map/lesson-xp-segment.service.js +194 -0
  132. package/dist/lesson-xp-map/lesson-xp-segment.service.js.map +1 -0
  133. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -0
  134. package/dist/lms.module.d.ts.map +1 -1
  135. package/dist/lms.module.js +17 -2
  136. package/dist/lms.module.js.map +1 -1
  137. package/dist/platforma/dto/update-profile.dto.d.ts +17 -0
  138. package/dist/platforma/dto/update-profile.dto.d.ts.map +1 -0
  139. package/dist/platforma/dto/update-profile.dto.js +87 -0
  140. package/dist/platforma/dto/update-profile.dto.js.map +1 -0
  141. package/dist/platforma/platforma.controller.d.ts +94 -7
  142. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  143. package/dist/platforma/platforma.controller.js +85 -2
  144. package/dist/platforma/platforma.controller.js.map +1 -1
  145. package/dist/platforma/platforma.service.d.ts +27 -0
  146. package/dist/platforma/platforma.service.d.ts.map +1 -0
  147. package/dist/platforma/platforma.service.js +274 -0
  148. package/dist/platforma/platforma.service.js.map +1 -0
  149. package/dist/student-xp/student-xp.controller.d.ts +41 -0
  150. package/dist/student-xp/student-xp.controller.d.ts.map +1 -0
  151. package/dist/student-xp/student-xp.controller.js +114 -0
  152. package/dist/student-xp/student-xp.controller.js.map +1 -0
  153. package/dist/student-xp/student-xp.module.d.ts +3 -0
  154. package/dist/student-xp/student-xp.module.d.ts.map +1 -0
  155. package/dist/student-xp/student-xp.module.js +25 -0
  156. package/dist/student-xp/student-xp.module.js.map +1 -0
  157. package/dist/student-xp/student-xp.service.d.ts +65 -0
  158. package/dist/student-xp/student-xp.service.d.ts.map +1 -0
  159. package/dist/student-xp/student-xp.service.js +197 -0
  160. package/dist/student-xp/student-xp.service.js.map +1 -0
  161. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts +12 -0
  162. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts.map +1 -0
  163. package/dist/xp-catalog/dto/create-xp-area.dto.js +63 -0
  164. package/dist/xp-catalog/dto/create-xp-area.dto.js.map +1 -0
  165. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts +11 -0
  166. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts.map +1 -0
  167. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js +57 -0
  168. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js.map +1 -0
  169. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts +11 -0
  170. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts.map +1 -0
  171. package/dist/xp-catalog/dto/create-xp-skill.dto.js +57 -0
  172. package/dist/xp-catalog/dto/create-xp-skill.dto.js.map +1 -0
  173. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts +12 -0
  174. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts.map +1 -0
  175. package/dist/xp-catalog/dto/update-xp-area.dto.js +66 -0
  176. package/dist/xp-catalog/dto/update-xp-area.dto.js.map +1 -0
  177. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts +11 -0
  178. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts.map +1 -0
  179. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js +60 -0
  180. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js.map +1 -0
  181. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts +11 -0
  182. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts.map +1 -0
  183. package/dist/xp-catalog/dto/update-xp-skill.dto.js +60 -0
  184. package/dist/xp-catalog/dto/update-xp-skill.dto.js.map +1 -0
  185. package/dist/xp-catalog/xp-area.controller.d.ts +25 -0
  186. package/dist/xp-catalog/xp-area.controller.d.ts.map +1 -0
  187. package/dist/xp-catalog/xp-area.controller.js +105 -0
  188. package/dist/xp-catalog/xp-area.controller.js.map +1 -0
  189. package/dist/xp-catalog/xp-area.service.d.ts +35 -0
  190. package/dist/xp-catalog/xp-area.service.d.ts.map +1 -0
  191. package/dist/xp-catalog/xp-area.service.js +168 -0
  192. package/dist/xp-catalog/xp-area.service.js.map +1 -0
  193. package/dist/xp-catalog/xp-catalog.module.d.ts +3 -0
  194. package/dist/xp-catalog/xp-catalog.module.d.ts.map +1 -0
  195. package/dist/xp-catalog/xp-catalog.module.js +29 -0
  196. package/dist/xp-catalog/xp-catalog.module.js.map +1 -0
  197. package/dist/xp-catalog/xp-learning-type.controller.d.ts +20 -0
  198. package/dist/xp-catalog/xp-learning-type.controller.d.ts.map +1 -0
  199. package/dist/xp-catalog/xp-learning-type.controller.js +96 -0
  200. package/dist/xp-catalog/xp-learning-type.controller.js.map +1 -0
  201. package/dist/xp-catalog/xp-learning-type.service.d.ts +30 -0
  202. package/dist/xp-catalog/xp-learning-type.service.d.ts.map +1 -0
  203. package/dist/xp-catalog/xp-learning-type.service.js +146 -0
  204. package/dist/xp-catalog/xp-learning-type.service.js.map +1 -0
  205. package/dist/xp-catalog/xp-skill.controller.d.ts +26 -0
  206. package/dist/xp-catalog/xp-skill.controller.d.ts.map +1 -0
  207. package/dist/xp-catalog/xp-skill.controller.js +113 -0
  208. package/dist/xp-catalog/xp-skill.controller.js.map +1 -0
  209. package/dist/xp-catalog/xp-skill.service.d.ts +37 -0
  210. package/dist/xp-catalog/xp-skill.service.d.ts.map +1 -0
  211. package/dist/xp-catalog/xp-skill.service.js +174 -0
  212. package/dist/xp-catalog/xp-skill.service.js.map +1 -0
  213. package/hedhog/data/menu.yaml +101 -0
  214. package/hedhog/data/role.yaml +8 -0
  215. package/hedhog/data/route.yaml +547 -0
  216. package/hedhog/data/setting_group.yaml +33 -0
  217. package/hedhog/data/xp_area.yaml +164 -0
  218. package/hedhog/data/xp_learning_type.yaml +131 -0
  219. package/hedhog/data/xp_skill.yaml +1834 -0
  220. package/hedhog/frontend/app/achievements/page.tsx.ejs +108 -118
  221. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +22 -34
  222. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +1453 -0
  223. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +21 -45
  224. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +40 -74
  225. package/hedhog/frontend/app/classes/page.tsx.ejs +56 -85
  226. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +3 -2
  227. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +48 -5
  228. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +4 -4
  229. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +19 -2
  230. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +1170 -0
  231. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +16 -0
  232. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +2 -0
  233. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +623 -0
  234. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +1458 -0
  235. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +55 -2
  236. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +442 -104
  237. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +296 -49
  238. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +3 -0
  239. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +1 -0
  240. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +101 -85
  241. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +21 -1
  242. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +3 -0
  243. package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +7 -1
  244. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +44 -0
  245. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +54 -0
  246. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +52 -0
  247. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-xp-overview.ts.ejs +76 -0
  248. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-xp-map.ts.ejs +128 -0
  249. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +30 -0
  250. package/hedhog/frontend/app/courses/[id]/structure/_utils/xp-color-config.ts.ejs +115 -0
  251. package/hedhog/frontend/app/courses/_components/CourseDeleteDialog.tsx.ejs +223 -0
  252. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +89 -0
  253. package/hedhog/frontend/app/courses/page.tsx.ejs +400 -230
  254. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -63
  255. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +53 -77
  256. package/hedhog/frontend/app/exams/page.tsx.ejs +54 -90
  257. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +23 -36
  258. package/hedhog/frontend/app/instructors/page.tsx.ejs +72 -81
  259. package/hedhog/frontend/app/paths/page.tsx.ejs +40 -68
  260. package/hedhog/frontend/app/training/page.tsx.ejs +40 -68
  261. package/hedhog/frontend/app/xp/areas/page.tsx.ejs +782 -0
  262. package/hedhog/frontend/app/xp/learning-types/page.tsx.ejs +690 -0
  263. package/hedhog/frontend/app/xp/skills/page.tsx.ejs +811 -0
  264. package/hedhog/frontend/messages/en.json +386 -3
  265. package/hedhog/frontend/messages/pt.json +386 -3
  266. package/hedhog/table/lesson_xp_map.yaml +50 -0
  267. package/hedhog/table/lesson_xp_segment.yaml +40 -0
  268. package/hedhog/table/lesson_xp_segment_area.yaml +24 -0
  269. package/hedhog/table/lesson_xp_segment_learning_type.yaml +24 -0
  270. package/hedhog/table/lesson_xp_segment_skill.yaml +24 -0
  271. package/hedhog/table/lms_bulk_upload_item.yaml +44 -0
  272. package/hedhog/table/lms_bulk_upload_session.yaml +42 -0
  273. package/hedhog/table/student_area_xp.yaml +30 -0
  274. package/hedhog/table/student_learning_type_xp.yaml +30 -0
  275. package/hedhog/table/student_skill_xp.yaml +30 -0
  276. package/hedhog/table/student_xp_event.yaml +34 -0
  277. package/hedhog/table/xp_area.yaml +39 -0
  278. package/hedhog/table/xp_learning_type.yaml +34 -0
  279. package/hedhog/table/xp_skill.yaml +39 -0
  280. package/package.json +9 -8
  281. package/src/bitcode-wallet/bitcode-wallet.service.ts +113 -0
  282. package/src/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.ts +32 -0
  283. package/src/course/course-audio-transcription.service.ts +58 -21
  284. package/src/course/course-lesson.controller.ts +6 -1
  285. package/src/course/course-structure.controller.ts +10 -0
  286. package/src/course/course-structure.service.ts +174 -1
  287. package/src/course/course-video-conversion.service.ts +113 -75
  288. package/src/course/course.controller.ts +22 -3
  289. package/src/course/course.module.ts +15 -3
  290. package/src/course/course.service.ts +847 -30
  291. package/src/course/dto/cleanup-course-storage.dto.ts +22 -0
  292. package/src/course/dto/cleanup-upload-history.dto.ts +26 -0
  293. package/src/course/dto/create-course-bulk-job.dto.ts +6 -0
  294. package/src/course/lms-bulk-upload-automation.service.ts +560 -0
  295. package/src/course/lms-bulk-upload-infra.service.ts +327 -0
  296. package/src/course/lms-bulk-upload.constants.ts +5 -0
  297. package/src/course/lms-bulk-upload.controller.ts +103 -0
  298. package/src/course/lms-bulk-upload.service.ts +1029 -0
  299. package/src/course/lms-setting.controller.ts +28 -1
  300. package/src/lesson-xp-map/dto/create-lesson-xp-map.dto.ts +17 -0
  301. package/src/lesson-xp-map/dto/create-lesson-xp-segment.dto.ts +102 -0
  302. package/src/lesson-xp-map/dto/review-lesson-xp-map.dto.ts +7 -0
  303. package/src/lesson-xp-map/dto/update-lesson-xp-map.dto.ts +36 -0
  304. package/src/lesson-xp-map/dto/update-lesson-xp-segment.dto.ts +78 -0
  305. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +396 -0
  306. package/src/lesson-xp-map/lesson-xp-map.controller.ts +116 -0
  307. package/src/lesson-xp-map/lesson-xp-map.module.ts +21 -0
  308. package/src/lesson-xp-map/lesson-xp-map.service.ts +442 -0
  309. package/src/lesson-xp-map/lesson-xp-segment.controller.ts +36 -0
  310. package/src/lesson-xp-map/lesson-xp-segment.service.ts +229 -0
  311. package/src/lms.module.ts +17 -2
  312. package/src/platforma/dto/update-profile.dto.ts +59 -0
  313. package/src/platforma/platforma.controller.ts +57 -2
  314. package/src/platforma/platforma.service.ts +268 -0
  315. package/src/student-xp/student-xp.controller.ts +76 -0
  316. package/src/student-xp/student-xp.module.ts +12 -0
  317. package/src/student-xp/student-xp.service.ts +236 -0
  318. package/src/xp-catalog/dto/create-xp-area.dto.ts +40 -0
  319. package/src/xp-catalog/dto/create-xp-learning-type.dto.ts +35 -0
  320. package/src/xp-catalog/dto/create-xp-skill.dto.ts +35 -0
  321. package/src/xp-catalog/dto/update-xp-area.dto.ts +43 -0
  322. package/src/xp-catalog/dto/update-xp-learning-type.dto.ts +38 -0
  323. package/src/xp-catalog/dto/update-xp-skill.dto.ts +38 -0
  324. package/src/xp-catalog/xp-area.controller.ts +64 -0
  325. package/src/xp-catalog/xp-area.service.ts +196 -0
  326. package/src/xp-catalog/xp-catalog.module.ts +16 -0
  327. package/src/xp-catalog/xp-learning-type.controller.ts +59 -0
  328. package/src/xp-catalog/xp-learning-type.service.ts +170 -0
  329. package/src/xp-catalog/xp-skill.controller.ts +71 -0
  330. package/src/xp-catalog/xp-skill.service.ts +205 -0
@@ -1,6 +1,7 @@
1
+ import { Role } from '@hed-hog/api';
2
+ import { PrismaService } from '@hed-hog/api-prisma';
1
3
  import { SettingService } from '@hed-hog/core';
2
4
  import { Controller, forwardRef, Get, Inject } from '@nestjs/common';
3
- import { Role } from '@hed-hog/api';
4
5
 
5
6
  @Role()
6
7
  @Controller('lms/settings')
@@ -8,6 +9,8 @@ export class LmsSettingController {
8
9
  constructor(
9
10
  @Inject(forwardRef(() => SettingService))
10
11
  private readonly settingService: SettingService,
12
+ @Inject(forwardRef(() => PrismaService))
13
+ private readonly prismaService: PrismaService,
11
14
  ) {}
12
15
 
13
16
  @Get()
@@ -18,13 +21,37 @@ export class LmsSettingController {
18
21
  'lms-audio-transcription-enabled',
19
22
  'lms-youtube-provider-enabled',
20
23
  'lms-vimeo-provider-enabled',
24
+ 'lms-bulk-upload-storage-profile-id',
25
+ 'lms-bulk-upload-lambda-role-arn',
26
+ 'lms-bulk-upload-sts-duration-seconds',
21
27
  ]);
28
+
29
+ const bulkUploadStorageProfileId =
30
+ Number(v['lms-bulk-upload-storage-profile-id'] || 0) || null;
31
+
32
+ let bulkUploadBucketName = '';
33
+ if (bulkUploadStorageProfileId) {
34
+ const profile = await this.prismaService.integration_profile.findUnique({
35
+ where: { id: bulkUploadStorageProfileId },
36
+ select: { config: true },
37
+ });
38
+ bulkUploadBucketName = String((profile?.config as any)?.bucket ?? '').trim();
39
+ }
40
+
22
41
  return {
23
42
  videoConversionEnabled: v['lms-video-conversion-enabled'] !== false,
24
43
  imageExtractionEnabled: v['lms-image-extraction-enabled'] !== false,
25
44
  transcriptionEnabled: v['lms-audio-transcription-enabled'] !== false,
26
45
  youtubeEnabled: v['lms-youtube-provider-enabled'] !== false,
27
46
  vimeoEnabled: v['lms-vimeo-provider-enabled'] !== false,
47
+ bulkUploadStorageProfileId,
48
+ bulkUploadBucketName,
49
+ bulkUploadLambdaRoleArn:
50
+ typeof v['lms-bulk-upload-lambda-role-arn'] === 'string'
51
+ ? v['lms-bulk-upload-lambda-role-arn'].trim()
52
+ : '',
53
+ bulkUploadStsDurationSeconds:
54
+ Number(v['lms-bulk-upload-sts-duration-seconds'] || 3600) || 3600,
28
55
  };
29
56
  }
30
57
  }
@@ -0,0 +1,17 @@
1
+ import { IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
2
+
3
+ export class CreateLessonXpMapDto {
4
+ @IsInt()
5
+ @Min(1)
6
+ courseLessonId: number;
7
+
8
+ @IsOptional()
9
+ @IsInt()
10
+ @Min(1)
11
+ version?: number;
12
+
13
+ @IsOptional()
14
+ @IsString()
15
+ @MaxLength(100)
16
+ aiModelVersion?: string | null;
17
+ }
@@ -0,0 +1,102 @@
1
+ import { Type } from 'class-transformer';
2
+ import {
3
+ IsArray,
4
+ IsBoolean,
5
+ IsEnum,
6
+ IsInt,
7
+ IsNumber,
8
+ IsOptional,
9
+ IsString,
10
+ Max,
11
+ Min,
12
+ ValidateNested,
13
+ } from 'class-validator';
14
+
15
+ export class SegmentAreaDto {
16
+ @IsInt()
17
+ @Min(1)
18
+ xpAreaId: number;
19
+
20
+ @IsNumber()
21
+ @Min(0.01)
22
+ @Max(100)
23
+ weightPercent: number;
24
+ }
25
+
26
+ export class SegmentSkillDto {
27
+ @IsInt()
28
+ @Min(1)
29
+ xpSkillId: number;
30
+
31
+ @IsNumber()
32
+ @Min(0.01)
33
+ @Max(100)
34
+ weightPercent: number;
35
+ }
36
+
37
+ export class SegmentLearningTypeDto {
38
+ @IsInt()
39
+ @Min(1)
40
+ xpLearningTypeId: number;
41
+
42
+ @IsNumber()
43
+ @Min(0.01)
44
+ @Max(100)
45
+ weightPercent: number;
46
+ }
47
+
48
+ export const SEGMENT_DIFFICULTY_VALUES = ['easy', 'medium', 'hard', 'expert'] as const;
49
+ export type SegmentDifficulty = (typeof SEGMENT_DIFFICULTY_VALUES)[number];
50
+
51
+ export class CreateLessonXpSegmentDto {
52
+ @IsNumber()
53
+ @Min(0)
54
+ startSeconds: number;
55
+
56
+ @IsNumber()
57
+ @Min(0)
58
+ endSeconds: number;
59
+
60
+ @IsInt()
61
+ @Min(0)
62
+ xpValue: number;
63
+
64
+ @IsEnum(SEGMENT_DIFFICULTY_VALUES)
65
+ difficulty: SegmentDifficulty;
66
+
67
+ @IsBoolean()
68
+ shouldGrantXp: boolean;
69
+
70
+ @IsOptional()
71
+ @IsString()
72
+ aiSummary?: string | null;
73
+
74
+ @IsOptional()
75
+ @IsNumber()
76
+ @Min(0)
77
+ @Max(1)
78
+ aiConfidence?: number | null;
79
+
80
+ @IsOptional()
81
+ @IsInt()
82
+ @Min(0)
83
+ sortOrder?: number;
84
+
85
+ @IsOptional()
86
+ @IsArray()
87
+ @ValidateNested({ each: true })
88
+ @Type(() => SegmentAreaDto)
89
+ areas?: SegmentAreaDto[];
90
+
91
+ @IsOptional()
92
+ @IsArray()
93
+ @ValidateNested({ each: true })
94
+ @Type(() => SegmentSkillDto)
95
+ skills?: SegmentSkillDto[];
96
+
97
+ @IsOptional()
98
+ @IsArray()
99
+ @ValidateNested({ each: true })
100
+ @Type(() => SegmentLearningTypeDto)
101
+ learningTypes?: SegmentLearningTypeDto[];
102
+ }
@@ -0,0 +1,7 @@
1
+ import { IsOptional, IsString } from 'class-validator';
2
+
3
+ export class RejectLessonXpMapDto {
4
+ @IsOptional()
5
+ @IsString()
6
+ reviewNotes?: string;
7
+ }
@@ -0,0 +1,36 @@
1
+ import { IsEnum, IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
2
+
3
+ export const LESSON_XP_MAP_STATUS_VALUES = [
4
+ 'pending',
5
+ 'processing',
6
+ 'ready',
7
+ 'needs_review',
8
+ 'approved',
9
+ 'rejected',
10
+ ] as const;
11
+
12
+ export type LessonXpMapStatus = (typeof LESSON_XP_MAP_STATUS_VALUES)[number];
13
+
14
+ export class UpdateLessonXpMapDto {
15
+ @IsOptional()
16
+ @IsEnum(LESSON_XP_MAP_STATUS_VALUES)
17
+ status?: LessonXpMapStatus;
18
+
19
+ @IsOptional()
20
+ @IsString()
21
+ reviewNotes?: string | null;
22
+
23
+ @IsOptional()
24
+ @IsString()
25
+ @MaxLength(100)
26
+ aiModelVersion?: string | null;
27
+
28
+ @IsOptional()
29
+ @IsString()
30
+ processingError?: string | null;
31
+
32
+ @IsOptional()
33
+ @IsInt()
34
+ @Min(0)
35
+ totalXp?: number;
36
+ }
@@ -0,0 +1,78 @@
1
+ import { Type } from 'class-transformer';
2
+ import {
3
+ IsArray,
4
+ IsBoolean,
5
+ IsEnum,
6
+ IsInt,
7
+ IsNumber,
8
+ IsOptional,
9
+ IsString,
10
+ Max,
11
+ Min,
12
+ ValidateNested,
13
+ } from 'class-validator';
14
+ import {
15
+ SEGMENT_DIFFICULTY_VALUES,
16
+ SegmentAreaDto,
17
+ SegmentLearningTypeDto,
18
+ SegmentSkillDto,
19
+ type SegmentDifficulty,
20
+ } from './create-lesson-xp-segment.dto';
21
+
22
+ export class UpdateLessonXpSegmentDto {
23
+ @IsOptional()
24
+ @IsNumber()
25
+ @Min(0)
26
+ startSeconds?: number;
27
+
28
+ @IsOptional()
29
+ @IsNumber()
30
+ @Min(0)
31
+ endSeconds?: number;
32
+
33
+ @IsOptional()
34
+ @IsInt()
35
+ @Min(0)
36
+ xpValue?: number;
37
+
38
+ @IsOptional()
39
+ @IsEnum(SEGMENT_DIFFICULTY_VALUES)
40
+ difficulty?: SegmentDifficulty;
41
+
42
+ @IsOptional()
43
+ @IsBoolean()
44
+ shouldGrantXp?: boolean;
45
+
46
+ @IsOptional()
47
+ @IsString()
48
+ aiSummary?: string | null;
49
+
50
+ @IsOptional()
51
+ @IsNumber()
52
+ @Min(0)
53
+ @Max(1)
54
+ aiConfidence?: number | null;
55
+
56
+ @IsOptional()
57
+ @IsInt()
58
+ @Min(0)
59
+ sortOrder?: number;
60
+
61
+ @IsOptional()
62
+ @IsArray()
63
+ @ValidateNested({ each: true })
64
+ @Type(() => SegmentAreaDto)
65
+ areas?: SegmentAreaDto[];
66
+
67
+ @IsOptional()
68
+ @IsArray()
69
+ @ValidateNested({ each: true })
70
+ @Type(() => SegmentSkillDto)
71
+ skills?: SegmentSkillDto[];
72
+
73
+ @IsOptional()
74
+ @IsArray()
75
+ @ValidateNested({ each: true })
76
+ @Type(() => SegmentLearningTypeDto)
77
+ learningTypes?: SegmentLearningTypeDto[];
78
+ }
@@ -0,0 +1,396 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import { buildAiConfigFromIntegration, SettingService } from '@hed-hog/core';
3
+ import { DatabaseQueueProvider, IJobHandler, QueueHandlerRegistry } from '@hed-hog/queue';
4
+ import {
5
+ BadRequestException,
6
+ forwardRef,
7
+ Inject,
8
+ Injectable,
9
+ Logger,
10
+ OnModuleInit,
11
+ } from '@nestjs/common';
12
+ import axios from 'axios';
13
+
14
+ type XpSegmentAiResult = {
15
+ startSeconds: number;
16
+ endSeconds: number;
17
+ xpValue: number;
18
+ difficulty: 'easy' | 'medium' | 'hard' | 'expert';
19
+ shouldGrantXp: boolean;
20
+ aiSummary: string;
21
+ aiConfidence: number;
22
+ areas: { xpAreaId: number; weightPercent: number }[];
23
+ skills: { xpSkillId: number; weightPercent: number }[];
24
+ learningTypes: { xpLearningTypeId: number; weightPercent: number }[];
25
+ };
26
+
27
+ @Injectable()
28
+ export class LessonXpAiCalculationService implements OnModuleInit, IJobHandler {
29
+ private readonly logger = new Logger(LessonXpAiCalculationService.name);
30
+
31
+ constructor(
32
+ @Inject(forwardRef(() => PrismaService))
33
+ private readonly prisma: PrismaService,
34
+ @Inject(forwardRef(() => SettingService))
35
+ private readonly settingService: SettingService,
36
+ @Inject(forwardRef(() => QueueHandlerRegistry))
37
+ private readonly registry: QueueHandlerRegistry,
38
+ @Inject(forwardRef(() => DatabaseQueueProvider))
39
+ private readonly dbQueue: DatabaseQueueProvider,
40
+ ) {}
41
+
42
+ onModuleInit() {
43
+ this.registry.register('lms.lesson.xp.calculate', this);
44
+ this.logger.log('Registered handler for "lms.lesson.xp.calculate"');
45
+ }
46
+
47
+ async triggerCalculation(lessonId: number, userId: number) {
48
+ const segments = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
49
+ `SELECT id FROM course_lesson_transcription_segment WHERE course_lesson_id = $1::int LIMIT 1`,
50
+ lessonId,
51
+ );
52
+
53
+ if (!segments.length) {
54
+ throw new BadRequestException(
55
+ 'Esta aula ainda não possui transcrição. Gere ou importe a transcrição antes de calcular o XP.',
56
+ );
57
+ }
58
+
59
+ const existing = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
60
+ `SELECT id FROM lesson_xp_map WHERE course_lesson_id = $1::int`,
61
+ lessonId,
62
+ );
63
+
64
+ let mapId: number;
65
+
66
+ if (existing.length) {
67
+ mapId = existing[0].id;
68
+ await this.prisma.$executeRawUnsafe(
69
+ `UPDATE lesson_xp_map
70
+ SET status = 'processing'::lesson_xp_map_status_d4e5f6a7b8_enum,
71
+ processing_error = NULL,
72
+ updated_at = NOW()
73
+ WHERE id = $1`,
74
+ mapId,
75
+ );
76
+ } else {
77
+ const created = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
78
+ `INSERT INTO lesson_xp_map (course_lesson_id, version, total_xp, status, created_at, updated_at)
79
+ VALUES ($1, 1, 0, 'processing'::lesson_xp_map_status_d4e5f6a7b8_enum, NOW(), NOW())
80
+ RETURNING id`,
81
+ lessonId,
82
+ );
83
+ mapId = created[0].id;
84
+ }
85
+
86
+ try {
87
+ await this.dbQueue.enqueue({
88
+ type: 'lms.lesson.xp.calculate',
89
+ queueName: 'lms.lesson.xp.calculate',
90
+ payload: { lessonId, mapId, userId },
91
+ maxAttempts: 3,
92
+ sourceModule: 'lms',
93
+ sourceEntity: 'course_lesson',
94
+ sourceEntityId: String(lessonId),
95
+ userId,
96
+ });
97
+ } catch (err: any) {
98
+ // P2002 = unique constraint — a job for this lesson is already queued/running
99
+ if (err?.code !== 'P2002') throw err;
100
+ }
101
+
102
+ const rows = await this.prisma.$queryRawUnsafe<any[]>(
103
+ `SELECT
104
+ m.id,
105
+ m.course_lesson_id AS "courseLessonId",
106
+ m.version,
107
+ m.total_xp AS "totalXp",
108
+ m.status::text AS status,
109
+ m.generated_at::text AS "generatedAt",
110
+ m.processing_error AS "processingError",
111
+ m.created_at::text AS "createdAt",
112
+ m.updated_at::text AS "updatedAt"
113
+ FROM lesson_xp_map m WHERE m.id = $1`,
114
+ mapId,
115
+ );
116
+ return { ...rows[0], segments: [] };
117
+ }
118
+
119
+ async handle(job: {
120
+ id: number;
121
+ type: string;
122
+ queue_name: string;
123
+ payload: Record<string, any>;
124
+ attempts: number;
125
+ max_attempts: number;
126
+ }): Promise<void> {
127
+ const { lessonId, mapId } = job.payload as { lessonId: number; mapId: number };
128
+
129
+ this.logger.log(`Starting XP calculation for lesson ${lessonId}, map ${mapId}`);
130
+
131
+ try {
132
+ const transcriptionRows = await this.prisma.$queryRawUnsafe<
133
+ { startSeconds: number; endSeconds: number; text: string }[]
134
+ >(
135
+ `SELECT start_seconds::float AS "startSeconds", end_seconds::float AS "endSeconds", text
136
+ FROM course_lesson_transcription_segment
137
+ WHERE course_lesson_id = $1
138
+ ORDER BY start_seconds ASC`,
139
+ lessonId,
140
+ );
141
+
142
+ if (!transcriptionRows.length) {
143
+ await this.markError(mapId, 'Aula sem segmentos de transcrição.');
144
+ return;
145
+ }
146
+
147
+ const [areas, skills, learningTypes] = await Promise.all([
148
+ this.prisma.$queryRawUnsafe<{ id: number; slug: string; name: string }[]>(
149
+ `SELECT a.id, a.slug, COALESCE(al.name, a.slug) AS name
150
+ FROM xp_area a
151
+ LEFT JOIN xp_area_locale al ON al.xp_area_id = a.id
152
+ LEFT JOIN locale l ON l.id = al.locale_id AND l.code = 'pt'
153
+ WHERE a.status = 'active'
154
+ ORDER BY a.sort_order ASC`,
155
+ ),
156
+ this.prisma.$queryRawUnsafe<{ id: number; slug: string; name: string; areaId: number | null }[]>(
157
+ `SELECT s.id, s.slug, COALESCE(sl.name, s.slug) AS name, s.xp_area_id AS "areaId"
158
+ FROM xp_skill s
159
+ LEFT JOIN xp_skill_locale sl ON sl.xp_skill_id = s.id
160
+ LEFT JOIN locale l ON l.id = sl.locale_id AND l.code = 'pt'
161
+ WHERE s.status = 'active'
162
+ ORDER BY s.sort_order ASC`,
163
+ ),
164
+ this.prisma.$queryRawUnsafe<{ id: number; slug: string; name: string; multiplier: number }[]>(
165
+ `SELECT lt.id, lt.slug, COALESCE(ltl.name, lt.slug) AS name, lt.multiplier::float AS multiplier
166
+ FROM xp_learning_type lt
167
+ LEFT JOIN xp_learning_type_locale ltl ON ltl.xp_learning_type_id = lt.id
168
+ LEFT JOIN locale l ON l.id = ltl.locale_id AND l.code = 'pt'
169
+ WHERE lt.status = 'active'
170
+ ORDER BY lt.sort_order ASC`,
171
+ ),
172
+ ]);
173
+
174
+ const settings = await this.settingService.getSettingValues(['ai-openai-profile-id']);
175
+ const profileId = Number(settings['ai-openai-profile-id']);
176
+
177
+ if (!profileId) {
178
+ await this.markError(mapId, 'Perfil de IA OpenAI não configurado em Settings → AI.');
179
+ return;
180
+ }
181
+
182
+ const profile = await (this.prisma as any).integration_profile.findUnique({
183
+ where: { id: profileId },
184
+ include: { integration_provider: { select: { slug: true } } },
185
+ });
186
+
187
+ if (!profile) {
188
+ await this.markError(mapId, 'Perfil de IA não encontrado.');
189
+ return;
190
+ }
191
+
192
+ const aiConfig = buildAiConfigFromIntegration(profile.integration_provider.slug, profile.config);
193
+
194
+ const transcriptionText = transcriptionRows
195
+ .map((s) => {
196
+ const start = this.formatSeconds(s.startSeconds);
197
+ const end = this.formatSeconds(s.endSeconds);
198
+ return `[${start} - ${end}] ${s.text}`;
199
+ })
200
+ .join('\n');
201
+
202
+ const prompt = this.buildPrompt(transcriptionText, areas, skills, learningTypes);
203
+
204
+ const response = await axios.post(
205
+ 'https://api.openai.com/v1/chat/completions',
206
+ {
207
+ model: 'gpt-4o',
208
+ temperature: 0.2,
209
+ response_format: { type: 'json_object' },
210
+ messages: [
211
+ {
212
+ role: 'system',
213
+ content:
214
+ 'Você é um especialista em design instrucional e mapeia competências técnicas de aulas de programação. Responda apenas com JSON válido.',
215
+ },
216
+ { role: 'user', content: prompt },
217
+ ],
218
+ },
219
+ {
220
+ headers: { Authorization: `Bearer ${aiConfig.apiKey}`, 'Content-Type': 'application/json' },
221
+ timeout: 120_000,
222
+ },
223
+ );
224
+
225
+ const rawContent: string = response.data?.choices?.[0]?.message?.content ?? '{}';
226
+ let parsed: { segments?: any[] };
227
+ try {
228
+ parsed = JSON.parse(rawContent);
229
+ } catch {
230
+ await this.markError(mapId, `IA retornou JSON inválido: ${rawContent.slice(0, 200)}`);
231
+ return;
232
+ }
233
+
234
+ const rawSegments: any[] = Array.isArray(parsed.segments) ? parsed.segments : [];
235
+ const areaIds = new Set(areas.map((a) => a.id));
236
+ const skillIds = new Set(skills.map((s) => s.id));
237
+ const ltIds = new Set(learningTypes.map((l) => l.id));
238
+
239
+ const validSegments: XpSegmentAiResult[] = rawSegments
240
+ .map((s: any) => ({
241
+ startSeconds: Number(s.startSeconds) || 0,
242
+ endSeconds: Number(s.endSeconds) || 60,
243
+ xpValue: Math.min(Math.max(Math.round(Number(s.xpValue) || 0), 0), 200),
244
+ difficulty: (['easy', 'medium', 'hard', 'expert'] as const).includes(s.difficulty)
245
+ ? s.difficulty
246
+ : 'medium',
247
+ shouldGrantXp: Boolean(s.shouldGrantXp),
248
+ aiSummary: String(s.aiSummary || '').slice(0, 500),
249
+ aiConfidence: Math.min(Math.max(Number(s.aiConfidence) || 0.5, 0), 1),
250
+ areas: (Array.isArray(s.areas) ? s.areas : [])
251
+ .filter((a: any) => areaIds.has(Number(a.xpAreaId)))
252
+ .map((a: any) => ({ xpAreaId: Number(a.xpAreaId), weightPercent: Number(a.weightPercent) || 0 })),
253
+ skills: (Array.isArray(s.skills) ? s.skills : [])
254
+ .filter((sk: any) => skillIds.has(Number(sk.xpSkillId)))
255
+ .map((sk: any) => ({ xpSkillId: Number(sk.xpSkillId), weightPercent: Number(sk.weightPercent) || 0 })),
256
+ learningTypes: (Array.isArray(s.learningTypes) ? s.learningTypes : [])
257
+ .filter((lt: any) => ltIds.has(Number(lt.xpLearningTypeId)))
258
+ .map((lt: any) => ({
259
+ xpLearningTypeId: Number(lt.xpLearningTypeId),
260
+ weightPercent: Number(lt.weightPercent) || 0,
261
+ })),
262
+ }))
263
+ .filter((s) => s.endSeconds > s.startSeconds);
264
+
265
+ await this.prisma.$executeRawUnsafe(
266
+ `DELETE FROM lesson_xp_segment WHERE lesson_xp_map_id = $1`,
267
+ mapId,
268
+ );
269
+
270
+ for (let i = 0; i < validSegments.length; i++) {
271
+ const seg = validSegments[i];
272
+ const inserted = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
273
+ `INSERT INTO lesson_xp_segment
274
+ (lesson_xp_map_id, start_seconds, end_seconds, xp_value, difficulty,
275
+ should_grant_xp, ai_summary, ai_confidence, sort_order, created_at, updated_at)
276
+ VALUES ($1, $2, $3, $4, $5::lesson_xp_segment_difficulty_e5f6a7b8c9_enum,
277
+ $6, $7, $8, $9, NOW(), NOW())
278
+ RETURNING id`,
279
+ mapId, seg.startSeconds, seg.endSeconds, seg.xpValue,
280
+ seg.difficulty, seg.shouldGrantXp, seg.aiSummary,
281
+ seg.aiConfidence, i,
282
+ );
283
+ const segId = inserted[0].id;
284
+
285
+ for (const area of seg.areas) {
286
+ await this.prisma.$executeRawUnsafe(
287
+ `INSERT INTO lesson_xp_segment_area (lesson_xp_segment_id, xp_area_id, weight_percent, created_at, updated_at)
288
+ VALUES ($1, $2, $3, NOW(), NOW())`,
289
+ segId, area.xpAreaId, area.weightPercent,
290
+ );
291
+ }
292
+ for (const skill of seg.skills) {
293
+ await this.prisma.$executeRawUnsafe(
294
+ `INSERT INTO lesson_xp_segment_skill (lesson_xp_segment_id, xp_skill_id, weight_percent, created_at, updated_at)
295
+ VALUES ($1, $2, $3, NOW(), NOW())`,
296
+ segId, skill.xpSkillId, skill.weightPercent,
297
+ );
298
+ }
299
+ for (const lt of seg.learningTypes) {
300
+ await this.prisma.$executeRawUnsafe(
301
+ `INSERT INTO lesson_xp_segment_learning_type (lesson_xp_segment_id, xp_learning_type_id, weight_percent, created_at, updated_at)
302
+ VALUES ($1, $2, $3, NOW(), NOW())`,
303
+ segId, lt.xpLearningTypeId, lt.weightPercent,
304
+ );
305
+ }
306
+ }
307
+
308
+ await this.prisma.$executeRawUnsafe(
309
+ `UPDATE lesson_xp_map
310
+ SET status = 'ready'::lesson_xp_map_status_d4e5f6a7b8_enum,
311
+ generated_at = NOW(),
312
+ ai_model_version = 'gpt-4o',
313
+ processing_error = NULL,
314
+ total_xp = COALESCE(
315
+ (SELECT SUM(xp_value) FROM lesson_xp_segment
316
+ WHERE lesson_xp_map_id = $1 AND should_grant_xp = true),
317
+ 0
318
+ ),
319
+ updated_at = NOW()
320
+ WHERE id = $1`,
321
+ mapId,
322
+ );
323
+
324
+ this.logger.log(
325
+ `XP calculation done for lesson ${lessonId}: ${validSegments.length} segments saved.`,
326
+ );
327
+ } catch (err: any) {
328
+ this.logger.error(`XP calculation failed for lesson ${lessonId}: ${err?.message}`);
329
+ await this.markError(mapId, err?.message ?? 'Erro desconhecido');
330
+ }
331
+ }
332
+
333
+ private async markError(mapId: number, message: string) {
334
+ await this.prisma.$executeRawUnsafe(
335
+ `UPDATE lesson_xp_map
336
+ SET status = 'needs_review'::lesson_xp_map_status_d4e5f6a7b8_enum,
337
+ processing_error = $1,
338
+ updated_at = NOW()
339
+ WHERE id = $2`,
340
+ message.slice(0, 1000),
341
+ mapId,
342
+ );
343
+ }
344
+
345
+ private buildPrompt(
346
+ transcription: string,
347
+ areas: { id: number; slug: string; name: string }[],
348
+ skills: { id: number; slug: string; name: string; areaId: number | null }[],
349
+ learningTypes: { id: number; slug: string; name: string; multiplier: number }[],
350
+ ): string {
351
+ return `Analise a transcrição abaixo e mapeie as competências técnicas de cada segmento de ~60 segundos.
352
+
353
+ CATÁLOGO DE ÁREAS MACRO (use APENAS estes IDs):
354
+ ${JSON.stringify(areas.map((a) => ({ id: a.id, slug: a.slug, name: a.name })))}
355
+
356
+ CATÁLOGO DE SKILLS (use APENAS estes IDs):
357
+ ${JSON.stringify(skills.map((s) => ({ id: s.id, slug: s.slug, name: s.name, areaId: s.areaId })))}
358
+
359
+ CATÁLOGO DE TIPOS DE APRENDIZADO (use APENAS estes IDs):
360
+ ${JSON.stringify(learningTypes.map((l) => ({ id: l.id, slug: l.slug, name: l.name, multiplier: l.multiplier })))}
361
+
362
+ TRANSCRIÇÃO DA AULA:
363
+ ${transcription}
364
+
365
+ Retorne um JSON com a chave "segments" contendo um array onde cada item representa um segmento de ~60s:
366
+ {
367
+ "segments": [
368
+ {
369
+ "startSeconds": 0,
370
+ "endSeconds": 60,
371
+ "xpValue": 10,
372
+ "difficulty": "medium",
373
+ "shouldGrantXp": true,
374
+ "aiSummary": "Resumo curto do conteúdo do segmento",
375
+ "aiConfidence": 0.9,
376
+ "areas": [{"xpAreaId": 1, "weightPercent": 70}, {"xpAreaId": 2, "weightPercent": 30}],
377
+ "skills": [{"xpSkillId": 5, "weightPercent": 100}],
378
+ "learningTypes": [{"xpLearningTypeId": 2, "weightPercent": 100}]
379
+ }
380
+ ]
381
+ }
382
+
383
+ Regras obrigatórias:
384
+ - Use APENAS IDs do catálogo acima. Nunca invente IDs.
385
+ - xpValue entre 0 e 50 por segmento. Segmentos com conteúdo irrelevante (intro, pausa, avisos) devem ter shouldGrantXp=false e xpValue=0.
386
+ - Os weightPercent de areas, skills e learningTypes devem somar 100 cada.
387
+ - difficulty: easy (conceitos básicos), medium (aplicação), hard (avançado), expert (arquitetura/otimização).
388
+ - aiConfidence: 0 a 1, indica certeza do mapeamento.`;
389
+ }
390
+
391
+ private formatSeconds(s: number): string {
392
+ const m = Math.floor(s / 60);
393
+ const sec = Math.floor(s % 60);
394
+ return `${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
395
+ }
396
+ }