cdp-edge 1.2.0 → 1.3.0

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 (142) hide show
  1. package/README.md +153 -306
  2. package/bin/cdp-edge.js +71 -61
  3. package/contracts/agent-versions.json +682 -0
  4. package/contracts/api-versions.json +372 -368
  5. package/contracts/types.ts +81 -0
  6. package/dist/commands/analyze.js +52 -52
  7. package/dist/commands/infra.js +54 -54
  8. package/dist/commands/install.js +26 -3
  9. package/dist/commands/server.js +174 -174
  10. package/dist/commands/setup.js +332 -100
  11. package/dist/commands/validate.js +248 -84
  12. package/dist/index.js +12 -12
  13. package/dist/sdk/cdpTrack.js +2095 -0
  14. package/dist/sdk/cdpTrack.min.js +64 -0
  15. package/dist/sdk/install-snippet.html +10 -0
  16. package/docs/whatsapp-ctwa.md +5 -4
  17. package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +89 -0
  18. package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +101 -0
  19. package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -364
  20. package/extracted-skill/tracking-events-generator/agents/ab-ltv-agent.md +196 -0
  21. package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +1 -1
  22. package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +41 -41
  23. package/extracted-skill/tracking-events-generator/agents/bidding-agent.md +347 -0
  24. package/extracted-skill/tracking-events-generator/agents/bing-agent.md +40 -50
  25. package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +174 -74
  26. package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +1 -1
  27. package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +25 -5
  28. package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +10 -10
  29. package/extracted-skill/tracking-events-generator/agents/database-agent.md +43 -42
  30. package/extracted-skill/tracking-events-generator/agents/debug-agent.md +22 -22
  31. package/extracted-skill/tracking-events-generator/agents/devops-agent.md +232 -0
  32. package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +23 -9
  33. package/extracted-skill/tracking-events-generator/agents/email-agent.md +28 -1
  34. package/extracted-skill/tracking-events-generator/agents/evo-crm-agent.md +244 -0
  35. package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +206 -1
  36. package/extracted-skill/tracking-events-generator/agents/fraud-detection-agent.md +143 -0
  37. package/extracted-skill/tracking-events-generator/agents/google-agent.md +128 -2
  38. package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +191 -31
  39. package/extracted-skill/tracking-events-generator/agents/lead-scoring-agent.md +282 -0
  40. package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +145 -34
  41. package/extracted-skill/tracking-events-generator/agents/localization-agent.md +1 -1
  42. package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +5 -5
  43. package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +81 -21
  44. package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +428 -190
  45. package/extracted-skill/tracking-events-generator/agents/match-quality-agent.md +304 -0
  46. package/extracted-skill/tracking-events-generator/agents/memory-agent.json +25 -109
  47. package/extracted-skill/tracking-events-generator/agents/memory-agent.md +190 -15
  48. package/extracted-skill/tracking-events-generator/agents/meta-agent.md +10 -2
  49. package/extracted-skill/tracking-events-generator/agents/ml-clustering-agent.md +749 -0
  50. package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +21 -4
  51. package/extracted-skill/tracking-events-generator/agents/performance-agent.md +41 -31
  52. package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +18 -8
  53. package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +14 -6
  54. package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +7 -7
  55. package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +16 -8
  56. package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +15 -7
  57. package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +157 -48
  58. package/extracted-skill/tracking-events-generator/agents/server-tracking.md +35 -35
  59. package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +15 -7
  60. package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +73 -2
  61. package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +104 -9
  62. package/extracted-skill/tracking-events-generator/agents/utm-agent.md +322 -0
  63. package/extracted-skill/tracking-events-generator/agents/validator-agent.md +13 -9
  64. package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +112 -4
  65. package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +58 -5
  66. package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +26 -18
  67. package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +152 -37
  68. package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -285
  69. package/extracted-skill/tracking-events-generator/cdpTrack.js +642 -641
  70. package/extracted-skill/tracking-events-generator/contracts/api-versions.json +14 -10
  71. package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -226
  72. package/extracted-skill/tracking-events-generator/evals/evals.json +235 -235
  73. package/extracted-skill/tracking-events-generator/integration-test.js +497 -497
  74. package/extracted-skill/tracking-events-generator/knowledge-base.md +172 -0
  75. package/extracted-skill/tracking-events-generator/micro-events.js +992 -992
  76. package/extracted-skill/tracking-events-generator/models/lancamento-imobiliario.md +344 -0
  77. package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -144
  78. package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -48
  79. package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -28
  80. package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +83 -19
  81. package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -205
  82. package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -56
  83. package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -19
  84. package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -425
  85. package/extracted-skill/tracking-events-generator/route-intent-capture.js +222 -0
  86. package/extracted-skill/tracking-events-generator/tracking.config.js +3 -3
  87. package/package.json +89 -75
  88. package/scripts/build-sdk.js +106 -0
  89. package/server-edge-tracker/.client.env.example +14 -0
  90. package/server-edge-tracker/INSTALAR.md +222 -23
  91. package/server-edge-tracker/SEGMENTATION-DOCS.md +513 -0
  92. package/server-edge-tracker/config/utm-mapping.json +64 -0
  93. package/server-edge-tracker/deploy-client.cjs +76 -0
  94. package/server-edge-tracker/index.ts +1230 -0
  95. package/server-edge-tracker/migrate-v7.sql +64 -0
  96. package/server-edge-tracker/modules/db.ts +710 -0
  97. package/server-edge-tracker/modules/dispatch/crm.ts +382 -0
  98. package/server-edge-tracker/modules/dispatch/ga4.ts +72 -0
  99. package/server-edge-tracker/modules/dispatch/meta.ts +143 -0
  100. package/server-edge-tracker/modules/dispatch/platforms.ts +255 -0
  101. package/server-edge-tracker/modules/dispatch/tiktok.ts +107 -0
  102. package/server-edge-tracker/modules/dispatch/whatsapp.ts +296 -0
  103. package/server-edge-tracker/modules/intelligence.ts +589 -0
  104. package/server-edge-tracker/modules/ml/bidding.ts +247 -0
  105. package/server-edge-tracker/modules/ml/fraud.ts +302 -0
  106. package/server-edge-tracker/modules/ml/logistic.ts +226 -0
  107. package/server-edge-tracker/modules/ml/ltv.ts +531 -0
  108. package/server-edge-tracker/modules/ml/matchquality.ts +232 -0
  109. package/server-edge-tracker/modules/ml/quiz.ts +343 -0
  110. package/server-edge-tracker/modules/ml/roas.ts +255 -0
  111. package/server-edge-tracker/modules/ml/segmentation.ts +407 -0
  112. package/server-edge-tracker/modules/nurture.ts +257 -0
  113. package/server-edge-tracker/modules/utils.ts +311 -0
  114. package/server-edge-tracker/modules/utm/utm-enricher.ts +231 -0
  115. package/server-edge-tracker/schema-ab-ltv.sql +97 -0
  116. package/server-edge-tracker/schema-bidding.sql +86 -0
  117. package/server-edge-tracker/schema-fraud.sql +90 -0
  118. package/server-edge-tracker/schema-indexes.sql +67 -0
  119. package/server-edge-tracker/schema-ltv-feedback.sql +11 -0
  120. package/server-edge-tracker/schema-quiz.sql +52 -0
  121. package/server-edge-tracker/schema-sales-engine.sql +113 -0
  122. package/server-edge-tracker/schema-segmentation.sql +219 -0
  123. package/server-edge-tracker/schema-utm.sql +82 -0
  124. package/server-edge-tracker/schema.sql +281 -265
  125. package/server-edge-tracker/types.ts +275 -0
  126. package/server-edge-tracker/wrangler.toml +140 -85
  127. package/templates/lancamento-imobiliario.md +344 -0
  128. package/templates/multi-step-checkout.md +3 -4
  129. package/templates/pinterest/conversions-api-template.js +144 -144
  130. package/templates/pinterest/event-mappings.json +48 -48
  131. package/templates/pinterest/tag-template.js +28 -28
  132. package/templates/quiz-funnel.md +83 -19
  133. package/templates/reddit/conversions-api-template.js +205 -205
  134. package/templates/reddit/event-mappings.json +56 -56
  135. package/templates/reddit/pixel-template.js +12 -39
  136. package/templates/scenarios/behavior-engine.js +45 -22
  137. package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
  138. package/docs/installation.md +0 -155
  139. package/docs/quick-start.md +0 -185
  140. package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +0 -1419
  141. package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +0 -643
  142. package/server-edge-tracker/worker.js +0 -2574
