@kaikybrofc/omnizap-system 2.2.10 → 2.3.1

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 (123) hide show
  1. package/README.md +13 -13
  2. package/app/config/adminIdentity.js +1 -3
  3. package/app/connection/socketController.js +10 -20
  4. package/app/controllers/messageController.js +7 -28
  5. package/app/modules/aiModule/catCommand.js +29 -192
  6. package/app/modules/broadcastModule/noticeCommand.js +28 -97
  7. package/app/modules/gameModule/diceCommand.js +6 -32
  8. package/app/modules/playModule/playCommand.js +57 -258
  9. package/app/modules/quoteModule/quoteCommand.js +2 -4
  10. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
  11. package/app/modules/statsModule/noMessageCommand.js +16 -84
  12. package/app/modules/statsModule/rankingCommand.js +5 -25
  13. package/app/modules/statsModule/rankingCommon.js +1 -9
  14. package/app/modules/stickerModule/convertToWebp.js +4 -27
  15. package/app/modules/stickerModule/stickerCommand.js +13 -24
  16. package/app/modules/stickerModule/stickerTextCommand.js +13 -25
  17. package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
  18. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
  19. package/app/modules/stickerPackModule/domainEvents.js +2 -11
  20. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
  21. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
  22. package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
  23. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
  24. package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
  25. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
  26. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
  27. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
  28. package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
  29. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
  30. package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
  31. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +40 -39
  32. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
  33. package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
  34. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
  35. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
  36. package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
  37. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
  38. package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
  39. package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
  40. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
  41. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
  42. package/app/modules/stickerPackModule/stickerPackService.js +50 -115
  43. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
  44. package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
  45. package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
  46. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
  47. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
  48. package/app/modules/systemMetricsModule/pingCommand.js +9 -39
  49. package/app/modules/tiktokModule/tiktokCommand.js +17 -109
  50. package/app/modules/userModule/userCommand.js +2 -88
  51. package/app/observability/metrics.js +5 -16
  52. package/app/services/captchaService.js +1 -6
  53. package/app/services/dbWriteQueue.js +3 -18
  54. package/app/services/featureFlagService.js +2 -8
  55. package/app/services/newsBroadcastService.js +0 -1
  56. package/app/services/queueUtils.js +2 -4
  57. package/app/services/whatsappLoginLinkService.js +7 -9
  58. package/app/store/premiumUserStore.js +1 -2
  59. package/app/utils/antiLink/antiLinkModule.js +3 -233
  60. package/app/utils/logger/loggerModule.js +9 -34
  61. package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
  62. package/database/index.js +1 -0
  63. package/database/init.js +1 -8
  64. package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
  65. package/docker-compose.yml +27 -27
  66. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
  67. package/docs/seo/satellite-page-template.md +2 -0
  68. package/docs/seo/satellite-pages-phase1.json +40 -177
  69. package/eslint.config.js +2 -15
  70. package/index.js +8 -36
  71. package/ml/clip_classifier/README.md +4 -6
  72. package/observability/alert-rules.yml +12 -12
  73. package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
  74. package/package.json +6 -3
  75. package/public/api-docs/index.html +220 -193
  76. package/public/bot-whatsapp-para-grupo/index.html +291 -261
  77. package/public/bot-whatsapp-sem-programar/index.html +291 -261
  78. package/public/comandos/index.html +421 -406
  79. package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
  80. package/public/como-criar-comandos-whatsapp/index.html +291 -261
  81. package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
  82. package/public/como-moderar-grupo-whatsapp/index.html +291 -261
  83. package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
  84. package/public/css/github-project-panel.css +13 -8
  85. package/public/css/stickers-admin.css +25 -9
  86. package/public/css/styles.css +23 -16
  87. package/public/index.html +1106 -993
  88. package/public/js/apps/apiDocsApp.js +17 -167
  89. package/public/js/apps/createPackApp.js +69 -332
  90. package/public/js/apps/homeApp.js +274 -101
  91. package/public/js/apps/loginApp.js +3 -12
  92. package/public/js/apps/stickersAdminApp.js +190 -181
  93. package/public/js/apps/stickersApp.js +482 -1411
  94. package/public/js/apps/userApp.js +217 -1
  95. package/public/js/catalog.js +11 -74
  96. package/public/js/github-panel/components/ErrorState.js +1 -8
  97. package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
  98. package/public/js/github-panel/components/SkeletonPanel.js +1 -11
  99. package/public/js/github-panel/components/StatCard.js +1 -7
  100. package/public/js/github-panel/vendor/react.js +1 -9
  101. package/public/js/runtime/react-runtime.js +1 -9
  102. package/public/licenca/index.html +200 -86
  103. package/public/login/index.html +315 -325
  104. package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
  105. package/public/stickers/admin/index.html +14 -19
  106. package/public/stickers/create/index.html +39 -44
  107. package/public/stickers/index.html +96 -107
  108. package/public/termos-de-uso/index.html +369 -122
  109. package/public/user/index.html +527 -350
  110. package/scripts/cache-bust.mjs +5 -24
  111. package/scripts/generate-seo-satellite-pages.mjs +10 -13
  112. package/scripts/run-prettier-all.mjs +25 -0
  113. package/scripts/sticker-catalog-loadtest.mjs +13 -11
  114. package/scripts/sticker-worker-task.mjs +1 -4
  115. package/scripts/sync-readme-snapshot.mjs +3 -2
  116. package/server/auth/googleWebAuth/googleWebAuthService.js +614 -0
  117. package/server/controllers/stickerCatalogController.js +297 -632
  118. package/server/http/httpServer.js +2 -10
  119. package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
  120. package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
  121. package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
  122. package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
  123. package/server/routes/stickerCatalog/catalogRouter.js +11 -13
