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
@@ -4,31 +4,32 @@
4
4
  */
5
5
 
6
6
  import { sha256, tryParseJson } from '../utils.js';
7
-
8
- // ── Listas de detecção ────────────────────────────────────────────────────────
9
- export const DISPOSABLE_EMAIL_DOMAINS = new Set([
10
- 'mailinator.com','guerrillamail.com','tempmail.com','throwaway.email',
11
- 'yopmail.com','sharklasers.com','guerrillamailblock.com','spam4.me',
12
- '10minutemail.com','trashmail.com','maildrop.cc','fakeinbox.com',
13
- 'dispostable.com','getairmail.com','mailnull.com',
14
- ]);
7
+ import { Env, TrackPayload } from '../../types.js';
8
+
9
+ // ── Tipos ───────────────────────────────────────────────────────────────────────
10
+ export interface FraudResult {
11
+ allowed: boolean;
12
+ score: number;
13
+ reasons: string[];
14
+ action: 'allowed' | 'flagged' | 'dropped';
15
+ }
15
16
 
16
17
  export const DATACENTER_PATTERNS = /amazon|google|microsoft|digitalocean|linode|ovh|vultr|hetzner|contabo|cloudflare|packet|rackspace|leaseweb/i;
17
18
 
18
19
  // ── checkFraudGate — roda ANTES de qualquer processamento de evento ────────────
19
20
  // Retorna { allowed, score, reasons, action }
20
21
  // Falhas no gate = fail-safe (deixa passar)
