@atproto/ozone 0.1.173 → 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 (411) hide show
  1. package/CHANGELOG.md +9 -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/job-cursor.d.ts +5 -0
  113. package/dist/daemon/job-cursor.d.ts.map +1 -0
  114. package/dist/daemon/job-cursor.js +28 -0
  115. package/dist/daemon/job-cursor.js.map +1 -0
  116. package/dist/daemon/queue-router.d.ts +17 -0
  117. package/dist/daemon/queue-router.d.ts.map +1 -0
  118. package/dist/daemon/queue-router.js +114 -0
  119. package/dist/daemon/queue-router.js.map +1 -0
  120. package/dist/daemon/stats-computer.d.ts +51 -0
  121. package/dist/daemon/stats-computer.d.ts.map +1 -0
  122. package/dist/daemon/stats-computer.js +117 -0
  123. package/dist/daemon/stats-computer.js.map +1 -0
  124. package/dist/daemon/strike-expiry-processor.d.ts.map +1 -1
  125. package/dist/daemon/strike-expiry-processor.js +4 -19
  126. package/dist/daemon/strike-expiry-processor.js.map +1 -1
  127. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts +4 -0
  128. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts.map +1 -0
  129. package/dist/db/migrations/20260219T164523000Z-create-report-table.js +126 -0
  130. package/dist/db/migrations/20260219T164523000Z-create-report-table.js.map +1 -0
  131. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts +4 -0
  132. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts.map +1 -0
  133. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js +35 -0
  134. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js.map +1 -0
  135. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts +4 -0
  136. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts.map +1 -0
  137. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js +36 -0
  138. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js.map +1 -0
  139. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts +4 -0
  140. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts.map +1 -0
  141. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js +39 -0
  142. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js.map +1 -0
  143. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts +4 -0
  144. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts.map +1 -0
  145. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js +34 -0
  146. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js.map +1 -0
  147. package/dist/db/migrations/index.d.ts +5 -0
  148. package/dist/db/migrations/index.d.ts.map +1 -1
  149. package/dist/db/migrations/index.js +6 -1
  150. package/dist/db/migrations/index.js.map +1 -1
  151. package/dist/db/pagination.d.ts +31 -0
  152. package/dist/db/pagination.d.ts.map +1 -1
  153. package/dist/db/pagination.js +74 -1
  154. package/dist/db/pagination.js.map +1 -1
  155. package/dist/db/schema/index.d.ts +6 -1
  156. package/dist/db/schema/index.d.ts.map +1 -1
  157. package/dist/db/schema/index.js.map +1 -1
  158. package/dist/db/schema/moderator_assignment.d.ts +14 -0
  159. package/dist/db/schema/moderator_assignment.d.ts.map +1 -0
  160. package/dist/db/schema/moderator_assignment.js +5 -0
  161. package/dist/db/schema/moderator_assignment.js.map +1 -0
  162. package/dist/db/schema/report.d.ts +25 -0
  163. package/dist/db/schema/report.d.ts.map +1 -0
  164. package/dist/db/schema/report.js +5 -0
  165. package/dist/db/schema/report.js.map +1 -0
  166. package/dist/db/schema/report_activity.d.ts +18 -0
  167. package/dist/db/schema/report_activity.d.ts.map +1 -0
  168. package/dist/db/schema/report_activity.js +5 -0
  169. package/dist/db/schema/report_activity.js.map +1 -0
  170. package/dist/db/schema/report_queue.d.ts +19 -0
  171. package/dist/db/schema/report_queue.d.ts.map +1 -0
  172. package/dist/db/schema/report_queue.js +5 -0
  173. package/dist/db/schema/report_queue.js.map +1 -0
  174. package/dist/db/schema/report_stat.d.ts +20 -0
  175. package/dist/db/schema/report_stat.d.ts.map +1 -0
  176. package/dist/db/schema/report_stat.js +5 -0
  177. package/dist/db/schema/report_stat.js.map +1 -0
  178. package/dist/lexicon/index.d.ts +50 -0
  179. package/dist/lexicon/index.d.ts.map +1 -1
  180. package/dist/lexicon/index.js +120 -2
  181. package/dist/lexicon/index.js.map +1 -1
  182. package/dist/lexicon/lexicons.d.ts +10535 -7389
  183. package/dist/lexicon/lexicons.d.ts.map +1 -1
  184. package/dist/lexicon/lexicons.js +1789 -122
  185. package/dist/lexicon/lexicons.js.map +1 -1
  186. package/dist/lexicon/types/app/bsky/embed/external.d.ts +2 -0
  187. package/dist/lexicon/types/app/bsky/embed/external.d.ts.map +1 -1
  188. package/dist/lexicon/types/app/bsky/embed/external.js.map +1 -1
  189. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +19 -0
  190. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  191. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js +9 -0
  192. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js.map +1 -1
  193. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts +27 -0
  194. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts.map +1 -0
  195. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js +7 -0
  196. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js.map +1 -0
  197. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts +35 -0
  198. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts.map +1 -0
  199. package/dist/lexicon/types/tools/ozone/queue/createQueue.js +7 -0
  200. package/dist/lexicon/types/tools/ozone/queue/createQueue.js.map +1 -0
  201. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts +62 -0
  202. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts.map +1 -0
  203. package/dist/lexicon/types/tools/ozone/queue/defs.js +34 -0
  204. package/dist/lexicon/types/tools/ozone/queue/defs.js.map +1 -0
  205. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts +29 -0
  206. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts.map +1 -0
  207. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js +7 -0
  208. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js.map +1 -0
  209. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts +30 -0
  210. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts.map +1 -0
  211. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js +7 -0
  212. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js.map +1 -0
  213. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts +32 -0
  214. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts.map +1 -0
  215. package/dist/lexicon/types/tools/ozone/queue/listQueues.js +7 -0
  216. package/dist/lexicon/types/tools/ozone/queue/listQueues.js.map +1 -0
  217. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts +31 -0
  218. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts.map +1 -0
  219. package/dist/lexicon/types/tools/ozone/queue/routeReports.js +7 -0
  220. package/dist/lexicon/types/tools/ozone/queue/routeReports.js.map +1 -0
  221. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts +18 -0
  222. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts.map +1 -0
  223. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js +7 -0
  224. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js.map +1 -0
  225. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts +32 -0
  226. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts.map +1 -0
  227. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js +7 -0
  228. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js.map +1 -0
  229. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts +31 -0
  230. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts.map +1 -0
  231. package/dist/lexicon/types/tools/ozone/report/assignModerator.js +7 -0
  232. package/dist/lexicon/types/tools/ozone/report/assignModerator.js.map +1 -0
  233. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts +37 -0
  234. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts.map +1 -0
  235. package/dist/lexicon/types/tools/ozone/report/createActivity.js +7 -0
  236. package/dist/lexicon/types/tools/ozone/report/createActivity.js.map +1 -0
  237. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +185 -0
  238. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  239. package/dist/lexicon/types/tools/ozone/report/defs.js +108 -0
  240. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  241. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts +30 -0
  242. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts.map +1 -0
  243. package/dist/lexicon/types/tools/ozone/report/getAssignments.js +7 -0
  244. package/dist/lexicon/types/tools/ozone/report/getAssignments.js.map +1 -0
  245. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts +36 -0
  246. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts.map +1 -0
  247. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js +7 -0
  248. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js.map +1 -0
  249. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts +21 -0
  250. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts.map +1 -0
  251. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js +7 -0
  252. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js.map +1 -0
  253. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts +27 -0
  254. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts.map +1 -0
  255. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js +7 -0
  256. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js.map +1 -0
  257. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts +22 -0
  258. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts.map +1 -0
  259. package/dist/lexicon/types/tools/ozone/report/getReport.js +7 -0
  260. package/dist/lexicon/types/tools/ozone/report/getReport.js.map +1 -0
  261. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts +26 -0
  262. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts.map +1 -0
  263. package/dist/lexicon/types/tools/ozone/report/listActivities.js +7 -0
  264. package/dist/lexicon/types/tools/ozone/report/listActivities.js.map +1 -0
  265. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts +48 -0
  266. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts.map +1 -0
  267. package/dist/lexicon/types/tools/ozone/report/queryReports.js +7 -0
  268. package/dist/lexicon/types/tools/ozone/report/queryReports.js.map +1 -0
  269. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts +31 -0
  270. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts.map +1 -0
  271. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js +7 -0
  272. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js.map +1 -0
  273. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts +28 -0
  274. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts.map +1 -0
  275. package/dist/lexicon/types/tools/ozone/report/refreshStats.js +7 -0
  276. package/dist/lexicon/types/tools/ozone/report/refreshStats.js.map +1 -0
  277. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts +25 -0
  278. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts.map +1 -0
  279. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js +7 -0
  280. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js.map +1 -0
  281. package/dist/mod-service/index.d.ts +3 -1
  282. package/dist/mod-service/index.d.ts.map +1 -1
  283. package/dist/mod-service/index.js +39 -2
  284. package/dist/mod-service/index.js.map +1 -1
  285. package/dist/mod-service/report.d.ts +64 -0
  286. package/dist/mod-service/report.d.ts.map +1 -0
  287. package/dist/mod-service/report.js +282 -0
  288. package/dist/mod-service/report.js.map +1 -0
  289. package/dist/mod-service/status.d.ts +20 -0
  290. package/dist/mod-service/status.d.ts.map +1 -1
  291. package/dist/queue/service.d.ts +86 -0
  292. package/dist/queue/service.d.ts.map +1 -0
  293. package/dist/queue/service.js +430 -0
  294. package/dist/queue/service.js.map +1 -0
  295. package/dist/report/activity.d.ts +77 -0
  296. package/dist/report/activity.d.ts.map +1 -0
  297. package/dist/report/activity.js +141 -0
  298. package/dist/report/activity.js.map +1 -0
  299. package/dist/report/handle-report-update.d.ts +47 -0
  300. package/dist/report/handle-report-update.d.ts.map +1 -0
  301. package/dist/report/handle-report-update.js +178 -0
  302. package/dist/report/handle-report-update.js.map +1 -0
  303. package/dist/report/reassign.d.ts +10 -0
  304. package/dist/report/reassign.d.ts.map +1 -0
  305. package/dist/report/reassign.js +75 -0
  306. package/dist/report/reassign.js.map +1 -0
  307. package/dist/report/stats.d.ts +105 -0
  308. package/dist/report/stats.d.ts.map +1 -0
  309. package/dist/report/stats.js +619 -0
  310. package/dist/report/stats.js.map +1 -0
  311. package/dist/report/views.d.ts +111 -0
  312. package/dist/report/views.d.ts.map +1 -0
  313. package/dist/report/views.js +156 -0
  314. package/dist/report/views.js.map +1 -0
  315. package/dist/team/index.d.ts +1 -0
  316. package/dist/team/index.d.ts.map +1 -1
  317. package/dist/team/index.js +11 -0
  318. package/dist/team/index.js.map +1 -1
  319. package/package.json +3 -3
  320. package/src/api/index.ts +40 -0
  321. package/src/api/moderation/emitEvent.ts +38 -0
  322. package/src/api/queue/assignModerator.ts +31 -0
  323. package/src/api/queue/createQueue.ts +62 -0
  324. package/src/api/queue/deleteQueue.ts +56 -0
  325. package/src/api/queue/getAssignments.ts +19 -0
  326. package/src/api/queue/listQueues.ts +39 -0
  327. package/src/api/queue/routeReports.ts +44 -0
  328. package/src/api/queue/unassignModerator.ts +26 -0
  329. package/src/api/queue/updateQueue.ts +54 -0
  330. package/src/api/report/assignModerator.ts +36 -0
  331. package/src/api/report/createActivity.ts +57 -0
  332. package/src/api/report/getAssignments.ts +20 -0
  333. package/src/api/report/getHistoricalStats.ts +41 -0
  334. package/src/api/report/getLatestReport.ts +44 -0
  335. package/src/api/report/getLiveStats.ts +26 -0
  336. package/src/api/report/getReport.ts +55 -0
  337. package/src/api/report/listActivities.ts +34 -0
  338. package/src/api/report/queryReports.ts +44 -0
  339. package/src/api/report/reassignQueue.ts +68 -0
  340. package/src/api/report/refreshStats.ts +27 -0
  341. package/src/api/report/unassignModerator.ts +21 -0
  342. package/src/api/util.ts +12 -0
  343. package/src/assignment/index.ts +731 -0
  344. package/src/config/config.ts +27 -0
  345. package/src/config/env.ts +8 -0
  346. package/src/context.ts +31 -0
  347. package/src/daemon/context.ts +34 -0
  348. package/src/daemon/job-cursor.ts +33 -0
  349. package/src/daemon/queue-router.ts +101 -0
  350. package/src/daemon/stats-computer.ts +101 -0
  351. package/src/daemon/strike-expiry-processor.ts +4 -20
  352. package/src/db/migrations/20260219T164523000Z-create-report-table.ts +155 -0
  353. package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +42 -0
  354. package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +41 -0
  355. package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +48 -0
  356. package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +35 -0
  357. package/src/db/migrations/index.ts +5 -0
  358. package/src/db/pagination.ts +85 -0
  359. package/src/db/schema/index.ts +10 -0
  360. package/src/db/schema/moderator_assignment.ts +16 -0
  361. package/src/db/schema/report.ts +27 -0
  362. package/src/db/schema/report_activity.ts +22 -0
  363. package/src/db/schema/report_queue.ts +21 -0
  364. package/src/db/schema/report_stat.ts +27 -0
  365. package/src/lexicon/index.ts +280 -0
  366. package/src/lexicon/lexicons.ts +1910 -160
  367. package/src/lexicon/types/app/bsky/embed/external.ts +2 -0
  368. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +24 -0
  369. package/src/lexicon/types/tools/ozone/queue/assignModerator.ts +46 -0
  370. package/src/lexicon/types/tools/ozone/queue/createQueue.ts +54 -0
  371. package/src/lexicon/types/tools/ozone/queue/defs.ts +99 -0
  372. package/src/lexicon/types/tools/ozone/queue/deleteQueue.ts +48 -0
  373. package/src/lexicon/types/tools/ozone/queue/getAssignments.ts +48 -0
  374. package/src/lexicon/types/tools/ozone/queue/listQueues.ts +50 -0
  375. package/src/lexicon/types/tools/ozone/queue/routeReports.ts +50 -0
  376. package/src/lexicon/types/tools/ozone/queue/unassignModerator.ts +37 -0
  377. package/src/lexicon/types/tools/ozone/queue/updateQueue.ts +51 -0
  378. package/src/lexicon/types/tools/ozone/report/assignModerator.ts +50 -0
  379. package/src/lexicon/types/tools/ozone/report/createActivity.ts +60 -0
  380. package/src/lexicon/types/tools/ozone/report/defs.ts +327 -0
  381. package/src/lexicon/types/tools/ozone/report/getAssignments.ts +48 -0
  382. package/src/lexicon/types/tools/ozone/report/getHistoricalStats.ts +54 -0
  383. package/src/lexicon/types/tools/ozone/report/getLatestReport.ts +39 -0
  384. package/src/lexicon/types/tools/ozone/report/getLiveStats.ts +45 -0
  385. package/src/lexicon/types/tools/ozone/report/getReport.ts +38 -0
  386. package/src/lexicon/types/tools/ozone/report/listActivities.ts +44 -0
  387. package/src/lexicon/types/tools/ozone/report/queryReports.ts +72 -0
  388. package/src/lexicon/types/tools/ozone/report/reassignQueue.ts +55 -0
  389. package/src/lexicon/types/tools/ozone/report/refreshStats.ts +46 -0
  390. package/src/lexicon/types/tools/ozone/report/unassignModerator.ts +44 -0
  391. package/src/mod-service/index.ts +45 -3
  392. package/src/mod-service/report.ts +408 -0
  393. package/src/queue/service.ts +599 -0
  394. package/src/report/activity.ts +234 -0
  395. package/src/report/handle-report-update.ts +209 -0
  396. package/src/report/reassign.ts +109 -0
  397. package/src/report/stats.ts +850 -0
  398. package/src/report/views.ts +241 -0
  399. package/src/team/index.ts +11 -0
  400. package/tests/get-report.test.ts +136 -0
  401. package/tests/query-reports.test.ts +608 -0
  402. package/tests/queue-assignment.test.ts +428 -0
  403. package/tests/queue-router.test.ts +306 -0
  404. package/tests/queues.test.ts +690 -0
  405. package/tests/report-action.test.ts +308 -0
  406. package/tests/report-activity.test.ts +567 -0
  407. package/tests/report-assignment.test.ts +517 -0
  408. package/tests/report-reassign-queue.test.ts +340 -0
  409. package/tests/report-routing.test.ts +245 -0
  410. package/tests/report-stats.test.ts +545 -0
  411. 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