@atproto/ozone 0.1.172 → 0.1.174

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 (450) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +40 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/moderation/emitEvent.d.ts.map +1 -1
  6. package/dist/api/moderation/emitEvent.js +31 -0
  7. package/dist/api/moderation/emitEvent.js.map +1 -1
  8. package/dist/api/queue/assignModerator.d.ts +4 -0
  9. package/dist/api/queue/assignModerator.d.ts.map +1 -0
  10. package/dist/api/queue/assignModerator.js +28 -0
  11. package/dist/api/queue/assignModerator.js.map +1 -0
  12. package/dist/api/queue/createQueue.d.ts +4 -0
  13. package/dist/api/queue/createQueue.d.ts.map +1 -0
  14. package/dist/api/queue/createQueue.js +44 -0
  15. package/dist/api/queue/createQueue.js.map +1 -0
  16. package/dist/api/queue/deleteQueue.d.ts +4 -0
  17. package/dist/api/queue/deleteQueue.d.ts.map +1 -0
  18. package/dist/api/queue/deleteQueue.js +40 -0
  19. package/dist/api/queue/deleteQueue.js.map +1 -0
  20. package/dist/api/queue/getAssignments.d.ts +4 -0
  21. package/dist/api/queue/getAssignments.d.ts.map +1 -0
  22. package/dist/api/queue/getAssignments.js +19 -0
  23. package/dist/api/queue/getAssignments.js.map +1 -0
  24. package/dist/api/queue/listQueues.d.ts +4 -0
  25. package/dist/api/queue/listQueues.d.ts.map +1 -0
  26. package/dist/api/queue/listQueues.js +29 -0
  27. package/dist/api/queue/listQueues.js.map +1 -0
  28. package/dist/api/queue/routeReports.d.ts +4 -0
  29. package/dist/api/queue/routeReports.d.ts.map +1 -0
  30. package/dist/api/queue/routeReports.js +33 -0
  31. package/dist/api/queue/routeReports.js.map +1 -0
  32. package/dist/api/queue/unassignModerator.d.ts +4 -0
  33. package/dist/api/queue/unassignModerator.d.ts.map +1 -0
  34. package/dist/api/queue/unassignModerator.js +24 -0
  35. package/dist/api/queue/unassignModerator.js.map +1 -0
  36. package/dist/api/queue/updateQueue.d.ts +4 -0
  37. package/dist/api/queue/updateQueue.d.ts.map +1 -0
  38. package/dist/api/queue/updateQueue.js +39 -0
  39. package/dist/api/queue/updateQueue.js.map +1 -0
  40. package/dist/api/report/assignModerator.d.ts +4 -0
  41. package/dist/api/report/assignModerator.d.ts.map +1 -0
  42. package/dist/api/report/assignModerator.js +33 -0
  43. package/dist/api/report/assignModerator.js.map +1 -0
  44. package/dist/api/report/createActivity.d.ts +4 -0
  45. package/dist/api/report/createActivity.d.ts.map +1 -0
  46. package/dist/api/report/createActivity.js +44 -0
  47. package/dist/api/report/createActivity.js.map +1 -0
  48. package/dist/api/report/getAssignments.d.ts +4 -0
  49. package/dist/api/report/getAssignments.d.ts.map +1 -0
  50. package/dist/api/report/getAssignments.js +19 -0
  51. package/dist/api/report/getAssignments.js.map +1 -0
  52. package/dist/api/report/getHistoricalStats.d.ts +4 -0
  53. package/dist/api/report/getHistoricalStats.d.ts.map +1 -0
  54. package/dist/api/report/getHistoricalStats.js +32 -0
  55. package/dist/api/report/getHistoricalStats.js.map +1 -0
  56. package/dist/api/report/getLatestReport.d.ts +4 -0
  57. package/dist/api/report/getLatestReport.d.ts.map +1 -0
  58. package/dist/api/report/getLatestReport.js +31 -0
  59. package/dist/api/report/getLatestReport.js.map +1 -0
  60. package/dist/api/report/getLiveStats.d.ts +4 -0
  61. package/dist/api/report/getLiveStats.d.ts.map +1 -0
  62. package/dist/api/report/getLiveStats.js +25 -0
  63. package/dist/api/report/getLiveStats.js.map +1 -0
  64. package/dist/api/report/getReport.d.ts +4 -0
  65. package/dist/api/report/getReport.d.ts.map +1 -0
  66. package/dist/api/report/getReport.js +35 -0
  67. package/dist/api/report/getReport.js.map +1 -0
  68. package/dist/api/report/listActivities.d.ts +4 -0
  69. package/dist/api/report/listActivities.d.ts.map +1 -0
  70. package/dist/api/report/listActivities.js +25 -0
  71. package/dist/api/report/listActivities.js.map +1 -0
  72. package/dist/api/report/queryReports.d.ts +4 -0
  73. package/dist/api/report/queryReports.d.ts.map +1 -0
  74. package/dist/api/report/queryReports.js +29 -0
  75. package/dist/api/report/queryReports.js.map +1 -0
  76. package/dist/api/report/reassignQueue.d.ts +4 -0
  77. package/dist/api/report/reassignQueue.d.ts.map +1 -0
  78. package/dist/api/report/reassignQueue.js +45 -0
  79. package/dist/api/report/reassignQueue.js.map +1 -0
  80. package/dist/api/report/refreshStats.d.ts +4 -0
  81. package/dist/api/report/refreshStats.d.ts.map +1 -0
  82. package/dist/api/report/refreshStats.js +26 -0
  83. package/dist/api/report/refreshStats.js.map +1 -0
  84. package/dist/api/report/unassignModerator.d.ts +4 -0
  85. package/dist/api/report/unassignModerator.d.ts.map +1 -0
  86. package/dist/api/report/unassignModerator.js +21 -0
  87. package/dist/api/report/unassignModerator.js.map +1 -0
  88. package/dist/api/util.d.ts +2 -0
  89. package/dist/api/util.d.ts.map +1 -1
  90. package/dist/api/util.js +9 -1
  91. package/dist/api/util.js.map +1 -1
  92. package/dist/assignment/index.d.ts +89 -0
  93. package/dist/assignment/index.d.ts.map +1 -0
  94. package/dist/assignment/index.js +537 -0
  95. package/dist/assignment/index.js.map +1 -0
  96. package/dist/config/config.d.ts +14 -0
  97. package/dist/config/config.d.ts.map +1 -1
  98. package/dist/config/config.js +9 -0
  99. package/dist/config/config.js.map +1 -1
  100. package/dist/config/env.d.ts +3 -0
  101. package/dist/config/env.d.ts.map +1 -1
  102. package/dist/config/env.js +3 -0
  103. package/dist/config/env.js.map +1 -1
  104. package/dist/context.d.ts +9 -0
  105. package/dist/context.d.ts.map +1 -1
  106. package/dist/context.js +31 -10
  107. package/dist/context.js.map +1 -1
  108. package/dist/daemon/context.d.ts +6 -0
  109. package/dist/daemon/context.d.ts.map +1 -1
  110. package/dist/daemon/context.js +28 -4
  111. package/dist/daemon/context.js.map +1 -1
  112. package/dist/daemon/event-reverser.d.ts +1 -0
  113. package/dist/daemon/event-reverser.d.ts.map +1 -1
  114. package/dist/daemon/event-reverser.js +42 -1
  115. package/dist/daemon/event-reverser.js.map +1 -1
  116. package/dist/daemon/job-cursor.d.ts +5 -0
  117. package/dist/daemon/job-cursor.d.ts.map +1 -0
  118. package/dist/daemon/job-cursor.js +28 -0
  119. package/dist/daemon/job-cursor.js.map +1 -0
  120. package/dist/daemon/queue-router.d.ts +17 -0
  121. package/dist/daemon/queue-router.d.ts.map +1 -0
  122. package/dist/daemon/queue-router.js +114 -0
  123. package/dist/daemon/queue-router.js.map +1 -0
  124. package/dist/daemon/stats-computer.d.ts +51 -0
  125. package/dist/daemon/stats-computer.d.ts.map +1 -0
  126. package/dist/daemon/stats-computer.js +117 -0
  127. package/dist/daemon/stats-computer.js.map +1 -0
  128. package/dist/daemon/strike-expiry-processor.d.ts.map +1 -1
  129. package/dist/daemon/strike-expiry-processor.js +4 -19
  130. package/dist/daemon/strike-expiry-processor.js.map +1 -1
  131. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts +4 -0
  132. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts.map +1 -0
  133. package/dist/db/migrations/20260219T164523000Z-create-report-table.js +126 -0
  134. package/dist/db/migrations/20260219T164523000Z-create-report-table.js.map +1 -0
  135. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts +4 -0
  136. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts.map +1 -0
  137. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js +35 -0
  138. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js.map +1 -0
  139. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts +4 -0
  140. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts.map +1 -0
  141. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js +36 -0
  142. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js.map +1 -0
  143. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts +4 -0
  144. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts.map +1 -0
  145. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js +39 -0
  146. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js.map +1 -0
  147. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts +4 -0
  148. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts.map +1 -0
  149. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js +34 -0
  150. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js.map +1 -0
  151. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.d.ts +4 -0
  152. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.d.ts.map +1 -0
  153. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.js +32 -0
  154. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.js.map +1 -0
  155. package/dist/db/migrations/index.d.ts +6 -0
  156. package/dist/db/migrations/index.d.ts.map +1 -1
  157. package/dist/db/migrations/index.js +7 -1
  158. package/dist/db/migrations/index.js.map +1 -1
  159. package/dist/db/pagination.d.ts +31 -0
  160. package/dist/db/pagination.d.ts.map +1 -1
  161. package/dist/db/pagination.js +74 -1
  162. package/dist/db/pagination.js.map +1 -1
  163. package/dist/db/schema/expiring_tag.d.ts +15 -0
  164. package/dist/db/schema/expiring_tag.d.ts.map +1 -0
  165. package/dist/db/schema/expiring_tag.js +5 -0
  166. package/dist/db/schema/expiring_tag.js.map +1 -0
  167. package/dist/db/schema/index.d.ts +7 -1
  168. package/dist/db/schema/index.d.ts.map +1 -1
  169. package/dist/db/schema/index.js.map +1 -1
  170. package/dist/db/schema/moderator_assignment.d.ts +14 -0
  171. package/dist/db/schema/moderator_assignment.d.ts.map +1 -0
  172. package/dist/db/schema/moderator_assignment.js +5 -0
  173. package/dist/db/schema/moderator_assignment.js.map +1 -0
  174. package/dist/db/schema/report.d.ts +25 -0
  175. package/dist/db/schema/report.d.ts.map +1 -0
  176. package/dist/db/schema/report.js +5 -0
  177. package/dist/db/schema/report.js.map +1 -0
  178. package/dist/db/schema/report_activity.d.ts +18 -0
  179. package/dist/db/schema/report_activity.d.ts.map +1 -0
  180. package/dist/db/schema/report_activity.js +5 -0
  181. package/dist/db/schema/report_activity.js.map +1 -0
  182. package/dist/db/schema/report_queue.d.ts +19 -0
  183. package/dist/db/schema/report_queue.d.ts.map +1 -0
  184. package/dist/db/schema/report_queue.js +5 -0
  185. package/dist/db/schema/report_queue.js.map +1 -0
  186. package/dist/db/schema/report_stat.d.ts +20 -0
  187. package/dist/db/schema/report_stat.d.ts.map +1 -0
  188. package/dist/db/schema/report_stat.js +5 -0
  189. package/dist/db/schema/report_stat.js.map +1 -0
  190. package/dist/lexicon/index.d.ts +50 -0
  191. package/dist/lexicon/index.d.ts.map +1 -1
  192. package/dist/lexicon/index.js +120 -2
  193. package/dist/lexicon/index.js.map +1 -1
  194. package/dist/lexicon/lexicons.d.ts +11255 -7885
  195. package/dist/lexicon/lexicons.d.ts.map +1 -1
  196. package/dist/lexicon/lexicons.js +1900 -120
  197. package/dist/lexicon/lexicons.js.map +1 -1
  198. package/dist/lexicon/types/app/bsky/embed/external.d.ts +2 -0
  199. package/dist/lexicon/types/app/bsky/embed/external.d.ts.map +1 -1
  200. package/dist/lexicon/types/app/bsky/embed/external.js.map +1 -1
  201. package/dist/lexicon/types/chat/bsky/actor/defs.d.ts +8 -2
  202. package/dist/lexicon/types/chat/bsky/actor/defs.d.ts.map +1 -1
  203. package/dist/lexicon/types/chat/bsky/actor/defs.js +9 -0
  204. package/dist/lexicon/types/chat/bsky/actor/defs.js.map +1 -1
  205. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +37 -10
  206. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
  207. package/dist/lexicon/types/chat/bsky/convo/defs.js +9 -0
  208. package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
  209. package/dist/lexicon/types/chat/bsky/convo/getMessages.d.ts +3 -0
  210. package/dist/lexicon/types/chat/bsky/convo/getMessages.d.ts.map +1 -1
  211. package/dist/lexicon/types/chat/bsky/convo/getMessages.js.map +1 -1
  212. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -0
  213. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  214. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  215. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +19 -0
  216. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  217. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js +9 -0
  218. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js.map +1 -1
  219. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts +27 -0
  220. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts.map +1 -0
  221. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js +7 -0
  222. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js.map +1 -0
  223. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts +35 -0
  224. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts.map +1 -0
  225. package/dist/lexicon/types/tools/ozone/queue/createQueue.js +7 -0
  226. package/dist/lexicon/types/tools/ozone/queue/createQueue.js.map +1 -0
  227. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts +62 -0
  228. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts.map +1 -0
  229. package/dist/lexicon/types/tools/ozone/queue/defs.js +34 -0
  230. package/dist/lexicon/types/tools/ozone/queue/defs.js.map +1 -0
  231. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts +29 -0
  232. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts.map +1 -0
  233. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js +7 -0
  234. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js.map +1 -0
  235. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts +30 -0
  236. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts.map +1 -0
  237. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js +7 -0
  238. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js.map +1 -0
  239. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts +32 -0
  240. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts.map +1 -0
  241. package/dist/lexicon/types/tools/ozone/queue/listQueues.js +7 -0
  242. package/dist/lexicon/types/tools/ozone/queue/listQueues.js.map +1 -0
  243. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts +31 -0
  244. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts.map +1 -0
  245. package/dist/lexicon/types/tools/ozone/queue/routeReports.js +7 -0
  246. package/dist/lexicon/types/tools/ozone/queue/routeReports.js.map +1 -0
  247. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts +18 -0
  248. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts.map +1 -0
  249. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js +7 -0
  250. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js.map +1 -0
  251. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts +32 -0
  252. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts.map +1 -0
  253. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js +7 -0
  254. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js.map +1 -0
  255. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts +31 -0
  256. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts.map +1 -0
  257. package/dist/lexicon/types/tools/ozone/report/assignModerator.js +7 -0
  258. package/dist/lexicon/types/tools/ozone/report/assignModerator.js.map +1 -0
  259. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts +37 -0
  260. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts.map +1 -0
  261. package/dist/lexicon/types/tools/ozone/report/createActivity.js +7 -0
  262. package/dist/lexicon/types/tools/ozone/report/createActivity.js.map +1 -0
  263. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +185 -0
  264. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  265. package/dist/lexicon/types/tools/ozone/report/defs.js +108 -0
  266. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  267. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts +30 -0
  268. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts.map +1 -0
  269. package/dist/lexicon/types/tools/ozone/report/getAssignments.js +7 -0
  270. package/dist/lexicon/types/tools/ozone/report/getAssignments.js.map +1 -0
  271. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts +36 -0
  272. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts.map +1 -0
  273. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js +7 -0
  274. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js.map +1 -0
  275. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts +21 -0
  276. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts.map +1 -0
  277. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js +7 -0
  278. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js.map +1 -0
  279. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts +27 -0
  280. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts.map +1 -0
  281. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js +7 -0
  282. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js.map +1 -0
  283. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts +22 -0
  284. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts.map +1 -0
  285. package/dist/lexicon/types/tools/ozone/report/getReport.js +7 -0
  286. package/dist/lexicon/types/tools/ozone/report/getReport.js.map +1 -0
  287. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts +26 -0
  288. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts.map +1 -0
  289. package/dist/lexicon/types/tools/ozone/report/listActivities.js +7 -0
  290. package/dist/lexicon/types/tools/ozone/report/listActivities.js.map +1 -0
  291. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts +48 -0
  292. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts.map +1 -0
  293. package/dist/lexicon/types/tools/ozone/report/queryReports.js +7 -0
  294. package/dist/lexicon/types/tools/ozone/report/queryReports.js.map +1 -0
  295. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts +31 -0
  296. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts.map +1 -0
  297. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js +7 -0
  298. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js.map +1 -0
  299. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts +28 -0
  300. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts.map +1 -0
  301. package/dist/lexicon/types/tools/ozone/report/refreshStats.js +7 -0
  302. package/dist/lexicon/types/tools/ozone/report/refreshStats.js.map +1 -0
  303. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts +25 -0
  304. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts.map +1 -0
  305. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js +7 -0
  306. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js.map +1 -0
  307. package/dist/mod-service/expiring-tags.d.ts +27 -0
  308. package/dist/mod-service/expiring-tags.d.ts.map +1 -0
  309. package/dist/mod-service/expiring-tags.js +62 -0
  310. package/dist/mod-service/expiring-tags.js.map +1 -0
  311. package/dist/mod-service/index.d.ts +3 -1
  312. package/dist/mod-service/index.d.ts.map +1 -1
  313. package/dist/mod-service/index.js +61 -2
  314. package/dist/mod-service/index.js.map +1 -1
  315. package/dist/mod-service/report.d.ts +64 -0
  316. package/dist/mod-service/report.d.ts.map +1 -0
  317. package/dist/mod-service/report.js +282 -0
  318. package/dist/mod-service/report.js.map +1 -0
  319. package/dist/mod-service/status.d.ts +24 -0
  320. package/dist/mod-service/status.d.ts.map +1 -1
  321. package/dist/queue/service.d.ts +86 -0
  322. package/dist/queue/service.d.ts.map +1 -0
  323. package/dist/queue/service.js +430 -0
  324. package/dist/queue/service.js.map +1 -0
  325. package/dist/report/activity.d.ts +77 -0
  326. package/dist/report/activity.d.ts.map +1 -0
  327. package/dist/report/activity.js +141 -0
  328. package/dist/report/activity.js.map +1 -0
  329. package/dist/report/handle-report-update.d.ts +47 -0
  330. package/dist/report/handle-report-update.d.ts.map +1 -0
  331. package/dist/report/handle-report-update.js +178 -0
  332. package/dist/report/handle-report-update.js.map +1 -0
  333. package/dist/report/reassign.d.ts +10 -0
  334. package/dist/report/reassign.d.ts.map +1 -0
  335. package/dist/report/reassign.js +75 -0
  336. package/dist/report/reassign.js.map +1 -0
  337. package/dist/report/stats.d.ts +105 -0
  338. package/dist/report/stats.d.ts.map +1 -0
  339. package/dist/report/stats.js +619 -0
  340. package/dist/report/stats.js.map +1 -0
  341. package/dist/report/views.d.ts +111 -0
  342. package/dist/report/views.d.ts.map +1 -0
  343. package/dist/report/views.js +156 -0
  344. package/dist/report/views.js.map +1 -0
  345. package/dist/team/index.d.ts +1 -0
  346. package/dist/team/index.d.ts.map +1 -1
  347. package/dist/team/index.js +11 -0
  348. package/dist/team/index.js.map +1 -1
  349. package/package.json +3 -3
  350. package/src/api/index.ts +40 -0
  351. package/src/api/moderation/emitEvent.ts +38 -0
  352. package/src/api/queue/assignModerator.ts +31 -0
  353. package/src/api/queue/createQueue.ts +62 -0
  354. package/src/api/queue/deleteQueue.ts +56 -0
  355. package/src/api/queue/getAssignments.ts +19 -0
  356. package/src/api/queue/listQueues.ts +39 -0
  357. package/src/api/queue/routeReports.ts +44 -0
  358. package/src/api/queue/unassignModerator.ts +26 -0
  359. package/src/api/queue/updateQueue.ts +54 -0
  360. package/src/api/report/assignModerator.ts +36 -0
  361. package/src/api/report/createActivity.ts +57 -0
  362. package/src/api/report/getAssignments.ts +20 -0
  363. package/src/api/report/getHistoricalStats.ts +41 -0
  364. package/src/api/report/getLatestReport.ts +44 -0
  365. package/src/api/report/getLiveStats.ts +26 -0
  366. package/src/api/report/getReport.ts +55 -0
  367. package/src/api/report/listActivities.ts +34 -0
  368. package/src/api/report/queryReports.ts +44 -0
  369. package/src/api/report/reassignQueue.ts +68 -0
  370. package/src/api/report/refreshStats.ts +27 -0
  371. package/src/api/report/unassignModerator.ts +21 -0
  372. package/src/api/util.ts +12 -0
  373. package/src/assignment/index.ts +731 -0
  374. package/src/config/config.ts +27 -0
  375. package/src/config/env.ts +8 -0
  376. package/src/context.ts +31 -0
  377. package/src/daemon/context.ts +34 -0
  378. package/src/daemon/event-reverser.ts +50 -1
  379. package/src/daemon/job-cursor.ts +33 -0
  380. package/src/daemon/queue-router.ts +101 -0
  381. package/src/daemon/stats-computer.ts +101 -0
  382. package/src/daemon/strike-expiry-processor.ts +4 -20
  383. package/src/db/migrations/20260219T164523000Z-create-report-table.ts +155 -0
  384. package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +42 -0
  385. package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +41 -0
  386. package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +48 -0
  387. package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +35 -0
  388. package/src/db/migrations/20260428T000000000Z-add-expiring-tag-table.ts +32 -0
  389. package/src/db/migrations/index.ts +6 -0
  390. package/src/db/pagination.ts +85 -0
  391. package/src/db/schema/expiring_tag.ts +17 -0
  392. package/src/db/schema/index.ts +13 -1
  393. package/src/db/schema/moderator_assignment.ts +16 -0
  394. package/src/db/schema/report.ts +27 -0
  395. package/src/db/schema/report_activity.ts +22 -0
  396. package/src/db/schema/report_queue.ts +21 -0
  397. package/src/db/schema/report_stat.ts +27 -0
  398. package/src/lexicon/index.ts +280 -0
  399. package/src/lexicon/lexicons.ts +2083 -214
  400. package/src/lexicon/types/app/bsky/embed/external.ts +2 -0
  401. package/src/lexicon/types/chat/bsky/actor/defs.ts +17 -1
  402. package/src/lexicon/types/chat/bsky/convo/defs.ts +50 -10
  403. package/src/lexicon/types/chat/bsky/convo/getMessages.ts +3 -0
  404. package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -0
  405. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +24 -0
  406. package/src/lexicon/types/tools/ozone/queue/assignModerator.ts +46 -0
  407. package/src/lexicon/types/tools/ozone/queue/createQueue.ts +54 -0
  408. package/src/lexicon/types/tools/ozone/queue/defs.ts +99 -0
  409. package/src/lexicon/types/tools/ozone/queue/deleteQueue.ts +48 -0
  410. package/src/lexicon/types/tools/ozone/queue/getAssignments.ts +48 -0
  411. package/src/lexicon/types/tools/ozone/queue/listQueues.ts +50 -0
  412. package/src/lexicon/types/tools/ozone/queue/routeReports.ts +50 -0
  413. package/src/lexicon/types/tools/ozone/queue/unassignModerator.ts +37 -0
  414. package/src/lexicon/types/tools/ozone/queue/updateQueue.ts +51 -0
  415. package/src/lexicon/types/tools/ozone/report/assignModerator.ts +50 -0
  416. package/src/lexicon/types/tools/ozone/report/createActivity.ts +60 -0
  417. package/src/lexicon/types/tools/ozone/report/defs.ts +327 -0
  418. package/src/lexicon/types/tools/ozone/report/getAssignments.ts +48 -0
  419. package/src/lexicon/types/tools/ozone/report/getHistoricalStats.ts +54 -0
  420. package/src/lexicon/types/tools/ozone/report/getLatestReport.ts +39 -0
  421. package/src/lexicon/types/tools/ozone/report/getLiveStats.ts +45 -0
  422. package/src/lexicon/types/tools/ozone/report/getReport.ts +38 -0
  423. package/src/lexicon/types/tools/ozone/report/listActivities.ts +44 -0
  424. package/src/lexicon/types/tools/ozone/report/queryReports.ts +72 -0
  425. package/src/lexicon/types/tools/ozone/report/reassignQueue.ts +55 -0
  426. package/src/lexicon/types/tools/ozone/report/refreshStats.ts +46 -0
  427. package/src/lexicon/types/tools/ozone/report/unassignModerator.ts +44 -0
  428. package/src/mod-service/expiring-tags.ts +98 -0
  429. package/src/mod-service/index.ts +71 -3
  430. package/src/mod-service/report.ts +408 -0
  431. package/src/queue/service.ts +599 -0
  432. package/src/report/activity.ts +234 -0
  433. package/src/report/handle-report-update.ts +209 -0
  434. package/src/report/reassign.ts +109 -0
  435. package/src/report/stats.ts +850 -0
  436. package/src/report/views.ts +241 -0
  437. package/src/team/index.ts +11 -0
  438. package/tests/expiring-tags.test.ts +231 -0
  439. package/tests/get-report.test.ts +136 -0
  440. package/tests/query-reports.test.ts +608 -0
  441. package/tests/queue-assignment.test.ts +428 -0
  442. package/tests/queue-router.test.ts +306 -0
  443. package/tests/queues.test.ts +690 -0
  444. package/tests/report-action.test.ts +308 -0
  445. package/tests/report-activity.test.ts +567 -0
  446. package/tests/report-assignment.test.ts +517 -0
  447. package/tests/report-reassign-queue.test.ts +340 -0
  448. package/tests/report-routing.test.ts +245 -0
  449. package/tests/report-stats.test.ts +545 -0
  450. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,619 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReportStatsService = exports.REPORT_TYPE_GROUPS = void 0;