@@ -0,0 +1,589 @@
1
+ /**
2
+ * CDP Edge — Intelligence Agent + Customer Match
3
+ * runIntelligenceAgent, customer match Meta/Google, health checks
4
+ */
5
+
6
+ import { sha256 } from './utils.js';
7
+ import { getHealthMetrics, generateDailyReport, logIntelligence } from './db.js';
8
+ import { sendCallMeBot } from './dispatch/whatsapp.js';
9
+ import { autoDecideAbWinner } from './ml/ltv.js';
10
+ import { analyzeMatchQuality, alertMatchQuality, purgeOldMatchQualityLogs } from './ml/matchquality.js';
11
+ import { trainLogisticRegression, extractFeatures, saveWeights, LTV_WEIGHTS_KV_KEY } from './ml/logistic.js';
12
+ import { computeRoasFeedback, sendRoasAlert } from './ml/roas.js';
13
+ import { runNurtureQueue } from './nurture.js';
14
+ import { Env } from '../types.js';
15
+
16
+ // ── Tipos ───────────────────────────────────────────────────────────────────────
17
+ export interface ApiVersionCheck {
18
+ platform: string;
19
+ current: string;
20
+ expected: string;
21
+ status: 'ok' | 'warning';
22
+ }
23
+
24
+ export interface ErrorRateAlert {
25
+ platform: string;
26
+ errorRate: number;
27
+ status: 'ok' | 'warning' | 'critical';
28
+ }
29
+
30
+ export interface LtvTrainResult {
31
+ trained?: boolean;
32
+ skipped?: string;
33
+ samples?: number;
34
+ accuracy?: number;
35
+ positiveRate?: number;
36
+ error?: string;
37
+ }
38
+
39
+ export interface IntelligenceAgentResult {
40
+ versionResults: ApiVersionCheck[];
41
+ errorAlerts: ErrorRateAlert[];
42
+ ltvTrainResult: LtvTrainResult;
43
+ abResult?: {
44
+ decided: boolean;
45
+ test_id?: number;
46
+ winner_name?: string;
47
+ improvement?: number;
48
+ };
49
+ mqAnalysis?: {
50
+ total?: number;
51
+ composite_score?: number;
52
+ email_rate?: number;
53
+ fbp_rate?: number;
54
+ alerts?: any[];
55
+ };
56
+ cmResult?: {
57
+ sent?: number;
58
+ received?: number;
59
+ skipped?: string;
60
+ error?: string;
61
+ };
62
+ roasResult?: {
63
+ campaigns: number;
64
+ total_revenue: number;
65
+ best_campaign: string | null;
66
+ skipped?: string;
67
+ };
68
+ nurtureResult?: {
69
+ processed: number;
70
+ sent: number;
71
+ failed: number;
72
+ };
73
+ lookalikeResult?: {
74
+ sent: number;
75
+ seed_type: string;
76
+ skipped?: string;
77
+ };
78
+ }
79
+
80
+ export interface CustomerMatchResult {
81
+ sent?: number;
82
+ received?: number;
83
+ num_received?: number;
84
+ skipped?: string;
85
+ error?: string;
86
+ }
87
+
88
+ export interface GoogleCustomerMatchExport {
89
+ hashed_email: string;
90
+ hashed_phone: string;
91
+ first_name: string;
92
+ last_name: string;
93
+ }
94
+
95
+ // ── Versões esperadas das APIs ────────────────────────────────────────────────
96
+ const EXPECTED_API_VERSIONS: Record<string, string> = {
97
+ meta: 'v22.0',
98
+ ga4: 'latest',
99
+ tiktok: 'v1.3',
100
+ pinterest: 'v5',
101
+ reddit: 'v2.0',
102
+ };
103
+
104
+ const ALERT_THRESHOLDS = {
105
+ errorRateCritical: 0.20,
106
+ errorRateWarning: 0.10,
107
+ };
108
+
109
+ // ── Alerta via CallMeBot ──────────────────────────────────────────────────────
110
+ export async function sendIntelligenceAlert(
111
+ env: Env,
112
+ severity: 'critical' | 'warning' | 'info',
113
+ title: string,
114
+ details: string
115
+ ): Promise<void> {
116
+ const icon = severity === 'critical' ? '🚨' : severity === 'warning' ? '⚠️' : 'ℹ️';
117
+ const texto = `${icon} CDP Edge — ${title}\n\n${details}\n\n${new Date().toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' })}`;
118
+ return sendCallMeBot(env, texto);
119
+ }
120
+
121
+ // ── Check de versões de API ───────────────────────────────────────────────────
122
+ export async function checkApiVersionsIntelligence(
123
+ env: Env,
124
+ runType: string
125
+ ): Promise<ApiVersionCheck[]> {
126
+ const results: ApiVersionCheck[] = [];
127
+
128
+ for (const [platform, expected] of Object.entries(EXPECTED_API_VERSIONS)) {
129
+ const currentMap: Record<string, string> = { meta: 'v22.0', tiktok: 'v1.3', ga4: 'latest', pinterest: 'v5', reddit: 'v2.0' };
130
+ const current = currentMap[platform] || 'unknown';
131
+ const isOk = current === expected || expected === 'latest';
132
+ const status = isOk ? 'ok' : 'warning';
133
+
134
+ if (env.DB) {
135
+ await logIntelligence(env.DB, runType, platform, 'api_version', status, current, expected,
136
+ isOk ? `${platform} ${current} — versão correta` : `${platform} ${current} desatualizado, esperado ${expected}`
137
+ );
138
+ }
139
+
140
+ results.push({ platform, current, expected, status });
141
+ }
142
+
143
+ return results;
144
+ }
145
+
146
+ // ── Auditoria de taxa de erro ─────────────────────────────────────────────────
147
+ export async function auditErrorRates(
148
+ env: Env,
149
+ runType: string
150
+ ): Promise<ErrorRateAlert[]> {
151
+ if (!env.DB) return [];
152
+ const alerts: ErrorRateAlert[] = [];
153
+
154
+ for (const platform of ['meta', 'ga4', 'tiktok']) {
155
+ const metrics = await getHealthMetrics(env.DB, platform, 24);
156
+ const errorRate = metrics.events_sent > 0 ? metrics.events_failed / metrics.events_sent : 0;
157
+
158
+ let status: 'ok' | 'warning' | 'critical' = 'ok';
159
+ if (errorRate >= ALERT_THRESHOLDS.errorRateCritical) status = 'critical';
160
+ else if (errorRate >= ALERT_THRESHOLDS.errorRateWarning) status = 'warning';
161
+
162
+ const message = `${platform}: ${metrics.events_sent} eventos, ${metrics.events_failed} falhas (${(errorRate * 100).toFixed(1)}%)`;
163
+ let alertSent: boolean | undefined = false;
164
+ if (status !== 'ok') {
165
+ await sendIntelligenceAlert(env, status, `Taxa de Erro Alta — ${platform.toUpperCase()}`,
166
+ `📊 ${message}\n🎯 Taxa: ${(errorRate * 100).toFixed(1)}% (limite: ${ALERT_THRESHOLDS.errorRateWarning * 100}%)`);
167
+ alertSent = true;
168
+ }
169
+
170
+ if (env.DB) {
171
+ await logIntelligence(env.DB, runType, platform, 'error_rate', status,
172
+ `${(errorRate * 100).toFixed(1)}%`, `${ALERT_THRESHOLDS.errorRateWarning * 100}%`, message, alertSent
173
+ );
174
+ }
175
+
176
+ if (status !== 'ok') alerts.push({ platform, errorRate, status });
177
+ }
178
+
179
+ return alerts;
180
+ }
181
+
182
+ // ── Treinar modelo LTV (regressão logística com dados reais do D1) ────────────
183
+ export async function trainLtvModel(env: Env): Promise<LtvTrainResult> {
184
+ if (!env.DB) return { skipped: 'DB não disponível' };
185
+
186
+ try {
187
+ // Busca leads com informação de conversão (compra confirmada)
188
+ const rows = await env.DB.prepare(`
189
+ SELECT
190
+ l.utm_source,
191
+ l.utm_medium,
192
+ l.engagement_score,
193
+ l.intention_level,
194
+ CAST(julianday('now') - julianday(l.created_at) AS INTEGER) AS days_since_lead,
195
+ CASE WHEN l.email IS NOT NULL AND l.email != '' THEN 1 ELSE 0 END AS has_email,
196
+ CASE WHEN l.phone IS NOT NULL AND l.phone != '' THEN 1 ELSE 0 END AS has_phone,
197
+ CASE WHEN (l.country = 'br' OR l.country = 'BR' OR l.country IS NULL) THEN 1 ELSE 0 END AS is_br,
198
+ CAST(strftime('%H', l.created_at) AS INTEGER) AS hour,
199
+ CASE WHEN EXISTS (
200
+ SELECT 1 FROM events e
201
+ WHERE e.user_id = l.user_id
202
+ AND e.event_name IN ('Purchase', 'purchase', 'PURCHASE')
203
+ AND e.created_at > l.created_at
204
+ ) THEN 1 ELSE 0 END AS label
205
+ FROM leads l
206
+ WHERE l.created_at >= datetime('now', '-90 days')
207
+ LIMIT 5000
208
+ `).all();
209
+
210
+ const dataset = (rows.results || []).map((row: any) => ({
211
+ features: extractFeatures(row),
212
+ label: row.label || 0,
213
+ }));
214
+
215
+ const model = trainLogisticRegression(dataset);
216
+
217
+ if (!model) {
218
+ console.log('[LTV Train] Dados insuficientes para treinar modelo');
219
+ return { skipped: 'dados insuficientes', samples: dataset.length };
220
+ }
221
+
222
+ await saveWeights(env.DB, model);
223
+
224
+ // Invalidar cache KV para que próximas requests carreguem o modelo novo
225
+ if (env.GEO_CACHE) {
226
+ env.GEO_CACHE.delete(LTV_WEIGHTS_KV_KEY).catch(() => {});
227
+ }
228
+
229
+ console.log(`[LTV Train] Modelo treinado: ${dataset.length} samples, accuracy=${(model.accuracy * 100).toFixed(1)}%, positive_rate=${(model.positiveRate * 100).toFixed(1)}%`);
230
+ return { trained: true, samples: dataset.length, accuracy: model.accuracy, positiveRate: model.positiveRate };
231
+
232
+ } catch (err: any) {
233
+ console.error('[LTV Train] Erro:', err?.message || String(err));
234
+ return { error: err?.message || String(err) };
235
+ }
236
+ }
237
+
238
+ // ── Runner principal do Intelligence Agent ────────────────────────────────────
239
+ export async function runIntelligenceAgent(
240
+ env: Env,
241
+ runType: string
242
+ ): Promise<IntelligenceAgentResult> {
243
+ console.log(`[Intelligence Agent] Iniciando ${runType}`);
244
+
245
+ // 1. Check de versões
246
+ const versionResults = await checkApiVersionsIntelligence(env, runType);
247
+ console.log(`[Intelligence Agent] Versões verificadas: ${versionResults.length} plataformas`);
248
+
249
+ // 2. Relatório diário
250
+ if (env.DB) {
251
+ const reports = await generateDailyReport(env.DB);
252
+ console.log(`[Intelligence Agent] Relatórios gerados: ${reports.length}`);
253
+ }
254
+
255
+ // 3. Auditoria de taxas de erro
256
+ const errorAlerts = await auditErrorRates(env, runType);
257
+ if (errorAlerts.length > 0) {
258
+ console.warn(`[Intelligence Agent] ${errorAlerts.length} alertas de taxa de erro enviados`);
259
+ }
260
+
261
+ // 4. Treinar modelo LTV (toda semana)
262
+ const ltvTrainResult = await trainLtvModel(env);
263
+ if (ltvTrainResult.trained) {
264
+ console.log(`[Intelligence Agent] LTV model treinado: accuracy=${(ltvTrainResult.accuracy! * 100).toFixed(1)}%`);
265
+ if (env.DB) {
266
+ await logIntelligence(env.DB, runType, 'ltv', 'model_training', 'ok',
267
+ `accuracy=${(ltvTrainResult.accuracy! * 100).toFixed(1)}%`, null,
268
+ `Modelo LTV re-treinado com ${ltvTrainResult.samples} amostras`
269
+ ).catch(() => {});
270
+ }
271
+ } else {
272
+ console.log(`[Intelligence Agent] LTV model: ${ltvTrainResult.skipped || ltvTrainResult.error || 'sem dados'}`);
273
+ }
274
+
275
+ // 5. Auto-decisão de winner no A/B LTV Test
276
+ let abResult: IntelligenceAgentResult['abResult'] = undefined;
277
+ try {
278
+ const abRes = await autoDecideAbWinner(env);
279
+ if (abRes?.decided) {
280
+ abResult = {
281
+ decided: abRes.decided,
282
+ test_id: abRes.test_id,
283
+ winner_name: abRes.winner_name,
284
+ improvement: abRes.improvement ? parseFloat(abRes.improvement) : undefined,
285
+ };
286
+ console.log(`[Intelligence Agent] A/B LTV winner auto-decidido: test_id=${abResult.test_id}, winner=${abResult.winner_name}`);
287
+
288
+ await sendIntelligenceAlert(env, 'info',
289
+ `A/B LTV Test — Winner Declarado Automaticamente`,
290
+ `🏆 Vencedor: ${abResult.winner_name}\n📈 Melhoria: +${abResult.improvement?.toFixed(1) ?? '?'}pp vs controle\n🆔 Test ID: ${abResult.test_id}\n\n✅ Prompt vencedor ativado automaticamente`
291
+ );
292
+
293
+ if (env.DB) {
294
+ await logIntelligence(env.DB, runType, 'ltv', 'ab_auto_winner', 'ok',
295
+ abResult.winner_name, null,
296
+ `A/B winner auto-decidido: test ${abResult.test_id}, melhoria ${abResult.improvement?.toFixed(1)}pp`
297
+ ).catch(() => {});
298
+ }
299
+ }
300
+ } catch (err: any) {
301
+ console.error('[Intelligence Agent] A/B auto-decide error:', err?.message || String(err));
302
+ }
303
+
304
+ // 6. Match Quality — análise + alertas
305
+ let mqAnalysis: IntelligenceAgentResult['mqAnalysis'] = undefined;
306
+ try {
307
+ const mqRes = await analyzeMatchQuality(env);
308
+ if (mqRes) {
309
+ mqAnalysis = mqRes;
310
+ console.log(`[Intelligence Agent] Match Quality: score=${mqAnalysis.composite_score ?? 0}%, alerts=${mqAnalysis.alerts?.length ?? 0}`);
311
+ await alertMatchQuality(env, mqRes);
312
+
313
+ if (env.DB && mqAnalysis.total && mqAnalysis.total > 0) {
314
+ await logIntelligence(env.DB, runType, 'meta', 'match_quality', (mqAnalysis.alerts && mqAnalysis.alerts.length > 0) ? 'warning' : 'ok',
315
+ `${mqAnalysis.composite_score ?? 0}%`, '45%',
316
+ `Match quality 2h: email=${mqAnalysis.email_rate ?? 0}%, fbp=${mqAnalysis.fbp_rate ?? 0}%, score=${mqAnalysis.composite_score ?? 0}%`
317
+ ).catch(() => {});
318
+ }
319
+ }
320
+ } catch (err: any) {
321
+ console.error('[Intelligence Agent] Match quality analysis error:', err?.message || String(err));
322
+ }
323
+
324
+ // 7. Auditoria mensal adicional
325
+ if (runType === 'monthly_audit') {
326
+ if (env.DB) {
327
+ try {
328
+ const ltvStats = await env.DB.prepare(`
329
+ SELECT predicted_ltv_class, COUNT(*) as count
330
+ FROM user_profiles
331
+ WHERE predicted_ltv_class IS NOT NULL AND updated_at > datetime('now', '-30 days')
332
+ GROUP BY predicted_ltv_class
333
+ `).all();
334
+
335
+ const summary = ltvStats.results?.map((r: any) => `${r.predicted_ltv_class}: ${r.count}`).join(', ') || 'sem dados';
336
+ await logIntelligence(env.DB, runType, 'all', 'ltv_distribution', 'ok', summary, null,
337
+ `Distribuição LTV últimos 30 dias: ${summary}`);
338
+ console.log(`[Intelligence Agent] LTV distribution: ${summary}`);
339
+ } catch (err: any) {
340
+ console.error('LTV audit error:', err?.message || String(err));
341
+ }
342
+
343
+ // Purge de logs antigos de match quality (> 30 dias)
344
+ if (env.DB) {
345
+ await purgeOldMatchQualityLogs(env.DB);
346
+ console.log('[Intelligence Agent] Match quality logs antigos purgados');
347
+ }
348
+ }
349
+ }
350
+
351
+ // 8. Customer Match sync semanal (high_intent → Meta Audience)
352
+ const cmResult = await syncMetaCustomAudience(env);
353
+ console.log(`[Intelligence Agent] Customer Match Meta: sent=${cmResult?.sent ?? 0}, received=${cmResult?.received ?? 0}`);
354
+
355
+ // 9. ROAS Feedback Loop — cruza leads com compras reais por campanha
356
+ let roasResult: IntelligenceAgentResult['roasResult'] = undefined;
357
+ try {
358
+ const report = await computeRoasFeedback(env, 30);
359
+ if (report) {
360
+ roasResult = {
361
+ campaigns: report.campaigns.length,
362
+ total_revenue: report.total_revenue,
363
+ best_campaign: report.best_campaign,
364
+ };
365
+ await sendRoasAlert(env, report);
366
+ console.log(`[Intelligence Agent] ROAS: ${report.campaigns.length} campanhas, R$${report.total_revenue} receita`);
367
+ } else {
368
+ roasResult = { campaigns: 0, total_revenue: 0, best_campaign: null, skipped: 'sem dados suficientes' };
369
+ }
370
+ } catch (err: any) {
371
+ console.error('[Intelligence Agent] ROAS error:', err?.message || String(err));
372
+ }
373
+
374
+ // 10. Nurture Queue — processa mensagens agendadas (D+1, D+3, D+7)
375
+ let nurtureResult: IntelligenceAgentResult['nurtureResult'] = undefined;
376
+ try {
377
+ const nr = await runNurtureQueue(env);
378
+ nurtureResult = { processed: nr.processed, sent: nr.sent, failed: nr.failed };
379
+ console.log(`[Intelligence Agent] Nurture: ${nr.sent}/${nr.processed} mensagens enviadas`);
380
+ } catch (err: any) {
381
+ console.error('[Intelligence Agent] Nurture error:', err?.message || String(err));
382
+ }
383
+
384
+ // 11. Lookalike Dinâmico — compradores confirmados → Meta Audience seed
385
+ let lookalikeResult: IntelligenceAgentResult['lookalikeResult'] = undefined;
386
+ try {
387
+ const lr = await syncMetaLookalikeSeed(env);
388
+ lookalikeResult = lr;
389
+ console.log(`[Intelligence Agent] Lookalike seed: sent=${lr.sent}, type=${lr.seed_type}`);
390
+ } catch (err: any) {
391
+ console.error('[Intelligence Agent] Lookalike error:', err?.message || String(err));
392
+ }
393
+
394
+ console.log(`[Intelligence Agent] ${runType} concluído — LTV, A/B, match quality, customer match, ROAS, nurture, lookalike`);
395
+
396
+ return {
397
+ versionResults,
398
+ errorAlerts,
399
+ ltvTrainResult,
400
+ abResult,
401
+ mqAnalysis,
402
+ cmResult,
403
+ roasResult,
404
+ nurtureResult,
405
+ lookalikeResult,
406
+ };
407
+ }
408
+
409
+ // ── syncMetaCustomAudience — D1 → Meta Custom Audiences ─────────────────────
410
+ export async function syncMetaCustomAudience(env: Env): Promise<CustomerMatchResult> {
411
+ if (!env.META_ACCESS_TOKEN || !env.META_AD_ACCOUNT_ID || !env.META_AUDIENCE_ID) {
412
+ console.log('[CustomerMatch] Meta: secrets não configurados — pulando sync');
413
+ return { skipped: 'META_AD_ACCOUNT_ID ou META_AUDIENCE_ID não configurados' };
414
+ }
415
+ if (!env.DB) return { skipped: 'DB não disponível' };
416
+
417
+ try {
418
+ const profiles = await env.DB.prepare(`
419
+ SELECT email, phone FROM user_profiles
420
+ WHERE cohort_label IN ('high_intent', 'buyer_lookalike')
421
+ AND updated_at > datetime('now', '-30 days')
422
+ AND email IS NOT NULL
423
+ LIMIT 10000
424
+ `).all();
425
+
426
+ if (!profiles.results || profiles.results.length === 0) {
427
+ console.log('[CustomerMatch] Meta: nenhum perfil elegível');
428
+ return { sent: 0 };
429
+ }
430
+
431
+ const data = await Promise.all(
432
+ profiles.results.map(async (p: any) => [
433
+ p.email ? await sha256(p.email) : '',
434
+ p.phone ? await sha256(p.phone) : '',
435
+ ])
436
+ );
437
+
438
+ const body = { payload: { schema: ['EMAIL_SHA256', 'PHONE_SHA256'], data } };
439
+ const endpoint = `https://graph.facebook.com/v22.0/${env.META_AUDIENCE_ID}/users`;
440
+
441
+ const res = await fetch(endpoint, {
442
+ method: 'POST',
443
+ headers: { 'Content-Type': 'application/json' },
444
+ body: JSON.stringify({ ...body, access_token: env.META_ACCESS_TOKEN }),
445
+ });
446
+
447
+ const result = await res.json() as any;
448
+
449
+ if (!res.ok) {
450
+ console.error('[CustomerMatch] Meta erro:', res.status, result.error?.message || 'unknown');
451
+ return { error: result.error?.message, sent: 0 };
452
+ }
453
+
454
+ console.log(`[CustomerMatch] Meta: ${profiles.results.length} perfis sincronizados`);
455
+ return { sent: profiles.results.length, num_received: result.num_received, received: result.num_received };
456
+
457
+ } catch (err: any) {
458
+ console.error('[CustomerMatch] Meta fetch error:', err?.message || String(err));
459
+ return { error: err?.message || String(err), sent: 0 };
460
+ }
461
+ }
462
+
463
+ // ── syncMetaLookalikeSeed — compradores confirmados → Meta Audience (Fase 7) ──
464
+ // Seed de Lookalike mais preciso: usa quem REALMENTE comprou (Purchase event)
465
+ // em vez de quem só teve intenção (cohort_label = high_intent).
466
+ // Separado do syncMetaCustomAudience para não misturar seeds de qualidade diferente.
467
+
468
+ export async function syncMetaLookalikeSeed(env: Env): Promise<{
469
+ sent: number;
470
+ seed_type: string;
471
+ skipped?: string;
472
+ }> {
473
+ if (!env.META_ACCESS_TOKEN || !env.META_AUDIENCE_ID) {
474
+ return { sent: 0, seed_type: 'buyer_confirmed', skipped: 'META secrets não configurados' };
475
+ }
476
+ if (!env.DB) return { sent: 0, seed_type: 'buyer_confirmed', skipped: 'DB não disponível' };
477
+
478
+ try {
479
+ // Busca perfis de compradores confirmados (Purchase event nos últimos 60 dias)
480
+ const confirmed = await env.DB.prepare(`
481
+ SELECT DISTINCT up.email, up.phone, up.first_name, up.last_name
482
+ FROM user_profiles up
483
+ JOIN leads l ON l.user_id = up.user_id
484
+ WHERE l.event_name IN ('Purchase','purchase')
485
+ AND l.created_at >= datetime('now', '-60 days')
486
+ AND up.email IS NOT NULL
487
+ UNION
488
+ SELECT DISTINCT up.email, up.phone, up.first_name, up.last_name
489
+ FROM user_profiles up
490
+ JOIN quiz_sessions qs ON qs.user_id = up.user_id
491
+ WHERE qs.qualification = 'comprador'
492
+ AND qs.created_at >= datetime('now', '-30 days')
493
+ AND up.email IS NOT NULL
494
+ LIMIT 10000
495
+ `).all();
496
+
497
+ if (!confirmed.results?.length) {
498
+ return { sent: 0, seed_type: 'buyer_confirmed', skipped: 'nenhum comprador confirmado no período' };
499
+ }
500
+
501
+ const data = await Promise.all(
502
+ confirmed.results.map(async (p: any) => [
503
+ p.email ? await sha256(p.email) : '',
504
+ p.phone ? await sha256(p.phone) : '',
505
+ ])
506
+ );
507
+
508
+ const body = { payload: { schema: ['EMAIL_SHA256', 'PHONE_SHA256'], data } };
509
+ const endpoint = `https://graph.facebook.com/v22.0/${env.META_AUDIENCE_ID}/users`;
510
+
511
+ const res = await fetch(endpoint, {
512
+ method: 'POST',
513
+ headers: { 'Content-Type': 'application/json' },
514
+ body: JSON.stringify({ ...body, access_token: env.META_ACCESS_TOKEN }),
515
+ });
516
+
517
+ const result = await res.json() as any;
518
+
519
+ // Persiste histórico do seed
520
+ if (env.DB) {
521
+ await env.DB.prepare(`
522
+ INSERT INTO lookalike_seeds (audience_id, seed_type, profiles_sent, profiles_received, period_days)
523
+ VALUES (?, 'buyer_confirmed', ?, ?, 60)
524
+ `).bind(
525
+ env.META_AUDIENCE_ID,
526
+ confirmed.results.length,
527
+ result.num_received ?? null,
528
+ ).run().catch(() => {});
529
+ }
530
+
531
+ if (!res.ok) {
532
+ console.error('[Lookalike] Meta erro:', result.error?.message);
533
+ return { sent: 0, seed_type: 'buyer_confirmed', skipped: result.error?.message };
534
+ }
535
+
536
+ // Atualiza cohort_label dos compradores para buyer_confirmed
537
+ await env.DB.prepare(`
538
+ UPDATE user_profiles
539
+ SET cohort_label = 'buyer_confirmed', updated_at = datetime('now')
540
+ WHERE user_id IN (
541
+ SELECT DISTINCT user_id FROM leads
542
+ WHERE event_name IN ('Purchase','purchase')
543
+ AND created_at >= datetime('now', '-60 days')
544
+ )
545
+ `).run().catch(() => {});
546
+
547
+ console.log(`[Lookalike] ${confirmed.results.length} compradores confirmados enviados ao Meta`);
548
+ return { sent: confirmed.results.length, seed_type: 'buyer_confirmed' };
549
+
550
+ } catch (err: any) {
551
+ console.error('[Lookalike] syncMetaLookalikeSeed error:', err?.message || String(err));
552
+ return { sent: 0, seed_type: 'buyer_confirmed', skipped: err?.message };
553
+ }
554
+ }
555
+
556
+ // ── buildGoogleCustomerMatchExport — gera JSON para Google Ads Customer Match ─
557
+ export async function buildGoogleCustomerMatchExport(env: Env): Promise<GoogleCustomerMatchExport[]> {
558
+ if (!env.DB) return [];
559
+
560
+ const profiles = await env.DB.prepare(`
561
+ SELECT email, phone, first_name, last_name FROM user_profiles
562
+ WHERE cohort_label IN ('high_intent', 'buyer_lookalike')
563
+ AND updated_at > datetime('now', '-30 days')
564
+ AND email IS NOT NULL
565
+ LIMIT 10000
566
+ `).all();
567
+
568
+ if (!profiles.results?.length) return [];
569
+
570
+ const results: GoogleCustomerMatchExport[] = [];
571
+ for (const p of profiles.results) {
572
+ const email = p.email as string | null | undefined;
573
+ const phone = p.phone as string | null | undefined;
574
+ const firstName = p.first_name as string | undefined;
575
+ const lastName = p.last_name as string | undefined;
576
+
577
+ const hashed_email = email ? await sha256(email) : '';
578
+ const hashed_phone = phone ? await sha256(phone) : '';
579
+ if (hashed_email || hashed_phone) {
580
+ results.push({
581
+ hashed_email: hashed_email || '',
582
+ hashed_phone: hashed_phone || '',
583
+ first_name: firstName || '',
584
+ last_name: lastName || '',
585
+ });
586
+ }
587
+ }
588
+ return results;
589
+ }