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
@@ -4,24 +4,32 @@
4
4
  */
5
5
 
6
6
  import { sha256, tryParseJson } from '../utils.js';
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
+ }
7
16
 
8
- // ── Listas de detecção ────────────────────────────────────────────────────────
9
17
  export const DATACENTER_PATTERNS = /amazon|google|microsoft|digitalocean|linode|ovh|vultr|hetzner|contabo|cloudflare|packet|rackspace|leaseweb/i;
10
18
 
11
19
  // ── checkFraudGate — roda ANTES de qualquer processamento de evento ────────────
12
20
  // Retorna { allowed, score, reasons, action }
13
21
  // Falhas no gate = fail-safe (deixa passar)
14
- export async function checkFraudGate(env, request, payload) {
15
- 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' };
16
24
 
17
25
  try {
18
26
  const ip = request.headers.get('CF-Connecting-IP') || '';
19
27
  const ua = request.headers.get('User-Agent') || '';
20
- const fingerprint = payload.fingerprint || '';
28
+ const fingerprint = (payload as any).fingerprint || '';
21
29
  const email = payload.email || '';
22
- const botScore = parseInt(payload.botScore || payload.bot_score || 0);
23
- const asn = String(request.cf?.asOrganization || '').toLowerCase();
24
- 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();
25
33
  const acceptLang = request.headers.get('Accept-Language');
26
34
 
27
35
  // 1. KV blocklist check — instantâneo (~0ms)
@@ -81,22 +89,22 @@ export async function checkFraudGate(env, request, payload) {
81
89
 
82
90
  return result;
83
91
 
84
- } catch (err) {
85
- console.error('[Fraud] checkFraudGate error:', err.message);
92
+ } catch (err: any) {
93
+ console.error('[Fraud] checkFraudGate error:', err?.message || String(err));
86
94
  return { allowed: true, score: 0, reasons: ['gate_error_fallback'], action: 'allowed' };
87
95
  }
88
96
  }
89
97
 
90
98
  // ── logFraudSignal — persiste no D1 em background ────────────────────────────
91
- export async function logFraudSignal(env, request, payload, fraudResult) {
99
+ export async function logFraudSignal(env: Env, request: Request, payload: TrackPayload, fraudResult: FraudResult): Promise<void> {
92
100
  if (!env.DB || fraudResult.action === 'allowed') return;
93
101
  try {
94
102
  const ip = request.headers.get('CF-Connecting-IP') || '';
95
103
  const ua = request.headers.get('User-Agent') || '';
96
- const fingerprint = payload.fingerprint || '';
97
- const botScore = parseInt(payload.botScore || payload.bot_score || 0);
98
- const asn = String(request.cf?.asOrganization || '');
99
- 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 || '';
100
108
  const velKey1h = `fraud_velocity:${ip}:h`;
101
109
  const vel1h = env.GEO_CACHE ? parseInt(await env.GEO_CACHE.get(velKey1h) || '0') : 0;
102
110
 
@@ -130,13 +138,13 @@ export async function logFraudSignal(env, request, payload, fraudResult) {
130
138
  updated_at = datetime('now')
131
139
  `).bind(ip, fraudResult.score, JSON.stringify(fraudResult.reasons)).run().catch(() => {});
132
140
  }
133
- } catch (err) {
134
- console.error('[Fraud] logFraudSignal error:', err.message);
141
+ } catch (err: any) {
142
+ console.error('[Fraud] logFraudSignal error:', err?.message || String(err));
135
143
  }
136
144
  }
137
145
 
138
146
  // ── GET /api/fraud/alerts ─────────────────────────────────────────────────────
139
- export async function handleFraudAlerts(env, request, headers) {
147
+ export async function handleFraudAlerts(env: Env, request: Request, headers: Headers): Promise<Response> {
140
148
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
141
149
 
142
150
  const url = new URL(request.url);
@@ -158,18 +166,18 @@ export async function handleFraudAlerts(env, request, headers) {
158
166
  LIMIT ?
159
167
  `).bind(...bindings).all();
160
168
 
161
- const signals = (result.results || []).map(s => ({ ...s, reasons: tryParseJson(s.reasons, []) }));
169
+ const signals = (result.results || []).map((s: any) => ({ ...s, reasons: tryParseJson(s.reasons, []) }));
162
170
  const stats = await env.DB.prepare(`SELECT * FROM v_fraud_dashboard`).first().catch(() => null);
163
171
 
164
172
  return new Response(JSON.stringify({ success: true, period_hours: hours, total: signals.length, stats, alerts: signals }), { status: 200, headers });
165
- } catch (err) {
166
- console.error('[Fraud] alerts error:', err.message);
167
- 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 });
168
176
  }
169
177
  }
