cdp-edge 1.24.2 → 1.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -1
- package/contracts/agent-versions.json +14 -0
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +71 -14
- package/extracted-skill/tracking-events-generator/agents/utm-agent.md +191 -0
- package/package.json +1 -1
- package/server-edge-tracker/.client.env.example +14 -0
- package/server-edge-tracker/config/utm-mapping.json +64 -0
- package/server-edge-tracker/deploy-client.js +76 -0
- package/server-edge-tracker/index.ts +163 -72
- package/server-edge-tracker/modules/db.ts +63 -3
- package/server-edge-tracker/modules/ml/fraud.ts +9 -1
- package/server-edge-tracker/modules/ml/logistic.ts +7 -1
- package/server-edge-tracker/modules/ml/ltv.ts +20 -5
- package/server-edge-tracker/modules/ml/matchquality.ts +14 -2
- package/server-edge-tracker/modules/utils.ts +123 -0
- package/server-edge-tracker/modules/utm/utm-enricher.ts +231 -0
- package/server-edge-tracker/schema-utm.sql +80 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UTM Enricher Module
|
|
3
|
+
* Obfusca/desobfusca UTMs sensíveis (valores de produto)
|
|
4
|
+
* Integração com Agente UTM
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Constants & Config
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
const UTM_SALT = 'CDP_EDGE_UTM_SALT';
|
|
12
|
+
const HASH_TRUNCATE_LENGTH = 8;
|
|
13
|
+
|
|
14
|
+
// Obfuscação: SHA256(original + salt) → truncate(8)
|
|
15
|
+
// Isso garante: mesmo valor → mesmo hash, mas irreversível sem o mapeamento
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Types
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
export interface UTMMapping {
|
|
22
|
+
obfuscated: string; // Hash truncado (ex: "8a3f1d2b")
|
|
23
|
+
original: string; // Valor real (ex: "700k-1M")
|
|
24
|
+
category: string; // "imovel", "automotivo", etc
|
|
25
|
+
pixel_audience?: string; // ID da custom audience Meta
|
|
26
|
+
platform_specific?: {
|
|
27
|
+
meta?: { custom_audience_id?: string };
|
|
28
|
+
tiktok?: { pixel_id?: string };
|
|
29
|
+
ga4?: { event_parameter?: string };
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface UTMMappingConfig {
|
|
34
|
+
method: 'sha256';
|
|
35
|
+
salt: string;
|
|
36
|
+
truncated_length: number;
|
|
37
|
+
mappings: UTMMapping[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface EnrichedUTM {
|
|
41
|
+
source?: string;
|
|
42
|
+
medium?: string;
|
|
43
|
+
campaign?: string;
|
|
44
|
+
content?: string;
|
|
45
|
+
faixa_obfuscada?: string; // Hash da faixa de valor
|
|
46
|
+
faixa_real?: string; // Valor real (de-obfuscado)
|
|
47
|
+
faixa_category?: string; // Categoria do produto
|
|
48
|
+
product_id_obfuscated?: string;
|
|
49
|
+
product_id_real?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Core Functions
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Obfusca um valor sensível usando SHA256 + truncate
|
|
58
|
+
* @param value - Valor a ser obfuscado (ex: "700k-1M")
|
|
59
|
+
* @returns Hash truncado de 8 caracteres
|
|
60
|
+
*/
|
|
61
|
+
export function obfuscateValue(value: string): string {
|
|
62
|
+
// sha256(value + salt) → truncate(8)
|
|
63
|
+
const hash = sha256(`${value}${UTM_SALT}`);
|
|
64
|
+
return hash.substring(0, HASH_TRUNCATE_LENGTH);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Verifica se um hash é válido (8 chars hex)
|
|
69
|
+
*/
|
|
70
|
+
export function isValidObfuscatedHash(hash: string): boolean {
|
|
71
|
+
return /^[a-f0-9]{8}$/.test(hash);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Desobfusca um valor usando o mapeamento
|
|
76
|
+
* @param obfuscated - Hash obfuscado
|
|
77
|
+
* @param mappings - Mapeamento de UTM (do config/utm-mapping.json)
|
|
78
|
+
* @returns UTM com valor real ou undefined se não encontrado
|
|
79
|
+
*/
|
|
80
|
+
export function deobfuscateValue(
|
|
81
|
+
obfuscated: string,
|
|
82
|
+
mappings: UTMMapping[]
|
|
83
|
+
): UTMMapping | undefined {
|
|
84
|
+
return mappings.find(m => m.obfuscated === obfuscated);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Enrich payload com UTMs, desobfuscando valores sensíveis
|
|
89
|
+
*/
|
|
90
|
+
export function enrichPayloadWithUTM(
|
|
91
|
+
payload: any,
|
|
92
|
+
utms: Record<string, string>,
|
|
93
|
+
mappings: UTMMapping[]
|
|
94
|
+
): { enriched: any; faixa?: UTMMapping } {
|
|
95
|
+
const enriched = { ...payload };
|
|
96
|
+
let faixa: UTMMapping | undefined;
|
|
97
|
+
|
|
98
|
+
// Desobfuscar faixa de valor
|
|
99
|
+
if (utms.faixa_obfuscada || utms.utm_faixa) {
|
|
100
|
+
const faixaHash = utms.faixa_obfuscada || utms.utm_faixa;
|
|
101
|
+
if (isValidObfuscatedHash(faixaHash)) {
|
|
102
|
+
faixa = deobfuscateValue(faixaHash, mappings);
|
|
103
|
+
if (faixa) {
|
|
104
|
+
enriched.faixa_real = faixa.original;
|
|
105
|
+
enriched.faixa_category = faixa.category;
|
|
106
|
+
enriched.pixel_audience = faixa.pixel_audience;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Extrair UTMs padrão
|
|
112
|
+
enriched.utm_source = utms.utm_source || utms.source;
|
|
113
|
+
enriched.utm_medium = utms.utm_medium || utms.medium;
|
|
114
|
+
enriched.utm_campaign = utms.utm_campaign || utms.campaign;
|
|
115
|
+
enriched.utm_content = utms.utm_content || utms.content;
|
|
116
|
+
|
|
117
|
+
return { enriched, faixa };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Gera UTM obfuscada para uma faixa de valor
|
|
122
|
+
* @param range - Faixa real (ex: "700k-1M")
|
|
123
|
+
* @param category - Categoria (ex: "imovel")
|
|
124
|
+
* @returns Object com hash obfuscado
|
|
125
|
+
*/
|
|
126
|
+
export function generateObfuscatedUTM(range: string, category: string) {
|
|
127
|
+
const obfuscated = obfuscateValue(range);
|
|
128
|
+
return {
|
|
129
|
+
utm_faixa: obfuscated,
|
|
130
|
+
utm_campaign: `${category}_${obfuscated}`,
|
|
131
|
+
original_range: range,
|
|
132
|
+
hash: obfuscated
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Cria um novo mapeamento de UTM
|
|
138
|
+
*/
|
|
139
|
+
export function createUTMMapping(
|
|
140
|
+
original: string,
|
|
141
|
+
category: string,
|
|
142
|
+
platform_specific?: any
|
|
143
|
+
): UTMMapping {
|
|
144
|
+
return {
|
|
145
|
+
obfuscated: obfuscateValue(original),
|
|
146
|
+
original,
|
|
147
|
+
category,
|
|
148
|
+
platform_specific
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Integration Functions (para uso no Worker)
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Verifica se payload tem UTMs de segmentação
|
|
158
|
+
*/
|
|
159
|
+
export function hasSegmentationUTM(utms: Record<string, string>): boolean {
|
|
160
|
+
return !!(
|
|
161
|
+
utms.faixa_obfuscada ||
|
|
162
|
+
utms.utm_faixa ||
|
|
163
|
+
(utms.utm_campaign && isValidObfuscatedHash(
|
|
164
|
+
utms.utm_campaign.split('_').pop() || ''
|
|
165
|
+
))
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Extrai faixa de valor do utm_campaign (pattern: category_hash)
|
|
171
|
+
*/
|
|
172
|
+
export function extractFaixaFromCampaign(
|
|
173
|
+
campaign: string
|
|
174
|
+
): string | null {
|
|
175
|
+
const parts = campaign.split('_');
|
|
176
|
+
const hash = parts.pop();
|
|
177
|
+
if (hash && isValidObfuscatedHash(hash)) {
|
|
178
|
+
return hash;
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Para Meta CAPI: adiciona segmentação ao external_id
|
|
185
|
+
*/
|
|
186
|
+
export function addSegmentationToExternalId(
|
|
187
|
+
cdp_uid: string,
|
|
188
|
+
faixa: UTMMapping
|
|
189
|
+
): string {
|
|
190
|
+
return `${cdp_uid}_${faixa.obfuscated}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Para GA4: cria custom parameter segmentado
|
|
195
|
+
*/
|
|
196
|
+
export function createSegmentationCustomParameter(faixa: UTMMapping) {
|
|
197
|
+
return {
|
|
198
|
+
'custom_faixa_categoria': faixa.category,
|
|
199
|
+
'custom_faixa_obfuscada': faixa.obfuscated,
|
|
200
|
+
'custom_faixa_audience': faixa.pixel_audience || 'UNKNOWN'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// Import de sha256 (reutilizar de utils.ts)
|
|
206
|
+
// ============================================================================
|
|
207
|
+
|
|
208
|
+
function sha256(message: string): string {
|
|
209
|
+
// Importado de utils.ts - implementação real do SHA256
|
|
210
|
+
// Aqui simulamos para o exemplo:
|
|
211
|
+
const crypto = require('crypto');
|
|
212
|
+
return crypto.createHash('sha256').update(message).digest('hex');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// Export
|
|
217
|
+
// ============================================================================
|
|
218
|
+
|
|
219
|
+
export const UTM_ENRICHER_VERSION = '1.0.0';
|
|
220
|
+
|
|
221
|
+
export default {
|
|
222
|
+
obfuscateValue,
|
|
223
|
+
deobfuscateValue,
|
|
224
|
+
enrichPayloadWithUTM,
|
|
225
|
+
generateObfuscatedUTM,
|
|
226
|
+
createUTMMapping,
|
|
227
|
+
hasSegmentationUTM,
|
|
228
|
+
extractFaixaFromCampaign,
|
|
229
|
+
addSegmentationToExternalId,
|
|
230
|
+
createSegmentationCustomParameter
|
|
231
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- SCHEMA UTM OBFUSCATION — Fase de Segmentação de Valor
|
|
3
|
+
-- ============================================================================
|
|
4
|
+
-- Este schema estende o schema.sql existente com colunas para UTMs obfuscadas
|
|
5
|
+
-- e uma tabela de mapeamento para de-obfuscação no runtime.
|
|
6
|
+
--
|
|
7
|
+
-- Como funciona:
|
|
8
|
+
-- 1. utm_mappings: mapeia hash obfuscado → valor real (configuração)
|
|
9
|
+
-- 2. leads: adiciona colunas faixa_real, faixa_category (segmentação)
|
|
10
|
+
-- 3. dispatch: payload enriquecido com faixa de-obfuscada para Meta CAPI
|
|
11
|
+
--
|
|
12
|
+
-- ============================================================================
|
|
13
|
+
-- TABELA: utm_mappings (configuração de segmentação)
|
|
14
|
+
-- ============================================================================
|
|
15
|
+
CREATE TABLE IF NOT EXISTS utm_mappings (
|
|
16
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17
|
+
obfuscated_hash TEXT NOT NULL UNIQUE, -- ex: "8a3f1d2b" (hash truncado de 8 chars)
|
|
18
|
+
original_value TEXT NOT NULL, -- ex: "700k-1M" (valor real)
|
|
19
|
+
category TEXT NOT NULL, -- ex: "imovel", "automotivo", "curso"
|
|
20
|
+
pixel_audience TEXT, -- ex: "AUDIENCE_MID" (Meta custom audience)
|
|
21
|
+
platform_specific TEXT, -- JSON com IDs específicos por plataforma
|
|
22
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
23
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
-- ============================================================================
|
|
27
|
+
-- ÍNDICES: utm_mappings
|
|
28
|
+
-- ============================================================================
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_utm_obfuscated ON utm_mappings(obfuscated_hash);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_utm_category ON utm_mappings(category);
|
|
31
|
+
|
|
32
|
+
-- ============================================================================
|
|
33
|
+
-- ALTER: leads (adicionar colunas de segmentação)
|
|
34
|
+
-- ============================================================================
|
|
35
|
+
ALTER TABLE leads ADD COLUMN IF NOT EXISTS faixa_obfuscada TEXT; -- Hash da faixa de valor (vem da URL)
|
|
36
|
+
ALTER TABLE leads ADD COLUMN IF NOT EXISTS faixa_real TEXT; -- Valor real de-obfuscado (ex: "700k-1M")
|
|
37
|
+
ALTER TABLE leads ADD COLUMN IF NOT EXISTS faixa_category TEXT; -- Categoria do produto (ex: "imovel")
|
|
38
|
+
|
|
39
|
+
-- ============================================================================
|
|
40
|
+
-- ÍNDICES: leads (novas colunas para segmentação)
|
|
41
|
+
-- ============================================================================
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_leads_faixa_real ON leads(faixa_real);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_leads_faixa_category ON leads(faixa_category);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_leads_faixa_obfuscada ON leads(faixa_obfuscada);
|
|
45
|
+
|
|
46
|
+
-- ============================================================================
|
|
47
|
+
-- VIEW: leads_segmented (para queries de segmentação)
|
|
48
|
+
-- ============================================================================
|
|
49
|
+
CREATE VIEW IF NOT EXISTS leads_segmented AS
|
|
50
|
+
SELECT
|
|
51
|
+
l.id,
|
|
52
|
+
l.user_id,
|
|
53
|
+
l.event,
|
|
54
|
+
l.event_id,
|
|
55
|
+
l.email,
|
|
56
|
+
l.phone,
|
|
57
|
+
l.city,
|
|
58
|
+
l.state,
|
|
59
|
+
l.faixa_obfuscada,
|
|
60
|
+
l.faixa_real,
|
|
61
|
+
l.faixa_category,
|
|
62
|
+
l.created_at,
|
|
63
|
+
l.value,
|
|
64
|
+
l.intent_score,
|
|
65
|
+
l.ltv_class,
|
|
66
|
+
-- Meta CAPI: custom audience de-obfuscada
|
|
67
|
+
u.pixel_audience AS meta_custom_audience
|
|
68
|
+
FROM leads l
|
|
69
|
+
LEFT JOIN utm_mappings u ON l.faixa_obfuscada = u.obfuscated_hash;
|
|
70
|
+
|
|
71
|
+
-- ============================================================================
|
|
72
|
+
-- EXEMPLO: Query para exportar leads por faixa de valor (para Meta Custom Audience)
|
|
73
|
+
-- ============================================================================
|
|
74
|
+
-- SELECT email, phone, city, state, faixa_real, meta_custom_audience
|
|
75
|
+
-- FROM leads_segmented
|
|
76
|
+
-- WHERE faixa_category = 'imovel' AND faixa_real = '700k-1M'
|
|
77
|
+
-- AND created_at >= datetime('now', '-30 days');
|
|
78
|
+
--
|
|
79
|
+
-- Isso gera um CSV pronto para upload como Custom Audience na Meta.
|
|
80
|
+
-- ============================================================================
|