@kaikybrofc/omnizap-system 2.1.8
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.
- package/.env.example +534 -0
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/RELEASE-v2.1.2.md +83 -0
- package/app/config/adminIdentity.js +87 -0
- package/app/config/baileysConfig.js +693 -0
- package/app/config/groupUtils.js +388 -0
- package/app/connection/socketController.js +992 -0
- package/app/controllers/messageController.js +354 -0
- package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
- package/app/modules/adminModule/groupEventHandlers.js +355 -0
- package/app/modules/aiModule/catCommand.js +1006 -0
- package/app/modules/broadcastModule/noticeCommand.js +416 -0
- package/app/modules/gameModule/diceCommand.js +67 -0
- package/app/modules/menuModule/common.js +311 -0
- package/app/modules/menuModule/menus.js +59 -0
- package/app/modules/playModule/playCommand.js +1615 -0
- package/app/modules/quoteModule/quoteCommand.js +851 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
- package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
- package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
- package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
- package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
- package/app/modules/statsModule/globalRankingCommand.js +65 -0
- package/app/modules/statsModule/noMessageCommand.js +288 -0
- package/app/modules/statsModule/rankingCommand.js +60 -0
- package/app/modules/statsModule/rankingCommon.js +889 -0
- package/app/modules/stickerModule/addStickerMetadata.js +239 -0
- package/app/modules/stickerModule/convertToWebp.js +390 -0
- package/app/modules/stickerModule/stickerCommand.js +454 -0
- package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
- package/app/modules/stickerModule/stickerTextCommand.js +657 -0
- package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
- package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
- package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
- package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
- package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
- package/app/modules/stickerPackModule/stickerPackService.js +788 -0
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
- package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
- package/app/modules/systemMetricsModule/pingCommand.js +421 -0
- package/app/modules/tiktokModule/tiktokCommand.js +798 -0
- package/app/modules/userModule/userCommand.js +1217 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
- package/app/observability/metrics.js +734 -0
- package/app/services/captchaService.js +492 -0
- package/app/services/dbWriteQueue.js +572 -0
- package/app/services/groupMetadataService.js +279 -0
- package/app/services/lidMapService.js +663 -0
- package/app/services/messagePersistenceService.js +56 -0
- package/app/services/newsBroadcastService.js +351 -0
- package/app/services/pokeApiService.js +398 -0
- package/app/services/queueUtils.js +57 -0
- package/app/services/socketState.js +7 -0
- package/app/store/aiPromptStore.js +38 -0
- package/app/store/groupConfigStore.js +58 -0
- package/app/store/premiumUserStore.js +36 -0
- package/app/utils/antiLink/antiLinkModule.js +804 -0
- package/app/utils/http/getImageBufferModule.js +18 -0
- package/app/utils/json/jsonSanitizer.js +113 -0
- package/app/utils/json/jsonSanitizer.test.js +40 -0
- package/app/utils/logger/loggerModule.js +262 -0
- package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
- package/database/index.js +2052 -0
- package/database/init.js +516 -0
- package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
- package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
- package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
- package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
- package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
- package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
- package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
- package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
- package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
- package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
- package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
- package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
- package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
- package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
- package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
- package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
- package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
- package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
- package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
- package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
- package/docker-compose.yml +103 -0
- package/ecosystem.prod.config.cjs +35 -0
- package/eslint.config.js +61 -0
- package/index.js +437 -0
- package/ml/clip_classifier/Dockerfile +16 -0
- package/ml/clip_classifier/README.md +120 -0
- package/ml/clip_classifier/adaptive_scoring.py +40 -0
- package/ml/clip_classifier/classifier.py +654 -0
- package/ml/clip_classifier/embedding_store.py +481 -0
- package/ml/clip_classifier/env_loader.py +15 -0
- package/ml/clip_classifier/llm_label_expander.py +144 -0
- package/ml/clip_classifier/main.py +213 -0
- package/ml/clip_classifier/requirements.txt +10 -0
- package/ml/clip_classifier/similarity_engine.py +74 -0
- package/observability/alert-rules.yml +60 -0
- package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
- package/observability/grafana/dashboards/omnizap-overview.json +170 -0
- package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
- package/observability/loki-config.yml +38 -0
- package/observability/mysql-exporter.cnf +5 -0
- package/observability/mysql-setup.sql +46 -0
- package/observability/prometheus.yml +32 -0
- package/observability/promtail-config.yml +84 -0
- package/package.json +109 -0
- package/public/api-docs/index.html +144 -0
- package/public/css/github-project-panel.css +297 -0
- package/public/css/stickers-admin.css +1272 -0
- package/public/css/styles.css +671 -0
- package/public/index.html +1311 -0
- package/public/js/apps/apiDocsApp.js +310 -0
- package/public/js/apps/createPackApp.js +2069 -0
- package/public/js/apps/homeApp.js +396 -0
- package/public/js/apps/stickersAdminApp.js +1744 -0
- package/public/js/apps/stickersApp.js +4830 -0
- package/public/js/catalog.js +1019 -0
- package/public/js/github-panel/components/CommitList.js +34 -0
- package/public/js/github-panel/components/ErrorState.js +16 -0
- package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
- package/public/js/github-panel/components/ReleaseList.js +38 -0
- package/public/js/github-panel/components/SkeletonPanel.js +22 -0
- package/public/js/github-panel/components/StatCard.js +15 -0
- package/public/js/github-panel/index.js +15 -0
- package/public/js/github-panel/useGithubRepoData.js +154 -0
- package/public/js/github-panel/vendor/react.js +11 -0
- package/public/js/runtime/react-runtime.js +19 -0
- package/public/licenca/index.html +106 -0
- package/public/stickers/admin/index.html +23 -0
- package/public/stickers/create/index.html +47 -0
- package/public/stickers/index.html +48 -0
- package/public/termos-de-uso/index.html +125 -0
- package/scripts/cache-bust.mjs +107 -0
- package/scripts/deploy.sh +458 -0
- package/scripts/github-deploy-notify.mjs +174 -0
- package/scripts/release.sh +129 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
|
|
4
|
+
import logger from '../../utils/logger/loggerModule.js';
|
|
5
|
+
import { setQueueDepth } from '../../observability/metrics.js';
|
|
6
|
+
import { listStickerAssetsPendingClassification, findStickerAssetById } from './stickerAssetRepository.js';
|
|
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';
|
|
24
|
+
|
|
25
|
+
const parseEnvBool = (value, fallback) => {
|
|
26
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
27
|
+
const normalized = String(value).trim().toLowerCase();
|
|
28
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
29
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
30
|
+
return fallback;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const BACKGROUND_ENABLED = parseEnvBool(process.env.STICKER_CLASSIFICATION_BACKGROUND_ENABLED, true);
|
|
34
|
+
const STARTUP_DELAY_MS = Math.max(1_000, Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_STARTUP_DELAY_MS) || 15_000);
|
|
35
|
+
const LEGACY_INTERVAL_MS = Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_INTERVAL_MS);
|
|
36
|
+
const INTERVAL_MIN_MS_RAW = Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_INTERVAL_MIN_MS);
|
|
37
|
+
const INTERVAL_MAX_MS_RAW = Number(process.env.STICKER_CLASSIFICATION_BACKGROUND_INTERVAL_MAX_MS);
|
|
38
|
+
const DEFAULT_INTERVAL_MIN_MS = 5 * 60_000;
|
|
39
|
+
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;
|
|
46
|
+
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;
|
|
50
|
+
const EFFECTIVE_INTERVAL_MIN_MS = LEGACY_FIXED_INTERVAL_MS || INTERVAL_MIN_MS;
|
|
51
|
+
const EFFECTIVE_INTERVAL_MAX_MS = LEGACY_FIXED_INTERVAL_MS || INTERVAL_MAX_MS;
|
|
52
|
+
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
|
+
);
|
|
61
|
+
|
|
62
|
+
const REPROCESS_ENABLED = parseEnvBool(process.env.STICKER_REPROCESS_QUEUE_ENABLED, true);
|
|
63
|
+
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
|
+
);
|
|
79
|
+
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
|
+
);
|
|
92
|
+
|
|
93
|
+
let cycleHandle = null;
|
|
94
|
+
let startupTimeoutHandle = null;
|
|
95
|
+
let running = false;
|
|
96
|
+
let schedulerEnabled = false;
|
|
97
|
+
let reprocessQueueAvailable = true;
|
|
98
|
+
|
|
99
|
+
const classifyAsset = async ({ asset, force = false }) => {
|
|
100
|
+
if (!asset?.storage_path || !asset?.id) {
|
|
101
|
+
return { ok: false, reason: 'asset_missing_storage' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const buffer = await fs.readFile(asset.storage_path);
|
|
105
|
+
const result = await ensureStickerAssetClassified({ asset, buffer, force });
|
|
106
|
+
return { ok: Boolean(result), reason: result ? null : 'classification_empty' };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const processPendingAssets = async () => {
|
|
110
|
+
const stats = {
|
|
111
|
+
processed: 0,
|
|
112
|
+
classified: 0,
|
|
113
|
+
failed: 0,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const assets = await listStickerAssetsPendingClassification({ limit: BATCH_SIZE });
|
|
117
|
+
if (!assets.length) {
|
|
118
|
+
logger.debug('Worker de classificação: sem assets pendentes.', {
|
|
119
|
+
action: 'sticker_classification_background_idle',
|
|
120
|
+
});
|
|
121
|
+
return stats;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let cursor = 0;
|
|
125
|
+
const workers = Array.from({ length: Math.min(BACKGROUND_CONCURRENCY, assets.length) }).map(async () => {
|
|
126
|
+
while (true) {
|
|
127
|
+
const index = cursor;
|
|
128
|
+
cursor += 1;
|
|
129
|
+
if (index >= assets.length) break;
|
|
130
|
+
|
|
131
|
+
const asset = assets[index];
|
|
132
|
+
stats.processed += 1;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await classifyAsset({ asset, force: false });
|
|
136
|
+
if (result.ok) {
|
|
137
|
+
stats.classified += 1;
|
|
138
|
+
} else {
|
|
139
|
+
stats.failed += 1;
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
stats.failed += 1;
|
|
143
|
+
logger.warn('Falha ao classificar asset no worker de background.', {
|
|
144
|
+
action: 'sticker_classification_background_asset_failed',
|
|
145
|
+
asset_id: asset?.id || null,
|
|
146
|
+
storage_path: asset?.storage_path || null,
|
|
147
|
+
error: error?.message,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await Promise.all(workers);
|
|
154
|
+
return stats;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const enqueueModelUpgradeCandidates = async () => {
|
|
158
|
+
if (!REPROCESS_ENABLED || !classifierConfig?.classification_version) return 0;
|
|
159
|
+
|
|
160
|
+
const assetIds = await listAssetsForModelUpgradeReprocess({
|
|
161
|
+
currentVersion: classifierConfig.classification_version,
|
|
162
|
+
limit: REPROCESS_MODEL_UPGRADE_SCAN_LIMIT,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let enqueued = 0;
|
|
166
|
+
for (const assetId of assetIds) {
|
|
167
|
+
const inserted = await enqueueStickerAssetReprocess({
|
|
168
|
+
assetId,
|
|
169
|
+
reason: 'MODEL_UPGRADE',
|
|
170
|
+
priority: 60,
|
|
171
|
+
});
|
|
172
|
+
if (inserted) enqueued += 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return enqueued;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const enqueueLowConfidenceCandidates = async () => {
|
|
179
|
+
if (!REPROCESS_ENABLED || !Number.isFinite(REPROCESS_LOW_CONFIDENCE_THRESHOLD)) return 0;
|
|
180
|
+
|
|
181
|
+
const assetIds = await listAssetsForLowConfidenceReprocess({
|
|
182
|
+
confidenceThreshold: REPROCESS_LOW_CONFIDENCE_THRESHOLD,
|
|
183
|
+
staleHours: REPROCESS_LOW_CONFIDENCE_STALE_HOURS,
|
|
184
|
+
limit: REPROCESS_LOW_CONFIDENCE_SCAN_LIMIT,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
let enqueued = 0;
|
|
188
|
+
for (const assetId of assetIds) {
|
|
189
|
+
const inserted = await enqueueStickerAssetReprocess({
|
|
190
|
+
assetId,
|
|
191
|
+
reason: 'LOW_CONFIDENCE',
|
|
192
|
+
priority: 70,
|
|
193
|
+
});
|
|
194
|
+
if (inserted) enqueued += 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return enqueued;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const enqueuePriorityBackfillCandidates = async () => {
|
|
201
|
+
if (!REPROCESS_ENABLED || !REPROCESS_PRIORITY_BACKFILL_ENABLED) return 0;
|
|
202
|
+
|
|
203
|
+
const assetIds = await listAssetsForPrioritySignalBackfillReprocess({
|
|
204
|
+
limit: REPROCESS_PRIORITY_BACKFILL_SCAN_LIMIT,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
let enqueued = 0;
|
|
208
|
+
for (const assetId of assetIds) {
|
|
209
|
+
const inserted = await enqueueStickerAssetReprocess({
|
|
210
|
+
assetId,
|
|
211
|
+
reason: 'MODEL_UPGRADE',
|
|
212
|
+
priority: REPROCESS_PRIORITY_BACKFILL_PRIORITY,
|
|
213
|
+
});
|
|
214
|
+
if (inserted) enqueued += 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return enqueued;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const processReprocessQueue = async ({ limit = REPROCESS_MAX_PER_CYCLE } = {}) => {
|
|
221
|
+
if (!REPROCESS_ENABLED || limit <= 0 || !reprocessQueueAvailable) {
|
|
222
|
+
return {
|
|
223
|
+
processed: 0,
|
|
224
|
+
classified: 0,
|
|
225
|
+
failed: 0,
|
|
226
|
+
enqueued_priority_backfill: 0,
|
|
227
|
+
enqueued_model_upgrade: 0,
|
|
228
|
+
enqueued_low_confidence: 0,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let enqueuedPriorityBackfill = 0;
|
|
233
|
+
let enqueuedModelUpgrade = 0;
|
|
234
|
+
let enqueuedLowConfidence = 0;
|
|
235
|
+
try {
|
|
236
|
+
enqueuedPriorityBackfill = await enqueuePriorityBackfillCandidates();
|
|
237
|
+
enqueuedModelUpgrade = await enqueueModelUpgradeCandidates();
|
|
238
|
+
enqueuedLowConfidence = await enqueueLowConfidenceCandidates();
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error?.code === 'ER_NO_SUCH_TABLE') {
|
|
241
|
+
reprocessQueueAvailable = false;
|
|
242
|
+
logger.warn('Fila de reprocessamento indisponivel (migração pendente). Seguindo sem reprocessar.', {
|
|
243
|
+
action: 'sticker_reprocess_queue_unavailable',
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
processed: 0,
|
|
247
|
+
classified: 0,
|
|
248
|
+
failed: 0,
|
|
249
|
+
enqueued_priority_backfill: 0,
|
|
250
|
+
enqueued_model_upgrade: 0,
|
|
251
|
+
enqueued_low_confidence: 0,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const stats = {
|
|
258
|
+
processed: 0,
|
|
259
|
+
classified: 0,
|
|
260
|
+
failed: 0,
|
|
261
|
+
enqueued_priority_backfill: enqueuedPriorityBackfill,
|
|
262
|
+
enqueued_model_upgrade: enqueuedModelUpgrade,
|
|
263
|
+
enqueued_low_confidence: enqueuedLowConfidence,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
for (let i = 0; i < limit; i += 1) {
|
|
267
|
+
const task = await claimStickerAssetReprocessTask();
|
|
268
|
+
if (!task) break;
|
|
269
|
+
|
|
270
|
+
stats.processed += 1;
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const asset = await findStickerAssetById(task.asset_id);
|
|
274
|
+
if (!asset?.id) {
|
|
275
|
+
await completeStickerAssetReprocessTask(task.id);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const result = await classifyAsset({ asset, force: true });
|
|
280
|
+
if (result.ok) {
|
|
281
|
+
await completeStickerAssetReprocessTask(task.id);
|
|
282
|
+
stats.classified += 1;
|
|
283
|
+
} else {
|
|
284
|
+
stats.failed += 1;
|
|
285
|
+
await failStickerAssetReprocessTask(task.id, {
|
|
286
|
+
error: result.reason || 'reprocess_failed',
|
|
287
|
+
retryDelaySeconds: REPROCESS_RETRY_DELAY_SECONDS,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
stats.failed += 1;
|
|
292
|
+
await failStickerAssetReprocessTask(task.id, {
|
|
293
|
+
error: error?.message || 'reprocess_exception',
|
|
294
|
+
retryDelaySeconds: REPROCESS_RETRY_DELAY_SECONDS,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
logger.warn('Falha ao reclassificar asset da fila de reprocessamento.', {
|
|
298
|
+
action: 'sticker_reprocess_queue_task_failed',
|
|
299
|
+
task_id: task.id,
|
|
300
|
+
asset_id: task.asset_id,
|
|
301
|
+
reason: task.reason,
|
|
302
|
+
attempts: task.attempts,
|
|
303
|
+
error: error?.message,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const [pendingDepth, processingDepth] = await Promise.all([
|
|
310
|
+
countStickerAssetReprocessQueueByStatus('pending'),
|
|
311
|
+
countStickerAssetReprocessQueueByStatus('processing'),
|
|
312
|
+
]);
|
|
313
|
+
setQueueDepth('sticker_reprocess_pending', pendingDepth);
|
|
314
|
+
setQueueDepth('sticker_reprocess_processing', processingDepth);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
if (error?.code === 'ER_NO_SUCH_TABLE') {
|
|
317
|
+
reprocessQueueAvailable = false;
|
|
318
|
+
} else {
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return stats;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const processDeterministicReclassification = async () => {
|
|
327
|
+
if (!deterministicReclassificationConfig.enabled) {
|
|
328
|
+
return {
|
|
329
|
+
enabled: false,
|
|
330
|
+
processed: 0,
|
|
331
|
+
updated: 0,
|
|
332
|
+
skipped: 0,
|
|
333
|
+
failed: 0,
|
|
334
|
+
batches: 0,
|
|
335
|
+
last_cursor: null,
|
|
336
|
+
entropy_threshold: deterministicReclassificationConfig.entropy_threshold,
|
|
337
|
+
affinity_threshold: deterministicReclassificationConfig.affinity_threshold,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return runDeterministicSemanticReclassification({
|
|
342
|
+
maxItems: deterministicReclassificationConfig.max_per_cycle,
|
|
343
|
+
batchSize: deterministicReclassificationConfig.batch_size,
|
|
344
|
+
entropyThreshold: deterministicReclassificationConfig.entropy_threshold,
|
|
345
|
+
affinityThreshold: deterministicReclassificationConfig.affinity_threshold,
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export const runStickerClassificationCycle = async ({
|
|
350
|
+
processPending = true,
|
|
351
|
+
processReprocess = true,
|
|
352
|
+
processDeterministic = true,
|
|
353
|
+
} = {}) => {
|
|
354
|
+
const shouldProcessClassifier = classifierConfig.enabled;
|
|
355
|
+
const shouldProcessDeterministic = deterministicReclassificationConfig.enabled;
|
|
356
|
+
|
|
357
|
+
if (!BACKGROUND_ENABLED || (!shouldProcessClassifier && !shouldProcessDeterministic)) {
|
|
358
|
+
return {
|
|
359
|
+
skipped: true,
|
|
360
|
+
reason: !BACKGROUND_ENABLED ? 'background_disabled' : 'no_active_processors',
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const startedAt = Date.now();
|
|
365
|
+
const reprocessStats = processReprocess && shouldProcessClassifier ? await processReprocessQueue() : null;
|
|
366
|
+
const pendingStats = processPending && shouldProcessClassifier ? await processPendingAssets() : null;
|
|
367
|
+
const deterministicStats = processDeterministic
|
|
368
|
+
? await processDeterministicReclassification()
|
|
369
|
+
: null;
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
skipped: false,
|
|
373
|
+
duration_ms: Date.now() - startedAt,
|
|
374
|
+
pending: pendingStats,
|
|
375
|
+
reprocess: reprocessStats,
|
|
376
|
+
deterministic_reclassification: deterministicStats,
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const clearCycleHandle = () => {
|
|
381
|
+
if (!cycleHandle) return;
|
|
382
|
+
clearTimeout(cycleHandle);
|
|
383
|
+
cycleHandle = null;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const resolveNextCycleDelayMs = () => {
|
|
387
|
+
if (EFFECTIVE_INTERVAL_MAX_MS <= EFFECTIVE_INTERVAL_MIN_MS) {
|
|
388
|
+
return EFFECTIVE_INTERVAL_MIN_MS;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return EFFECTIVE_INTERVAL_MIN_MS
|
|
392
|
+
+ Math.floor(Math.random() * (EFFECTIVE_INTERVAL_MAX_MS - EFFECTIVE_INTERVAL_MIN_MS + 1));
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const scheduleNextCycle = () => {
|
|
396
|
+
if (!schedulerEnabled) return;
|
|
397
|
+
clearCycleHandle();
|
|
398
|
+
|
|
399
|
+
const safeDelay = Math.max(1_000, resolveNextCycleDelayMs());
|
|
400
|
+
cycleHandle = setTimeout(() => {
|
|
401
|
+
cycleHandle = null;
|
|
402
|
+
if (!schedulerEnabled) return;
|
|
403
|
+
scheduleNextCycle();
|
|
404
|
+
void classifyBatch().catch((error) => {
|
|
405
|
+
logger.error('Falha ao executar ciclo agendado de classificação em background.', {
|
|
406
|
+
action: 'sticker_classification_background_schedule_failed',
|
|
407
|
+
error: error?.message,
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}, safeDelay);
|
|
411
|
+
|
|
412
|
+
if (typeof cycleHandle.unref === 'function') {
|
|
413
|
+
cycleHandle.unref();
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const classifyBatch = async () => {
|
|
418
|
+
if (running) {
|
|
419
|
+
return {
|
|
420
|
+
executed: false,
|
|
421
|
+
reason: 'already_running',
|
|
422
|
+
gain_count: 0,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (!BACKGROUND_ENABLED || (!classifierConfig.enabled && !deterministicReclassificationConfig.enabled)) {
|
|
426
|
+
return {
|
|
427
|
+
executed: false,
|
|
428
|
+
reason: 'disabled',
|
|
429
|
+
gain_count: 0,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
running = true;
|
|
434
|
+
const startedAt = Date.now();
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
const result = await runStickerClassificationCycle({
|
|
438
|
+
processPending: true,
|
|
439
|
+
processReprocess: true,
|
|
440
|
+
processDeterministic: true,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const pending = result?.pending || { processed: 0, classified: 0, failed: 0 };
|
|
444
|
+
const reprocess = result?.reprocess || {
|
|
445
|
+
processed: 0,
|
|
446
|
+
classified: 0,
|
|
447
|
+
failed: 0,
|
|
448
|
+
enqueued_priority_backfill: 0,
|
|
449
|
+
enqueued_model_upgrade: 0,
|
|
450
|
+
enqueued_low_confidence: 0,
|
|
451
|
+
};
|
|
452
|
+
const deterministic = result?.deterministic_reclassification || {
|
|
453
|
+
processed: 0,
|
|
454
|
+
updated: 0,
|
|
455
|
+
skipped: 0,
|
|
456
|
+
failed: 0,
|
|
457
|
+
batches: 0,
|
|
458
|
+
last_cursor: null,
|
|
459
|
+
entropy_threshold: deterministicReclassificationConfig.entropy_threshold,
|
|
460
|
+
affinity_threshold: deterministicReclassificationConfig.affinity_threshold,
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const processed =
|
|
464
|
+
Number(pending.processed || 0)
|
|
465
|
+
+ Number(reprocess.processed || 0)
|
|
466
|
+
+ Number(deterministic.processed || 0);
|
|
467
|
+
const classified =
|
|
468
|
+
Number(pending.classified || 0)
|
|
469
|
+
+ Number(reprocess.classified || 0)
|
|
470
|
+
+ Number(deterministic.updated || 0);
|
|
471
|
+
const failed =
|
|
472
|
+
Number(pending.failed || 0)
|
|
473
|
+
+ Number(reprocess.failed || 0)
|
|
474
|
+
+ Number(deterministic.failed || 0);
|
|
475
|
+
const gainCount = classified;
|
|
476
|
+
|
|
477
|
+
if (
|
|
478
|
+
processed > 0
|
|
479
|
+
|| reprocess.enqueued_priority_backfill > 0
|
|
480
|
+
|| reprocess.enqueued_model_upgrade > 0
|
|
481
|
+
|| reprocess.enqueued_low_confidence > 0
|
|
482
|
+
|| deterministic.updated > 0
|
|
483
|
+
) {
|
|
484
|
+
logger.info('Worker de classificação executado.', {
|
|
485
|
+
action: 'sticker_classification_background_cycle',
|
|
486
|
+
processed,
|
|
487
|
+
classified,
|
|
488
|
+
failed,
|
|
489
|
+
reprocess_processed: Number(reprocess.processed || 0),
|
|
490
|
+
reprocess_classified: Number(reprocess.classified || 0),
|
|
491
|
+
reprocess_failed: Number(reprocess.failed || 0),
|
|
492
|
+
reprocess_enqueued_priority_backfill: Number(reprocess.enqueued_priority_backfill || 0),
|
|
493
|
+
reprocess_enqueued_model_upgrade: Number(reprocess.enqueued_model_upgrade || 0),
|
|
494
|
+
reprocess_enqueued_low_confidence: Number(reprocess.enqueued_low_confidence || 0),
|
|
495
|
+
deterministic_reclassification_processed: Number(deterministic.processed || 0),
|
|
496
|
+
deterministic_reclassification_updated: Number(deterministic.updated || 0),
|
|
497
|
+
deterministic_reclassification_skipped: Number(deterministic.skipped || 0),
|
|
498
|
+
deterministic_reclassification_failed: Number(deterministic.failed || 0),
|
|
499
|
+
deterministic_reclassification_batches: Number(deterministic.batches || 0),
|
|
500
|
+
deterministic_reclassification_last_cursor: deterministic.last_cursor || null,
|
|
501
|
+
duration_ms: Date.now() - startedAt,
|
|
502
|
+
batch_size: BATCH_SIZE,
|
|
503
|
+
concurrency: BACKGROUND_CONCURRENCY,
|
|
504
|
+
gain_count: gainCount,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
executed: true,
|
|
509
|
+
reason: 'ok',
|
|
510
|
+
gain_count: Number(gainCount || 0),
|
|
511
|
+
processed: Number(processed || 0),
|
|
512
|
+
classified: Number(classified || 0),
|
|
513
|
+
failed: Number(failed || 0),
|
|
514
|
+
duration_ms: Date.now() - startedAt,
|
|
515
|
+
};
|
|
516
|
+
} catch (error) {
|
|
517
|
+
logger.error('Falha no loop de classificação em background.', {
|
|
518
|
+
action: 'sticker_classification_background_cycle_failed',
|
|
519
|
+
error: error?.message,
|
|
520
|
+
});
|
|
521
|
+
return {
|
|
522
|
+
executed: true,
|
|
523
|
+
reason: 'failed',
|
|
524
|
+
gain_count: 0,
|
|
525
|
+
duration_ms: Date.now() - startedAt,
|
|
526
|
+
};
|
|
527
|
+
} finally {
|
|
528
|
+
running = false;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
export const startStickerClassificationBackground = () => {
|
|
533
|
+
if (cycleHandle || startupTimeoutHandle || schedulerEnabled) return;
|
|
534
|
+
|
|
535
|
+
if (!BACKGROUND_ENABLED) {
|
|
536
|
+
logger.info('Worker de classificação em background desabilitado.', {
|
|
537
|
+
action: 'sticker_classification_background_disabled',
|
|
538
|
+
});
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (!classifierConfig.enabled && !deterministicReclassificationConfig.enabled) {
|
|
543
|
+
logger.info('Worker de classificação em background ignorado (nenhum processador ativo).', {
|
|
544
|
+
action: 'sticker_classification_background_all_processors_disabled',
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
schedulerEnabled = true;
|
|
549
|
+
|
|
550
|
+
logger.info('Iniciando worker de classificação em background.', {
|
|
551
|
+
action: 'sticker_classification_background_start',
|
|
552
|
+
startup_delay_ms: STARTUP_DELAY_MS,
|
|
553
|
+
interval_min_ms: EFFECTIVE_INTERVAL_MIN_MS,
|
|
554
|
+
interval_max_ms: EFFECTIVE_INTERVAL_MAX_MS,
|
|
555
|
+
scheduler_mode: 'timer_non_chained_random_window',
|
|
556
|
+
interval_source: LEGACY_FIXED_INTERVAL_MS ? 'legacy_fixed_interval_ms' : 'interval_window',
|
|
557
|
+
batch_size: BATCH_SIZE,
|
|
558
|
+
concurrency: BACKGROUND_CONCURRENCY,
|
|
559
|
+
classifier_api: classifierConfig.api_url,
|
|
560
|
+
reprocess_enabled: REPROCESS_ENABLED,
|
|
561
|
+
reprocess_max_per_cycle: REPROCESS_MAX_PER_CYCLE,
|
|
562
|
+
reprocess_priority_backfill_enabled: REPROCESS_PRIORITY_BACKFILL_ENABLED,
|
|
563
|
+
reprocess_priority_backfill_scan_limit: REPROCESS_PRIORITY_BACKFILL_SCAN_LIMIT,
|
|
564
|
+
reprocess_priority_backfill_priority: REPROCESS_PRIORITY_BACKFILL_PRIORITY,
|
|
565
|
+
deterministic_reclassification_enabled: deterministicReclassificationConfig.enabled,
|
|
566
|
+
deterministic_reclassification_batch_size: deterministicReclassificationConfig.batch_size,
|
|
567
|
+
deterministic_reclassification_max_per_cycle: deterministicReclassificationConfig.max_per_cycle,
|
|
568
|
+
deterministic_reclassification_entropy_threshold: deterministicReclassificationConfig.entropy_threshold,
|
|
569
|
+
deterministic_reclassification_affinity_threshold: deterministicReclassificationConfig.affinity_threshold,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
startupTimeoutHandle = setTimeout(() => {
|
|
573
|
+
startupTimeoutHandle = null;
|
|
574
|
+
if (!schedulerEnabled) return;
|
|
575
|
+
scheduleNextCycle();
|
|
576
|
+
void classifyBatch().catch((error) => {
|
|
577
|
+
logger.error('Falha ao executar ciclo inicial de classificação em background.', {
|
|
578
|
+
action: 'sticker_classification_background_initial_cycle_failed',
|
|
579
|
+
error: error?.message,
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
}, STARTUP_DELAY_MS);
|
|
583
|
+
|
|
584
|
+
if (typeof startupTimeoutHandle.unref === 'function') {
|
|
585
|
+
startupTimeoutHandle.unref();
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
export const stopStickerClassificationBackground = () => {
|
|
590
|
+
schedulerEnabled = false;
|
|
591
|
+
|
|
592
|
+
if (startupTimeoutHandle) {
|
|
593
|
+
clearTimeout(startupTimeoutHandle);
|
|
594
|
+
startupTimeoutHandle = null;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
clearCycleHandle();
|
|
598
|
+
};
|