cdp-edge 1.23.2 → 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 (42) hide show
  1. package/README.md +82 -21
  2. package/bin/cdp-edge.js +10 -1
  3. package/contracts/agent-versions.json +42 -41
  4. package/contracts/types.ts +81 -0
  5. package/dist/commands/install.js +6 -1
  6. package/dist/commands/server.js +4 -4
  7. package/docs/whatsapp-ctwa.md +3 -2
  8. package/extracted-skill/tracking-events-generator/agents/database-agent.md +5 -4
  9. package/extracted-skill/tracking-events-generator/agents/fraud-detection-agent.md +0 -1
  10. package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +1 -1
  11. package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +4 -4
  12. package/extracted-skill/tracking-events-generator/agents/ml-clustering-agent.md +81 -70
  13. package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +6 -2
  14. package/extracted-skill/tracking-events-generator/cdpTrack.js +7 -0
  15. package/extracted-skill/tracking-events-generator/models/lancamento-imobiliario.md +344 -0
  16. package/extracted-skill/tracking-events-generator/route-intent-capture.js +222 -0
  17. package/package.json +9 -5
  18. package/server-edge-tracker/INSTALAR.md +5 -5
  19. package/server-edge-tracker/{index.js → index.ts} +186 -72
  20. package/server-edge-tracker/modules/{db.js → db.ts} +180 -69
  21. package/server-edge-tracker/modules/dispatch/{ga4.js → ga4.ts} +12 -10
  22. package/server-edge-tracker/modules/dispatch/meta.ts +138 -0
  23. package/server-edge-tracker/modules/dispatch/{platforms.js → platforms.ts} +58 -56
  24. package/server-edge-tracker/modules/dispatch/{tiktok.js → tiktok.ts} +22 -20
  25. package/server-edge-tracker/modules/dispatch/{whatsapp.js → whatsapp.ts} +59 -25
  26. package/server-edge-tracker/modules/{intelligence.js → intelligence.ts} +175 -60
  27. package/server-edge-tracker/modules/ml/{bidding.js → bidding.ts} +37 -35
  28. package/server-edge-tracker/modules/ml/{fraud.js → fraud.ts} +49 -56
  29. package/server-edge-tracker/modules/ml/{logistic.js → logistic.ts} +44 -19
  30. package/server-edge-tracker/modules/ml/{ltv.js → ltv.ts} +179 -83
  31. package/server-edge-tracker/modules/ml/{matchquality.js → matchquality.ts} +70 -26
  32. package/server-edge-tracker/modules/ml/segmentation.ts +407 -0
  33. package/server-edge-tracker/modules/utils.ts +186 -0
  34. package/server-edge-tracker/schema-ltv-feedback.sql +11 -0
  35. package/server-edge-tracker/types.ts +251 -0
  36. package/server-edge-tracker/wrangler.toml +24 -6
  37. package/templates/lancamento-imobiliario.md +344 -0
  38. package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
  39. package/server-edge-tracker/modules/dispatch/meta.js +0 -119
  40. package/server-edge-tracker/modules/ml/segmentation.js +0 -316
  41. package/server-edge-tracker/modules/utils.js +0 -89
  42. package/server-edge-tracker/worker.js +0 -4577
@@ -5,9 +5,49 @@
5
5
  */
6
6
 
7
7
  import { sha256, normalizePhone, normalizeCity } from './utils.js';
8
+ import { Env, TrackPayload } from '../types.js';
9
+ import { D1Database } from '@cloudflare/workers-types';
10
+
11
+ // ── Tipos ───────────────────────────────────────────────────────────────────────
12
+ export interface GeoData {
13
+ country: string | null;
14
+ continent: string | null;
15
+ asn: string | null;
16
+ asOrg: string | null;
17
+ colo: string | null;
18
+ city: string | null;
19
+ region: string | null;
20
+ regionCode: string | null;
21
+ postalCode: string | null;
22
+ latitude: number | null;
23
+ longitude: number | null;
24
+ timezone: string | null;
25
+ metroCode: string | null;
26
+ }
27
+
28
+ export interface LtvResult {
29
+ value: number;
30
+ class: string;
31
+ score?: number;
32
+ }
33
+
34
+ export interface HealthMetrics {
35
+ platform: string;
36
+ hours: number;
37
+ events_sent: number;
38
+ events_failed: number;
39
+ success_rate: number;
40
+ errors_detected: Array<{ code: string; count: number }>;
41
+ issues: string[];
42
+ }
43
+
44
+ export interface DailyReport {
45
+ platform: string;
46
+ status: string;
47
+ }
8
48
 