4
+ const kysely_1 = require("kysely");
5
+ const common_1 = require("@atproto/common");
6
+ const pagination_1 = require("../db/pagination");
7
+ const types_1 = require("../db/types");
8
+ const logger_1 = require("../logger");
9
+ /**
10
+ * Grouped report types. Stats are computed per group rather than per individual report type.
11
+ */
12
+ exports.REPORT_TYPE_GROUPS = {
13
+ Legacy: [
14
+ 'com.atproto.moderation.defs#reasonSpam',
15
+ 'com.atproto.moderation.defs#reasonViolation',
16
+ 'com.atproto.moderation.defs#reasonMisleading',
17
+ 'com.atproto.moderation.defs#reasonSexual',
18
+ 'com.atproto.moderation.defs#reasonRude',
19
+ 'com.atproto.moderation.defs#reasonOther',
20
+ 'com.atproto.moderation.defs#reasonAppeal',
21
+ ],
22
+ Appeal: ['tools.ozone.report.defs#reasonAppeal'],
23
+ Violence: [
24
+ 'tools.ozone.report.defs#reasonViolenceAnimalWelfare',
25
+ 'tools.ozone.report.defs#reasonViolenceThreats',
26
+ 'tools.ozone.report.defs#reasonViolenceGraphicContent',
27
+ 'tools.ozone.report.defs#reasonViolenceSelfHarm',
28
+ 'tools.ozone.report.defs#reasonViolenceGlorification',
29
+ 'tools.ozone.report.defs#reasonViolenceExtremistContent',
30
+ 'tools.ozone.report.defs#reasonViolenceTrafficking',
31
+ 'tools.ozone.report.defs#reasonViolenceOther',
32
+ ],
33
+ Sexual: [
34
+ 'tools.ozone.report.defs#reasonSexualAbuseContent',
35
+ 'tools.ozone.report.defs#reasonSexualNCII',
36
+ 'tools.ozone.report.defs#reasonSexualSextortion',
37
+ 'tools.ozone.report.defs#reasonSexualDeepfake',
38
+ 'tools.ozone.report.defs#reasonSexualAnimal',
39
+ 'tools.ozone.report.defs#reasonSexualUnlabeled',
40
+ 'tools.ozone.report.defs#reasonSexualOther',
41
+ ],
42
+ 'Child Safety': [
43
+ 'tools.ozone.report.defs#reasonChildSafetyCSAM',
44
+ 'tools.ozone.report.defs#reasonChildSafetyGroom',
45
+ 'tools.ozone.report.defs#reasonChildSafetyMinorPrivacy',
46
+ 'tools.ozone.report.defs#reasonChildSafetyEndangerment',
47
+ 'tools.ozone.report.defs#reasonChildSafetyHarassment',
48
+ 'tools.ozone.report.defs#reasonChildSafetyPromotion',
49
+ 'tools.ozone.report.defs#reasonChildSafetyOther',
50
+ ],
51
+ Harassment: [
52
+ 'tools.ozone.report.defs#reasonHarassmentTroll',
53
+ 'tools.ozone.report.defs#reasonHarassmentTargeted',
54
+ 'tools.ozone.report.defs#reasonHarassmentHateSpeech',
55
+ 'tools.ozone.report.defs#reasonHarassmentDoxxing',
56
+ 'tools.ozone.report.defs#reasonHarassmentOther',
57
+ ],
58
+ Misleading: [
59
+ 'tools.ozone.report.defs#reasonMisleadingBot',
60
+ 'tools.ozone.report.defs#reasonMisleadingImpersonation',
61
+ 'tools.ozone.report.defs#reasonMisleadingSpam',
62
+ 'tools.ozone.report.defs#reasonMisleadingScam',
63
+ 'tools.ozone.report.defs#reasonMisleadingSyntheticContent',
64
+ 'tools.ozone.report.defs#reasonMisleadingMisinformation',
65
+ 'tools.ozone.report.defs#reasonMisleadingOther',
66
+ ],
67
+ 'Rule Violations': [
68
+ 'tools.ozone.report.defs#reasonRuleSiteSecurity',
69
+ 'tools.ozone.report.defs#reasonRuleStolenContent',
70
+ 'tools.ozone.report.defs#reasonRuleProhibitedSales',
71
+ 'tools.ozone.report.defs#reasonRuleBanEvasion',
72
+ 'tools.ozone.report.defs#reasonRuleOther',
73
+ ],
74
+ Civic: [
75
+ 'tools.ozone.report.defs#reasonCivicElectoralProcess',
76
+ 'tools.ozone.report.defs#reasonCivicDisclosure',
77
+ 'tools.ozone.report.defs#reasonCivicInterference',
78
+ 'tools.ozone.report.defs#reasonCivicMisinformation',
79
+ 'tools.ozone.report.defs#reasonCivicImpersonation',
80
+ ],
81
+ };
82
+ const REPORT_STAT_LIVE_TTL = 15 * common_1.MINUTE;
83
+ class ReportStatsService {
84
+ constructor(db) {
85
+ Object.defineProperty(this, "db", {
86
+ enumerable: true,
87
+ configurable: true,
88
+ writable: true,
89
+ value: db
90
+ });
91
+ }
92
+ static creator() {
93
+ return (db) => new ReportStatsService(db);
94
+ }
95
+ /**
96
+ * Compute stats for today and finalize yesterday if needed.
97
+ * Called periodically by the StatsComputer daemon.
98
+ */
99
+ async materializeAll(opts) {
100
+ try {
101
+ const start = Date.now();
102
+ const today = toDateString(new Date());
103
+ const yesterday = toDateString(new Date(Date.now() - 24 * 60 * 60 * 1000));
104
+ // Always compute today's stats
105
+ await this.materializeDate(today, opts);
106
+ // Finalize yesterday if its snapshot is missing or stale
107
+ if (!opts?.force) {
108
+ const yesterdayRow = await this.db.db
109
+ .selectFrom('report_stat')
110
+ .select('computedAt')
111
+ .where('date', '=', yesterday)
112
+ .orderBy('computedAt', 'desc')
113
+ .executeTakeFirst();
114
+ const endOfYesterday = new Date(`${yesterday}T23:59:59.999Z`).getTime();
115
+ if (!yesterdayRow ||
116
+ new Date(yesterdayRow.computedAt).getTime() < endOfYesterday) {
117
+ await this.materializeDate(yesterday, { force: true });
118
+ }
119
+ }
120
+ else {
121
+ await this.materializeDate(yesterday, { force: true });
122
+ }
123
+ const duration = Date.now() - start;
124
+ logger_1.dbLogger.info({ duration }, 'report stats materialization completed');
125
+ }
126
+ catch (err) {
127
+ logger_1.dbLogger.error({ err }, 'report stats materialization errored');
128
+ }
129
+ }
130
+ /**
131
+ * Compute stats for a specific date range. Used by the refreshStats endpoint.
132
+ */
133
+ async refreshDateRange(opts) {
134
+ const start = new Date(opts.startDate);
135
+ const end = new Date(opts.endDate);
136
+ for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
137
+ const dateStr = toDateString(d);
138
+ if (opts.queueIds?.length) {
139
+ // Recompute only specific queue groups for this date
140
+ const batched = await this.computeBatchedStats(dateStr);
141
+ const rows = [];
142
+ for (const queueId of opts.queueIds) {
143
+ const group = {
144
+ queueId,
145
+ moderatorDid: null,
146
+ reportTypes: null,
147
+ };
148
+ const stats = this.resolveGroupStats(group, batched);
149
+ rows.push(this.buildUpsertRow(dateStr, group, stats));
150
+ }
151
+ await this.bulkUpsert(rows);
152
+ }
153
+ else {
154
+ await this.materializeDate(dateStr, { force: true });
155
+ }
156
+ }
157
+ }
158
+ /** Compute and write all groups for a single date. */
159
+ async materializeDate(date, opts) {
160
+ const groups = await this.enumerateGroups();
161
+ const batched = await this.computeBatchedStats(date);
162
+ const today = toDateString(new Date());
163
+ const isToday = date === today;
164
+ // Batch the cache check so we don't issue one SELECT per group.
165
+ const existingByKey = !opts?.force
166
+ ? await this.fetchExistingStatsByKey(date)
167
+ : null;
168
+ const rows = [];
169
+ for (const group of groups) {
170
+ try {
171
+ if (existingByKey) {
172
+ const cached = existingByKey.get(groupKey(group));
173
+ if (cached) {
174
+ // Historical dates: never recompute. Today: recompute if stale.
175
+ if (!isToday)
176
+ continue;
177
+ const age = Date.now() - new Date(cached.computedAt).getTime();
178
+ if (age < REPORT_STAT_LIVE_TTL)
179
+ continue;
180
+ }
181
+ }
182
+ const stats = this.resolveGroupStats(group, batched);
183
+ rows.push(this.buildUpsertRow(date, group, stats));
184
+ }
185
+ catch (err) {
186
+ logger_1.dbLogger.error({ err, group, date }, 'error preparing report stats group');
187
+ }
188
+ }
189
+ await this.bulkUpsert(rows);
190
+ }
191
+ /** Fetch all stat rows for a date, keyed by groupKey for O(1) lookup. */
192
+ async fetchExistingStatsByKey(date) {
193
+ const existing = await this.db.db
194
+ .selectFrom('report_stat')
195
+ .selectAll()
196
+ .where('date', '=', date)
197
+ .execute();
198
+ const map = new Map();
199
+ for (const row of existing) {
200
+ map.set(groupKey({
201
+ queueId: row.queueId,
202
+ moderatorDid: row.moderatorDid,
203
+ reportTypes: row.reportTypes,
204
+ }), row);
205
+ }
206
+ return map;
207
+ }
208
+ /** List out the groups to compute stats for. */
209
+ async enumerateGroups() {
210
+ const groups = [];
211
+ const queues = await this.db.db
212
+ .selectFrom('report_queue')
213
+ .selectAll()
214
+ .where('enabled', '=', true)
215
+ .where('deletedAt', 'is', null)
216
+ .execute();
217
+ const members = await this.db.db
218
+ .selectFrom('member')
219
+ .select('did')
220
+ .where('disabled', '=', false)
221
+ .where('role', 'in', [
222
+ 'tools.ozone.team.defs#roleAdmin',
223
+ 'tools.ozone.team.defs#roleModerator',
224
+ 'tools.ozone.team.defs#roleTriage',
225
+ ])
226
+ .execute();
227
+ // aggregate
228
+ groups.push({ queueId: null, moderatorDid: null, reportTypes: null });
229
+ // per queue
230
+ for (const queue of queues) {
231
+ groups.push({ queueId: queue.id, moderatorDid: null, reportTypes: null });
232
+ }
233
+ // unqueued
234
+ groups.push({ queueId: -1, moderatorDid: null, reportTypes: null });
235
+ // per moderator
236
+ for (const member of members) {
237
+ groups.push({
238
+ queueId: null,
239
+ moderatorDid: member.did,
240
+ reportTypes: null,
241
+ });
242
+ }
243
+ // per report type group
244
+ for (const groupTypes of Object.values(exports.REPORT_TYPE_GROUPS)) {
245
+ groups.push({
246
+ queueId: null,
247
+ moderatorDid: null,
248
+ reportTypes: groupTypes,
249
+ });
250
+ }
251
+ return groups;
252
+ }
253
+ /**
254
+ * Run batched GROUP BY queries for a calendar date.
255
+ * Returns 5 result sets covering all group types.
256
+ */
257
+ async computeBatchedStats(date) {
258
+ const dayStart = `${date}T00:00:00.000Z`;
259
+ const dayEnd = `${nextDate(date)}T00:00:00.000Z`;
260
+ const [queuePending, aggregatePending] = await Promise.all([
261
+ // Pending count is a snapshot of all non-closed reports at time of computation
262
+ this.db.db
263
+ .selectFrom('report')
264
+ .select(['queueId', (0, kysely_1.sql) `count(*)`.as('count')])
265
+ .where('status', '!=', 'closed')
266
+ .where('queueId', 'is not', null)
267
+ .groupBy('queueId')
268
+ .execute(),
269
+ // Aggregate pending (includes all reports, even un-routed)
270
+ this.db.db
271
+ .selectFrom('report')
272
+ .select((0, kysely_1.sql) `count(*)`.as('count'))
273
+ .where('status', '!=', 'closed')
274
+ .executeTakeFirst(),
275
+ ]);
276
+ const queueWindow = await this.db.db
277
+ .selectFrom('report')
278
+ .select([
279
+ 'queueId',
280
+ (0, kysely_1.sql) `count(*)`.as('inboundCount'),
281
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'closed' and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('actionedCount'),
282
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'escalated')`.as('escalatedCount'),
283
+ (0, kysely_1.sql) `sum(extract(epoch from ("closedAt"::timestamp - "createdAt"::timestamp))) filter (where "status" = 'closed' and "closedAt" is not null and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('handlingTimeSum'),
284
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'closed' and "closedAt" is not null and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('handlingTimeCount'),
285
+ ])
286
+ .where('createdAt', '>=', dayStart)
287
+ .where('createdAt', '<', dayEnd)
288
+ .where('queueId', 'is not', null)
289
+ .groupBy('queueId')
290
+ .execute();
291
+ // Aggregate windowed (includes all reports)
292
+ const aggregateWindow = await this.db.db
293
+ .selectFrom('report')
294
+ .select([
295
+ (0, kysely_1.sql) `count(*)`.as('inboundCount'),
296
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'closed' and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('actionedCount'),
297
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'escalated')`.as('escalatedCount'),
298
+ (0, kysely_1.sql) `sum(extract(epoch from ("closedAt"::timestamp - "createdAt"::timestamp))) filter (where "status" = 'closed' and "closedAt" is not null and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('handlingTimeSum'),
299
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'closed' and "closedAt" is not null and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('handlingTimeCount'),
300
+ ])
301
+ .where('createdAt', '>=', dayStart)
302
+ .where('createdAt', '<', dayEnd)
303
+ .executeTakeFirst();
304
+ const typePending = await this.db.db
305
+ .selectFrom('report')
306
+ .select(['reportType', (0, kysely_1.sql) `count(*)`.as('count')])
307
+ .where('status', '!=', 'closed')
308
+ .groupBy('reportType')
309
+ .execute();
310
+ const typeWindow = await this.db.db
311
+ .selectFrom('report')
312
+ .select([
313
+ 'reportType',
314
+ (0, kysely_1.sql) `count(*)`.as('inboundCount'),
315
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'closed' and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('actionedCount'),
316
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'escalated')`.as('escalatedCount'),
317
+ (0, kysely_1.sql) `sum(extract(epoch from ("closedAt"::timestamp - "createdAt"::timestamp))) filter (where "status" = 'closed' and "closedAt" is not null and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('handlingTimeSum'),
318
+ (0, kysely_1.sql) `count(*) filter (where "status" = 'closed' and "closedAt" is not null and "closedAt" >= ${dayStart} and "closedAt" < ${dayEnd})`.as('handlingTimeCount'),
319
+ ])
320
+ .where('createdAt', '>=', dayStart)
321
+ .where('createdAt', '<', dayEnd)
322
+ .groupBy('reportType')
323
+ .execute();
324
+ const moderator = await this.db.db
325
+ .selectFrom('report as r')
326
+ .innerJoin('moderator_assignment as ma', (join) => join.onRef('ma.reportId', '=', 'r.id').on('ma.endAt', 'is', null))
327
+ .select([
328
+ 'ma.did',
329
+ (0, kysely_1.sql) `count(*)`.as('inboundCount'),
330
+ (0, kysely_1.sql) `count(*) filter (where r."status" = 'closed')`.as('actionedCount'),
331
+ (0, kysely_1.sql) `sum(extract(epoch from (r."closedAt"::timestamp - ma."startAt"::timestamp))) filter (where r."status" = 'closed' and r."closedAt" is not null)`.as('handlingTimeSum'),
332
+ (0, kysely_1.sql) `count(*) filter (where r."status" = 'closed' and r."closedAt" is not null)`.as('handlingTimeCount'),
333
+ ])
334
+ .where('r.createdAt', '>=', dayStart)
335
+ .where('r.createdAt', '<', dayEnd)
336
+ .groupBy('ma.did')
337
+ .execute();
338
+ // Inject aggregate as a synthetic row with queueId=null so resolveQueueStats can find it
339
+ const allQueuePending = [
340
+ ...queuePending,
341
+ { queueId: null, count: aggregatePending?.count ?? '0' },
342
+ ];
343
+ const allQueueWindow = aggregateWindow
344
+ ? [
345
+ ...queueWindow,
346
+ {
347
+ queueId: null,
348
+ inboundCount: aggregateWindow.inboundCount,
349
+ actionedCount: aggregateWindow.actionedCount,
350
+ escalatedCount: aggregateWindow.escalatedCount,
351
+ handlingTimeSum: aggregateWindow.handlingTimeSum,
352
+ handlingTimeCount: aggregateWindow.handlingTimeCount,
353
+ },
354
+ ]
355
+ : queueWindow;
356
+ return {
357
+ queuePending: allQueuePending,
358
+ queueWindow: allQueueWindow,
359
+ typePending,
360
+ typeWindow,
361
+ moderator,
362
+ };
363
+ }
364
+ /** Resolve a single group's stats from batched query results (pure in-memory). */
365
+ resolveGroupStats(group, batched) {
366
+ if (group.moderatorDid) {
367
+ return this.resolveModeratorStats(group.moderatorDid, batched.moderator);
368
+ }
369
+ if (group.reportTypes !== null) {
370
+ return this.resolveReportTypeStats(group.reportTypes, batched);
371
+ }
372
+ return this.resolveQueueStats(group.queueId, batched);
373
+ }
374
+ resolveQueueStats(queueId, batched) {
375
+ // queueId=null is the synthetic aggregate row
376
+ const pending = batched.queuePending.find((r) => r.queueId === queueId);
377
+ const window = batched.queueWindow.find((r) => r.queueId === queueId);
378
+ const pendingCount = num(pending?.count);
379
+ const inboundCount = num(window?.inboundCount);
380
+ const actionedCount = num(window?.actionedCount);
381
+ const escalatedCount = num(window?.escalatedCount);
382
+ const handlingTimeSum = Number(window?.handlingTimeSum ?? 0);
383
+ const handlingTimeCount = num(window?.handlingTimeCount);
384
+ const actionRate = inboundCount > 0 ? Math.round((actionedCount / inboundCount) * 100) : 0;
385
+ const avgHandlingTimeSec = handlingTimeCount > 0
386
+ ? Math.round(handlingTimeSum / handlingTimeCount)
387
+ : undefined;
388
+ return {
389
+ inboundCount,
390
+ pendingCount,
391
+ actionedCount,
392
+ escalatedCount,
393
+ actionRate,
394
+ avgHandlingTimeSec,
395
+ };
396
+ }
397
+ resolveReportTypeStats(reportTypes, batched) {
398
+ const types = new Set(reportTypes);
399
+ const matchingPending = batched.typePending.filter((r) => types.has(r.reportType));
400
+ const matchingWindow = batched.typeWindow.filter((r) => types.has(r.reportType));
401
+ const pendingCount = sumNum(matchingPending, 'count');
402
+ const inboundCount = sumNum(matchingWindow, 'inboundCount');
403
+ const actionedCount = sumNum(matchingWindow, 'actionedCount');
404
+ const escalatedCount = sumNum(matchingWindow, 'escalatedCount');
405
+ const handlingTimeSum = matchingWindow.reduce((sum, r) => sum + Number(r.handlingTimeSum ?? 0), 0);
406
+ const handlingTimeCount = sumNum(matchingWindow, 'handlingTimeCount');
407
+ const actionRate = inboundCount > 0 ? Math.round((actionedCount / inboundCount) * 100) : 0;
408
+ const avgHandlingTimeSec = handlingTimeCount > 0
409
+ ? Math.round(handlingTimeSum / handlingTimeCount)
410
+ : undefined;
411
+ return {
412
+ inboundCount,
413
+ pendingCount,
414
+ actionedCount,
415
+ escalatedCount,
416
+ actionRate,
417
+ avgHandlingTimeSec,
418
+ };
419
+ }
420
+ resolveModeratorStats(moderatorDid, rows) {
421
+ const row = rows.find((r) => r.did === moderatorDid);
422
+ const inboundCount = num(row?.inboundCount);
423
+ const actionedCount = num(row?.actionedCount);
424
+ const handlingTimeCount = num(row?.handlingTimeCount);
425
+ const avgHandlingTimeSec = handlingTimeCount > 0 && row?.handlingTimeSum
426
+ ? Math.round(Number(row.handlingTimeSum) / handlingTimeCount)
427
+ : undefined;
428
+ return { inboundCount, actionedCount, avgHandlingTimeSec };
429
+ }
430
+ /** Build an upsert row from (date, group, stats). */
431
+ buildUpsertRow(date, group, stats) {
432
+ const pendingCount = 'pendingCount' in stats ? stats.pendingCount ?? null : null;
433
+ const escalatedCount = 'escalatedCount' in stats ? stats.escalatedCount ?? null : null;
434
+ const actionRate = 'actionRate' in stats ? stats.actionRate ?? null : null;
435
+ return {
436
+ date,
437
+ queueId: group.queueId,
438
+ moderatorDid: group.moderatorDid,
439
+ reportTypes: group.reportTypes,
440
+ inboundCount: stats.inboundCount ?? null,
441
+ pendingCount,
442
+ actionedCount: stats.actionedCount ?? null,
443
+ escalatedCount,
444
+ actionRate,
445
+ avgHandlingTimeSec: stats.avgHandlingTimeSec ?? null,
446
+ computedAt: new Date().toISOString(),
447
+ };
448
+ }
449
+ /**
450
+ * Wraps a DELETE+INSERT for each row in a single transaction so we pay one
451
+ * commit per cycle instead of one per group. NULL-aware WHERE clauses match
452
+ * the existing PG <15 NULL semantics without needing a unique index.
453
+ */
454
+ async bulkUpsert(rows) {
455
+ if (!rows.length)
456
+ return;
457
+ await this.db.transaction(async (dbTxn) => {
458
+ for (const r of rows) {
459
+ let del = dbTxn.db.deleteFrom('report_stat').where('date', '=', r.date);
460
+ del =
461
+ r.queueId !== null
462
+ ? del.where('queueId', '=', r.queueId)
463
+ : del.where('queueId', 'is', null);
464
+ del =
465
+ r.moderatorDid !== null
466
+ ? del.where('moderatorDid', '=', r.moderatorDid)
467
+ : del.where('moderatorDid', 'is', null);
468
+ del =
469
+ r.reportTypes !== null
470
+ ? del.where((0, kysely_1.sql) `"reportTypes"::jsonb = ${(0, types_1.jsonb)(r.reportTypes)}::jsonb`)
471
+ : del.where('reportTypes', 'is', null);
472
+ await del.execute();
473
+ await dbTxn.db
474
+ .insertInto('report_stat')
475
+ .values({
476
+ date: r.date,
477
+ queueId: r.queueId,
478
+ moderatorDid: r.moderatorDid,
479
+ reportTypes: r.reportTypes !== null ? (0, types_1.jsonb)(r.reportTypes) : null,
480
+ inboundCount: r.inboundCount,
481
+ pendingCount: r.pendingCount,
482
+ actionedCount: r.actionedCount,
483
+ escalatedCount: r.escalatedCount,
484
+ actionRate: r.actionRate,
485
+ avgHandlingTimeSec: r.avgHandlingTimeSec,
486
+ computedAt: r.computedAt,
487
+ })
488
+ .execute();
489
+ }
490
+ });
491
+ }
492
+ // ─── Read methods ───
493
+ /** Get a single stat row for a date + group. */
494
+ async getStatForDate(date, group) {
495
+ let qb = this.db.db
496
+ .selectFrom('report_stat')
497
+ .selectAll()
498
+ .where('date', '=', date);
499
+ if (group.queueId !== null) {
500
+ qb = qb.where('queueId', '=', group.queueId);
501
+ }
502
+ else {
503
+ qb = qb.where('queueId', 'is', null);
504
+ }
505
+ if (group.moderatorDid) {
506
+ qb = qb.where('moderatorDid', '=', group.moderatorDid);
507
+ }
508
+ else {
509
+ qb = qb.where('moderatorDid', 'is', null);
510
+ }
511
+ if (group.reportTypes !== null) {
512
+ qb = qb.where((0, kysely_1.sql) `"reportTypes"::jsonb = ${(0, types_1.jsonb)(group.reportTypes)}::jsonb`);
513
+ }
514
+ else {
515
+ qb = qb.where('reportTypes', 'is', null);
516
+ }
517
+ return qb.executeTakeFirst();
518
+ }
519
+ /** Get today's live stats for a group. */
520
+ async getLiveStats(group) {
521
+ const today = toDateString(new Date());
522
+ return this.getStatForDate(today, group);
523
+ }
524
+ /** Get live stats for multiple queues in a single query. */
525
+ async getLiveStatsForQueues(queueIds) {
526
+ if (!queueIds.length)
527
+ return new Map();
528
+ const today = toDateString(new Date());
529
+ const rows = await this.db.db
530
+ .selectFrom('report_stat')
531
+ .selectAll()
532
+ .where('date', '=', today)
533
+ .where('queueId', 'in', queueIds)
534
+ .where('moderatorDid', 'is', null)
535
+ .where('reportTypes', 'is', null)
536
+ .execute();
537
+ const result = new Map();
538
+ for (const row of rows) {
539
+ if (row.queueId !== null) {
540
+ result.set(row.queueId, row);
541
+ }
542
+ }
543
+ return result;
544
+ }
545
+ /** Get historical stats for a date range, paginated. */
546
+ async getHistoricalStats(opts) {
547
+ const { group, startDate, endDate, limit } = opts;
548
+ const { queueId, moderatorDid, reportTypes } = group;
549
+ const { ref } = this.db.db.dynamic;
550
+ let qb = this.db.db.selectFrom('report_stat').selectAll();
551
+ if (queueId !== null) {
552
+ qb = qb.where('queueId', '=', queueId);
553
+ }
554
+ else {
555
+ qb = qb.where('queueId', 'is', null);
556
+ }
557
+ if (moderatorDid) {
558
+ qb = qb.where('moderatorDid', '=', moderatorDid);
559
+ }
560
+ else {
561
+ qb = qb.where('moderatorDid', 'is', null);
562
+ }
563
+ if (reportTypes !== null) {
564
+ qb = qb.where((0, kysely_1.sql) `"reportTypes"::jsonb = ${(0, types_1.jsonb)(reportTypes)}::jsonb`);
565
+ }
566
+ else {
567
+ qb = qb.where('reportTypes', 'is', null);
568
+ }
569
+ if (startDate) {
570
+ qb = qb.where('date', '>=', toDateString(new Date(startDate)));
571
+ }
572
+ if (endDate) {
573
+ qb = qb.where('date', '<=', toDateString(new Date(endDate)));
574
+ }
575
+ const keyset = new pagination_1.ComputedAtIdKeyset(ref('computedAt'), ref('id'));
576
+ const paginatedBuilder = (0, pagination_1.paginate)(qb, {
577
+ limit,
578
+ cursor: opts.cursor,
579
+ keyset,
580
+ direction: 'desc',
581
+ tryIndex: true,
582
+ });
583
+ const stats = await paginatedBuilder.execute();
584
+ return { stats, cursor: keyset.packFromResult(stats) };
585
+ }
586
+ }
587
+ exports.ReportStatsService = ReportStatsService;
588
+ // ─── Helpers ───
589
+ /** Parse a pg bigint string to number, defaulting to 0. */
590
+ function num(val) {
591
+ return val ? Number(val) : 0;
592
+ }
593
+ /** Sum a numeric string field across rows. */
594
+ function sumNum(rows, field) {
595
+ return rows.reduce((sum, r) => sum + Number(r[field] ?? 0), 0);
596
+ }
597
+ /**
598
+ * Stable cache-key for a stat group. Used to look up an existing row in the
599
+ * batched cache map without issuing per-group SELECTs. Report types are
600
+ * stringified in stored order, which matches REPORT_TYPE_GROUPS.
601
+ */
602
+ function groupKey(g) {
603
+ return [
604
+ g.queueId ?? 'null',
605
+ g.moderatorDid ?? 'null',
606
+ g.reportTypes ? JSON.stringify(g.reportTypes) : 'null',
607
+ ].join('|');
608
+ }
609
+ /** Convert a Date to an ISO date string (YYYY-MM-DD). */
610
+ function toDateString(d) {
611
+ return d.toISOString().slice(0, 10);
612
+ }
613
+ /** Get the next calendar date string. */
614
+ function nextDate(dateStr) {
615
+ const d = new Date(`${dateStr}T00:00:00.000Z`);
616
+ d.setUTCDate(d.getUTCDate() + 1);
617
+ return toDateString(d);
618
+ }
619
+ //# sourceMappingURL=stats.js.map