cdp-edge 1.2.2 → 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.
- package/README.md +153 -306
- package/bin/cdp-edge.js +71 -61
- package/contracts/agent-versions.json +682 -0
- package/contracts/api-versions.json +372 -368
- package/contracts/types.ts +81 -0
- package/dist/commands/analyze.js +52 -52
- package/dist/commands/infra.js +54 -54
- package/dist/commands/install.js +26 -3
- package/dist/commands/server.js +174 -174
- package/dist/commands/setup.js +332 -100
- package/dist/commands/validate.js +248 -84
- package/dist/index.js +12 -12
- package/dist/sdk/cdpTrack.js +2095 -0
- package/dist/sdk/cdpTrack.min.js +64 -0
- package/dist/sdk/install-snippet.html +10 -0
- package/docs/whatsapp-ctwa.md +5 -4
- package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +89 -0
- package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +101 -0
- package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -364
- package/extracted-skill/tracking-events-generator/agents/ab-ltv-agent.md +196 -0
- package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +41 -41
- package/extracted-skill/tracking-events-generator/agents/bidding-agent.md +347 -0
- package/extracted-skill/tracking-events-generator/agents/bing-agent.md +40 -50
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +174 -74
- package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +25 -5
- package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +10 -10
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +43 -42
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +22 -22
- package/extracted-skill/tracking-events-generator/agents/devops-agent.md +232 -0
- package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +23 -9
- package/extracted-skill/tracking-events-generator/agents/email-agent.md +28 -1
- package/extracted-skill/tracking-events-generator/agents/evo-crm-agent.md +244 -0
- package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +206 -1
- package/extracted-skill/tracking-events-generator/agents/fraud-detection-agent.md +143 -0
- package/extracted-skill/tracking-events-generator/agents/google-agent.md +128 -2
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +191 -31
- package/extracted-skill/tracking-events-generator/agents/lead-scoring-agent.md +282 -0
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +145 -34
- package/extracted-skill/tracking-events-generator/agents/localization-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +5 -5
- package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +81 -21
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +313 -93
- package/extracted-skill/tracking-events-generator/agents/match-quality-agent.md +304 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +190 -15
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +10 -2
- package/extracted-skill/tracking-events-generator/agents/ml-clustering-agent.md +749 -0
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +21 -4
- package/extracted-skill/tracking-events-generator/agents/performance-agent.md +41 -31
- package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +18 -8
- package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +14 -6
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +7 -7
- package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +16 -8
- package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +15 -7
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +157 -48
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +35 -35
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +15 -7
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +73 -2
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +104 -9
- package/extracted-skill/tracking-events-generator/agents/utm-agent.md +322 -0
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +13 -9
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +112 -4
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +58 -5
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +26 -18
- package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +152 -37
- package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -285
- package/extracted-skill/tracking-events-generator/cdpTrack.js +642 -641
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +14 -10
- package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -226
- package/extracted-skill/tracking-events-generator/evals/evals.json +235 -235
- package/extracted-skill/tracking-events-generator/integration-test.js +497 -497
- package/extracted-skill/tracking-events-generator/knowledge-base.md +172 -0
- package/extracted-skill/tracking-events-generator/micro-events.js +992 -992
- package/extracted-skill/tracking-events-generator/models/lancamento-imobiliario.md +344 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -144
- package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -48
- package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -28
- package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +83 -19
- package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -205
- package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -56
- package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -19
- package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -425
- package/extracted-skill/tracking-events-generator/route-intent-capture.js +222 -0
- package/extracted-skill/tracking-events-generator/tracking.config.js +3 -3
- package/package.json +89 -75
- package/scripts/build-sdk.js +106 -0
- package/server-edge-tracker/.client.env.example +14 -0
- package/server-edge-tracker/INSTALAR.md +222 -23
- package/server-edge-tracker/SEGMENTATION-DOCS.md +513 -0
- package/server-edge-tracker/config/utm-mapping.json +64 -0
- package/server-edge-tracker/deploy-client.cjs +76 -0
- package/server-edge-tracker/index.ts +1230 -0
- package/server-edge-tracker/migrate-v7.sql +64 -0
- package/server-edge-tracker/modules/db.ts +710 -0
- package/server-edge-tracker/modules/dispatch/crm.ts +382 -0
- package/server-edge-tracker/modules/dispatch/ga4.ts +72 -0
- package/server-edge-tracker/modules/dispatch/meta.ts +143 -0
- package/server-edge-tracker/modules/dispatch/platforms.ts +255 -0
- package/server-edge-tracker/modules/dispatch/tiktok.ts +107 -0
- package/server-edge-tracker/modules/dispatch/whatsapp.ts +296 -0
- package/server-edge-tracker/modules/intelligence.ts +589 -0
- package/server-edge-tracker/modules/ml/bidding.ts +247 -0
- package/server-edge-tracker/modules/ml/fraud.ts +302 -0
- package/server-edge-tracker/modules/ml/logistic.ts +226 -0
- package/server-edge-tracker/modules/ml/ltv.ts +531 -0
- package/server-edge-tracker/modules/ml/matchquality.ts +232 -0
- package/server-edge-tracker/modules/ml/quiz.ts +343 -0
- package/server-edge-tracker/modules/ml/roas.ts +255 -0
- package/server-edge-tracker/modules/ml/segmentation.ts +407 -0
- package/server-edge-tracker/modules/nurture.ts +257 -0
- package/server-edge-tracker/modules/utils.ts +311 -0
- package/server-edge-tracker/modules/utm/utm-enricher.ts +231 -0
- package/server-edge-tracker/schema-ab-ltv.sql +97 -0
- package/server-edge-tracker/schema-bidding.sql +86 -0
- package/server-edge-tracker/schema-fraud.sql +90 -0
- package/server-edge-tracker/schema-indexes.sql +67 -0
- package/server-edge-tracker/schema-ltv-feedback.sql +11 -0
- package/server-edge-tracker/schema-quiz.sql +52 -0
- package/server-edge-tracker/schema-sales-engine.sql +113 -0
- package/server-edge-tracker/schema-segmentation.sql +219 -0
- package/server-edge-tracker/schema-utm.sql +82 -0
- package/server-edge-tracker/schema.sql +281 -265
- package/server-edge-tracker/types.ts +275 -0
- package/server-edge-tracker/wrangler.toml +140 -85
- package/templates/lancamento-imobiliario.md +344 -0
- package/templates/multi-step-checkout.md +3 -4
- package/templates/pinterest/conversions-api-template.js +144 -144
- package/templates/pinterest/event-mappings.json +48 -48
- package/templates/pinterest/tag-template.js +28 -28
- package/templates/quiz-funnel.md +83 -19
- package/templates/reddit/conversions-api-template.js +205 -205
- package/templates/reddit/event-mappings.json +56 -56
- package/templates/reddit/pixel-template.js +12 -39
- package/templates/scenarios/behavior-engine.js +45 -22
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/docs/installation.md +0 -155
- package/docs/quick-start.md +0 -185
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +0 -1419
- package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +0 -643
- 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
|
+
}
|