@@ -5,22 +5,9 @@ import logger from '../../utils/logger/loggerModule.js';
5
5
  import { recordStickerClassificationCycle, setQueueDepth } from '../../observability/metrics.js';
6
6
  import { listStickerAssetsPendingClassification, findStickerAssetById } from './stickerAssetRepository.js';
7
7
  import { classifierConfig, ensureStickerAssetClassified } from './stickerClassificationService.js';
8
- import {
9
- listAssetsForPrioritySignalBackfillReprocess,
10
- listAssetsForLowConfidenceReprocess,
11
- listAssetsForModelUpgradeReprocess,
12
- } from './stickerAssetClassificationRepository.js';
13
- import {
14
- claimStickerAssetReprocessTask,
15
- completeStickerAssetReprocessTask,
16
- countStickerAssetReprocessQueueByStatus,
17
- enqueueStickerAssetReprocess,
18
- failStickerAssetReprocessTask,
19
- } from './stickerAssetReprocessQueueRepository.js';
20
- import {
21
- batchReprocess as runDeterministicSemanticReclassification,
22
- deterministicReclassificationConfig,
23
- } from './semanticReclassificationEngine.js';
8
+ import { listAssetsForPrioritySignalBackfillReprocess, listAssetsForLowConfidenceReprocess, listAssetsForModelUpgradeReprocess } from './stickerAssetClassificationRepository.js';
9
+ import { claimStickerAssetReprocessTask, completeStickerAssetReprocessTask, countStickerAssetReprocessQueueByStatus, enqueueStickerAssetReprocess, failStickerAssetReprocessTask } from './stickerAssetReprocessQueueRepository.js';
10
+ import { batchReprocess as runDeterministicSemanticReclassification, deterministicReclassificationConfig } from './semanticReclassificationEngine.js';
24
11
 
25
12
  const parseEnvBool = (value, fallback) => {
26
13
  if (value === undefined || value === null || value === '') return fallback;
@@ -37,58 +24,26 @@ const INTERVAL_MIN_MS_RAW = Number(process.env.STICKER_CLASSIFICATION_BACKGROUND
37
24
  const INTERVAL_MAX_MS_RAW = Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_INTERVAL_MAX_MS);
38
25
  const DEFAULT_INTERVAL_MIN_MS = 5 * 60_000;
39
26
  const DEFAULT_INTERVAL_MAX_MS = 10 * 60_000;
40
- const INTERVAL_MIN_MS = Number.isFinite(INTERVAL_MIN_MS_RAW)
41
- ? Math.max(60_000, Math.min(3_600_000, INTERVAL_MIN_MS_RAW))
42
- : DEFAULT_INTERVAL_MIN_MS;
43
- const INTERVAL_MAX_MS_FROM_ENV = Number.isFinite(INTERVAL_MAX_MS_RAW)
44
- ? Math.max(60_000, Math.min(3_600_000, INTERVAL_MAX_MS_RAW))
45
- : DEFAULT_INTERVAL_MAX_MS;
27
+ const INTERVAL_MIN_MS = Number.isFinite(INTERVAL_MIN_MS_RAW) ? Math.max(60_000, Math.min(3_600_000, INTERVAL_MIN_MS_RAW)) : DEFAULT_INTERVAL_MIN_MS;
28
+ const INTERVAL_MAX_MS_FROM_ENV = Number.isFinite(INTERVAL_MAX_MS_RAW) ? Math.max(60_000, Math.min(3_600_000, INTERVAL_MAX_MS_RAW)) : DEFAULT_INTERVAL_MAX_MS;
46
29
  const INTERVAL_MAX_MS = Math.max(INTERVAL_MIN_MS, INTERVAL_MAX_MS_FROM_ENV);
47
- const LEGACY_FIXED_INTERVAL_MS = Number.isFinite(LEGACY_INTERVAL_MS) && LEGACY_INTERVAL_MS > 0
48
- ? Math.max(60_000, Math.min(3_600_000, LEGACY_INTERVAL_MS))
49
- : null;
30
+ const LEGACY_FIXED_INTERVAL_MS = Number.isFinite(LEGACY_INTERVAL_MS) && LEGACY_INTERVAL_MS > 0 ? Math.max(60_000, Math.min(3_600_000, LEGACY_INTERVAL_MS)) : null;
50
31
  const EFFECTIVE_INTERVAL_MIN_MS = LEGACY_FIXED_INTERVAL_MS || INTERVAL_MIN_MS;
51
32
  const EFFECTIVE_INTERVAL_MAX_MS = LEGACY_FIXED_INTERVAL_MS || INTERVAL_MAX_MS;
52
33
  const cpuCount = Math.max(1, Number(os.cpus()?.length || 1));
53
- const BACKGROUND_CONCURRENCY = Math.max(
54
- 1,
55
- Math.min(16, Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_CONCURRENCY) || cpuCount),
56
- );
57
- const BATCH_SIZE = Math.max(
58
- 1,
59
- Math.min(300, Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_BATCH_SIZE) || BACKGROUND_CONCURRENCY * 2),
60
- );
34
+ const BACKGROUND_CONCURRENCY = Math.max(1, Math.min(16, Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_CONCURRENCY) || cpuCount));
35
+ const BATCH_SIZE = Math.max(1, Math.min(300, Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_BATCH_SIZE) || BACKGROUND_CONCURRENCY * 2));
61
36
 
