@hed-hog/lms 0.0.361 → 0.0.365

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 (434) hide show
  1. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +66 -0
  2. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
  3. package/dist/bitcode-wallet/bitcode-wallet.service.js +91 -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-export-scorm12-worker.service.d.ts +21 -0
  16. package/dist/course/course-export-scorm12-worker.service.d.ts.map +1 -0
  17. package/dist/course/course-export-scorm12-worker.service.js +109 -0
  18. package/dist/course/course-export-scorm12-worker.service.js.map +1 -0
  19. package/dist/course/course-export-scorm12.service.d.ts +42 -0
  20. package/dist/course/course-export-scorm12.service.d.ts.map +1 -0
  21. package/dist/course/course-export-scorm12.service.js +628 -0
  22. package/dist/course/course-export-scorm12.service.js.map +1 -0
  23. package/dist/course/course-export.service.d.ts +84 -0
  24. package/dist/course/course-export.service.d.ts.map +1 -0
  25. package/dist/course/course-export.service.js +237 -0
  26. package/dist/course/course-export.service.js.map +1 -0
  27. package/dist/course/course-lesson.controller.d.ts +4 -0
  28. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  29. package/dist/course/course-lesson.controller.js +10 -0
  30. package/dist/course/course-lesson.controller.js.map +1 -1
  31. package/dist/course/course-structure.controller.d.ts +24 -9
  32. package/dist/course/course-structure.controller.d.ts.map +1 -1
  33. package/dist/course/course-structure.controller.js +30 -3
  34. package/dist/course/course-structure.controller.js.map +1 -1
  35. package/dist/course/course-structure.service.d.ts +25 -3
  36. package/dist/course/course-structure.service.d.ts.map +1 -1
  37. package/dist/course/course-structure.service.js +234 -24
  38. package/dist/course/course-structure.service.js.map +1 -1
  39. package/dist/course/course-video-conversion.service.d.ts +8 -0
  40. package/dist/course/course-video-conversion.service.d.ts.map +1 -1
  41. package/dist/course/course-video-conversion.service.js +87 -51
  42. package/dist/course/course-video-conversion.service.js.map +1 -1
  43. package/dist/course/course-video-hls.service.d.ts +57 -0
  44. package/dist/course/course-video-hls.service.d.ts.map +1 -0
  45. package/dist/course/course-video-hls.service.js +767 -0
  46. package/dist/course/course-video-hls.service.js.map +1 -0
  47. package/dist/course/course.controller.d.ts +115 -11
  48. package/dist/course/course.controller.d.ts.map +1 -1
  49. package/dist/course/course.controller.js +66 -28
  50. package/dist/course/course.controller.js.map +1 -1
  51. package/dist/course/course.mcp-tools.js +1 -1
  52. package/dist/course/course.mcp-tools.js.map +1 -1
  53. package/dist/course/course.module.d.ts.map +1 -1
  54. package/dist/course/course.module.js +13 -0
  55. package/dist/course/course.module.js.map +1 -1
  56. package/dist/course/course.service.d.ts +112 -11
  57. package/dist/course/course.service.d.ts.map +1 -1
  58. package/dist/course/course.service.js +682 -72
  59. package/dist/course/course.service.js.map +1 -1
  60. package/dist/course/dto/cleanup-course-storage.dto.d.ts +6 -0
  61. package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -0
  62. package/dist/course/dto/cleanup-course-storage.dto.js +34 -0
  63. package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -0
  64. package/dist/course/dto/cleanup-upload-history.dto.d.ts +9 -0
  65. package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -0
  66. package/dist/course/dto/cleanup-upload-history.dto.js +36 -0
  67. package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -0
  68. package/dist/course/dto/create-course-bulk-job.dto.d.ts +5 -0
  69. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -0
  70. package/dist/course/dto/create-course-bulk-job.dto.js +26 -0
  71. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -0
  72. package/dist/course/dto/create-course-export.dto.d.ts +14 -0
  73. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -0
  74. package/dist/course/dto/create-course-export.dto.js +71 -0
  75. package/dist/course/dto/create-course-export.dto.js.map +1 -0
  76. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +2 -2
  77. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  78. package/dist/course/dto/create-course-structure-lesson.dto.js +3 -2
  79. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  80. package/dist/course/lms-bulk-upload-automation.service.d.ts +54 -0
  81. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -0
  82. package/dist/course/lms-bulk-upload-automation.service.js +537 -0
  83. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -0
  84. package/dist/course/lms-bulk-upload-infra.service.d.ts +32 -0
  85. package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -0
  86. package/dist/course/lms-bulk-upload-infra.service.js +301 -0
  87. package/dist/course/lms-bulk-upload-infra.service.js.map +1 -0
  88. package/dist/course/lms-bulk-upload.constants.d.ts +4 -0
  89. package/dist/course/lms-bulk-upload.constants.d.ts.map +1 -0
  90. package/dist/course/lms-bulk-upload.constants.js +7 -0
  91. package/dist/course/lms-bulk-upload.constants.js.map +1 -0
  92. package/dist/course/lms-bulk-upload.controller.d.ts +144 -1
  93. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  94. package/dist/course/lms-bulk-upload.controller.js +114 -4
  95. package/dist/course/lms-bulk-upload.controller.js.map +1 -1
  96. package/dist/course/lms-bulk-upload.service.d.ts +153 -3
  97. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  98. package/dist/course/lms-bulk-upload.service.js +659 -21
  99. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  100. package/dist/course/lms-setting.controller.d.ts +6 -2
  101. package/dist/course/lms-setting.controller.d.ts.map +1 -1
  102. package/dist/course/lms-setting.controller.js +25 -8
  103. package/dist/course/lms-setting.controller.js.map +1 -1
  104. package/dist/course/scorm12-schemas.d.ts +4 -0
  105. package/dist/course/scorm12-schemas.d.ts.map +1 -0
  106. package/dist/course/scorm12-schemas.js +9 -0
  107. package/dist/course/scorm12-schemas.js.map +1 -0
  108. package/dist/enterprise/enterprise.controller.d.ts +20 -20
  109. package/dist/enterprise/enterprise.service.d.ts +20 -20
  110. package/dist/enterprise/training/training-admin.controller.d.ts +11 -11
  111. package/dist/enterprise/training/training-admin.service.d.ts +11 -11
  112. package/dist/enterprise/training/training-instructor.controller.d.ts +2 -2
  113. package/dist/enterprise/training/training-instructor.service.d.ts +2 -2
  114. package/dist/enterprise/training/training-student.controller.d.ts +1 -1
  115. package/dist/enterprise/training/training-student.service.d.ts +52 -1
  116. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  117. package/dist/enterprise/training/training-student.service.js +217 -4
  118. package/dist/enterprise/training/training-student.service.js.map +1 -1
  119. package/dist/enterprise/training/training-viewer.controller.d.ts +2 -2
  120. package/dist/evaluation/evaluation.controller.d.ts +8 -8
  121. package/dist/evaluation/evaluation.service.d.ts +26 -8
  122. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  123. package/dist/evaluation/evaluation.service.js +125 -0
  124. package/dist/evaluation/evaluation.service.js.map +1 -1
  125. package/dist/exam/dto/create-standalone-question.dto.d.ts +12 -0
  126. package/dist/exam/dto/create-standalone-question.dto.d.ts.map +1 -0
  127. package/dist/exam/dto/create-standalone-question.dto.js +70 -0
  128. package/dist/exam/dto/create-standalone-question.dto.js.map +1 -0
  129. package/dist/exam/exam.module.d.ts.map +1 -1
  130. package/dist/exam/exam.module.js +2 -1
  131. package/dist/exam/exam.module.js.map +1 -1
  132. package/dist/exam/exam.service.d.ts +21 -0
  133. package/dist/exam/exam.service.d.ts.map +1 -1
  134. package/dist/exam/exam.service.js +80 -0
  135. package/dist/exam/exam.service.js.map +1 -1
  136. package/dist/exam/question.controller.d.ts +27 -0
  137. package/dist/exam/question.controller.d.ts.map +1 -0
  138. package/dist/exam/question.controller.js +53 -0
  139. package/dist/exam/question.controller.js.map +1 -0
  140. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts +6 -0
  141. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts.map +1 -0
  142. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js +34 -0
  143. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js.map +1 -0
  144. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts +28 -0
  145. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts.map +1 -0
  146. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js +123 -0
  147. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js.map +1 -0
  148. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts +4 -0
  149. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts.map +1 -0
  150. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js +22 -0
  151. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js.map +1 -0
  152. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts +10 -0
  153. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts.map +1 -0
  154. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js +52 -0
  155. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js.map +1 -0
  156. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts +15 -0
  157. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts.map +1 -0
  158. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js +86 -0
  159. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js.map +1 -0
  160. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +30 -0
  161. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -0
  162. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +440 -0
  163. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -0
  164. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts +87 -0
  165. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts.map +1 -0
  166. package/dist/lesson-xp-map/lesson-xp-map.controller.js +185 -0
  167. package/dist/lesson-xp-map/lesson-xp-map.controller.js.map +1 -0
  168. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts +3 -0
  169. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -0
  170. package/dist/lesson-xp-map/lesson-xp-map.module.js +34 -0
  171. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -0
  172. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts +84 -0
  173. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts.map +1 -0
  174. package/dist/lesson-xp-map/lesson-xp-map.service.js +353 -0
  175. package/dist/lesson-xp-map/lesson-xp-map.service.js.map +1 -0
  176. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts +10 -0
  177. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts.map +1 -0
  178. package/dist/lesson-xp-map/lesson-xp-segment.controller.js +63 -0
  179. package/dist/lesson-xp-map/lesson-xp-segment.controller.js.map +1 -0
  180. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts +27 -0
  181. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts.map +1 -0
  182. package/dist/lesson-xp-map/lesson-xp-segment.service.js +194 -0
  183. package/dist/lesson-xp-map/lesson-xp-segment.service.js.map +1 -0
  184. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -0
  185. package/dist/lms-commerce-access.subscriber.d.ts +11 -0
  186. package/dist/lms-commerce-access.subscriber.d.ts.map +1 -0
  187. package/dist/lms-commerce-access.subscriber.js +74 -0
  188. package/dist/lms-commerce-access.subscriber.js.map +1 -0
  189. package/dist/lms.module.d.ts.map +1 -1
  190. package/dist/lms.module.js +21 -5
  191. package/dist/lms.module.js.map +1 -1
  192. package/dist/platforma/dto/update-profile.dto.d.ts +17 -0
  193. package/dist/platforma/dto/update-profile.dto.d.ts.map +1 -0
  194. package/dist/platforma/dto/update-profile.dto.js +87 -0
  195. package/dist/platforma/dto/update-profile.dto.js.map +1 -0
  196. package/dist/platforma/platforma-video.service.d.ts +39 -0
  197. package/dist/platforma/platforma-video.service.d.ts.map +1 -0
  198. package/dist/platforma/platforma-video.service.js +301 -0
  199. package/dist/platforma/platforma-video.service.js.map +1 -0
  200. package/dist/platforma/platforma.controller.d.ts +182 -1
  201. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  202. package/dist/platforma/platforma.controller.js +243 -2
  203. package/dist/platforma/platforma.controller.js.map +1 -1
  204. package/dist/platforma/platforma.service.d.ts +27 -0
  205. package/dist/platforma/platforma.service.d.ts.map +1 -0
  206. package/dist/platforma/platforma.service.js +274 -0
  207. package/dist/platforma/platforma.service.js.map +1 -0
  208. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts +5 -0
  209. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts.map +1 -0
  210. package/dist/student-xp/dto/grant-skill-card-xp.dto.js +26 -0
  211. package/dist/student-xp/dto/grant-skill-card-xp.dto.js.map +1 -0
  212. package/dist/student-xp/student-xp.controller.d.ts +56 -0
  213. package/dist/student-xp/student-xp.controller.d.ts.map +1 -0
  214. package/dist/student-xp/student-xp.controller.js +138 -0
  215. package/dist/student-xp/student-xp.controller.js.map +1 -0
  216. package/dist/student-xp/student-xp.module.d.ts +3 -0
  217. package/dist/student-xp/student-xp.module.d.ts.map +1 -0
  218. package/dist/student-xp/student-xp.module.js +25 -0
  219. package/dist/student-xp/student-xp.module.js.map +1 -0
  220. package/dist/student-xp/student-xp.service.d.ts +81 -0
  221. package/dist/student-xp/student-xp.service.d.ts.map +1 -0
  222. package/dist/student-xp/student-xp.service.js +247 -0
  223. package/dist/student-xp/student-xp.service.js.map +1 -0
  224. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts +12 -0
  225. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts.map +1 -0
  226. package/dist/xp-catalog/dto/create-xp-area.dto.js +63 -0
  227. package/dist/xp-catalog/dto/create-xp-area.dto.js.map +1 -0
  228. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts +11 -0
  229. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts.map +1 -0
  230. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js +57 -0
  231. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js.map +1 -0
  232. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts +11 -0
  233. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts.map +1 -0
  234. package/dist/xp-catalog/dto/create-xp-skill.dto.js +57 -0
  235. package/dist/xp-catalog/dto/create-xp-skill.dto.js.map +1 -0
  236. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts +12 -0
  237. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts.map +1 -0
  238. package/dist/xp-catalog/dto/update-xp-area.dto.js +66 -0
  239. package/dist/xp-catalog/dto/update-xp-area.dto.js.map +1 -0
  240. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts +11 -0
  241. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts.map +1 -0
  242. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js +60 -0
  243. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js.map +1 -0
  244. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts +11 -0
  245. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts.map +1 -0
  246. package/dist/xp-catalog/dto/update-xp-skill.dto.js +60 -0
  247. package/dist/xp-catalog/dto/update-xp-skill.dto.js.map +1 -0
  248. package/dist/xp-catalog/xp-area.controller.d.ts +25 -0
  249. package/dist/xp-catalog/xp-area.controller.d.ts.map +1 -0
  250. package/dist/xp-catalog/xp-area.controller.js +105 -0
  251. package/dist/xp-catalog/xp-area.controller.js.map +1 -0
  252. package/dist/xp-catalog/xp-area.service.d.ts +35 -0
  253. package/dist/xp-catalog/xp-area.service.d.ts.map +1 -0
  254. package/dist/xp-catalog/xp-area.service.js +168 -0
  255. package/dist/xp-catalog/xp-area.service.js.map +1 -0
  256. package/dist/xp-catalog/xp-catalog.module.d.ts +3 -0
  257. package/dist/xp-catalog/xp-catalog.module.d.ts.map +1 -0
  258. package/dist/xp-catalog/xp-catalog.module.js +29 -0
  259. package/dist/xp-catalog/xp-catalog.module.js.map +1 -0
  260. package/dist/xp-catalog/xp-learning-type.controller.d.ts +20 -0
  261. package/dist/xp-catalog/xp-learning-type.controller.d.ts.map +1 -0
  262. package/dist/xp-catalog/xp-learning-type.controller.js +96 -0
  263. package/dist/xp-catalog/xp-learning-type.controller.js.map +1 -0
  264. package/dist/xp-catalog/xp-learning-type.service.d.ts +30 -0
  265. package/dist/xp-catalog/xp-learning-type.service.d.ts.map +1 -0
  266. package/dist/xp-catalog/xp-learning-type.service.js +146 -0
  267. package/dist/xp-catalog/xp-learning-type.service.js.map +1 -0
  268. package/dist/xp-catalog/xp-skill.controller.d.ts +26 -0
  269. package/dist/xp-catalog/xp-skill.controller.d.ts.map +1 -0
  270. package/dist/xp-catalog/xp-skill.controller.js +113 -0
  271. package/dist/xp-catalog/xp-skill.controller.js.map +1 -0
  272. package/dist/xp-catalog/xp-skill.service.d.ts +37 -0
  273. package/dist/xp-catalog/xp-skill.service.d.ts.map +1 -0
  274. package/dist/xp-catalog/xp-skill.service.js +174 -0
  275. package/dist/xp-catalog/xp-skill.service.js.map +1 -0
  276. package/hedhog/data/evaluation_topic.yaml +17 -0
  277. package/hedhog/data/menu.yaml +91 -7
  278. package/hedhog/data/queue_definition.yaml +48 -0
  279. package/hedhog/data/route.yaml +511 -29
  280. package/hedhog/data/setting_group.yaml +20 -20
  281. package/hedhog/data/xp_area.yaml +164 -0
  282. package/hedhog/data/xp_learning_type.yaml +131 -0
  283. package/hedhog/data/xp_skill.yaml +1834 -0
  284. package/hedhog/frontend/app/achievements/page.tsx.ejs +108 -118
  285. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +22 -34
  286. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +1749 -0
  287. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +21 -45
  288. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +40 -74
  289. package/hedhog/frontend/app/classes/page.tsx.ejs +56 -85
  290. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +3 -2
  291. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +48 -5
  292. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +73 -8
  293. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +420 -0
  294. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +308 -0
  295. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +19 -2
  296. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +1172 -0
  297. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +16 -0
  298. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +2 -0
  299. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +623 -0
  300. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +1458 -0
  301. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +87 -46
  302. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +8 -3
  303. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +31 -8
  304. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +16 -9
  305. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +618 -480
  306. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +672 -737
  307. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -2
  308. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +3 -0
  309. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +1 -0
  310. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +101 -85
  311. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +24 -10
  312. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +3 -0
  313. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +1 -1
  314. package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +7 -1
  315. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +44 -0
  316. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +6 -10
  317. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +49 -0
  318. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -3
  319. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +53 -0
  320. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +106 -0
  321. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +80 -1
  322. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-xp-overview.ts.ejs +76 -0
  323. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-xp-map.ts.ejs +128 -0
  324. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +0 -2
  325. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +30 -0
  326. package/hedhog/frontend/app/courses/[id]/structure/_utils/xp-color-config.ts.ejs +115 -0
  327. package/hedhog/frontend/app/courses/_components/CourseDeleteDialog.tsx.ejs +223 -0
  328. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +89 -0
  329. package/hedhog/frontend/app/courses/page.tsx.ejs +445 -230
  330. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -63
  331. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +53 -77
  332. package/hedhog/frontend/app/exams/page.tsx.ejs +54 -90
  333. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +23 -36
  334. package/hedhog/frontend/app/instructors/page.tsx.ejs +72 -81
  335. package/hedhog/frontend/app/paths/page.tsx.ejs +40 -68
  336. package/hedhog/frontend/app/training/page.tsx.ejs +40 -68
  337. package/hedhog/frontend/app/xp/areas/page.tsx.ejs +782 -0
  338. package/hedhog/frontend/app/xp/learning-types/page.tsx.ejs +690 -0
  339. package/hedhog/frontend/app/xp/skills/page.tsx.ejs +811 -0
  340. package/hedhog/frontend/messages/en.json +412 -31
  341. package/hedhog/frontend/messages/pt.json +412 -31
  342. package/hedhog/table/course_export.yaml +62 -0
  343. package/hedhog/table/lesson_xp_map.yaml +50 -0
  344. package/hedhog/table/lesson_xp_segment.yaml +40 -0
  345. package/hedhog/table/lesson_xp_segment_area.yaml +24 -0
  346. package/hedhog/table/lesson_xp_segment_learning_type.yaml +24 -0
  347. package/hedhog/table/lesson_xp_segment_skill.yaml +24 -0
  348. package/hedhog/table/lms_bulk_upload_item.yaml +44 -0
  349. package/hedhog/table/lms_bulk_upload_session.yaml +42 -0
  350. package/hedhog/table/student_area_xp.yaml +30 -0
  351. package/hedhog/table/student_learning_type_xp.yaml +30 -0
  352. package/hedhog/table/student_skill_xp.yaml +30 -0
  353. package/hedhog/table/student_xp_event.yaml +34 -0
  354. package/hedhog/table/xp_area.yaml +39 -0
  355. package/hedhog/table/xp_learning_type.yaml +34 -0
  356. package/hedhog/table/xp_skill.yaml +39 -0
  357. package/package.json +13 -8
  358. package/src/bitcode-wallet/bitcode-wallet.service.ts +152 -0
  359. package/src/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.ts +32 -0
  360. package/src/course/course-audio-transcription.service.ts +58 -21
  361. package/src/course/course-export-scorm12-worker.service.ts +124 -0
  362. package/src/course/course-export-scorm12.service.ts +668 -0
  363. package/src/course/course-export.service.ts +280 -0
  364. package/src/course/course-lesson.controller.ts +6 -1
  365. package/src/course/course-structure.controller.ts +23 -1
  366. package/src/course/course-structure.service.ts +273 -7
  367. package/src/course/course-video-conversion.service.ts +113 -75
  368. package/src/course/course-video-hls.service.ts +946 -0
  369. package/src/course/course.controller.ts +54 -21
  370. package/src/course/course.mcp-tools.ts +1 -1
  371. package/src/course/course.module.ts +13 -0
  372. package/src/course/course.service.ts +906 -76
  373. package/src/course/dto/cleanup-course-storage.dto.ts +23 -0
  374. package/src/course/dto/cleanup-upload-history.dto.ts +26 -0
  375. package/src/course/dto/create-course-bulk-job.dto.ts +10 -0
  376. package/src/course/dto/create-course-export.dto.ts +56 -0
  377. package/src/course/dto/create-course-structure-lesson.dto.ts +4 -3
  378. package/src/course/lms-bulk-upload-automation.service.ts +707 -0
  379. package/src/course/lms-bulk-upload-infra.service.ts +360 -0
  380. package/src/course/lms-bulk-upload.constants.ts +5 -0
  381. package/src/course/lms-bulk-upload.controller.ts +110 -4
  382. package/src/course/lms-bulk-upload.service.ts +1092 -204
  383. package/src/course/lms-setting.controller.ts +26 -8
  384. package/src/course/scorm12-schemas.ts +9 -0
  385. package/src/enterprise/training/training-student.service.ts +221 -2
  386. package/src/evaluation/evaluation.service.ts +123 -0
  387. package/src/exam/dto/create-standalone-question.dto.ts +66 -0
  388. package/src/exam/exam.module.ts +2 -1
  389. package/src/exam/exam.service.ts +86 -0
  390. package/src/exam/question.controller.ts +28 -0
  391. package/src/lesson-xp-map/dto/create-lesson-xp-map.dto.ts +17 -0
  392. package/src/lesson-xp-map/dto/create-lesson-xp-segment.dto.ts +102 -0
  393. package/src/lesson-xp-map/dto/review-lesson-xp-map.dto.ts +7 -0
  394. package/src/lesson-xp-map/dto/update-lesson-xp-map.dto.ts +36 -0
  395. package/src/lesson-xp-map/dto/update-lesson-xp-segment.dto.ts +78 -0
  396. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +570 -0
  397. package/src/lesson-xp-map/lesson-xp-map.controller.ts +116 -0
  398. package/src/lesson-xp-map/lesson-xp-map.module.ts +21 -0
  399. package/src/lesson-xp-map/lesson-xp-map.service.ts +442 -0
  400. package/src/lesson-xp-map/lesson-xp-segment.controller.ts +36 -0
  401. package/src/lesson-xp-map/lesson-xp-segment.service.ts +229 -0
  402. package/src/lms-commerce-access.subscriber.ts +88 -0
  403. package/src/lms.module.ts +21 -5
  404. package/src/platforma/dto/update-profile.dto.ts +59 -0
  405. package/src/platforma/platforma-video.service.ts +346 -0
  406. package/src/platforma/platforma.controller.ts +152 -3
  407. package/src/platforma/platforma.service.ts +268 -0
  408. package/src/student-xp/dto/grant-skill-card-xp.dto.ts +10 -0
  409. package/src/student-xp/student-xp.controller.ts +92 -0
  410. package/src/student-xp/student-xp.module.ts +12 -0
  411. package/src/student-xp/student-xp.service.ts +318 -0
  412. package/src/xp-catalog/dto/create-xp-area.dto.ts +40 -0
  413. package/src/xp-catalog/dto/create-xp-learning-type.dto.ts +35 -0
  414. package/src/xp-catalog/dto/create-xp-skill.dto.ts +35 -0
  415. package/src/xp-catalog/dto/update-xp-area.dto.ts +43 -0
  416. package/src/xp-catalog/dto/update-xp-learning-type.dto.ts +38 -0
  417. package/src/xp-catalog/dto/update-xp-skill.dto.ts +38 -0
  418. package/src/xp-catalog/xp-area.controller.ts +64 -0
  419. package/src/xp-catalog/xp-area.service.ts +196 -0
  420. package/src/xp-catalog/xp-catalog.module.ts +16 -0
  421. package/src/xp-catalog/xp-learning-type.controller.ts +59 -0
  422. package/src/xp-catalog/xp-learning-type.service.ts +170 -0
  423. package/src/xp-catalog/xp-skill.controller.ts +71 -0
  424. package/src/xp-catalog/xp-skill.service.ts +205 -0
  425. package/hedhog/data/video_resolution_profile.yaml +0 -7
  426. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +0 -607
  427. package/hedhog/table/course_video_resolution_profile.yaml +0 -22
  428. package/hedhog/table/video_resolution_profile.yaml +0 -18
  429. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +0 -16
  430. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +0 -16
  431. package/src/video-resolution-profile/video-resolution-profile.controller.ts +0 -62
  432. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +0 -128
  433. package/src/video-resolution-profile/video-resolution-profile.module.ts +0 -13
  434. package/src/video-resolution-profile/video-resolution-profile.service.ts +0 -117
