cdp-edge 1.23.3 → 1.24.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 (24) hide show
  1. package/README.md +39 -2
  2. package/bin/cdp-edge.js +10 -1
  3. package/contracts/types.ts +81 -0
  4. package/dist/commands/install.js +6 -1
  5. package/docs/whatsapp-ctwa.md +3 -2
  6. package/package.json +7 -4
  7. package/server-edge-tracker/{index.js → index.ts} +91 -82
  8. package/server-edge-tracker/modules/{db.js → db.ts} +116 -76
  9. package/server-edge-tracker/modules/dispatch/{ga4.js → ga4.ts} +12 -10
  10. package/server-edge-tracker/modules/dispatch/{meta.js → meta.ts} +35 -28
  11. package/server-edge-tracker/modules/dispatch/{platforms.js → platforms.ts} +58 -56
  12. package/server-edge-tracker/modules/dispatch/{tiktok.js → tiktok.ts} +22 -20
  13. package/server-edge-tracker/modules/dispatch/{whatsapp.js → whatsapp.ts} +59 -25
  14. package/server-edge-tracker/modules/{intelligence.js → intelligence.ts} +175 -60
  15. package/server-edge-tracker/modules/ml/{bidding.js → bidding.ts} +37 -35
  16. package/server-edge-tracker/modules/ml/{fraud.js → fraud.ts} +48 -40
  17. package/server-edge-tracker/modules/ml/{logistic.js → logistic.ts} +44 -19
  18. package/server-edge-tracker/modules/ml/{ltv.js → ltv.ts} +135 -90
  19. package/server-edge-tracker/modules/ml/{matchquality.js → matchquality.ts} +70 -26
  20. package/server-edge-tracker/modules/ml/{segmentation.js → segmentation.ts} +109 -48
  21. package/server-edge-tracker/modules/{utils.js → utils.ts} +41 -22
  22. package/server-edge-tracker/types.ts +251 -0
  23. package/server-edge-tracker/wrangler.toml +8 -8
  24. package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
@@ -9,9 +9,73 @@ import { sendCallMeBot } from './dispatch/whatsapp.js';
9
9
  import { autoDecideAbWinner } from './ml/ltv.js';
10
10
  import { analyzeMatchQuality, alertMatchQuality, purgeOldMatchQualityLogs } from './ml/matchquality.js';
11
11
  import { trainLogisticRegression, extractFeatures, saveWeights, LTV_WEIGHTS_KV_KEY } from './ml/logistic.js';
12
+ import { Env } from '../types.js';
13
+
14
+ // ── Tipos ───────────────────────────────────────────────────────────────────────
15
+ export interface ApiVersionCheck {
16
+ platform: string;
17
+ current: string;
18
+ expected: string;
19
+ status: 'ok' | 'warning';
20
+ }
21
+
22
+ export interface ErrorRateAlert {
23
+ platform: string;
24
+ errorRate: number;
25
+ status: 'ok' | 'warning' | 'critical';
26
+ }
27
+
28
+ export interface LtvTrainResult {
29
+ trained?: boolean;
30
+ skipped?: string;
31
+ samples?: number;
32
+ accuracy?: number;
33
+ positiveRate?: number;
34
+ error?: string;
35
+ }
36
+
37
+ export interface IntelligenceAgentResult {
38
+ versionResults: ApiVersionCheck[];
39
+ errorAlerts: ErrorRateAlert[];
40
+ ltvTrainResult: LtvTrainResult;
41
+ abResult?: {
42
+ decided: boolean;
43
+ test_id?: number;
44
+ winner_name?: string;
45
+ improvement?: number;
46
+ };
47
+ mqAnalysis?: {
48
+ total?: number;
49
+ composite_score?: number;
50
+ email_rate?: number;
51
+ fbp_rate?: number;
52
+ alerts?: any[];
53
+ };
54
+ cmResult?: {
55
+ sent?: number;
56
+ received?: number;
57
+ skipped?: string;
58
+ error?: string;
59
+ };
60
+ }
61
+
62
+ export interface CustomerMatchResult {
63
+ sent?: number;
64
+ received?: number;
65
+ num_received?: number;
66
+ skipped?: string;
67
+ error?: string;
68
+ }
69
+
70
+ export interface GoogleCustomerMatchExport {
71
+ hashed_email: string;
72
+ hashed_phone: string;
73
+ first_name: string;
74
+ last_name: string;
75
+ }
12
76
 