21
- export async function checkFraudGate(env, request, payload) {
22
- const result = { allowed: true, score: 0, reasons: [], action: 'allowed' };
22
+ export async function checkFraudGate(env: Env, request: Request, payload: TrackPayload): Promise<FraudResult> {
23
+ const result: FraudResult = { allowed: true, score: 0, reasons: [], action: 'allowed' };
23
24
 
24
25
  try {
25
26
  const ip = request.headers.get('CF-Connecting-IP') || '';
26
27
  const ua = request.headers.get('User-Agent') || '';
27
- const fingerprint = payload.fingerprint || '';
28
+ const fingerprint = (payload as any).fingerprint || '';
28
29
  const email = payload.email || '';
29
- const botScore = parseInt(payload.botScore || payload.bot_score || 0);
30
- const asn = String(request.cf?.asOrganization || '').toLowerCase();
31
- const country = (request.cf?.country || '').toUpperCase();
30
+ const botScore = parseInt(String(payload.botScore || (payload as any).bot_score || 0));
31
+ const asn = String((request as any).cf?.asOrganization || '').toLowerCase();
32
+ const country = ((request as any).cf?.country || '').toUpperCase();
32
33
  const acceptLang = request.headers.get('Accept-Language');
33
34
 
34
35
  // 1. KV blocklist check — instantâneo (~0ms)
@@ -64,15 +65,7 @@ export async function checkFraudGate(env, request, payload) {
64
65
  result.score += 20; result.reasons.push('no_accept_language');
65
66
  }
66
67
 
67
- // 6. Email descartável
68
- if (email) {
69
- const domain = email.split('@')[1]?.toLowerCase();
70
- if (domain && DISPOSABLE_EMAIL_DOMAINS.has(domain)) {
71
- result.score += 25; result.reasons.push('disposable_email');
72
- }
73
- }
74
-
75
- // 7. Velocity check via KV
68
+ // 6. Velocity check via KV
76
69
  if (env.GEO_CACHE && ip) {
77
70
  const velKey1h = `fraud_velocity:${ip}:h`;
78
71
  const velStr = await env.GEO_CACHE.get(velKey1h);
@@ -96,22 +89,22 @@ export async function checkFraudGate(env, request, payload) {
96
89
 
97
90
  return result;
98
91
 
99
- } catch (err) {
100
- console.error('[Fraud] checkFraudGate error:', err.message);
92
+ } catch (err: any) {
93
+ console.error('[Fraud] checkFraudGate error:', err?.message || String(err));
101
94
  return { allowed: true, score: 0, reasons: ['gate_error_fallback'], action: 'allowed' };
102
95
  }
103
96
  }
104
97
 
105
98
  // ── logFraudSignal — persiste no D1 em background ────────────────────────────
106
- export async function logFraudSignal(env, request, payload, fraudResult) {
99
+ export async function logFraudSignal(env: Env, request: Request, payload: TrackPayload, fraudResult: FraudResult): Promise<void> {
107
100
  if (!env.DB || fraudResult.action === 'allowed') return;
108
101
  try {
109
102
  const ip = request.headers.get('CF-Connecting-IP') || '';
110
103
  const ua = request.headers.get('User-Agent') || '';
111
- const fingerprint = payload.fingerprint || '';
112
- const botScore = parseInt(payload.botScore || payload.bot_score || 0);
113
- const asn = String(request.cf?.asOrganization || '');
114
- const country = (request.cf?.country || '');
104
+ const fingerprint = (payload as any).fingerprint || '';
105
+ const botScore = parseInt(String(payload.botScore || (payload as any).bot_score || 0));
106
+ const asn = String((request as any).cf?.asOrganization || '');
107
+ const country = (request as any).cf?.country || '';
115
108
  const velKey1h = `fraud_velocity:${ip}:h`;
116
109
  const vel1h = env.GEO_CACHE ? parseInt(await env.GEO_CACHE.get(velKey1h) || '0') : 0;
117
110
 
@@ -145,13 +138,13 @@ export async function logFraudSignal(env, request, payload, fraudResult) {
145
138
  updated_at = datetime('now')
146
139
  `).bind(ip, fraudResult.score, JSON.stringify(fraudResult.reasons)).run().catch(() => {});
147
140
  }
148
- } catch (err) {
149
- console.error('[Fraud] logFraudSignal error:', err.message);
141
+ } catch (err: any) {
142
+ console.error('[Fraud] logFraudSignal error:', err?.message || String(err));
150
143
  }
151
144
  }
152
145
 
153
146
  // ── GET /api/fraud/alerts ─────────────────────────────────────────────────────
154
- export async function handleFraudAlerts(env, request, headers) {
147
+ export async function handleFraudAlerts(env: Env, request: Request, headers: Headers): Promise<Response> {
155
148
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
156
149
 
157
150
  const url = new URL(request.url);
@@ -173,18 +166,18 @@ export async function handleFraudAlerts(env, request, headers) {
173
166
  LIMIT ?
174
167
  `).bind(...bindings).all();
175
168
 
176
- const signals = (result.results || []).map(s => ({ ...s, reasons: tryParseJson(s.reasons, []) }));
169
+ const signals = (result.results || []).map((s: any) => ({ ...s, reasons: tryParseJson(s.reasons, []) }));
177
170
  const stats = await env.DB.prepare(`SELECT * FROM v_fraud_dashboard`).first().catch(() => null);
178
171
 
179
172
  return new Response(JSON.stringify({ success: true, period_hours: hours, total: signals.length, stats, alerts: signals }), { status: 200, headers });
180
- } catch (err) {
181
- console.error('[Fraud] alerts error:', err.message);
182
- return new Response(JSON.stringify({ error: err.message }), { status: 500, headers });
173
+ } catch (err: any) {
174
+ console.error('[Fraud] alerts error:', err?.message || String(err));
175
+ return new Response(JSON.stringify({ error: err?.message || String(err) }), { status: 500, headers });
183
176
  }
184
177
  }
185
178
 
186
179
  // ── GET /api/fraud/blocklist ──────────────────────────────────────────────────
187
- export async function handleFraudBlocklist(env, request, headers) {
180
+ export async function handleFraudBlocklist(env: Env, request: Request, headers: Headers): Promise<Response> {
188
181
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
189
182
 
190
183
  try {
@@ -194,19 +187,19 @@ export async function handleFraudBlocklist(env, request, headers) {
194
187
  FROM fraud_alerts WHERE is_blocked = 1 ORDER BY events_dropped DESC LIMIT 100
195
188
  `).all();
196
189
 
197
- const blocklist = (result.results || []).map(r => ({ ...r, top_reasons: tryParseJson(r.top_reasons, []) }));
190
+ const blocklist = (result.results || []).map((r: any) => ({ ...r, top_reasons: tryParseJson(r.top_reasons, []) }));
198
191
  return new Response(JSON.stringify({ success: true, total: blocklist.length, blocklist }), { status: 200, headers });
199
- } catch (err) {
200
- console.error('[Fraud] blocklist error:', err.message);
201
- return new Response(JSON.stringify({ error: err.message }), { status: 500, headers });
192
+ } catch (err: any) {
193
+ console.error('[Fraud] blocklist error:', err?.message || String(err));
194
+ return new Response(JSON.stringify({ error: err?.message || String(err) }), { status: 500, headers });
202
195
  }
203
196
  }
204
197
 
205
198
  // ── POST /api/fraud/blocklist/add ─────────────────────────────────────────────
206
- export async function handleFraudBlocklistAdd(env, request, headers) {
199
+ export async function handleFraudBlocklistAdd(env: Env, request: Request, headers: Headers): Promise<Response> {
207
200
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
208
201
 
209
- let body;
202
+ let body: any;
210
203
  try { body = await request.json(); }
211
204
  catch { return new Response(JSON.stringify({ error: 'JSON inválido' }), { status: 400, headers }); }
212
205
 
@@ -237,17 +230,17 @@ export async function handleFraudBlocklistAdd(env, request, headers) {
237
230
  success: true, entity_type, entity_value, kv_key: kvKey, ttl_hours, expires_at: expiresAt,
238
231
  message: `${entity_type} '${entity_value}' bloqueado por ${ttl_hours}h. Efeito imediato via KV.`,
239
232
  }), { status: 200, headers });
240
- } catch (err) {
241
- console.error('[Fraud] blocklist add error:', err.message);
242
- return new Response(JSON.stringify({ error: err.message }), { status: 500, headers });
233
+ } catch (err: any) {
234
+ console.error('[Fraud] blocklist add error:', err?.message || String(err));
235
+ return new Response(JSON.stringify({ error: err?.message || String(err) }), { status: 500, headers });
243
236
  }
244
237
  }
245
238
 
246
239
  // ── DELETE /api/fraud/blocklist/remove ───────────────────────────────────────
247
- export async function handleFraudBlocklistRemove(env, request, headers) {
240
+ export async function handleFraudBlocklistRemove(env: Env, request: Request, headers: Headers): Promise<Response> {
248
241
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
249
242
 
250
- let body;
243
+ let body: any;
251
244
  try { body = await request.json(); }
252
245
  catch { return new Response(JSON.stringify({ error: 'JSON inválido' }), { status: 400, headers }); }
253
246
 
@@ -265,14 +258,14 @@ export async function handleFraudBlocklistRemove(env, request, headers) {
265
258
  success: true, entity_type, entity_value,
266
259
  message: `${entity_type} '${entity_value}' removido do blocklist. Efeito imediato via KV.`,
267
260
  }), { status: 200, headers });
268
- } catch (err) {
269
- console.error('[Fraud] blocklist remove error:', err.message);
270
- return new Response(JSON.stringify({ error: err.message }), { status: 500, headers });
261
+ } catch (err: any) {
262
+ console.error('[Fraud] blocklist remove error:', err?.message || String(err));
263
+ return new Response(JSON.stringify({ error: err?.message || String(err) }), { status: 500, headers });
271
264
  }
272
265
  }
273
266
 
274
267
  // ── GET /api/fraud/stats ──────────────────────────────────────────────────────
275
- export async function handleFraudStats(env, request, headers) {
268
+ export async function handleFraudStats(env: Env, request: Request, headers: Headers): Promise<Response> {
276
269
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
277
270
 
278
271
  try {
@@ -294,8 +287,8 @@ export async function handleFraudStats(env, request, headers) {
294
287
  top_attacking_ips: topIps.results || [],
295
288
  by_action: topReasons.results || [],
296
289
  }), { status: 200, headers });
297
- } catch (err) {
298
- console.error('[Fraud] stats error:', err.message);
299
- return new Response(JSON.stringify({ error: err.message }), { status: 500, headers });
290
+ } catch (err: any) {
291
+ console.error('[Fraud] stats error:', err?.message || String(err));
292
+ return new Response(JSON.stringify({ error: err?.message || String(err) }), { status: 500, headers });
300
293
  }
301
294
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CDP Edge — Logistic Regression (pure JS, sem deps externas)
2
+ * CDP Edge — Logistic Regression (pure TS, sem deps externas)
3
3
  * Treina modelo de predição de conversão com dados reais do D1.
4
4
  *
5
5
  * Features usadas (todas normalizadas 0-1):
@@ -7,9 +7,40 @@
7
7
  * has_email, has_phone, is_br, hour_normalized
8
8
  */
9
9
 
10
+ import { Env } from '../../types.js';
11
+ import { D1Database, KVNamespace } from '@cloudflare/workers-types';
12
+
13
+ // ── Tipos ───────────────────────────────────────────────────────────────────────
14
+ export interface DatasetRow {
15
+ features: number[];
16
+ label: number;
17
+ }
18
+
19
+ export interface LogisticModel {
20
+ bias: number;
21
+ weights: number[];
22
+ accuracy: number;
23
+ positiveRate: number;
24
+ sampleSize: number;
25
+ threshold: number;
26
+ featureNames: string[];
27
+ trainedAt: string;
28
+ }
29
+
30
+ export interface ExtractedFeatures {
31
+ utm_score: number;
32
+ engagement: number;
33
+ intention: number;
34
+ recency: number;
35
+ has_email: number;
36
+ has_phone: number;
37
+ is_br: number;
38
+ hour: number;
39
+ }
40
+
10
41
  // ── Feature Engineering ───────────────────────────────────────────────────────
11
42
 
12
- const UTM_SCORES = {
43
+ const UTM_SCORES: Record<string, number> = {
13
44
  facebook: 0.90, instagram: 0.90, meta: 0.90,
14
45
  google: 0.82, youtube: 0.82,
15
46
  tiktok: 0.75,
@@ -18,14 +49,14 @@ const UTM_SCORES = {
18
49
  direct: 0.20,
19
50
  };
20
51
 
21
- const INTENTION_SCORES = {
52
+ const INTENTION_SCORES: Record<string, number> = {
22
53
  comprador: 1.00, high_intent: 1.00,
23
54
  interessado: 0.60,
24
55
  nurture: 0.30,
25
56
  curioso: 0.15,
26
57
  };
27
58
 
28
- export function extractFeatures(row) {
59
+ export function extractFeatures(row: any): number[] {
29
60
  const src = (row.utm_source || '').toLowerCase().trim();
30
61
  const intention = (row.intention_level || '').toLowerCase().trim();
31
62
  const daysSince = row.days_since_lead || 0;
@@ -44,13 +75,13 @@ export function extractFeatures(row) {
44
75
 
45
76
  // ── Sigmoid ───────────────────────────────────────────────────────────────────
46
77
 
47
- function sigmoid(z) {
78
+ function sigmoid(z: number): number {
48
79
  if (z > 20) return 1;
49
80
  if (z < -20) return 0;
50
81
  return 1 / (1 + Math.exp(-z));
51
82
  }
52
83
 
53
- function dot(weights, features) {
84
+ function dot(weights: number[], features: number[]): number {
54
85
  return features.reduce((sum, f, i) => sum + (weights[i] || 0) * f, 0);
55
86
  }
56
87
 
@@ -58,11 +89,8 @@ function dot(weights, features) {
58
89
 
59
90
  /**
60
91
  * Treina regressão logística com gradiente descendente.
61
- * @param {Array<{features: number[], label: number}>} dataset
62
- * @param {{ iterations?, learningRate?, lambda? }} opts
63
- * @returns {{ bias, weights, accuracy, positiveRate }}
64
92
  */
65
- export function trainLogisticRegression(dataset, opts = {}) {
93
+ export function trainLogisticRegression(dataset: DatasetRow[], opts: { iterations?: number; learningRate?: number; lambda?: number } = {}): LogisticModel | null {
66
94
  if (!dataset || dataset.length < 50) {
67
95
  return null; // dados insuficientes
68
96
  }
@@ -132,11 +160,8 @@ export function trainLogisticRegression(dataset, opts = {}) {
132
160
 
133
161
  /**
134
162
  * Prediz score de conversão (0-100) usando pesos treinados.
135
- * @param {{ bias, weights, threshold }} model
136
- * @param {number[]} features
137
- * @returns {number} score 0-100
138
163
  */
139
- export function predictWithWeights(model, features) {
164
+ export function predictWithWeights(model: LogisticModel, features: number[]): number {
140
165
  const z = dot(model.weights, features) + model.bias;
141
166
  const prob = sigmoid(z);
142
167
  return Math.round(prob * 100);
@@ -146,11 +171,11 @@ export function predictWithWeights(model, features) {
146
171
 
147
172
  export const LTV_WEIGHTS_KV_KEY = 'ltv_weights_active';
148
173
 
149
- export async function loadActiveWeights(env) {
174
+ export async function loadActiveWeights(env: Env): Promise<LogisticModel | null> {
150
175
  // 1. Tentar KV (cache ~7 dias)
151
176
  if (env.GEO_CACHE) {
152
177
  try {
153
- const cached = await env.GEO_CACHE.get(LTV_WEIGHTS_KV_KEY, 'json');
178
+ const cached = await env.GEO_CACHE.get(LTV_WEIGHTS_KV_KEY, 'json') as LogisticModel | null;
154
179
  if (cached?.weights?.length) return cached;
155
180
  } catch {}
156
181
  }
@@ -161,8 +186,8 @@ export async function loadActiveWeights(env) {
161
186
  const row = await env.DB.prepare(
162
187
  `SELECT weights_json FROM ltv_model_weights WHERE is_active = 1 ORDER BY trained_at DESC LIMIT 1`
163
188
  ).first();
164
- if (!row?.weights_json) return null;
165
- const model = JSON.parse(row.weights_json);
189
+ if (!row || !(row as any).weights_json) return null;
190
+ const model = JSON.parse((row as any).weights_json) as LogisticModel;
166
191
 
167
192
  // Popular KV para próximas requests
168
193
  if (env.GEO_CACHE && model?.weights?.length) {
@@ -174,7 +199,7 @@ export async function loadActiveWeights(env) {
174
199
  }
175
200
  }
176
201
 
177
- export async function saveWeights(DB, model) {
202
+ export async function saveWeights(DB: D1Database, model: LogisticModel): Promise<void> {
178
203
  if (!DB || !model) return;
179
204
  const now = new Date().toISOString();
180
205