@@ -0,0 +1,570 @@
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
+ type LessonMeta = {
28
+ type: 'video' | 'text' | 'quiz';
29
+ content: string | null;
30
+ description: string | null;
31
+ durationSeconds: number;
32
+ };
33
+
34
+ @Injectable()
35
+ export class LessonXpAiCalculationService implements OnModuleInit, IJobHandler {
36
+ private readonly logger = new Logger(LessonXpAiCalculationService.name);
37
+
38
+ constructor(
39
+ @Inject(forwardRef(() => PrismaService))
40
+ private readonly prisma: PrismaService,
41
+ @Inject(forwardRef(() => SettingService))
42
+ private readonly settingService: SettingService,
43
+ @Inject(forwardRef(() => QueueHandlerRegistry))
44
+ private readonly registry: QueueHandlerRegistry,
45
+ @Inject(forwardRef(() => DatabaseQueueProvider))
46
+ private readonly dbQueue: DatabaseQueueProvider,
47
+ ) {}
48
+
49
+ onModuleInit() {
50
+ this.registry.register('lms.lesson.xp.calculate', this);
51
+ this.logger.log('Registered handler for "lms.lesson.xp.calculate"');
52
+ }
53
+
54
+ async triggerCalculation(lessonId: number, userId: number) {
55
+ const lesson = await this.fetchLessonMeta(lessonId);
56
+
57
+ if (!lesson) {
58
+ throw new BadRequestException('Aula não encontrada.');
59
+ }
60
+
61
+ if (lesson.type === 'video') {
62
+ const segments = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
63
+ `SELECT id FROM course_lesson_transcription_segment WHERE course_lesson_id = $1::int LIMIT 1`,
64
+ lessonId,
65
+ );
66
+ if (!segments.length) {
67
+ throw new BadRequestException(
68
+ 'Esta aula ainda não possui transcrição. Gere ou importe a transcrição antes de calcular o XP.',
69
+ );
70
+ }
71
+ } else if (lesson.type === 'text') {
72
+ if (!lesson.durationSeconds || lesson.durationSeconds <= 0) {
73
+ throw new BadRequestException(
74
+ 'Esta aula do tipo post precisa ter uma duração prevista configurada antes de calcular o XP.',
75
+ );
76
+ }
77
+ let parsed: any = {};
78
+ try { parsed = JSON.parse(lesson.content ?? '{}'); } catch { /* ignore */ }
79
+ if (!parsed.conteudoPost && !lesson.description) {
80
+ throw new BadRequestException(
81
+ 'Esta aula do tipo post não possui conteúdo nem descrição pública. Adicione o conteúdo antes de calcular o XP.',
82
+ );
83
+ }
84
+ } else if (lesson.type === 'quiz') {
85
+ const questions = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
86
+ `SELECT id FROM course_lesson_question WHERE course_lesson_id = $1::int LIMIT 1`,
87
+ lessonId,
88
+ );
89
+ if (!questions.length) {
90
+ throw new BadRequestException(
91
+ 'Esta aula do tipo questão não possui questões vinculadas. Adicione questões antes de calcular o XP.',
92
+ );
93
+ }
94
+ }
95
+
96
+ const existing = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
97
+ `SELECT id FROM lesson_xp_map WHERE course_lesson_id = $1::int`,
98
+ lessonId,
99
+ );
100
+
101
+ let mapId: number;
102
+
103
+ if (existing.length) {
104
+ mapId = existing[0].id;
105
+ await this.prisma.$executeRawUnsafe(
106
+ `UPDATE lesson_xp_map
107
+ SET status = 'processing'::lesson_xp_map_status_d4e5f6a7b8_enum,
108
+ processing_error = NULL,
109
+ updated_at = NOW()
110
+ WHERE id = $1`,
111
+ mapId,
112
+ );
113
+ } else {
114
+ const created = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
115
+ `INSERT INTO lesson_xp_map (course_lesson_id, version, total_xp, status, created_at, updated_at)
116
+ VALUES ($1, 1, 0, 'processing'::lesson_xp_map_status_d4e5f6a7b8_enum, NOW(), NOW())
117
+ RETURNING id`,
118
+ lessonId,
119
+ );
120
+ mapId = created[0].id;
121
+ }
122
+
123
+ try {
124
+ await this.dbQueue.enqueue({
125
+ type: 'lms.lesson.xp.calculate',
126
+ queueName: 'lms.lesson.xp.calculate',
127
+ payload: { lessonId, mapId, userId },
128
+ maxAttempts: 3,
129
+ sourceModule: 'lms',
130
+ sourceEntity: 'course_lesson',
131
+ sourceEntityId: String(lessonId),
132
+ userId,
133
+ });
134
+ } catch (err: any) {
135
+ // P2002 = unique constraint — a job for this lesson is already queued/running
136
+ if (err?.code !== 'P2002') throw err;
137
+ }
138
+
139
+ const rows = await this.prisma.$queryRawUnsafe<any[]>(
140
+ `SELECT
141
+ m.id,
142
+ m.course_lesson_id AS "courseLessonId",
143
+ m.version,
144
+ m.total_xp AS "totalXp",
145
+ m.status::text AS status,
146
+ m.generated_at::text AS "generatedAt",
147
+ m.processing_error AS "processingError",
148
+ m.created_at::text AS "createdAt",
149
+ m.updated_at::text AS "updatedAt"
150
+ FROM lesson_xp_map m WHERE m.id = $1`,
151
+ mapId,
152
+ );
153
+ return { ...rows[0], segments: [] };
154
+ }
155
+
156
+ async handle(job: {
157
+ id: number;
158
+ type: string;
159
+ queue_name: string;
160
+ payload: Record<string, any>;
161
+ attempts: number;
162
+ max_attempts: number;
163
+ }): Promise<void> {
164
+ const { lessonId, mapId } = job.payload as { lessonId: number; mapId: number };
165
+
166
+ this.logger.log(`Starting XP calculation for lesson ${lessonId}, map ${mapId}`);
167
+
168
+ try {
169
+ const lesson = await this.fetchLessonMeta(lessonId);
170
+
171
+ if (!lesson) {
172
+ await this.markError(mapId, 'Aula não encontrada.');
173
+ return;
174
+ }
175
+
176
+ let contentText: string;
177
+ const durationSeconds = lesson.durationSeconds ?? 0;
178
+
179
+ if (lesson.type === 'video') {
180
+ const transcriptionRows = await this.prisma.$queryRawUnsafe<
181
+ { startSeconds: number; endSeconds: number; text: string }[]
182
+ >(
183
+ `SELECT start_seconds::float AS "startSeconds", end_seconds::float AS "endSeconds", text
184
+ FROM course_lesson_transcription_segment
185
+ WHERE course_lesson_id = $1
186
+ ORDER BY start_seconds ASC`,
187
+ lessonId,
188
+ );
189
+
190
+ if (!transcriptionRows.length) {
191
+ await this.markError(mapId, 'Aula sem segmentos de transcrição.');
192
+ return;
193
+ }
194
+
195
+ contentText = transcriptionRows
196
+ .map((s) => {
197
+ const start = this.formatSeconds(s.startSeconds);
198
+ const end = this.formatSeconds(s.endSeconds);
199
+ return `[${start} - ${end}] ${s.text}`;
200
+ })
201
+ .join('\n');
202
+ } else if (lesson.type === 'text') {
203
+ contentText = this.buildPostContentText(lesson.content, lesson.description);
204
+ if (!contentText.trim()) {
205
+ await this.markError(mapId, 'Aula do tipo post sem conteúdo disponível para análise.');
206
+ return;
207
+ }
208
+ } else if (lesson.type === 'quiz') {
209
+ contentText = await this.buildQuizContentText(lessonId);
210
+ if (!contentText.trim()) {
211
+ await this.markError(mapId, 'Aula do tipo questão sem questões disponíveis para análise.');
212
+ return;
213
+ }
214
+ } else {
215
+ await this.markError(mapId, `Tipo de aula não suportado: ${(lesson as any).type}`);
216
+ return;
217
+ }
218
+
219
+ const [areas, skills, learningTypes] = await Promise.all([
220
+ this.prisma.$queryRawUnsafe<{ id: number; slug: string; name: string }[]>(
221
+ `SELECT a.id, a.slug, COALESCE(al.name, a.slug) AS name
222
+ FROM xp_area a
223
+ LEFT JOIN xp_area_locale al ON al.xp_area_id = a.id
224
+ LEFT JOIN locale l ON l.id = al.locale_id AND l.code = 'pt'
225
+ WHERE a.status = 'active'
226
+ ORDER BY a.sort_order ASC`,
227
+ ),
228
+ this.prisma.$queryRawUnsafe<{ id: number; slug: string; name: string; areaId: number | null }[]>(
229
+ `SELECT s.id, s.slug, COALESCE(sl.name, s.slug) AS name, s.xp_area_id AS "areaId"
230
+ FROM xp_skill s
231
+ LEFT JOIN xp_skill_locale sl ON sl.xp_skill_id = s.id
232
+ LEFT JOIN locale l ON l.id = sl.locale_id AND l.code = 'pt'
233
+ WHERE s.status = 'active'
234
+ ORDER BY s.sort_order ASC`,
235
+ ),
236
+ this.prisma.$queryRawUnsafe<{ id: number; slug: string; name: string; multiplier: number }[]>(
237
+ `SELECT lt.id, lt.slug, COALESCE(ltl.name, lt.slug) AS name, lt.multiplier::float AS multiplier
238
+ FROM xp_learning_type lt
239
+ LEFT JOIN xp_learning_type_locale ltl ON ltl.xp_learning_type_id = lt.id
240
+ LEFT JOIN locale l ON l.id = ltl.locale_id AND l.code = 'pt'
241
+ WHERE lt.status = 'active'
242
+ ORDER BY lt.sort_order ASC`,
243
+ ),
244
+ ]);
245
+
246
+ const settings = await this.settingService.getSettingValues(['ai-openai-profile-id']);
247
+ const profileId = Number(settings['ai-openai-profile-id']);
248
+
249
+ if (!profileId) {
250
+ await this.markError(mapId, 'Perfil de IA OpenAI não configurado em Settings → AI.');
251
+ return;
252
+ }
253
+
254
+ const profile = await (this.prisma as any).integration_profile.findUnique({
255
+ where: { id: profileId },
256
+ include: { integration_provider: { select: { slug: true } } },
257
+ });
258
+
259
+ if (!profile) {
260
+ await this.markError(mapId, 'Perfil de IA não encontrado.');
261
+ return;
262
+ }
263
+
264
+ const aiConfig = buildAiConfigFromIntegration(profile.integration_provider.slug, profile.config);
265
+
266
+ const prompt = this.buildPrompt(contentText, areas, skills, learningTypes, lesson.type, durationSeconds);
267
+
268
+ const response = await axios.post(
269
+ 'https://api.openai.com/v1/chat/completions',
270
+ {
271
+ model: 'gpt-4o',
272
+ temperature: 0.2,
273
+ response_format: { type: 'json_object' },
274
+ messages: [
275
+ {
276
+ role: 'system',
277
+ content:
278
+ 'Você é um especialista em design instrucional e mapeia competências técnicas de aulas de programação. Responda apenas com JSON válido.',
279
+ },
280
+ { role: 'user', content: prompt },
281
+ ],
282
+ },
283
+ {
284
+ headers: { Authorization: `Bearer ${aiConfig.apiKey}`, 'Content-Type': 'application/json' },
285
+ timeout: 120_000,
286
+ },
287
+ );
288
+
289
+ const rawContent: string = response.data?.choices?.[0]?.message?.content ?? '{}';
290
+ let parsed: { segments?: any[] };
291
+ try {
292
+ parsed = JSON.parse(rawContent);
293
+ } catch {
294
+ await this.markError(mapId, `IA retornou JSON inválido: ${rawContent.slice(0, 200)}`);
295
+ return;
296
+ }
297
+
298
+ const rawSegments: any[] = Array.isArray(parsed.segments) ? parsed.segments : [];
299
+ const areaIds = new Set(areas.map((a) => a.id));
300
+ const skillIds = new Set(skills.map((s) => s.id));
301
+ const ltIds = new Set(learningTypes.map((l) => l.id));
302
+
303
+ const validSegments: XpSegmentAiResult[] = rawSegments
304
+ .map((s: any) => ({
305
+ startSeconds: Number(s.startSeconds) || 0,
306
+ endSeconds: Number(s.endSeconds) || 60,
307
+ xpValue: Math.min(Math.max(Math.round(Number(s.xpValue) || 0), 0), 200),
308
+ difficulty: (['easy', 'medium', 'hard', 'expert'] as const).includes(s.difficulty)
309
+ ? s.difficulty
310
+ : 'medium',
311
+ shouldGrantXp: Boolean(s.shouldGrantXp),
312
+ aiSummary: String(s.aiSummary || '').slice(0, 500),
313
+ aiConfidence: Math.min(Math.max(Number(s.aiConfidence) || 0.5, 0), 1),
314
+ areas: (Array.isArray(s.areas) ? s.areas : [])
315
+ .filter((a: any) => areaIds.has(Number(a.xpAreaId)))
316
+ .map((a: any) => ({ xpAreaId: Number(a.xpAreaId), weightPercent: Number(a.weightPercent) || 0 })),
317
+ skills: (Array.isArray(s.skills) ? s.skills : [])
318
+ .filter((sk: any) => skillIds.has(Number(sk.xpSkillId)))
319
+ .map((sk: any) => ({ xpSkillId: Number(sk.xpSkillId), weightPercent: Number(sk.weightPercent) || 0 })),
320
+ learningTypes: (Array.isArray(s.learningTypes) ? s.learningTypes : [])
321
+ .filter((lt: any) => ltIds.has(Number(lt.xpLearningTypeId)))
322
+ .map((lt: any) => ({
323
+ xpLearningTypeId: Number(lt.xpLearningTypeId),
324
+ weightPercent: Number(lt.weightPercent) || 0,
325
+ })),
326
+ }))
327
+ .filter((s) => s.endSeconds > s.startSeconds);
328
+
329
+ await this.prisma.$executeRawUnsafe(
330
+ `DELETE FROM lesson_xp_segment WHERE lesson_xp_map_id = $1`,
331
+ mapId,
332
+ );
333
+
334
+ for (let i = 0; i < validSegments.length; i++) {
335
+ const seg = validSegments[i];
336
+ const inserted = await this.prisma.$queryRawUnsafe<{ id: number }[]>(
337
+ `INSERT INTO lesson_xp_segment
338
+ (lesson_xp_map_id, start_seconds, end_seconds, xp_value, difficulty,
339
+ should_grant_xp, ai_summary, ai_confidence, sort_order, created_at, updated_at)
340
+ VALUES ($1, $2, $3, $4, $5::lesson_xp_segment_difficulty_e5f6a7b8c9_enum,
341
+ $6, $7, $8, $9, NOW(), NOW())
342
+ RETURNING id`,
343
+ mapId, seg.startSeconds, seg.endSeconds, seg.xpValue,
344
+ seg.difficulty, seg.shouldGrantXp, seg.aiSummary,
345
+ seg.aiConfidence, i,
346
+ );
347
+ const segId = inserted[0].id;
348
+
349
+ for (const area of seg.areas) {
350
+ await this.prisma.$executeRawUnsafe(
351
+ `INSERT INTO lesson_xp_segment_area (lesson_xp_segment_id, xp_area_id, weight_percent, created_at, updated_at)
352
+ VALUES ($1, $2, $3, NOW(), NOW())`,
353
+ segId, area.xpAreaId, area.weightPercent,
354
+ );
355
+ }
356
+ for (const skill of seg.skills) {
357
+ await this.prisma.$executeRawUnsafe(
358
+ `INSERT INTO lesson_xp_segment_skill (lesson_xp_segment_id, xp_skill_id, weight_percent, created_at, updated_at)
359
+ VALUES ($1, $2, $3, NOW(), NOW())`,
360
+ segId, skill.xpSkillId, skill.weightPercent,
361
+ );
362
+ }
363
+ for (const lt of seg.learningTypes) {
364
+ await this.prisma.$executeRawUnsafe(
365
+ `INSERT INTO lesson_xp_segment_learning_type (lesson_xp_segment_id, xp_learning_type_id, weight_percent, created_at, updated_at)
366
+ VALUES ($1, $2, $3, NOW(), NOW())`,
367
+ segId, lt.xpLearningTypeId, lt.weightPercent,
368
+ );
369
+ }
370
+ }
371
+
372
+ await this.prisma.$executeRawUnsafe(
373
+ `UPDATE lesson_xp_map
374
+ SET status = 'ready'::lesson_xp_map_status_d4e5f6a7b8_enum,
375
+ generated_at = NOW(),
376
+ ai_model_version = 'gpt-4o',
377
+ processing_error = NULL,
378
+ total_xp = COALESCE(
379
+ (SELECT SUM(xp_value) FROM lesson_xp_segment
380
+ WHERE lesson_xp_map_id = $1 AND should_grant_xp = true),
381
+ 0
382
+ ),
383
+ updated_at = NOW()
384
+ WHERE id = $1`,
385
+ mapId,
386
+ );
387
+
388
+ this.logger.log(
389
+ `XP calculation done for lesson ${lessonId}: ${validSegments.length} segments saved.`,
390
+ );
391
+ } catch (err: any) {
392
+ this.logger.error(`XP calculation failed for lesson ${lessonId}: ${err?.message}`);
393
+ await this.markError(mapId, err?.message ?? 'Erro desconhecido');
394
+ }
395
+ }
396
+
397
+ private async fetchLessonMeta(lessonId: number): Promise<LessonMeta | null> {
398
+ const rows = await this.prisma.$queryRawUnsafe<LessonMeta[]>(
399
+ `SELECT type::text AS type, content, description, duration_seconds AS "durationSeconds"
400
+ FROM course_lesson WHERE id = $1`,
401
+ lessonId,
402
+ );
403
+ return rows[0] ?? null;
404
+ }
405
+
406
+ private buildPostContentText(content: string | null, description: string | null): string {
407
+ const parts: string[] = [];
408
+
409
+ if (description?.trim()) {
410
+ parts.push(`Descrição pública:\n${description.trim()}`);
411
+ }
412
+
413
+ let postContent = '';
414
+ try {
415
+ const parsed = JSON.parse(content ?? '{}');
416
+ postContent = String(parsed.conteudoPost ?? '').trim();
417
+ } catch { /* ignore */ }
418
+
419
+ if (postContent) {
420
+ parts.push(`Conteúdo do post:\n${this.stripHtml(postContent)}`);
421
+ }
422
+
423
+ return parts.join('\n\n');
424
+ }
425
+
426
+ private async buildQuizContentText(lessonId: number): Promise<string> {
427
+ const questions = await this.prisma.$queryRawUnsafe<{
428
+ statement: string;
429
+ explanation: string | null;
430
+ options: { text: string; isCorrect: boolean; feedback: string | null }[] | null;
431
+ }[]>(
432
+ `SELECT
433
+ q.statement,
434
+ q.explanation,
435
+ COALESCE(
436
+ json_agg(
437
+ json_build_object('text', o.option_text, 'isCorrect', o.is_correct, 'feedback', o.feedback)
438
+ ORDER BY o.position ASC
439
+ ) FILTER (WHERE o.id IS NOT NULL),
440
+ '[]'
441
+ ) AS options
442
+ FROM course_lesson_question clq
443
+ JOIN question q ON q.id = clq.question_id
444
+ LEFT JOIN exam_option o ON o.question_id = q.id
445
+ WHERE clq.course_lesson_id = $1
446
+ GROUP BY clq.order, q.statement, q.explanation
447
+ ORDER BY clq.order ASC`,
448
+ lessonId,
449
+ );
450
+
451
+ if (!questions.length) return '';
452
+
453
+ return questions.map((q, i) => {
454
+ const lines: string[] = [`Questão ${i + 1}:`];
455
+ lines.push(`Enunciado: ${q.statement}`);
456
+
457
+ const opts = Array.isArray(q.options) ? q.options : [];
458
+ if (opts.length) {
459
+ const labels = 'abcdefghijklmnopqrstuvwxyz';
460
+ lines.push(
461
+ 'Alternativas: ' +
462
+ opts
463
+ .map((o, idx) => `${labels[idx] ?? idx + 1}) ${o.text}${o.isCorrect ? ' [CORRETA]' : ''}`)
464
+ .join('; '),
465
+ );
466
+ }
467
+
468
+ if (q.explanation?.trim()) {
469
+ lines.push(`Explicação: ${q.explanation.trim()}`);
470
+ }
471
+
472
+ return lines.join('\n');
473
+ }).join('\n\n');
474
+ }
475
+
476
+ private async markError(mapId: number, message: string) {
477
+ await this.prisma.$executeRawUnsafe(
478
+ `UPDATE lesson_xp_map
479
+ SET status = 'needs_review'::lesson_xp_map_status_d4e5f6a7b8_enum,
480
+ processing_error = $1,
481
+ updated_at = NOW()
482
+ WHERE id = $2`,
483
+ message.slice(0, 1000),
484
+ mapId,
485
+ );
486
+ }
487
+
488
+ private buildPrompt(
489
+ content: string,
490
+ areas: { id: number; slug: string; name: string }[],
491
+ skills: { id: number; slug: string; name: string; areaId: number | null }[],
492
+ learningTypes: { id: number; slug: string; name: string; multiplier: number }[],
493
+ lessonType: 'video' | 'text' | 'quiz',
494
+ durationSeconds: number,
495
+ ): string {
496
+ const totalMinutes = Math.max(Math.ceil(durationSeconds / 60), 1);
497
+
498
+ const contentHeader =
499
+ lessonType === 'video'
500
+ ? 'TRANSCRIÇÃO DA AULA'
501
+ : lessonType === 'text'
502
+ ? 'CONTEÚDO DO POST'
503
+ : 'QUESTÕES DA AULA';
504
+
505
+ const segmentInstruction =
506
+ lessonType === 'video'
507
+ ? 'Analise a transcrição abaixo e mapeie as competências técnicas de cada segmento de ~60 segundos.'
508
+ : lessonType === 'text'
509
+ ? `Analise o conteúdo do post abaixo (duração estimada: ${totalMinutes} minutos) e mapeie as competências técnicas. Divida o conteúdo em segmentos de ~60 segundos cobrindo a duração total. Os valores de startSeconds e endSeconds devem cobrir de 0 até ${durationSeconds} segundos.`
510
+ : `Analise as questões da aula abaixo (duração estimada: ${totalMinutes} minutos). Cada questão representa um segmento; distribua o tempo total de ${durationSeconds} segundos igualmente entre as questões. Os valores de startSeconds e endSeconds devem cobrir de 0 até ${durationSeconds} segundos.`;
511
+
512
+ return `${segmentInstruction}
513
+
514
+ CATÁLOGO DE ÁREAS MACRO (use APENAS estes IDs):
515
+ ${JSON.stringify(areas.map((a) => ({ id: a.id, slug: a.slug, name: a.name })))}
516
+
517
+ CATÁLOGO DE SKILLS (use APENAS estes IDs):
518
+ ${JSON.stringify(skills.map((s) => ({ id: s.id, slug: s.slug, name: s.name, areaId: s.areaId })))}
519
+
520
+ CATÁLOGO DE TIPOS DE APRENDIZADO (use APENAS estes IDs):
521
+ ${JSON.stringify(learningTypes.map((l) => ({ id: l.id, slug: l.slug, name: l.name, multiplier: l.multiplier })))}
522
+
523
+ ${contentHeader}:
524
+ ${content}
525
+
526
+ Retorne um JSON com a chave "segments" contendo um array onde cada item representa um segmento de ~60s:
527
+ {
528
+ "segments": [
529
+ {
530
+ "startSeconds": 0,
531
+ "endSeconds": 60,
532
+ "xpValue": 10,
533
+ "difficulty": "medium",
534
+ "shouldGrantXp": true,
535
+ "aiSummary": "Resumo curto do conteúdo do segmento",
536
+ "aiConfidence": 0.9,
537
+ "areas": [{"xpAreaId": 1, "weightPercent": 70}, {"xpAreaId": 2, "weightPercent": 30}],
538
+ "skills": [{"xpSkillId": 5, "weightPercent": 100}],
539
+ "learningTypes": [{"xpLearningTypeId": 2, "weightPercent": 100}]
540
+ }
541
+ ]
542
+ }
543
+
544
+ Regras obrigatórias:
545
+ - Use APENAS IDs do catálogo acima. Nunca invente IDs.
546
+ - xpValue entre 0 e 50 por segmento. Segmentos com conteúdo irrelevante (intro, pausa, avisos) devem ter shouldGrantXp=false e xpValue=0.
547
+ - Os weightPercent de areas, skills e learningTypes devem somar 100 cada.
548
+ - difficulty: easy (conceitos básicos), medium (aplicação), hard (avançado), expert (arquitetura/otimização).
549
+ - aiConfidence: 0 a 1, indica certeza do mapeamento.`;
550
+ }
551
+
552
+ private stripHtml(html: string): string {
553
+ return html
554
+ .replace(/<[^>]+>/g, ' ')
555
+ .replace(/&amp;/g, '&')
556
+ .replace(/&lt;/g, '<')
557
+ .replace(/&gt;/g, '>')
558
+ .replace(/&quot;/g, '"')
559
+ .replace(/&#39;/g, "'")
560
+ .replace(/&nbsp;/g, ' ')
561
+ .replace(/\s{2,}/g, ' ')
562
+ .trim();
563
+ }
564
+
565
+ private formatSeconds(s: number): string {
566
+ const m = Math.floor(s / 60);
567
+ const sec = Math.floor(s % 60);
568
+ return `${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
569
+ }
570
+ }
@@ -0,0 +1,116 @@
1
+ import { Role } from '@hed-hog/api';
2
+ import {
3
+ Body,
4
+ Controller,
5
+ Delete,
6
+ Get,
7
+ HttpCode,
8
+ HttpStatus,
9
+ Param,
10
+ ParseIntPipe,
11
+ Patch,
12
+ Post,
13
+ Query,
14
+ Request,
15
+ } from '@nestjs/common';
16
+ import { CreateLessonXpMapDto } from './dto/create-lesson-xp-map.dto';
17
+ import { CreateLessonXpSegmentDto } from './dto/create-lesson-xp-segment.dto';
18
+ import { RejectLessonXpMapDto } from './dto/review-lesson-xp-map.dto';
19
+ import { UpdateLessonXpMapDto } from './dto/update-lesson-xp-map.dto';
20
+ import { LessonXpAiCalculationService } from './lesson-xp-ai-calculation.service';
21
+ import { LessonXpMapService } from './lesson-xp-map.service';
22
+ import { LessonXpSegmentService } from './lesson-xp-segment.service';
23
+
24
+ @Role()
25
+ @Controller('lms/xp/lesson-maps')
26
+ export class LessonXpMapController {
27
+ constructor(
28
+ private readonly lessonXpMapService: LessonXpMapService,
29
+ private readonly lessonXpSegmentService: LessonXpSegmentService,
30
+ private readonly lessonXpAiCalculationService: LessonXpAiCalculationService,
31
+ ) {}
32
+
33
+ @Get()
34
+ list(
35
+ @Query('page') page?: string,
36
+ @Query('pageSize') pageSize?: string,
37
+ @Query('search') search?: string,
38
+ @Query('status') status?: string,
39
+ @Query('courseLessonId') courseLessonId?: string,
40
+ ) {
41
+ return this.lessonXpMapService.list({
42
+ page: Number(page) || 1,
43
+ pageSize: Number(pageSize) || 20,
44
+ search,
45
+ status,
46
+ courseLessonId: courseLessonId ? Number(courseLessonId) : undefined,
47
+ });
48
+ }
49
+
50
+ @Post()
51
+ create(@Body() dto: CreateLessonXpMapDto) {
52
+ return this.lessonXpMapService.create(dto);
53
+ }
54
+
55
+ @Get('lesson/:lessonId')
56
+ getByLesson(@Param('lessonId', ParseIntPipe) lessonId: number) {
57
+ return this.lessonXpMapService.getByLessonId(lessonId);
58
+ }
59
+
60
+ @Get('course/:courseId/overview')
61
+ getCourseOverview(@Param('courseId', ParseIntPipe) courseId: number) {
62
+ return this.lessonXpMapService.getCourseOverview(courseId);
63
+ }
64
+
65
+ @Post('lesson/:lessonId/recalculate')
66
+ recalculate(@Param('lessonId', ParseIntPipe) lessonId: number, @Request() req: any) {
67
+ return this.lessonXpAiCalculationService.triggerCalculation(lessonId, req.user?.id ?? 0);
68
+ }
69
+
70
+ @Get(':id')
71
+ getById(@Param('id', ParseIntPipe) id: number) {
72
+ return this.lessonXpMapService.getById(id);
73
+ }
74
+
75
+ @Patch(':id')
76
+ update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateLessonXpMapDto) {
77
+ return this.lessonXpMapService.update(id, dto);
78
+ }
79
+
80
+ @Delete(':id')
81
+ @HttpCode(HttpStatus.NO_CONTENT)
82
+ remove(@Param('id', ParseIntPipe) id: number) {
83
+ return this.lessonXpMapService.delete(id);
84
+ }
85
+
86
+ @Post(':id/approve')
87
+ approve(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
88
+ const reviewerPersonId: number = req.user?.personId ?? req.user?.id ?? 0;
89
+ return this.lessonXpMapService.approve(id, reviewerPersonId);
90
+ }
91
+
92
+ @Post(':id/reject')
93
+ reject(
94
+ @Param('id', ParseIntPipe) id: number,
95
+ @Body() dto: RejectLessonXpMapDto,
96
+ @Request() req: any,
97
+ ) {
98
+ const reviewerPersonId: number = req.user?.personId ?? req.user?.id ?? 0;
99
+ return this.lessonXpMapService.reject(id, reviewerPersonId, dto.reviewNotes);
100
+ }
101
+
102
+ // Segment sub-resource
103
+ @Get(':id/segments')
104
+ listSegments(
105
+ @Param('id', ParseIntPipe) id: number,
106
+ @Query('page') page?: string,
107
+ @Query('pageSize') pageSize?: string,
108
+ ) {
109
+ return this.lessonXpSegmentService.listByMap(id, { page: Number(page) || 1, pageSize: Number(pageSize) || 50 });
110
+ }
111
+
112
+ @Post(':id/segments')
113
+ createSegment(@Param('id', ParseIntPipe) id: number, @Body() dto: CreateLessonXpSegmentDto) {
114
+ return this.lessonXpSegmentService.create(id, dto);
115
+ }
116
+ }