62
37
  const REPROCESS_ENABLED = parseEnvBool(process.env.STICKER_REPROCESS_QUEUE_ENABLED, true);
63
38
  const REPROCESS_MAX_PER_CYCLE = Math.max(0, Math.min(300, Number(process.env.STICKER_REPROCESS_MAX_PER_CYCLE) || BATCH_SIZE));
64
- const REPROCESS_MODEL_UPGRADE_SCAN_LIMIT = Math.max(
65
- 0,
66
- Math.min(2000, Number(process.env.STICKER_REPROCESS_MODEL_UPGRADE_SCAN_LIMIT) || 350),
67
- );
68
- const REPROCESS_LOW_CONFIDENCE_SCAN_LIMIT = Math.max(
69
- 0,
70
- Math.min(2000, Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_SCAN_LIMIT) || 250),
71
- );
72
- const REPROCESS_LOW_CONFIDENCE_THRESHOLD = Number.isFinite(Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_THRESHOLD))
73
- ? Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_THRESHOLD)
74
- : 0.65;
75
- const REPROCESS_LOW_CONFIDENCE_STALE_HOURS = Math.max(
76
- 1,
77
- Math.min(24 * 365, Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_STALE_HOURS) || 48),
78
- );
39
+ const REPROCESS_MODEL_UPGRADE_SCAN_LIMIT = Math.max(0, Math.min(2000, Number(process.env.STICKER_REPROCESS_MODEL_UPGRADE_SCAN_LIMIT) || 350));
40
+ const REPROCESS_LOW_CONFIDENCE_SCAN_LIMIT = Math.max(0, Math.min(2000, Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_SCAN_LIMIT) || 250));
41
+ const REPROCESS_LOW_CONFIDENCE_THRESHOLD = Number.isFinite(Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_THRESHOLD)) ? Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_THRESHOLD) : 0.65;
42
+ const REPROCESS_LOW_CONFIDENCE_STALE_HOURS = Math.max(1, Math.min(24 * 365, Number(process.env.STICKER_REPROCESS_LOW_CONFIDENCE_STALE_HOURS) || 48));
79
43
  const REPROCESS_PRIORITY_BACKFILL_ENABLED = parseEnvBool(process.env.STICKER_REPROCESS_PRIORITY_BACKFILL_ENABLED, true);
80
- const REPROCESS_PRIORITY_BACKFILL_SCAN_LIMIT = Math.max(
81
- 0,
82
- Math.min(3000, Number(process.env.STICKER_REPROCESS_PRIORITY_BACKFILL_SCAN_LIMIT) || 300),
83
- );
84
- const REPROCESS_PRIORITY_BACKFILL_PRIORITY = Math.max(
85
- 1,
86
- Math.min(100, Number(process.env.STICKER_REPROCESS_PRIORITY_BACKFILL_PRIORITY) || 95),
87
- );
88
- const REPROCESS_RETRY_DELAY_SECONDS = Math.max(
89
- 5,
90
- Math.min(3600, Number(process.env.STICKER_REPROCESS_RETRY_DELAY_SECONDS) || 120),
91
- );
44
+ const REPROCESS_PRIORITY_BACKFILL_SCAN_LIMIT = Math.max(0, Math.min(3000, Number(process.env.STICKER_REPROCESS_PRIORITY_BACKFILL_SCAN_LIMIT) || 300));
45
+ const REPROCESS_PRIORITY_BACKFILL_PRIORITY = Math.max(1, Math.min(100, Number(process.env.STICKER_REPROCESS_PRIORITY_BACKFILL_PRIORITY) || 95));
46
+ const REPROCESS_RETRY_DELAY_SECONDS = Math.max(5, Math.min(3600, Number(process.env.STICKER_REPROCESS_RETRY_DELAY_SECONDS) || 120));
92
47
 
93
48
  let cycleHandle = null;
94
49
  let startupTimeoutHandle = null;
@@ -306,10 +261,7 @@ const processReprocessQueue = async ({ limit = REPROCESS_MAX_PER_CYCLE } = {}) =
306
261
  }
307
262
 
308
263
  try {
309
- const [pendingDepth, processingDepth] = await Promise.all([
310
- countStickerAssetReprocessQueueByStatus('pending'),
311
- countStickerAssetReprocessQueueByStatus('processing'),
312
- ]);
264
+ const [pendingDepth, processingDepth] = await Promise.all([countStickerAssetReprocessQueueByStatus('pending'), countStickerAssetReprocessQueueByStatus('processing')]);
313
265
  setQueueDepth('sticker_reprocess_pending', pendingDepth);
314
266
  setQueueDepth('sticker_reprocess_processing', processingDepth);
315
267
  } catch (error) {
@@ -346,11 +298,7 @@ const processDeterministicReclassification = async () => {
346
298
  });
347
299
  };
348
300
 