170
178
 
171
179
  // ── GET /api/fraud/blocklist ──────────────────────────────────────────────────
172
- export async function handleFraudBlocklist(env, request, headers) {
180
+ export async function handleFraudBlocklist(env: Env, request: Request, headers: Headers): Promise<Response> {
173
181
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
174
182
 
175
183
  try {
@@ -179,19 +187,19 @@ export async function handleFraudBlocklist(env, request, headers) {
179
187
  FROM fraud_alerts WHERE is_blocked = 1 ORDER BY events_dropped DESC LIMIT 100
180
188
  `).all();
181
189
 
182
- 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, []) }));
183
191
  return new Response(JSON.stringify({ success: true, total: blocklist.length, blocklist }), { status: 200, headers });
184
- } catch (err) {
185
- console.error('[Fraud] blocklist error:', err.message);
186
- 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 });
187
195
  }
188
196
  }
189
197
 
190
198
  // ── POST /api/fraud/blocklist/add ─────────────────────────────────────────────
191
- export async function handleFraudBlocklistAdd(env, request, headers) {
199
+ export async function handleFraudBlocklistAdd(env: Env, request: Request, headers: Headers): Promise<Response> {
192
200
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
193
201
 
194
- let body;
202
+ let body: any;
195
203
  try { body = await request.json(); }
196
204
  catch { return new Response(JSON.stringify({ error: 'JSON inválido' }), { status: 400, headers }); }
197
205
 
@@ -222,17 +230,17 @@ export async function handleFraudBlocklistAdd(env, request, headers) {
222
230
  success: true, entity_type, entity_value, kv_key: kvKey, ttl_hours, expires_at: expiresAt,
223
231
  message: `${entity_type} '${entity_value}' bloqueado por ${ttl_hours}h. Efeito imediato via KV.`,
224
232
  }), { status: 200, headers });
225
- } catch (err) {
226
- console.error('[Fraud] blocklist add error:', err.message);
227
- 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 });
228
236
  }
229
237
  }
230
238
 
231
239
  // ── DELETE /api/fraud/blocklist/remove ───────────────────────────────────────
232
- export async function handleFraudBlocklistRemove(env, request, headers) {
240
+ export async function handleFraudBlocklistRemove(env: Env, request: Request, headers: Headers): Promise<Response> {
233
241
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
234
242
 
235
- let body;
243
+ let body: any;
236
244
  try { body = await request.json(); }
237
245
  catch { return new Response(JSON.stringify({ error: 'JSON inválido' }), { status: 400, headers }); }
238
246
 
@@ -250,14 +258,14 @@ export async function handleFraudBlocklistRemove(env, request, headers) {
250
258
  success: true, entity_type, entity_value,
251
259
  message: `${entity_type} '${entity_value}' removido do blocklist. Efeito imediato via KV.`,
252
260
  }), { status: 200, headers });
253
- } catch (err) {
254
- console.error('[Fraud] blocklist remove error:', err.message);
255
- 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 });
256
264
  }
257
265
  }
258
266
 
259
267
  // ── GET /api/fraud/stats ──────────────────────────────────────────────────────
260
- export async function handleFraudStats(env, request, headers) {
268
+ export async function handleFraudStats(env: Env, request: Request, headers: Headers): Promise<Response> {
261
269
  if (!env.DB) return new Response(JSON.stringify({ error: 'DB não configurado' }), { status: 503, headers });
262
270
 
263
271
  try {
@@ -279,8 +287,8 @@ export async function handleFraudStats(env, request, headers) {
279
287
  top_attacking_ips: topIps.results || [],
280
288
  by_action: topReasons.results || [],
281
289
  }), { status: 200, headers });
282
- } catch (err) {
283
- console.error('[Fraud] stats error:', err.message);
284
- 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 });
285
293
  }
286
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