9
49
  // ── saveLead — inserir evento de conversão ────────────────────────────────────
10
- export async function saveLead(env, eventName, payload, request, platform = 'website') {
50
+ export async function saveLead(env: Env, eventName: string, payload: TrackPayload, request: Request, platform: string = 'website'): Promise<void> {
11
51
  if (!env.DB) return;
12
52
  try {
13
53
  const {
@@ -36,7 +76,7 @@ export async function saveLead(env, eventName, payload, request, platform = 'web
36
76
  lastName || null,
37
77
  city || null,
38
78
  state || null,
39
- (country || request.cf?.country || null),
79
+ (country || (request as any).cf?.country || null),
40
80
  fbp || null,
41
81
  fbc || null,
42
82
  userId || null,
@@ -46,22 +86,22 @@ export async function saveLead(env, eventName, payload, request, platform = 'web
46
86
  utmContent || null,
47
87
  utmTerm || null,
48
88
  pageUrl || null,
49
- value !== undefined ? parseFloat(value) : null,
89
+ value !== undefined ? parseFloat(String(value)) : null,
50
90
  currency || 'BRL',
51
91
  request.headers.get('CF-Connecting-IP') || null,
52
92
  platform,
53
93
  botScore || 0,
54
- engagementScore !== undefined ? parseFloat(engagementScore) : null,
94
+ engagementScore !== undefined ? parseFloat(String(engagementScore)) : null,
55
95
  intentionLevel || null,
56
96
  utmRestored ? 1 : 0,
57
97
  ).run();
58
- } catch (err) {
59
- console.error('D1 saveLead error:', err.message);
98
+ } catch (err: any) {
99
+ console.error('D1 saveLead error:', err?.message || String(err));
60
100
  }
61
101
  }
62
102
 
63
103
  // ── calculateCohortLabel ──────────────────────────────────────────────────────
64
- export function calculateCohortLabel(score, eventName) {
104
+ export function calculateCohortLabel(score: number, eventName: string): string {
65
105
  if (eventName === 'Purchase') return 'buyer_lookalike';
66
106
  if (score >= 80) return 'high_intent';
67
107
  if (score >= 30) return 'nurture';
@@ -69,7 +109,7 @@ export function calculateCohortLabel(score, eventName) {
69
109
  }
70
110
 
71
111
  // ── upsertProfile — acumula cookies/scores entre visitas ─────────────────────
72
- export async function upsertProfile(env, eventName, payload, request) {
112
+ export async function upsertProfile(env: Env, eventName: string, payload: TrackPayload, request: Request): Promise<void> {
73
113
  if (!env.DB || !payload.userId) return;
74
114
  try {
75
115
  const {
@@ -79,7 +119,7 @@ export async function upsertProfile(env, eventName, payload, request) {
79
119
  engagementScore, userScore,
80
120
  } = payload;
81
121
 
82
- const scoreMap = { PageView: 5, ViewContent: 10, ScrollDepth: 3, TimeOnPage: 5, Lead: 30, InitiateCheckout: 50, Purchase: 100 };
122
+ const scoreMap: Record<string, number> = { PageView: 5, ViewContent: 10, ScrollDepth: 3, TimeOnPage: 5, Lead: 30, InitiateCheckout: 50, Purchase: 100 };
83
123
  const eventScore = scoreMap[eventName] || 2;
84
124
 
85
125
  const behaviorBonus = userScore
@@ -120,23 +160,23 @@ export async function upsertProfile(env, eventName, payload, request) {
120
160
  gaClientId || null,
121
161
  city || null,
122
162
  state || null,
123
- (country || request.cf?.country || null),
163
+ (country || (request as any).cf?.country || null),
124
164
  totalDelta,
125
165
  calculateCohortLabel(totalDelta, eventName),
126
166
  ).run();
127
- } catch (err) {
128
- console.error('D1 upsertProfile error:', err.message);
167
+ } catch (err: any) {
168
+ console.error('D1 upsertProfile error:', err?.message || String(err));
129
169
  }
130
170
  }
131
171
 
132
172
  // ── resolveDeviceGraph — Cross-Device Identity ────────────────────────────────
133
- export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
173
+ export async function resolveDeviceGraph(DB: D1Database, currentUserId: string, email?: string | null, phone?: string | null): Promise<void> {
134
174
  if (!DB || !currentUserId) return;
135
175
  if (!email && !phone) return;
136
176
 
137
177
  try {
138
- const conditions = [];
139
- const bindings = [];
178
+ const conditions: string[] = [];
179
+ const bindings: (string | number)[] = [];
140
180
 
141
181
  if (email) {
142
182
  conditions.push('email = ?');
@@ -166,7 +206,7 @@ export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
166
206
 
167
207
  for (const match of rows.results) {
168
208
  const emailMatch = email && match.email &&
169
- email.toLowerCase().trim() === match.email.toLowerCase().trim();
209
+ email.toLowerCase().trim() === (match.email as string).toLowerCase().trim();
170
210
  const phoneMatch = phone && match.phone && (() => {
171
211
  const a = String(phone).replace(/\D/g, '');
172
212
  const b = String(match.phone).replace(/\D/g, '');
@@ -177,7 +217,7 @@ export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
177
217
 
178
218
  const matchType = emailMatch && phoneMatch ? 'email+phone' : (emailMatch ? 'email' : 'phone');
179
219
  const matchConfidence = emailMatch && phoneMatch ? 0.99 : (emailMatch ? 0.95 : 0.85);
180
- const primary = match.user_id;
220
+ const primary = match.user_id as string;
181
221
  const secondary = currentUserId;
182
222
 
183
223
  await DB.prepare(`
@@ -188,13 +228,13 @@ export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
188
228
 
189
229
  console.log(`[DeviceGraph] Linked ${secondary} → ${primary} via ${matchType} (confidence: ${matchConfidence})`);
190
230
  }
191
- } catch (err) {
192
- console.error('resolveDeviceGraph error:', err.message);
231
+ } catch (err: any) {
232
+ console.error('resolveDeviceGraph error:', err?.message || String(err));
193
233
  }
194
234
  }
195
235
 
196
236
  // ── fireAutomation — dispara regras de automação (WA/Email) ──────────────────
197
- export async function fireAutomation(env, eventName, leadId, payload) {
237
+ export async function fireAutomation(env: Env, eventName: string, leadId: number | null, payload: TrackPayload): Promise<void> {
198
238
  if (!env.DB) return;
199
239
 
200
240
  try {
@@ -209,20 +249,20 @@ export async function fireAutomation(env, eventName, leadId, payload) {
209
249
 
210
250
  if (!rules || rules.length === 0) return;
211
251
 
212
- const vars = {
213
- name: String(payload.firstName || payload.name || ''),
252
+ const vars: Record<string, string> = {
253
+ name: String(payload.firstName || (payload as any).name || ''),
214
254
  email: String(payload.email || ''),
215
255
  phone: String(payload.phone || ''),
216
- campaign: String(payload.utm_campaign || payload.utmCampaign || ''),
256
+ campaign: String(payload.utmCampaign || payload.utm_campaign || ''),
217
257
  intention: String(payload.intentionLevel || payload.intention_level || ''),
218
258
  };
219
259
 
220
- const interpolate = (tpl) =>
260
+ const interpolate = (tpl: string) =>
221
261
  tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? '');
222
262
 
223
263
  for (const rule of rules) {
224
- const message = interpolate(rule.message_template);
225
- const subject = rule.subject_template ? interpolate(rule.subject_template) : null;
264
+ const message = interpolate(rule.message_template as string);
265
+ const subject = rule.subject_template ? interpolate(rule.subject_template as string) : null;
226
266
 
227
267
  try {
228
268
  if (rule.channel === 'whatsapp' && payload.phone && env.WHATSAPP_ACCESS_TOKEN && env.WHATSAPP_PHONE_NUMBER_ID) {
@@ -238,7 +278,7 @@ export async function fireAutomation(env, eventName, leadId, payload) {
238
278
  );
239
279
  const waData = await waRes.json();
240
280
  const status = waRes.ok ? 'sent' : 'failed';
241
- const meta = waRes.ok ? (waData.messages?.[0]?.id ?? null) : JSON.stringify(waData);
281
+ const meta = waRes.ok ? ((waData as any).messages?.[0]?.id ?? null) : JSON.stringify(waData);
242
282
  await env.DB.prepare(
243
283
  `INSERT INTO messaging_history (lead_id, channel, recipient, subject, content, status, meta) VALUES (?1,?2,?3,?4,?5,?6,?7)`
244
284
  ).bind(leadId, 'whatsapp', e164, null, message, status, meta).run();
@@ -256,22 +296,22 @@ export async function fireAutomation(env, eventName, leadId, payload) {
256
296
  });
257
297
  const resendData = await resendRes.json();
258
298
  const status = resendRes.ok ? 'sent' : 'failed';
259
- const meta = resendRes.ok ? (resendData.id ?? null) : JSON.stringify(resendData);
299
+ const meta = resendRes.ok ? ((resendData as any).id ?? null) : JSON.stringify(resendData);
260
300
  await env.DB.prepare(
261
301
  `INSERT INTO messaging_history (lead_id, channel, recipient, subject, content, status, meta) VALUES (?1,?2,?3,?4,?5,?6,?7)`
262
302
  ).bind(leadId, 'email', payload.email, subject, message, status, meta).run();
263
303
  }
264
- } catch (err) {
265
- console.error(`[Automation] rule ${rule.id} error:`, err.message);
304
+ } catch (err: any) {
305
+ console.error(`[Automation] rule ${(rule as any).id} error:`, err?.message || String(err));
266
306
  }
267
307
  }
268
- } catch (err) {
269
- console.error('[Automation] fireAutomation error:', err.message);
308
+ } catch (err: any) {
309
+ console.error('[Automation] fireAutomation error:', err?.message || String(err));
270
310
  }
271
311
  }
272
312
 
273
313
  // ── getProfileByEmail ─────────────────────────────────────────────────────────
274
- export async function getProfileByEmail(env, email) {
314
+ export async function getProfileByEmail(env: Env, email: string): Promise<any | null> {
275
315
  if (!env.DB || !email) return null;
276
316
  try {
277
317
  return await env.DB.prepare(
@@ -283,14 +323,14 @@ export async function getProfileByEmail(env, email) {
283
323
  }
284
324
 
285
325
  // ── enrichGeoFromEdge — enriquece payload com dados de geolocalização ─────────
286
- export async function enrichGeoFromEdge(request, env, payload) {
287
- const cf = request.cf || {};
326
+ export async function enrichGeoFromEdge(request: Request, env: Env, payload: TrackPayload): Promise<GeoData> {
327
+ const cf = (request as any).cf || {};
288
328
  const ip = request.headers.get('CF-Connecting-IP') || '';
289
329
 
290
- let geoData = null;
330
+ let geoData: GeoData | null = null;
291
331
  if (env.GEO_CACHE && ip) {
292
332
  try {
293
- const cached = await env.GEO_CACHE.get(`geo:${ip}`, 'json');
333
+ const cached = await env.GEO_CACHE.get(`geo:${ip}`, 'json') as GeoData | null;
294
334
  if (cached) geoData = cached;
295
335
  } catch {}
296
336
  }
@@ -323,13 +363,13 @@ export async function enrichGeoFromEdge(request, env, payload) {
323
363
  payload.city = payload.city || geoData.city;
324
364
  payload.state = payload.state || geoData.regionCode;
325
365
  payload.zip = payload.zip || geoData.postalCode;
326
- payload.geo = geoData;
366
+ (payload as any).geo = geoData;
327
367
 
328
368
  return geoData;
329
369
  }
330
370
 
331
371
  // ── writeAuditLog — grava evento no R2 ───────────────────────────────────────
332
- export async function writeAuditLog(env, eventName, payload, geoData) {
372
+ export async function writeAuditLog(env: Env, eventName: string, payload: TrackPayload, geoData: GeoData | null): Promise<void> {
333
373
  if (!env.AUDIT_LOGS) return;
334
374
  try {
335
375
  const now = new Date();
@@ -360,14 +400,14 @@ export async function writeAuditLog(env, eventName, payload, geoData) {
360
400
  await env.AUDIT_LOGS.put(key, JSON.stringify(log), {
361
401
  httpMetadata: { contentType: 'application/json' },
362
402
  });
363
- } catch (err) {
364
- console.error('[R2 Audit] Error:', err.message);
403
+ } catch (err: any) {
404
+ console.error('[R2 Audit] Error:', err?.message || String(err));
365
405
  }
366
406
  }
367
407
 
368
408
  // ── generateEdgeFingerprint ───────────────────────────────────────────────────
369
- export async function generateEdgeFingerprint(request) {
370
- const asn = String(request.cf?.asn || '0');
409
+ export async function generateEdgeFingerprint(request: Request): Promise<string | undefined> {
410
+ const asn = String((request as any).cf?.asn || '0');
371
411
  const lang = (request.headers.get('Accept-Language') || 'unknown').split(',')[0].trim();
372
412
  const ua = request.headers.get('User-Agent') || '';
373
413
 
@@ -386,7 +426,7 @@ export async function generateEdgeFingerprint(request) {
386
426
  }
387
427
 
388
428
  // ── saveEdgeFingerprint ───────────────────────────────────────────────────────
389
- export async function saveEdgeFingerprint(DB, fingerprint, userId, payload) {
429
+ export async function saveEdgeFingerprint(DB: D1Database, fingerprint: string | undefined, userId: string | undefined, payload: TrackPayload): Promise<void> {
390
430
  if (!DB || !fingerprint) return;
391
431
  const { utmSource, utmMedium, utmCampaign, utmContent, utmTerm } = payload;
392
432
  if (!utmSource) return;
@@ -404,13 +444,13 @@ export async function saveEdgeFingerprint(DB, fingerprint, userId, payload) {
404
444
  utmContent || null,
405
445
  utmTerm || null,
406
446
  ).run();
407
- } catch (err) {
408
- console.error('saveEdgeFingerprint error:', err.message);
447
+ } catch (err: any) {
448
+ console.error('saveEdgeFingerprint error:', err?.message || String(err));
409
449
  }
410
450
  }
411
451
 
412
452
  // ── resurrectUTM ──────────────────────────────────────────────────────────────
413
- export async function resurrectUTM(DB, fingerprint) {
453
+ export async function resurrectUTM(DB: D1Database, fingerprint: string | undefined): Promise<any | null> {
414
454
  if (!DB || !fingerprint) return null;
415
455
  try {
416
456
  return await DB.prepare(`
@@ -428,7 +468,7 @@ export async function resurrectUTM(DB, fingerprint) {
428
468
  }
429
469
 
430
470
  // ── upsertLtvProfile — persiste LTV no perfil ────────────────────────────────
431
- export async function upsertLtvProfile(env, userId, ltv) {
471
+ export async function upsertLtvProfile(env: Env, userId: string, ltv: LtvResult): Promise<void> {
432
472
  if (!env.DB || !userId) return;
433
473
  try {
434
474
  await env.DB.prepare(`
@@ -438,25 +478,96 @@ export async function upsertLtvProfile(env, userId, ltv) {
438
478
  updated_at = datetime('now')
439
479
  WHERE user_id = ?
440
480
  `).bind(ltv.class, ltv.value, userId).run();
441
- } catch (err) {
442
- console.error('upsertLtvProfile error:', err.message);
481
+ } catch (err: any) {
482
+ console.error('upsertLtvProfile error:', err?.message || String(err));
483
+ }
484
+ }
485
+
486
+ // ── recordLtvFeedback — fecha o ciclo preditivo com valor real de compra ─────
487
+ // Chamado em background quando um Purchase chega com payload.value > 0.
488
+ // Atualiza user_profiles + ltv_ab_assignments + ltv_ab_variations em cascata.
489
+ export async function recordLtvFeedback(env: Env, userId: string, realValue: number): Promise<void> {
490
+ if (!env.DB || !userId || !realValue || realValue <= 0) return;
491
+
492
+ try {
493
+ // 1. Busca predicted_ltv_value atual do perfil
494
+ const profile = await env.DB.prepare(`
495
+ SELECT predicted_ltv_value FROM user_profiles WHERE user_id = ?
496
+ `).bind(userId).first();
497
+
498
+ // accuracy = 1 - |pred-real|/real (0–1, mesmo padrão do A/B test accuracy_score)
499
+ const predictedValue = profile?.predicted_ltv_value;
500
+ const ltv_accuracy = (predictedValue !== null && predictedValue !== undefined)
501
+ ? Math.max(0, Math.round((1 - Math.abs(Number(predictedValue) - realValue) / Math.max(realValue, 1)) * 100) / 100)
502
+ : null;
503
+
504
+ // 2. Grava valor real + accuracy no perfil
505
+ await env.DB.prepare(`
506
+ UPDATE user_profiles
507
+ SET real_ltv_value = ?,
508
+ ltv_accuracy = ?,
509
+ ltv_feedback_at = datetime('now'),
510
+ updated_at = datetime('now')
511
+ WHERE user_id = ?
512
+ `).bind(realValue, ltv_accuracy, userId).run();
513
+
514
+ // 3. Fecha assignment do A/B test mais recente não convertido (janela 60 dias)
515
+ const assignment = await env.DB.prepare(`
516
+ SELECT id, variation_id, predicted_ltv
517
+ FROM ltv_ab_assignments
518
+ WHERE user_id = ?
519
+ AND converted = 0
520
+ AND assigned_at > datetime('now', '-60 days')
521
+ ORDER BY assigned_at DESC
522
+ LIMIT 1
523
+ `).bind(userId).first();
524
+
525
+ if (!assignment) return;
526
+
527
+ // 3a. Marca assignment como convertido
528
+ await env.DB.prepare(`
529
+ UPDATE ltv_ab_assignments
530
+ SET converted = 1,
531
+ real_revenue = ?,
532
+ converted_at = datetime('now')
533
+ WHERE id = ?
534
+ `).bind(realValue, (assignment as any).id).run();
535
+
536
+ // 3b. Atualiza métricas acumuladas da variação (running average — safe para concorrência D1)
537
+ const predLtv = (assignment as any).predicted_ltv || 0;
538
+ const indivAcc = Math.max(0, 1 - Math.abs(predLtv - realValue) / Math.max(realValue, 1));
539
+
540
+ await env.DB.prepare(`
541
+ UPDATE ltv_ab_variations
542
+ SET total_purchases = total_purchases + 1,
543
+ sum_real_revenue = sum_real_revenue + ?,
544
+ avg_real_revenue = (sum_real_revenue + ?) / (total_purchases + 1),
545
+ accuracy_score = ROUND(
546
+ (COALESCE(accuracy_score, 0) * total_purchases + ?) / (total_purchases + 1),
547
+ 4
548
+ )
549
+ WHERE id = ?
550
+ `).bind(realValue, realValue, indivAcc, (assignment as any).variation_id).run();
551
+
552
+ } catch (err: any) {
553
+ console.error('[LTV-Feedback] recordLtvFeedback error:', err?.message || String(err));
443
554
  }
444
555
  }
445
556
 
446
557
  // ── Feedback Loop — Log de falhas e métricas de saúde ────────────────────────
447
558
 
448
- export async function logApiFailure(DB, platform, eventName, errorCode, errorMessage, eventId, rawPayload) {
559
+ export async function logApiFailure(DB: D1Database, platform: string, eventName: string, errorCode: string | number, errorMessage: string, eventId: string, rawPayload: string): Promise<void> {
449
560
  try {
450
561
  await DB.prepare(`
451
562
  INSERT INTO api_failures (platform, event_name, error_code, error_message, event_id, raw_payload, retry_count, final_status)
452
563
  VALUES (?, ?, ?, ?, ?, ?, 0, 'failed')
453
564
  `).bind(platform, eventName, String(errorCode), errorMessage, eventId, rawPayload).run();
454
- } catch (err) {
455
- console.error('Failed to log API failure:', err.message);
565
+ } catch (err: any) {
566
+ console.error('Failed to log API failure:', err?.message || String(err));
456
567
  }
457
568
  }
458
569
 
459
- export async function getHealthMetrics(DB, platform, hours = 24) {
570
+ export async function getHealthMetrics(DB: D1Database, platform: string, hours: number = 24): Promise<HealthMetrics> {
460
571
  try {
461
572
  const failures = await DB.prepare(`
462
573
  SELECT COUNT(*) as count, error_code
@@ -471,30 +582,30 @@ export async function getHealthMetrics(DB, platform, hours = 24) {
471
582
  WHERE platform = ? AND created_at > datetime('now', '-${hours} hours')
472
583
  `).bind(platform).first();
473
584
 
474
- const totalFailed = failures.results?.reduce((sum, f) => sum + f.count, 0) || 0;
475
- const successRate = totalSent?.count > 0
476
- ? ((totalSent.count - totalFailed) / totalSent.count) * 100
585
+ const totalFailed = failures.results?.reduce((sum: number, f: any) => sum + f.count, 0) || 0;
586
+ const successRate = (totalSent as any)?.count > 0
587
+ ? (((totalSent as any).count - totalFailed) / (totalSent as any).count) * 100
477
588
  : 100;
478
589
 
479
590
  return {
480
591
  platform,
481
592
  hours,
482
- events_sent: totalSent?.count || 0,
593
+ events_sent: (totalSent as any)?.count || 0,
483
594
  events_failed: totalFailed,
484
595
  success_rate: successRate,
485
- errors_detected: (failures.results || []).map(f => ({ code: f.error_code, count: f.count })),
486
- issues: totalFailed > (totalSent?.count || 0) * 0.1 ? ['high_error_rate'] : [],
596
+ errors_detected: (failures.results || []).map((f: any) => ({ code: f.error_code, count: f.count })),
597
+ issues: totalFailed > ((totalSent as any)?.count || 0) * 0.1 ? ['high_error_rate'] : [],
487
598
  };
488
- } catch (err) {
489
- console.error('Failed to get health metrics:', err.message);
599
+ } catch (err: any) {
600
+ console.error('Failed to get health metrics:', err?.message || String(err));
490
601
  return { platform, hours, events_sent: 0, events_failed: 0, success_rate: 0, errors_detected: [], issues: ['metrics_unavailable'] };
491
602
  }
492
603
  }
493
604
 
494
- export async function generateDailyReport(DB) {
605
+ export async function generateDailyReport(DB: D1Database): Promise<DailyReport[]> {
495
606
  const platforms = ['meta', 'ga4', 'tiktok', 'pinterest', 'reddit'];
496
607
  const today = new Date().toISOString().split('T')[0];
497
- const reports = [];
608
+ const reports: DailyReport[] = [];
498
609
 
499
610
  for (const platform of platforms) {
500
611
  const metrics = await getHealthMetrics(DB, platform, 24);
@@ -509,8 +620,8 @@ export async function generateDailyReport(DB) {
509
620
  JSON.stringify(metrics.errors_detected), JSON.stringify(metrics.issues)
510
621
  ).run();
511
622
  reports.push({ platform, status: 'ok' });
512
- } catch (err) {
513
- console.error(`Failed to generate report for ${platform}:`, err.message);
623
+ } catch (err: any) {
624
+ console.error(`Failed to generate report for ${platform}:`, err?.message || String(err));
514
625
  reports.push({ platform, status: 'failed' });
515
626
  }
516
627
  }
@@ -518,14 +629,14 @@ export async function generateDailyReport(DB) {
518
629
  return reports;
519
630
  }
520
631
 
521
- export async function logIntelligence(DB, runType, platform, checkType, status, currentValue, expectedValue, message, alertSent = false) {
632
+ export async function logIntelligence(DB: D1Database, runType: string, platform: string, checkType: string, status: string, currentValue: any, expectedValue: any, message: string, alertSent: boolean = false): Promise<void> {
522
633
  if (!DB) return;
523
634
  try {
524
635
  await DB.prepare(`
525
636
  INSERT INTO intelligence_logs (run_type, platform, check_type, status, current_value, expected_value, message, alert_sent)
526
637
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
527
638
  `).bind(runType, platform, checkType, status, String(currentValue ?? ''), String(expectedValue ?? ''), message, alertSent ? 1 : 0).run();
528
- } catch (err) {
529
- console.error('logIntelligence error:', err.message);
639
+ } catch (err: any) {
640
+ console.error('logIntelligence error:', err?.message || String(err));
530
641
  }
531
642
  }
@@ -5,12 +5,14 @@
5
5
 
6
6
  import { normalizePhone } from '../utils.js';
7
7
  import { logApiFailure } from '../db.js';
8
+ import { Env, TrackPayload } from '../../types.js';
9
+ import { ExecutionContext } from '@cloudflare/workers-types';
8
10
 
9
- export async function sendGA4Mp(env, ga4EventName, payload, ctx) {
11
+ export async function sendGA4Mp(env: Env, ga4EventName: string, payload: TrackPayload, ctx: ExecutionContext | null): Promise<{ ok?: boolean; status?: number; skipped?: string; error?: string }> {
10
12
  if (!env.GA4_API_SECRET) return { skipped: 'GA4_API_SECRET not set' };
11
13
 
12
14
  const {
13
- clientId, sessionId,
15
+ gaClientId: clientId, sessionId,
14
16
  value, currency, contentName,
15
17
  email, phone, firstName,
16
18
  orderId,
@@ -18,13 +20,13 @@ export async function sendGA4Mp(env, ga4EventName, payload, ctx) {
18
20
 
19
21
  if (!clientId) return { skipped: 'no clientId' };
20
22
 
21
- const eventParams = {
22
- ...(value !== undefined && { value: parseFloat(value) }),
23
+ const eventParams: Record<string, string | number> = {
24
+ ...(value !== undefined && { value: parseFloat(String(value)) }),
23
25
  ...(currency && { currency: String(currency).toUpperCase() }),
24
26
  ...(contentName && { content_name: contentName }),
25
27
  ...(orderId && { transaction_id: orderId }),
26
28
  ...(email && { user_data_email_address: email.toLowerCase().trim() }),
27
- ...(phone && { user_data_phone_number: normalizePhone(phone) }),
29
+ ...(phone && { user_data_phone_number: normalizePhone(phone) || '' }),
28
30
  ...(firstName && { user_data_first_name: firstName.toLowerCase().trim() }),
29
31
  ...(sessionId && { session_id: sessionId }),
30
32
  engagement_time_msec: 100,
@@ -48,18 +50,18 @@ export async function sendGA4Mp(env, ga4EventName, payload, ctx) {
48
50
 
49
51
  if (res.status !== 204) {
50
52
  if (env.DB && ctx) {
51
- ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, String(res.status), 'GA4 returned non-204 status', null, JSON.stringify(body)));
53
+ ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, String(res.status), 'GA4 returned non-204 status', '', JSON.stringify(body)));
52
54
  }
53
55
  }
54
56
 
55
57
  return res.status === 204 ? { ok: true } : { status: res.status };
56
- } catch (err) {
57
- console.error('GA4 MP fetch failed:', err.message);
58
+ } catch (err: any) {
59
+ console.error('GA4 MP fetch failed:', err?.message || String(err));
58
60
 
59
61
  if (env.DB && ctx) {
60
- ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, 'FETCH_ERROR', err.message, null, JSON.stringify(body)));
62
+ ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, 'FETCH_ERROR', err?.message || String(err), '', JSON.stringify(body)));
61
63
  }
62
64
 
63
- return { error: err.message };
65
+ return { error: err?.message || String(err) };
64
66
  }
65
67
  }