349
- export const runStickerClassificationCycle = async ({
350
- processPending = true,
351
- processReprocess = true,
352
- processDeterministic = true,
353
- } = {}) => {
301
+ export const runStickerClassificationCycle = async ({ processPending = true, processReprocess = true, processDeterministic = true } = {}) => {
354
302
  const startedAt = Date.now();
355
303
  const shouldProcessClassifier = classifierConfig.enabled;
356
304
  const shouldProcessDeterministic = deterministicReclassificationConfig.enabled;
@@ -372,22 +320,11 @@ export const runStickerClassificationCycle = async ({
372
320
  try {
373
321
  const reprocessStats = processReprocess && shouldProcessClassifier ? await processReprocessQueue() : null;
374
322
  const pendingStats = processPending && shouldProcessClassifier ? await processPendingAssets() : null;
375
- const deterministicStats = processDeterministic
376
- ? await processDeterministicReclassification()
377
- : null;
378
-
379
- const processed =
380
- Number(pendingStats?.processed || 0)
381
- + Number(reprocessStats?.processed || 0)
382
- + Number(deterministicStats?.processed || 0);
383
- const classified =
384
- Number(pendingStats?.classified || 0)
385
- + Number(reprocessStats?.classified || 0)
386
- + Number(deterministicStats?.updated || 0);
387
- const failed =
388
- Number(pendingStats?.failed || 0)
389
- + Number(reprocessStats?.failed || 0)
390
- + Number(deterministicStats?.failed || 0);
323
+ const deterministicStats = processDeterministic ? await processDeterministicReclassification() : null;
324
+
325
+ const processed = Number(pendingStats?.processed || 0) + Number(reprocessStats?.processed || 0) + Number(deterministicStats?.processed || 0);
326
+ const classified = Number(pendingStats?.classified || 0) + Number(reprocessStats?.classified || 0) + Number(deterministicStats?.updated || 0);
327
+ const failed = Number(pendingStats?.failed || 0) + Number(reprocessStats?.failed || 0) + Number(deterministicStats?.failed || 0);
391
328
  const durationMs = Date.now() - startedAt;
392
329
 
393
330
  recordStickerClassificationCycle({
@@ -428,8 +365,7 @@ const resolveNextCycleDelayMs = () => {
428
365
  return EFFECTIVE_INTERVAL_MIN_MS;
429
366
  }
430
367
 
431
- return EFFECTIVE_INTERVAL_MIN_MS
432
- + Math.floor(Math.random() * (EFFECTIVE_INTERVAL_MAX_MS - EFFECTIVE_INTERVAL_MIN_MS + 1));
368
+ return EFFECTIVE_INTERVAL_MIN_MS + Math.floor(Math.random() * (EFFECTIVE_INTERVAL_MAX_MS - EFFECTIVE_INTERVAL_MIN_MS + 1));
433
369
  };
434
370
 
435
371
  const scheduleNextCycle = () => {
@@ -500,27 +436,12 @@ const classifyBatch = async () => {
500
436
  affinity_threshold: deterministicReclassificationConfig.affinity_threshold,
501
437
  };
502
438
 
503
- const processed =
504
- Number(pending.processed || 0)
505
- + Number(reprocess.processed || 0)
506
- + Number(deterministic.processed || 0);
507
- const classified =
508
- Number(pending.classified || 0)
509
- + Number(reprocess.classified || 0)
510
- + Number(deterministic.updated || 0);
511
- const failed =
512
- Number(pending.failed || 0)
513
- + Number(reprocess.failed || 0)
514
- + Number(deterministic.failed || 0);
439
+ const processed = Number(pending.processed || 0) + Number(reprocess.processed || 0) + Number(deterministic.processed || 0);
440
+ const classified = Number(pending.classified || 0) + Number(reprocess.classified || 0) + Number(deterministic.updated || 0);
441
+ const failed = Number(pending.failed || 0) + Number(reprocess.failed || 0) + Number(deterministic.failed || 0);
515
442
  const gainCount = classified;
516
443
 
517
- if (
518
- processed > 0
519
- || reprocess.enqueued_priority_backfill > 0
520
- || reprocess.enqueued_model_upgrade > 0
521
- || reprocess.enqueued_low_confidence > 0
522
- || deterministic.updated > 0
523
- ) {
444
+ if (processed > 0 || reprocess.enqueued_priority_backfill > 0 || reprocess.enqueued_model_upgrade > 0 || reprocess.enqueued_low_confidence > 0 || deterministic.updated > 0) {
524
445
  logger.info('Worker de classificação executado.', {
525
446
  action: 'sticker_classification_background_cycle',
526
447
  processed,
@@ -1,9 +1,5 @@
1
1
  import logger from '../../utils/logger/loggerModule.js';
2
- import {
3
- findStickerClassificationByAssetId,
4
- listStickerClassificationsByAssetIds,
5
- upsertStickerAssetClassification,
6
- } from './stickerAssetClassificationRepository.js';
2
+ import { findStickerClassificationByAssetId, listStickerClassificationsByAssetIds, upsertStickerAssetClassification } from './stickerAssetClassificationRepository.js';
7
3
  import { enqueueSemanticClusterResolution } from './semanticThemeClusterService.js';
8
4
 
9
5
  const parseEnvBool = (value, fallback) => {
@@ -15,26 +11,14 @@ const parseEnvBool = (value, fallback) => {
15
11
  };
16
12
 
17
13
  const CLIP_CLASSIFIER_ENABLED = parseEnvBool(process.env.CLIP_CLASSIFIER_ENABLED, true);
18
- const CLIP_CLASSIFIER_API_URL =
19
- String(process.env.CLIP_CLASSIFIER_API_URL || 'http://127.0.0.1:8008/classify').trim() ||
20
- 'http://127.0.0.1:8008/classify';
21
- const CLIP_CLASSIFIER_FEEDBACK_API_URL = String(
22
- process.env.CLIP_CLASSIFIER_FEEDBACK_API_URL
23
- || CLIP_CLASSIFIER_API_URL.replace(/\/classify\/?$/i, '/feedback'),
24
- ).trim();
14
+ const CLIP_CLASSIFIER_API_URL = String(process.env.CLIP_CLASSIFIER_API_URL || 'http://127.0.0.1:8008/classify').trim() || 'http://127.0.0.1:8008/classify';
15
+ const CLIP_CLASSIFIER_FEEDBACK_API_URL = String(process.env.CLIP_CLASSIFIER_FEEDBACK_API_URL || CLIP_CLASSIFIER_API_URL.replace(/\/classify\/?$/i, '/feedback')).trim();
25
16
  const CLIP_CLASSIFIER_TIMEOUT_MS = Math.max(500, Number(process.env.CLIP_CLASSIFIER_TIMEOUT_MS) || 3000);
26
17
  const CLIP_CLASSIFIER_PROVIDER = String(process.env.CLIP_CLASSIFIER_PROVIDER || 'clip').trim() || 'clip';
27
- const CLIP_CLASSIFIER_CLASSIFICATION_VERSION =
28
- String(process.env.CLIP_CLASSIFIER_CLASSIFICATION_VERSION || process.env.CLIP_CLASSIFIER_MODEL_VERSION || 'v1').trim() || 'v1';
29
- const CLIP_CLASSIFIER_NSFW_THRESHOLD = Number.isFinite(Number(process.env.CLIP_CLASSIFIER_NSFW_THRESHOLD))
30
- ? Number(process.env.CLIP_CLASSIFIER_NSFW_THRESHOLD)
31
- : null;
32
- const STICKER_TAG_MIN_SCORE = Number.isFinite(Number(process.env.STICKER_CLASSIFICATION_TAG_MIN_SCORE))
33
- ? Number(process.env.STICKER_CLASSIFICATION_TAG_MIN_SCORE)
34
- : 0.2;
35
- const PACK_TAG_MIN_SCORE = Number.isFinite(Number(process.env.PACK_CLASSIFICATION_TAG_MIN_SCORE))
36
- ? Number(process.env.PACK_CLASSIFICATION_TAG_MIN_SCORE)
37
- : 0.18;
18
+ const CLIP_CLASSIFIER_CLASSIFICATION_VERSION = String(process.env.CLIP_CLASSIFIER_CLASSIFICATION_VERSION || process.env.CLIP_CLASSIFIER_MODEL_VERSION || 'v1').trim() || 'v1';
19
+ const CLIP_CLASSIFIER_NSFW_THRESHOLD = Number.isFinite(Number(process.env.CLIP_CLASSIFIER_NSFW_THRESHOLD)) ? Number(process.env.CLIP_CLASSIFIER_NSFW_THRESHOLD) : null;
20
+ const STICKER_TAG_MIN_SCORE = Number.isFinite(Number(process.env.STICKER_CLASSIFICATION_TAG_MIN_SCORE)) ? Number(process.env.STICKER_CLASSIFICATION_TAG_MIN_SCORE) : 0.2;
21
+ const PACK_TAG_MIN_SCORE = Number.isFinite(Number(process.env.PACK_CLASSIFICATION_TAG_MIN_SCORE)) ? Number(process.env.PACK_CLASSIFICATION_TAG_MIN_SCORE) : 0.18;
38
22
  const MAX_TAGS_PER_ENTITY = Math.max(1, Math.min(10, Number(process.env.CLASSIFICATION_MAX_TAGS) || 6));
39
23
 
40
24
  const LABEL_TO_TAG = {
@@ -59,7 +43,9 @@ const normalizeTag = (value) => {
59
43
  };
60
44
 
61
45
  const mapLabelToTag = (label) => {
62
- const key = String(label || '').trim().toLowerCase();
46
+ const key = String(label || '')
47
+ .trim()
48
+ .toLowerCase();
63
49
  return LABEL_TO_TAG[key] || normalizeTag(key);
64
50
  };
65
51
 
@@ -116,32 +102,18 @@ const normalizeClassificationResult = (payload) => {
116
102
  const topFromScores = scoreEntries[0] || null;
117
103
  const topFromLabels = topLabels[0] || null;
118
104
  const category = explicitCategory || topFromLabels?.label || topFromScores?.[0] || null;
119
- const confidence = Number.isFinite(Number(payload?.confidence))
120
- ? Number(Number(payload.confidence).toFixed(6))
121
- : Number.isFinite(Number(topFromLabels?.score))
122
- ? Number(Number(topFromLabels.score).toFixed(6))
123
- : topFromScores
124
- ? Number(Number(topFromScores[1]).toFixed(6))
125
- : null;
105
+ const confidence = Number.isFinite(Number(payload?.confidence)) ? Number(Number(payload.confidence).toFixed(6)) : Number.isFinite(Number(topFromLabels?.score)) ? Number(Number(topFromLabels.score).toFixed(6)) : topFromScores ? Number(Number(topFromScores[1]).toFixed(6)) : null;
126
106
  const nsfwScore = Number.isFinite(Number(payload?.nsfw_score)) ? Number(Number(payload.nsfw_score).toFixed(6)) : null;
127
107
  const entropy = Number.isFinite(Number(payload?.entropy)) ? Number(Number(payload.entropy).toFixed(6)) : null;
128
- const entropyNormalized = Number.isFinite(Number(payload?.entropy_normalized))
129
- ? Number(Number(payload.entropy_normalized).toFixed(6))
130
- : null;
131
- const confidenceMargin = Number.isFinite(Number(payload?.confidence_margin))
132
- ? Number(Number(payload.confidence_margin).toFixed(6))
133
- : null;
134
- const affinityWeight = Number.isFinite(Number(payload?.affinity_weight))
135
- ? Number(Number(payload.affinity_weight).toFixed(6))
136
- : null;
137
- const affinityWeightRaw = Number.isFinite(Number(payload?.affinity_weight_raw))
138
- ? Number(Number(payload.affinity_weight_raw).toFixed(6))
139
- : null;
140
- const imageHash = String(payload?.image_hash || '').trim().toLowerCase();
141
-
142
- const llmExpansion = payload?.llm_expansion && typeof payload.llm_expansion === 'object'
143
- ? payload.llm_expansion
144
- : {};
108
+ const entropyNormalized = Number.isFinite(Number(payload?.entropy_normalized)) ? Number(Number(payload.entropy_normalized).toFixed(6)) : null;
109
+ const confidenceMargin = Number.isFinite(Number(payload?.confidence_margin)) ? Number(Number(payload.confidence_margin).toFixed(6)) : null;
110
+ const affinityWeight = Number.isFinite(Number(payload?.affinity_weight)) ? Number(Number(payload.affinity_weight).toFixed(6)) : null;
111
+ const affinityWeightRaw = Number.isFinite(Number(payload?.affinity_weight_raw)) ? Number(Number(payload.affinity_weight_raw).toFixed(6)) : null;
112
+ const imageHash = String(payload?.image_hash || '')
113
+ .trim()
114
+ .toLowerCase();
115
+
116
+ const llmExpansion = payload?.llm_expansion && typeof payload.llm_expansion === 'object' ? payload.llm_expansion : {};
145
117
  const llmSubtags = normalizeListOfStrings(llmExpansion?.subtags, 40);
146
118
  const llmStyleTraits = normalizeListOfStrings(llmExpansion?.style_traits, 20);
147
119
  const llmEmotions = normalizeListOfStrings(llmExpansion?.emotions, 20);
@@ -149,13 +121,15 @@ const normalizeClassificationResult = (payload) => {
149
121
 
150
122
  const similarImages = Array.isArray(payload?.similar_images)
151
123
  ? payload.similar_images
152
- .map((entry) => ({
153
- image_hash: String(entry?.image_hash || '').trim().toLowerCase(),
154
- asset_id: entry?.asset_id ? String(entry.asset_id) : null,
155
- similarity: Number.isFinite(Number(entry?.similarity)) ? Number(Number(entry.similarity).toFixed(6)) : null,
156
- }))
157
- .filter((entry) => entry.image_hash && Number.isFinite(Number(entry.similarity)))
158
- .slice(0, 40)
124
+ .map((entry) => ({
125
+ image_hash: String(entry?.image_hash || '')
126
+ .trim()
127
+ .toLowerCase(),
128
+ asset_id: entry?.asset_id ? String(entry.asset_id) : null,
129
+ similarity: Number.isFinite(Number(entry?.similarity)) ? Number(Number(entry.similarity).toFixed(6)) : null,
130
+ }))
131
+ .filter((entry) => entry.image_hash && Number.isFinite(Number(entry.similarity)))
132
+ .slice(0, 40)
159
133
  : [];
160
134
 
161
135
  return {
@@ -188,18 +162,12 @@ const isClassifierNonRetryableError = (error) => {
188
162
  if (NON_RETRYABLE_CLASSIFIER_HTTP_STATUSES.has(status)) return true;
189
163
 
190
164
  const message = String(error?.message || '').toLowerCase();
191
- return (
192
- message.includes('could not create decoder object')
193
- || message.includes('nao foi possivel decodificar a imagem')
194
- || message.includes('não foi possível decodificar a imagem')
195
- );
165
+ return message.includes('could not create decoder object') || message.includes('nao foi possivel decodificar a imagem') || message.includes('não foi possível decodificar a imagem');
196
166
  };
197
167
 
198
168
  const buildFallbackClassificationFromError = ({ asset, error }) => {
199
169
  const message = String(error?.message || '').toLowerCase();
200
- const reasonTag = message.includes('decoder') || message.includes('decodificar')
201
- ? 'decoder-error'
202
- : 'non-retryable-error';
170
+ const reasonTag = message.includes('decoder') || message.includes('decodificar') ? 'decoder-error' : 'non-retryable-error';
203
171
 
204
172
  return {
205
173
  asset_id: asset.id,
@@ -210,12 +178,14 @@ const buildFallbackClassificationFromError = ({ asset, error }) => {
210
178
  confidence: 0,
211
179
  entropy: 0,
212
180
  confidence_margin: 0,
213
- top_labels: [{
214
- label: 'invalid image',
215
- score: 1,
216
- logit: null,
217
- clip_score: null,
218
- }],
181
+ top_labels: [
182
+ {
183
+ label: 'invalid image',
184
+ score: 1,
185
+ logit: null,
186
+ clip_score: null,
187
+ },
188
+ ],
219
189
  affinity_weight: 0,
220
190
  image_hash: asset?.sha256 || null,
221
191
  ambiguous: true,
@@ -541,15 +511,14 @@ export const classifierConfig = {
541
511
  classification_version: CLIP_CLASSIFIER_CLASSIFICATION_VERSION,
542
512
  };
543
513
 
544
- export const submitStickerClassificationFeedback = async ({
545
- imageHash,
546
- theme,
547
- accepted,
548
- assetId = null,
549
- }) => {
514
+ export const submitStickerClassificationFeedback = async ({ imageHash, theme, accepted, assetId = null }) => {
550
515
  if (!CLIP_CLASSIFIER_ENABLED) return false;
551
- const normalizedHash = String(imageHash || '').trim().toLowerCase();
552
- const normalizedTheme = String(theme || '').trim().toLowerCase();
516
+ const normalizedHash = String(imageHash || '')
517
+ .trim()
518
+ .toLowerCase();
519
+ const normalizedTheme = String(theme || '')
520
+ .trim()
521
+ .toLowerCase();
553
522
  if (!normalizedHash || !normalizedTheme) return false;
554
523
  if (typeof globalThis.fetch !== 'function') return false;
555
524
 
@@ -3,12 +3,7 @@ import { setQueueDepth } from '../../observability/metrics.js';
3
3
  import { isFeatureEnabled } from '../../services/featureFlagService.js';
4
4
  import { runStickerClassificationCycle } from './stickerClassificationBackgroundRuntime.js';
5
5
  import { runStickerAutoPackByTagsCycle } from './stickerAutoPackByTagsRuntime.js';
6
- import {
7
- claimWorkerTask,
8
- completeWorkerTask,
9
- countWorkerTasksByStatus,
10
- failWorkerTask,
11
- } from './stickerWorkerTaskQueueRepository.js';
6
+ import { claimWorkerTask, completeWorkerTask, countWorkerTasksByStatus, failWorkerTask } from './stickerWorkerTaskQueueRepository.js';
12
7
 
13
8
  const parseEnvBool = (value, fallback) => {
14
9
  if (value === undefined || value === null || value === '') return fallback;
@@ -26,36 +21,17 @@ const clampInt = (value, fallback, min, max) => {
26
21
 
27
22
  const DEDICATED_WORKERS_ENABLED = parseEnvBool(process.env.STICKER_DEDICATED_WORKERS_ENABLED, true);
28
23
  const DEDICATED_WORKERS_FORCE_ENABLED = parseEnvBool(process.env.STICKER_DEDICATED_WORKERS_FORCE_ENABLED, false);
29
- const DEDICATED_WORKER_RETRY_DELAY_SECONDS = clampInt(
30
- process.env.STICKER_DEDICATED_WORKER_RETRY_DELAY_SECONDS,
31
- 60,
32
- 5,
33
- 3600,
34
- );
35
- const DEDICATED_WORKER_POLL_INTERVAL_MS = clampInt(
36
- process.env.STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS,
37
- 2500,
38
- 250,
39
- 60_000,
40
- );
41
- const DEDICATED_WORKER_MAX_TASKS_PER_TICK = clampInt(
42
- process.env.STICKER_DEDICATED_WORKER_MAX_TASKS_PER_TICK,
43
- 1,
44
- 1,
45
- 25,
46
- );
47
- const DEDICATED_WORKER_COHORT_KEY =
48
- String(process.env.STICKER_DEDICATED_WORKER_COHORT_KEY || process.env.HOSTNAME || process.pid).trim()
49
- || 'worker';
50
-
51
- const SUPPORTED_TASK_TYPES = new Set([
52
- 'classification_cycle',
53
- 'curation_cycle',
54
- 'rebuild_cycle',
55
- ]);
24
+ const DEDICATED_WORKER_RETRY_DELAY_SECONDS = clampInt(process.env.STICKER_DEDICATED_WORKER_RETRY_DELAY_SECONDS, 60, 5, 3600);
25
+ const DEDICATED_WORKER_POLL_INTERVAL_MS = clampInt(process.env.STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS, 2500, 250, 60_000);
26
+ const DEDICATED_WORKER_MAX_TASKS_PER_TICK = clampInt(process.env.STICKER_DEDICATED_WORKER_MAX_TASKS_PER_TICK, 1, 1, 25);
27
+ const DEDICATED_WORKER_COHORT_KEY = String(process.env.STICKER_DEDICATED_WORKER_COHORT_KEY || process.env.HOSTNAME || process.pid).trim() || 'worker';
28
+
29
+ const SUPPORTED_TASK_TYPES = new Set(['classification_cycle', 'curation_cycle', 'rebuild_cycle']);
56
30
 
57
31
  const normalizeTaskType = (value) => {
58
- const normalized = String(value || '').trim().toLowerCase();
32
+ const normalized = String(value || '')
33
+ .trim()
34
+ .toLowerCase();
59
35
  return SUPPORTED_TASK_TYPES.has(normalized) ? normalized : null;
60
36
  };
61
37
 
@@ -86,11 +62,7 @@ const runTaskHandler = async (taskType, payload = {}) => {
86
62
  };
87
63
 
88
64
  const refreshQueueDepthMetrics = async () => {
89
- const [pending, processing, failed] = await Promise.all([
90
- countWorkerTasksByStatus('pending'),
91
- countWorkerTasksByStatus('processing'),
92
- countWorkerTasksByStatus('failed'),
93
- ]);
65
+ const [pending, processing, failed] = await Promise.all([countWorkerTasksByStatus('pending'), countWorkerTasksByStatus('processing'), countWorkerTasksByStatus('failed')]);
94
66
  setQueueDepth('sticker_worker_tasks_pending', pending);
95
67
  setQueueDepth('sticker_worker_tasks_processing', processing);
96
68
  setQueueDepth('sticker_worker_tasks_failed', failed);
@@ -107,13 +79,7 @@ const canRunDedicatedWorkers = async (taskType) => {
107
79
 
108
80
  export const isSupportedStickerWorkerTaskType = (taskType) => Boolean(normalizeTaskType(taskType));
109
81
 
110
- export const runDedicatedStickerWorkerTick = async (
111
- {
112
- taskType,
113
- maxTasks = DEDICATED_WORKER_MAX_TASKS_PER_TICK,
114
- retryDelaySeconds = DEDICATED_WORKER_RETRY_DELAY_SECONDS,
115
- } = {},
116
- ) => {
82
+ export const runDedicatedStickerWorkerTick = async ({ taskType, maxTasks = DEDICATED_WORKER_MAX_TASKS_PER_TICK, retryDelaySeconds = DEDICATED_WORKER_RETRY_DELAY_SECONDS } = {}) => {
117
83
  const normalizedTaskType = normalizeTaskType(taskType);
118
84
  if (!normalizedTaskType) {
119
85
  return { executed: false, reason: 'invalid_task_type', task_type: taskType || null };
@@ -167,13 +133,7 @@ export const runDedicatedStickerWorkerTick = async (
167
133
  return stats;
168
134
  };
169
135
 
170
- export const startDedicatedStickerWorker = ({
171
- taskType,
172
- pollIntervalMs = DEDICATED_WORKER_POLL_INTERVAL_MS,
173
- maxTasksPerTick = DEDICATED_WORKER_MAX_TASKS_PER_TICK,
174
- retryDelaySeconds = DEDICATED_WORKER_RETRY_DELAY_SECONDS,
175
- label = '',
176
- } = {}) => {
136
+ export const startDedicatedStickerWorker = ({ taskType, pollIntervalMs = DEDICATED_WORKER_POLL_INTERVAL_MS, maxTasksPerTick = DEDICATED_WORKER_MAX_TASKS_PER_TICK, retryDelaySeconds = DEDICATED_WORKER_RETRY_DELAY_SECONDS, label = '' } = {}) => {
177
137
  const normalizedTaskType = normalizeTaskType(taskType);
178
138
  if (!normalizedTaskType) {
179
139
  throw new Error(`invalid_task_type:${taskType}`);
@@ -2,36 +2,30 @@ import logger from '../../utils/logger/loggerModule.js';
2
2
  import { isFeatureEnabled } from '../../services/featureFlagService.js';
3
3
  import { enqueueDomainEvent } from './domainEventOutboxRepository.js';
4
4
 
5
- const resolveDefaultIdempotencyKey = ({
6
- eventType,
7
- aggregateType,
8
- aggregateId,
9
- payload = null,
10
- }) => {
5
+ const resolveDefaultIdempotencyKey = ({ eventType, aggregateType, aggregateId, payload = null }) => {
11
6
  const payloadKey = payload && typeof payload === 'object' ? JSON.stringify(payload).slice(0, 80) : '';
12
7
  return `${eventType}:${aggregateType}:${aggregateId}:${payloadKey}`.slice(0, 180);
13
8
  };
14
9
 
15
- export const publishStickerDomainEvent = async (
16
- eventPayload,
17
- { connection = null, force = false } = {},
18
- ) => {
10
+ export const publishStickerDomainEvent = async (eventPayload, { connection = null, force = false } = {}) => {
19
11
  const eventType = String(eventPayload?.eventType || '').trim();
20
12
  const aggregateType = String(eventPayload?.aggregateType || '').trim();
21
13
  const aggregateId = String(eventPayload?.aggregateId || '').trim();
22
14
 
23
15
  if (!eventType || !aggregateType || !aggregateId) return false;
24
16
 
25
- const enabled = force ? true : await isFeatureEnabled('enable_domain_event_outbox', {
26
- fallback: true,
27
- subjectKey: `${aggregateType}:${aggregateId}`,
28
- });
17
+ const enabled = force
18
+ ? true
19
+ : await isFeatureEnabled('enable_domain_event_outbox', {
20
+ fallback: true,
21
+ subjectKey: `${aggregateType}:${aggregateId}`,
22
+ });
29
23
  if (!enabled) return false;
30
24
 
31
25
  try {
32
26
  const idempotencyKey =
33
- String(eventPayload?.idempotencyKey || '').trim()
34
- || resolveDefaultIdempotencyKey({
27
+ String(eventPayload?.idempotencyKey || '').trim() ||
28
+ resolveDefaultIdempotencyKey({
35
29
  eventType,
36
30
  aggregateType,
37
31
  aggregateId,