13
77
  // ── Versões esperadas das APIs ────────────────────────────────────────────────
14
- const EXPECTED_API_VERSIONS = {
78
+ const EXPECTED_API_VERSIONS: Record<string, string> = {
15
79
  meta: 'v22.0',
16
80
  ga4: 'latest',
17
81
  tiktok: 'v1.3',
@@ -25,25 +89,35 @@ const ALERT_THRESHOLDS = {
25
89
  };
26
90
 
27
91
  // ── Alerta via CallMeBot ──────────────────────────────────────────────────────
28
- export async function sendIntelligenceAlert(env, severity, title, details) {
29
- const icon = severity === 'critical' ? '🚨' : '⚠️';
92
+ export async function sendIntelligenceAlert(
93
+ env: Env,
94
+ severity: 'critical' | 'warning' | 'info',
95
+ title: string,
96
+ details: string
97
+ ): Promise<void> {
98
+ const icon = severity === 'critical' ? '🚨' : severity === 'warning' ? '⚠️' : 'ℹ️';
30
99
  const texto = `${icon} CDP Edge — ${title}\n\n${details}\n\n${new Date().toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' })}`;
31
100
  return sendCallMeBot(env, texto);
32
101
  }
33
102
 
34
103
  // ── Check de versões de API ───────────────────────────────────────────────────
35
- export async function checkApiVersionsIntelligence(env, runType) {
36
- const results = [];
104
+ export async function checkApiVersionsIntelligence(
105
+ env: Env,
106
+ runType: string
107
+ ): Promise<ApiVersionCheck[]> {
108
+ const results: ApiVersionCheck[] = [];
37
109
 
38
110
  for (const [platform, expected] of Object.entries(EXPECTED_API_VERSIONS)) {
39
- const currentMap = { meta: 'v22.0', tiktok: 'v1.3', ga4: 'latest', pinterest: 'v5', reddit: 'v2.0' };
111
+ const currentMap: Record<string, string> = { meta: 'v22.0', tiktok: 'v1.3', ga4: 'latest', pinterest: 'v5', reddit: 'v2.0' };
40
112
  const current = currentMap[platform] || 'unknown';
41
113
  const isOk = current === expected || expected === 'latest';
42
114
  const status = isOk ? 'ok' : 'warning';
43
115
 
44
- await logIntelligence(env.DB, runType, platform, 'api_version', status, current, expected,
45
- isOk ? `${platform} ${current} versão correta` : `${platform} ${current} desatualizado, esperado ${expected}`
46
- );
116
+ if (env.DB) {
117
+ await logIntelligence(env.DB, runType, platform, 'api_version', status, current, expected,
118
+ isOk ? `${platform} ${current} — versão correta` : `${platform} ${current} desatualizado, esperado ${expected}`
119
+ );
120
+ }
47
121
 
48
122
  results.push({ platform, current, expected, status });
49
123
  }
@@ -52,27 +126,34 @@ export async function checkApiVersionsIntelligence(env, runType) {
52
126
  }
53
127
 
54
128
  // ── Auditoria de taxa de erro ─────────────────────────────────────────────────
55
- export async function auditErrorRates(env, runType) {
129
+ export async function auditErrorRates(
130
+ env: Env,
131
+ runType: string
132
+ ): Promise<ErrorRateAlert[]> {
56
133
  if (!env.DB) return [];
57
- const alerts = [];
134
+ const alerts: ErrorRateAlert[] = [];
58
135
 
59
136
  for (const platform of ['meta', 'ga4', 'tiktok']) {
60
137
  const metrics = await getHealthMetrics(env.DB, platform, 24);
61
138
  const errorRate = metrics.events_sent > 0 ? metrics.events_failed / metrics.events_sent : 0;
62
139
 
63
- let status = 'ok';
140
+ let status: 'ok' | 'warning' | 'critical' = 'ok';
64
141
  if (errorRate >= ALERT_THRESHOLDS.errorRateCritical) status = 'critical';
65
142
  else if (errorRate >= ALERT_THRESHOLDS.errorRateWarning) status = 'warning';
66
143
 
67
144
  const message = `${platform}: ${metrics.events_sent} eventos, ${metrics.events_failed} falhas (${(errorRate * 100).toFixed(1)}%)`;
68
- const alertSent = status !== 'ok'
69
- ? await sendIntelligenceAlert(env, status, `Taxa de Erro Alta — ${platform.toUpperCase()}`,
70
- `📊 ${message}\n🎯 Taxa: ${(errorRate * 100).toFixed(1)}% (limite: ${ALERT_THRESHOLDS.errorRateWarning * 100}%)`)
71
- : false;
145
+ let alertSent: boolean | undefined = false;
146
+ if (status !== 'ok') {
147
+ await sendIntelligenceAlert(env, status, `Taxa de Erro Alta ${platform.toUpperCase()}`,
148
+ `📊 ${message}\n🎯 Taxa: ${(errorRate * 100).toFixed(1)}% (limite: ${ALERT_THRESHOLDS.errorRateWarning * 100}%)`);
149
+ alertSent = true;
150
+ }
72
151
 
73
- await logIntelligence(env.DB, runType, platform, 'error_rate', status,
74
- `${(errorRate * 100).toFixed(1)}%`, `${ALERT_THRESHOLDS.errorRateWarning * 100}%`, message, alertSent
75
- );
152
+ if (env.DB) {
153
+ await logIntelligence(env.DB, runType, platform, 'error_rate', status,
154
+ `${(errorRate * 100).toFixed(1)}%`, `${ALERT_THRESHOLDS.errorRateWarning * 100}%`, message, alertSent
155
+ );
156
+ }
76
157
 
77
158
  if (status !== 'ok') alerts.push({ platform, errorRate, status });
78
159
  }
@@ -81,7 +162,7 @@ export async function auditErrorRates(env, runType) {
81
162
  }
82
163
 
83
164
  // ── Treinar modelo LTV (regressão logística com dados reais do D1) ────────────
84
- export async function trainLtvModel(env) {
165
+ export async function trainLtvModel(env: Env): Promise<LtvTrainResult> {
85
166
  if (!env.DB) return { skipped: 'DB não disponível' };
86
167
 
87
168
  try {
@@ -108,7 +189,7 @@ export async function trainLtvModel(env) {
108
189
  LIMIT 5000
109
190
  `).all();
110
191
 
111
- const dataset = (rows.results || []).map(row => ({
192
+ const dataset = (rows.results || []).map((row: any) => ({
112
193
  features: extractFeatures(row),
113
194
  label: row.label || 0,
114
195
  }));
@@ -130,14 +211,17 @@ export async function trainLtvModel(env) {
130
211
  console.log(`[LTV Train] Modelo treinado: ${dataset.length} samples, accuracy=${(model.accuracy * 100).toFixed(1)}%, positive_rate=${(model.positiveRate * 100).toFixed(1)}%`);
131
212
  return { trained: true, samples: dataset.length, accuracy: model.accuracy, positiveRate: model.positiveRate };
132
213
 
133
- } catch (err) {
134
- console.error('[LTV Train] Erro:', err.message);
135
- return { error: err.message };
214
+ } catch (err: any) {
215
+ console.error('[LTV Train] Erro:', err?.message || String(err));
216
+ return { error: err?.message || String(err) };
136
217
  }
137
218
  }
138
219
 
139
220
  // ── Runner principal do Intelligence Agent ────────────────────────────────────
140
- export async function runIntelligenceAgent(env, runType) {
221
+ export async function runIntelligenceAgent(
222
+ env: Env,
223
+ runType: string
224
+ ): Promise<IntelligenceAgentResult> {
141
225
  console.log(`[Intelligence Agent] Iniciando ${runType}`);
142
226
 
143
227
  // 1. Check de versões
@@ -159,10 +243,10 @@ export async function runIntelligenceAgent(env, runType) {
159
243
  // 4. Treinar modelo LTV (toda semana)
160
244
  const ltvTrainResult = await trainLtvModel(env);
161
245
  if (ltvTrainResult.trained) {
162
- console.log(`[Intelligence Agent] LTV model treinado: accuracy=${(ltvTrainResult.accuracy * 100).toFixed(1)}%`);
246
+ console.log(`[Intelligence Agent] LTV model treinado: accuracy=${(ltvTrainResult.accuracy! * 100).toFixed(1)}%`);
163
247
  if (env.DB) {
164
248
  await logIntelligence(env.DB, runType, 'ltv', 'model_training', 'ok',
165
- `accuracy=${(ltvTrainResult.accuracy * 100).toFixed(1)}%`, null,
249
+ `accuracy=${(ltvTrainResult.accuracy! * 100).toFixed(1)}%`, null,
166
250
  `Modelo LTV re-treinado com ${ltvTrainResult.samples} amostras`
167
251
  ).catch(() => {});
168
252
  }
@@ -171,9 +255,16 @@ export async function runIntelligenceAgent(env, runType) {
171
255
  }
172
256
 
173
257
  // 5. Auto-decisão de winner no A/B LTV Test
258
+ let abResult: IntelligenceAgentResult['abResult'] = undefined;
174
259
  try {
175
- const abResult = await autoDecideAbWinner(env);
176
- if (abResult?.decided) {
260
+ const abRes = await autoDecideAbWinner(env);
261
+ if (abRes?.decided) {
262
+ abResult = {
263
+ decided: abRes.decided,
264
+ test_id: abRes.test_id,
265
+ winner_name: abRes.winner_name,
266
+ improvement: abRes.improvement ? parseFloat(abRes.improvement) : undefined,
267
+ };
177
268
  console.log(`[Intelligence Agent] A/B LTV winner auto-decidido: test_id=${abResult.test_id}, winner=${abResult.winner_name}`);
178
269
 
179
270
  await sendIntelligenceAlert(env, 'info',
@@ -188,26 +279,28 @@ export async function runIntelligenceAgent(env, runType) {
188
279
  ).catch(() => {});
189
280
  }
190
281
  }
191
- } catch (err) {
192
- console.error('[Intelligence Agent] A/B auto-decide error:', err.message);
282
+ } catch (err: any) {
283
+ console.error('[Intelligence Agent] A/B auto-decide error:', err?.message || String(err));
193
284
  }
194
285
 
195
286
  // 6. Match Quality — análise + alertas
287
+ let mqAnalysis: IntelligenceAgentResult['mqAnalysis'] = undefined;
196
288
  try {
197
- const mqAnalysis = await analyzeMatchQuality(env);
198
- if (mqAnalysis) {
289
+ const mqRes = await analyzeMatchQuality(env);
290
+ if (mqRes) {
291
+ mqAnalysis = mqRes;
199
292
  console.log(`[Intelligence Agent] Match Quality: score=${mqAnalysis.composite_score ?? 0}%, alerts=${mqAnalysis.alerts?.length ?? 0}`);
200
- await alertMatchQuality(env, mqAnalysis);
293
+ await alertMatchQuality(env, mqRes);
201
294
 
202
- if (env.DB && mqAnalysis.total > 0) {
203
- await logIntelligence(env.DB, runType, 'meta', 'match_quality', mqAnalysis.alerts?.length > 0 ? 'warning' : 'ok',
295
+ if (env.DB && mqAnalysis.total && mqAnalysis.total > 0) {
296
+ await logIntelligence(env.DB, runType, 'meta', 'match_quality', (mqAnalysis.alerts && mqAnalysis.alerts.length > 0) ? 'warning' : 'ok',
204
297
  `${mqAnalysis.composite_score ?? 0}%`, '45%',
205
298
  `Match quality 2h: email=${mqAnalysis.email_rate ?? 0}%, fbp=${mqAnalysis.fbp_rate ?? 0}%, score=${mqAnalysis.composite_score ?? 0}%`
206
299
  ).catch(() => {});
207
300
  }
208
301
  }
209
- } catch (err) {
210
- console.error('[Intelligence Agent] Match quality analysis error:', err.message);
302
+ } catch (err: any) {
303
+ console.error('[Intelligence Agent] Match quality analysis error:', err?.message || String(err));
211
304
  }
212
305
 
213
306
  // 7. Auditoria mensal adicional
@@ -221,29 +314,40 @@ export async function runIntelligenceAgent(env, runType) {
221
314
  GROUP BY predicted_ltv_class
222
315
  `).all();
223
316
 
224
- const summary = ltvStats.results?.map(r => `${r.predicted_ltv_class}: ${r.count}`).join(', ') || 'sem dados';
317
+ const summary = ltvStats.results?.map((r: any) => `${r.predicted_ltv_class}: ${r.count}`).join(', ') || 'sem dados';
225
318
  await logIntelligence(env.DB, runType, 'all', 'ltv_distribution', 'ok', summary, null,
226
319
  `Distribuição LTV últimos 30 dias: ${summary}`);
227
320
  console.log(`[Intelligence Agent] LTV distribution: ${summary}`);
228
- } catch (err) {
229
- console.error('LTV audit error:', err.message);
321
+ } catch (err: any) {
322
+ console.error('LTV audit error:', err?.message || String(err));
230
323
  }
231
324
 
232
325
  // Purge de logs antigos de match quality (> 30 dias)
233
- await purgeOldMatchQualityLogs(env.DB);
234
- console.log('[Intelligence Agent] Match quality logs antigos purgados');
326
+ if (env.DB) {
327
+ await purgeOldMatchQualityLogs(env.DB);
328
+ console.log('[Intelligence Agent] Match quality logs antigos purgados');
329
+ }
235
330
  }
236
331
  }
237
332
 
238
333
  // 8. Customer Match sync semanal
239
334
  const cmResult = await syncMetaCustomAudience(env);
240
- console.log(`[Intelligence Agent] Customer Match Meta: sent=${cmResult?.sent ?? 0}, received=${cmResult?.num_received ?? 0}`);
335
+ console.log(`[Intelligence Agent] Customer Match Meta: sent=${cmResult?.sent ?? 0}, received=${cmResult?.received ?? 0}`);
241
336
 
242
337
  console.log(`[Intelligence Agent] ${runType} concluído — LTV model, A/B auto-decide, match quality, customer match`);
338
+
339
+ return {
340
+ versionResults,
341
+ errorAlerts,
342
+ ltvTrainResult,
343
+ abResult,
344
+ mqAnalysis,
345
+ cmResult,
346
+ };
243
347
  }
244
348
 
245
349
  // ── syncMetaCustomAudience — D1 → Meta Custom Audiences ─────────────────────
246
- export async function syncMetaCustomAudience(env) {
350
+ export async function syncMetaCustomAudience(env: Env): Promise<CustomerMatchResult> {
247
351
  if (!env.META_ACCESS_TOKEN || !env.META_AD_ACCOUNT_ID || !env.META_AUDIENCE_ID) {
248
352
  console.log('[CustomerMatch] Meta: secrets não configurados — pulando sync');
249
353
  return { skipped: 'META_AD_ACCOUNT_ID ou META_AUDIENCE_ID não configurados' };
@@ -265,7 +369,7 @@ export async function syncMetaCustomAudience(env) {
265
369
  }
266
370
 
267
371
  const data = await Promise.all(
268
- profiles.results.map(async (p) => [
372
+ profiles.results.map(async (p: any) => [
269
373
  p.email ? await sha256(p.email) : '',
270
374
  p.phone ? await sha256(p.phone) : '',
271
375
  ])
@@ -280,7 +384,7 @@ export async function syncMetaCustomAudience(env) {
280
384
  body: JSON.stringify({ ...body, access_token: env.META_ACCESS_TOKEN }),
281
385
  });
282
386
 
283
- const result = await res.json();
387
+ const result = await res.json() as any;
284
388
 
285
389
  if (!res.ok) {
286
390
  console.error('[CustomerMatch] Meta erro:', res.status, result.error?.message || 'unknown');
@@ -288,16 +392,16 @@ export async function syncMetaCustomAudience(env) {
288
392
  }
289
393
 
290
394
  console.log(`[CustomerMatch] Meta: ${profiles.results.length} perfis sincronizados`);
291
- return { sent: profiles.results.length, num_received: result.num_received };
395
+ return { sent: profiles.results.length, num_received: result.num_received, received: result.num_received };
292
396
 
293
- } catch (err) {
294
- console.error('[CustomerMatch] Meta fetch error:', err.message);
295
- return { error: err.message, sent: 0 };
397
+ } catch (err: any) {
398
+ console.error('[CustomerMatch] Meta fetch error:', err?.message || String(err));
399
+ return { error: err?.message || String(err), sent: 0 };
296
400
  }
297
401
  }
298
402
 
299
403
  // ── buildGoogleCustomerMatchExport — gera JSON para Google Ads Customer Match ─
300
- export async function buildGoogleCustomerMatchExport(env) {
404
+ export async function buildGoogleCustomerMatchExport(env: Env): Promise<GoogleCustomerMatchExport[]> {
301
405
  if (!env.DB) return [];
302
406
 
303
407
  const profiles = await env.DB.prepare(`
@@ -310,12 +414,23 @@ export async function buildGoogleCustomerMatchExport(env) {
310
414
 
311
415
  if (!profiles.results?.length) return [];
312
416
 
313
- return Promise.all(
314
- profiles.results.map(async (p) => ({
315
- hashed_email: p.email ? await sha256(p.email) : '',
316
- hashed_phone: p.phone ? await sha256(p.phone) : '',
317
- first_name: p.first_name || '',
318
- last_name: p.last_name || '',
319
- }))
320
- );
417
+ const results: GoogleCustomerMatchExport[] = [];
418
+ for (const p of profiles.results) {
419
+ const email = p.email as string | null | undefined;
420
+ const phone = p.phone as string | null | undefined;
421
+ const firstName = p.first_name as string | undefined;
422
+ const lastName = p.last_name as string | undefined;
423
+
424
+ const hashed_email = email ? await sha256(email) : '';
425
+ const hashed_phone = phone ? await sha256(phone) : '';
426
+ if (hashed_email || hashed_phone) {
427
+ results.push({
428
+ hashed_email: hashed_email || '',
429
+ hashed_phone: hashed_phone || '',
430
+ first_name: firstName || '',
431
+ last_name: lastName || '',
432
+ });
433
+ }
434
+ }
435
+ return results;
321
436
  }
@@ -4,13 +4,15 @@
4
4
  */
5
5
 
6
6
  import { tryParseJson } from '../utils.js';
7
+ import { Env } from '../../types.js';
8
+ import { ExecutionContext } from '@cloudflare/workers-types';
7
9
 
8
10
  // ── Constantes de calibração ──────────────────────────────────────────────────
9
- const PLATFORM_FACTORS = { meta: 0.85, google: 0.90, tiktok: 0.75 };
11
+ const PLATFORM_FACTORS: Record<string, number> = { meta: 0.85, google: 0.90, tiktok: 0.75 };
10
12
 
11
- function getSegmentMultiplier(avgLtvClass, avgBehaviorScore) {
12
- const ltv = parseFloat(avgLtvClass || 0);
13
- const eng = parseFloat(avgBehaviorScore || 0);
13
+ function getSegmentMultiplier(avgLtvClass: string | number, avgBehaviorScore: string | number): number {
14
+ const ltv = parseFloat(String(avgLtvClass) || '0');
15
+ const eng = parseFloat(String(avgBehaviorScore) || '0');
14
16
  if (ltv >= 0.7 && eng >= 0.7) return 1.4;
15
17
  if (ltv >= 0.7 && eng >= 0.4) return 1.2;
16
18
  if (ltv >= 0.4 && eng >= 0.7) return 1.0;
@@ -18,17 +20,17 @@ function getSegmentMultiplier(avgLtvClass, avgBehaviorScore) {
18
20
  return 0.6;
19
21
  }
20
22
 
21
- function getConfidenceAdjustment(confidence) {
23
+ function getConfidenceAdjustment(confidence: number): number {
22
24
  if (confidence >= 0.7) return 1.00;
23
25
  if (confidence >= 0.4) return 0.85;
24
26
  return 0.70;
25
27
  }
26
28
 
27
29
  // ── POST /api/bidding/recommend ───────────────────────────────────────────────
28
- export async function handleBiddingRecommend(env, request, headers) {
30
+ export async function handleBiddingRecommend(env: Env, request: Request, headers: Headers): Promise<Response> {
29
31
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
30
32
 
31
- let body;
33
+ let body: any;
32
34
  try { body = await request.json(); }
33
35
  catch { return new Response(JSON.stringify({ error: 'JSON inválido no body' }), { status: 400, headers }); }
34
36
 
@@ -79,9 +81,9 @@ export async function handleBiddingRecommend(env, request, headers) {
79
81
  AND (bot_score IS NULL OR bot_score < 2)
80
82
  `).bind(period_days).first();
81
83
 
82
- globalLeads = globalRes?.total_leads || 0;
83
- globalLtv = globalRes?.avg_ltv || 0;
84
- globalConversions = globalRes?.conversions || 0;
84
+ globalLeads = Number((globalRes as any)?.total_leads) || 0;
85
+ globalLtv = Number((globalRes as any)?.avg_ltv) || 0;
86
+ globalConversions = Number((globalRes as any)?.conversions) || 0;
85
87
 
86
88
  if (globalLeads < 10) {
87
89
  return new Response(JSON.stringify({
@@ -92,27 +94,27 @@ export async function handleBiddingRecommend(env, request, headers) {
92
94
  }
93
95
 
94
96
  const now = new Date().toISOString();
95
- const recommendations = [];
97
+ const recommendations: any[] = [];
96
98
 
97
99
  const targetSegments = segments.length > 0
98
100
  ? segments
99
101
  : [{ segment_id: null, cluster_name: 'Global (sem segmentação)', avg_ltv_class: 0.5, avg_behavior_score: 0.5, avg_engagement_score: 0.5, member_count: globalLeads, real_avg_ltv: globalLtv, conversions: globalConversions }];
100
102
 
101
103
  for (const seg of targetSegments) {
102
- const avgLtv = parseFloat(seg.real_avg_ltv || 0);
103
- const convs = parseInt(seg.conversions || 0);
104
+ const avgLtv = parseFloat(String(seg.real_avg_ltv) || '0');
105
+ const convs = parseInt(String(seg.conversions || '0'));
104
106
  const confidence = Math.min(1, convs / 100);
105
107
 
106
108
  const estimatedLtv = avgLtv > 0 ? avgLtv :
107
- seg.avg_ltv_class >= 0.7 ? 497 : seg.avg_ltv_class >= 0.4 ? 297 : 97;
109
+ Number(seg.avg_ltv_class) >= 0.7 ? 497 : Number(seg.avg_ltv_class) >= 0.4 ? 297 : 97;
108
110
 
109
111
  const cpaTarget = estimatedLtv / target_roi;
110
- const segMult = getSegmentMultiplier(seg.avg_ltv_class, seg.avg_behavior_score);
112
+ const segMult = getSegmentMultiplier(String(seg.avg_ltv_class), String(seg.avg_behavior_score));
111
113
  const confAdj = getConfidenceAdjustment(confidence);
112
114
  const alertMsg = convs < 30 ? `Atenção: apenas ${convs} conversões no período. Bid baseado em estimativa de LTV — aplique com cautela.` : null;
113
115
 
114
116
  for (const plat of platforms) {
115
- const platFactor = PLATFORM_FACTORS[plat];
117
+ const platFactor = PLATFORM_FACTORS[plat] || 0.8;
116
118
  const recommendedBid = Math.max(5, cpaTarget * platFactor * segMult * confAdj);
117
119
  const expectedRoi = estimatedLtv / (recommendedBid / platFactor);
118
120
 
@@ -138,12 +140,12 @@ export async function handleBiddingRecommend(env, request, headers) {
138
140
  confidence, expectedRoi, reasoning, alertMsg || null,
139
141
  platFactor, confAdj, segMult,
140
142
  ).run();
141
- } catch (e) { console.error('[Bidding] D1 insert error:', e.message); }
143
+ } catch (e: any) { console.error('[Bidding] D1 insert error:', e?.message || String(e)); }
142
144
 
143
145
  recommendations.push({
144
146
  platform: plat, segment: seg.cluster_name, segment_id: seg.segment_id || null,
145
147
  avg_ltv: Math.round(estimatedLtv * 100) / 100,
146
- avg_ltv_class: seg.avg_ltv_class >= 0.7 ? 'High' : seg.avg_ltv_class >= 0.4 ? 'Medium' : 'Low',
148
+ avg_ltv_class: Number(seg.avg_ltv_class) >= 0.7 ? 'High' : Number(seg.avg_ltv_class) >= 0.4 ? 'Medium' : 'Low',
147
149
  cpa_target: Math.round(cpaTarget * 100) / 100,
148
150
  recommended_bid: Math.round(recommendedBid * 100) / 100,
149
151
  bid_currency: 'BRL', confidence: Math.round(confidence * 100) / 100,
@@ -161,8 +163,8 @@ export async function handleBiddingRecommend(env, request, headers) {
161
163
  return new Response(JSON.stringify({
162
164
  success: true, generated_at: now, vertical, period_days, target_roi,
163
165
  data_quality: {
164
- leads_analyzed: targetSegments.reduce((s, sg) => s + (sg.member_count || 0), 0),
165
- conversions_found: targetSegments.reduce((s, sg) => s + (sg.conversions || 0), 0),
166
+ leads_analyzed: targetSegments.reduce((s: number, sg: any) => s + (sg.member_count || 0), 0),
167
+ conversions_found: targetSegments.reduce((s: number, sg: any) => s + (sg.conversions || 0), 0),
166
168
  segments_active: segments.length, confidence: Math.round(avgConfidence * 100) / 100,
167
169
  },
168
170
  recommendations,
@@ -174,14 +176,14 @@ export async function handleBiddingRecommend(env, request, headers) {
174
176
  },
175
177
  }), { status: 200, headers });
176
178
 
177
- } catch (err) {
178
- console.error('[Bidding] recommend error:', err.message);
179
- return new Response(JSON.stringify({ error: 'Erro ao gerar recomendações', message: err.message }), { status: 500, headers });
179
+ } catch (err: any) {
180
+ console.error('[Bidding] recommend error:', err?.message || String(err));
181
+ return new Response(JSON.stringify({ error: 'Erro ao gerar recomendações', message: err?.message || String(err) }), { status: 500, headers });
180
182
  }
181
183
  }
182
184
 
183
185
  // ── GET /api/bidding/history ──────────────────────────────────────────────────
184
- export async function handleBiddingHistory(env, request, headers) {
186
+ export async function handleBiddingHistory(env: Env, request: Request, headers: Headers): Promise<Response> {
185
187
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
186
188
 
187
189
  const url = new URL(request.url);
@@ -190,8 +192,8 @@ export async function handleBiddingHistory(env, request, headers) {
190
192
  const limit = Math.min(parseInt(url.searchParams.get('limit') || '20'), 100);
191
193
 
192
194
  try {
193
- const conditions = [];
194
- const bindings = [];
195
+ const conditions: string[] = [];
196
+ const bindings: (string | number)[] = [];
195
197
  if (vertical) { conditions.push('vertical = ?'); bindings.push(vertical); }
196
198
  if (platform) { conditions.push('platform = ?'); bindings.push(platform); }
197
199
  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
@@ -208,16 +210,16 @@ export async function handleBiddingHistory(env, request, headers) {
208
210
  LIMIT ?
209
211
  `).bind(...bindings, limit).all();
210
212
 
211
- const items = (result.results || []).map(r => ({ ...r, applied_result: tryParseJson(r.applied_result, null) }));
213
+ const items = (result.results || []).map((r: any) => ({ ...r, applied_result: tryParseJson(r.applied_result, null) }));
212
214
  return new Response(JSON.stringify({ success: true, total: items.length, history: items }), { status: 200, headers });
213
- } catch (err) {
214
- console.error('[Bidding] history error:', err.message);
215
- return new Response(JSON.stringify({ error: err.message }), { status: 500, headers });
215
+ } catch (err: any) {
216
+ console.error('[Bidding] history error:', err?.message || String(err));
217
+ return new Response(JSON.stringify({ error: err?.message || String(err) }), { status: 500, headers });
216
218
  }
217
219
  }
218
220
 
219
221
  // ── GET /api/bidding/status ───────────────────────────────────────────────────
220
- export async function handleBiddingStatus(env, request, headers) {
222
+ export async function handleBiddingStatus(env: Env, request: Request, headers: Headers): Promise<Response> {
221
223
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
222
224
 
223
225
  const url = new URL(request.url);
@@ -232,14 +234,14 @@ export async function handleBiddingStatus(env, request, headers) {
232
234
  FROM bid_recommendations
233
235
  WHERE is_active = 1
234
236
  `;
235
- const bindings = [];
237
+ const bindings: (string | number)[] = [];
236
238
  if (vertical) { query += ' AND vertical = ?'; bindings.push(vertical); }
237
239
  query += ' GROUP BY platform, vertical ORDER BY last_generated DESC';
238
240
 
239
241
  const result = await env.DB.prepare(query).bind(...bindings).all();
240
242
  return new Response(JSON.stringify({ success: true, status: result.results || [] }), { status: 200, headers });
241
- } catch (err) {
242
- console.error('[Bidding] status error:', err.message);
243
- return new Response(JSON.stringify({ error: err.message }), { status: 500, headers });
243
+ } catch (err: any) {
244
+ console.error('[Bidding] status error:', err?.message || String(err));
245
+ return new Response(JSON.stringify({ error: err?.message || String(err) }), { status: 500, headers });
244
246
  }
245
247
  }