cdp-edge 1.18.0 → 2.0.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.
Files changed (40) hide show
  1. package/README.md +308 -308
  2. package/bin/cdp-edge.js +61 -61
  3. package/dist/commands/analyze.js +52 -52
  4. package/dist/commands/infra.js +54 -54
  5. package/dist/commands/install.js +186 -0
  6. package/dist/commands/server.js +174 -174
  7. package/dist/commands/setup.js +18 -1
  8. package/dist/commands/validate.js +84 -84
  9. package/dist/index.js +12 -12
  10. package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -364
  11. package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +172 -72
  12. package/extracted-skill/tracking-events-generator/agents/google-agent.md +118 -0
  13. package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +86 -0
  14. package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +8 -641
  15. package/extracted-skill/tracking-events-generator/agents/memory-agent.md +98 -0
  16. package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +42 -0
  17. package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -285
  18. package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -641
  19. package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -226
  20. package/extracted-skill/tracking-events-generator/evals/evals.json +235 -235
  21. package/extracted-skill/tracking-events-generator/integration-test.js +497 -497
  22. package/extracted-skill/tracking-events-generator/micro-events.js +992 -992
  23. package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -144
  24. package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -48
  25. package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -28
  26. package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -205
  27. package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -56
  28. package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -19
  29. package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -425
  30. package/package.json +76 -76
  31. package/server-edge-tracker/schema.sql +265 -265
  32. package/server-edge-tracker/worker.js +4160 -4160
  33. package/server-edge-tracker/wrangler.toml +103 -103
  34. package/templates/pinterest/conversions-api-template.js +144 -144
  35. package/templates/pinterest/event-mappings.json +48 -48
  36. package/templates/pinterest/tag-template.js +28 -28
  37. package/templates/reddit/conversions-api-template.js +205 -205
  38. package/templates/reddit/event-mappings.json +56 -56
  39. package/templates/reddit/pixel-template.js +19 -19
  40. package/templates/scenarios/behavior-engine.js +425 -425
@@ -109,99 +109,199 @@ Implementado via `anti-blocking.js`:
109
109
 
110
110
  ## 💻 EXEMPLO DE CÓDIGO GERADO
111
111
 
112
- ### `cdpTrack.js` (SDK Principal)
112
+ ### `cdpTrack.js` (SDK Principal — Padrão Multi-Plataforma)
113
+
114
+ > Este é o padrão canônico do cdpTrack SDK para Meta, TikTok e GA4.
115
+ > Cada agente de plataforma injeta seus eventos neste SDK via Browser Tracking Agent.
113
116
 
114
117
  ```javascript
115
118
  /**
116
119
  * cdpTrack SDK - CDP Edge Quantum Tier
117
120
  * Browser Tracking SDK Principal
121
+ * Suporta: Meta Pixel, TikTok Pixel, GA4, Pinterest Tag, Reddit Pixel, Spotify Pixel
118
122
  */
119
123
 
120
- (function(w, d, s, l) {
121
- w._pbq = w._pbq || [];
122
- w._pbq.push = w._pbq.push || [];
123
- w._spotify = w._spotify || {};
124
-
125
- // Carregar configuração
126
- const config = window.cdpTrack?.config || {};
124
+ (function(w) {
125
+ 'use strict';
127
126
 
128
- // Inicializar Spotify Pixel (se configurado)
129
- if (config.spotifyPixelId) {
130
- w._spotify.pixelId = config.spotifyPixelId;
131
- w._spotify.currency = config.currency || 'USD';
127
+ // ──────────────────────────────────────────────
128
+ // CORE: Geração de event_id único (deduplicação)
129
+ // O mesmo event_id deve ser usado no browser E no servidor (CAPI)
130
+ // ──────────────────────────────────────────────
131
+ function generateEventId() {
132
+ return 'evt_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
132
133
  }
133
134
 
134
- // Função principal de envio
135
- w._spotify.trackEvent = function(eventName, params) {
136
- const eventId = cdpTrack.generateEventId();
137
-
138
- // Track localmente
139
- w._spq.push({
140
- e: eventName,
141
- params: params,
142
- eventId: eventId,
143
- platform: 'spotify'
144
- });
145
-
146
- // Enviar para servidor (via cdpTrack)
147
- if (window.cdpTrack && window.cdpTrack.submit) {
148
- window.cdpTrack.submit('spotify', {
149
- event: eventName,
150
- event_id: eventId,
151
- ...params
135
+ // ──────────────────────────────────────────────
136
+ // CORE: Envio para Cloudflare Worker (same-domain)
137
+ // Usa /track no mesmo domínio — imune a ad-blockers
138
+ // ──────────────────────────────────────────────
139
+ async function sendToWorker(eventName, payload) {
140
+ const eventId = generateEventId();
141
+
142
+ const body = {
143
+ event: eventName,
144
+ event_id: eventId, // CRÍTICO: mesmo ID usado nas CAPIs
145
+ url: window.location.href,
146
+ referrer: document.referrer,
147
+ timestamp: Date.now(),
148
+ ...payload
149
+ };
150
+
151
+ try {
152
+ // Tentativa primária: fetch
153
+ await fetch('/track', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify(body),
157
+ keepalive: true
152
158
  });
159
+ } catch (_) {
160
+ // Fallback: Beacon API (funciona mesmo no unload da página)
161
+ navigator.sendBeacon('/track', JSON.stringify(body));
153
162
  }
154
- };
155
163
 
156
- // Eventos Padrão Spotify
157
- w._spotify.Content = function(contentName, contentId, params) {
158
- w._spotify.trackEvent('ViewContent', {
159
- content_name: contentName,
160
- content_id: contentId,
161
- ...params
162
- });
163
- };
164
+ return eventId;
165
+ }
164
166
 
165
- w._spotify.AddToCart = function(contentName, contentId, cartId, params) {
166
- w._spotify.trackEvent('AddToCart', {
167
- content_name: contentName,
168
- content_id: contentId,
169
- cart_id: cartId,
170
- ...params
171
- });
172
- };
167
+ // ──────────────────────────────────────────────
168
+ // CORE: Captura de cookies first-party
169
+ // ──────────────────────────────────────────────
170
+ function getCookie(name) {
171
+ const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
172
+ return match ? decodeURIComponent(match[2]) : null;
173
+ }
173
174
 
174
- w._spotify.Purchase = function(contentName, contentId, value, currency, params) {
175
- w._spotify.trackEvent('Purchase', {
176
- content_name: contentName,
177
- content_id: contentId,
178
- value: value,
179
- currency: currency,
180
- ...params
181
- });
182
- };
175
+ // ──────────────────────────────────────────────
176
+ // CORE: Captura de click IDs da URL (Meta, TikTok, Google)
177
+ // ──────────────────────────────────────────────
178
+ function getClickIds() {
179
+ const params = new URLSearchParams(window.location.search);
180
+ return {
181
+ fbclid: params.get('fbclid') || getCookie('fbclid') || null,
182
+ ttclid: params.get('ttclid') || getCookie('ttclid') || null,
183
+ gclid: params.get('gclid') || null,
184
+ gbraid: params.get('gbraid') || null,
185
+ wbraid: params.get('wbraid') || null,
186
+ fbp: getCookie('_fbp') || null,
187
+ fbc: getCookie('_fbc') || null,
188
+ ttp: getCookie('_ttp') || null,
189
+ uid: getCookie('_cdp_uid') || null // Identity Graph first-party cookie
190
+ };
191
+ }
183
192
 
184
- w._spotify.Lead = function(contentName, contentId, leadType, params) {
185
- w._spotify.trackEvent('Lead', {
186
- content_name: contentName,
187
- content_id: contentId,
188
- lead_type: leadType,
189
- ...params
190
- });
191
- };
193
+ // ──────────────────────────────────────────────
194
+ // API PÚBLICA
195
+ // ──────────────────────────────────────────────
196
+ const cdpTrack = {
197
+ generateEventId,
198
+
199
+ /**
200
+ * Rastrear evento genérico — enviado para o Worker
201
+ * O Worker despacha para Meta CAPI, GA4 MP, TikTok Events API etc.
202
+ *
203
+ * @param {string} eventName - Nome do evento (ex: 'Lead', 'Purchase', 'PageView')
204
+ * @param {Object} params - Parâmetros adicionais do evento
205
+ */
206
+ track(eventName, params = {}) {
207
+ const clickIds = getClickIds();
208
+ return sendToWorker(eventName, { ...clickIds, ...params });
209
+ },
210
+
211
+ /**
212
+ * Rastrear Lead (captura de formulário)
213
+ * Enviar dados PII crus — o Worker faz SHA-256 no servidor
214
+ */
215
+ trackLead(userData = {}) {
216
+ const clickIds = getClickIds();
217
+ return sendToWorker('Lead', {
218
+ ...clickIds,
219
+ email: userData.email || null, // Worker aplica SHA-256
220
+ phone: userData.phone || null, // Worker aplica E.164 + SHA-256
221
+ first_name: userData.first_name || null,
222
+ last_name: userData.last_name || null,
223
+ city: userData.city || null,
224
+ state: userData.state || null,
225
+ zip: userData.zip || null,
226
+ country: userData.country || 'BR'
227
+ });
228
+ },
192
229
 
193
- w._spotify.Signup = function(contentName, contentId, signupType, params) {
194
- w._spotify.trackEvent('Signup', {
195
- content_name: contentName,
196
- content_id: contentId,
197
- signup_type: signupType,
198
- ...params
199
- });
230
+ /**
231
+ * Rastrear Purchase (confirmação de compra)
232
+ */
233
+ trackPurchase(orderData = {}) {
234
+ const clickIds = getClickIds();
235
+ return sendToWorker('Purchase', {
236
+ ...clickIds,
237
+ value: orderData.value || 0,
238
+ currency: orderData.currency || 'BRL',
239
+ order_id: orderData.order_id || null,
240
+ content_name: orderData.product || null
241
+ });
242
+ },
243
+
244
+ /**
245
+ * Rastrear PageView — chamar no load da página
246
+ */
247
+ trackPageView() {
248
+ const clickIds = getClickIds();
249
+ return sendToWorker('PageView', { ...clickIds });
250
+ },
251
+
252
+ /**
253
+ * Rastrear InitiateCheckout
254
+ */
255
+ trackInitiateCheckout(checkoutData = {}) {
256
+ const clickIds = getClickIds();
257
+ return sendToWorker('InitiateCheckout', {
258
+ ...clickIds,
259
+ value: checkoutData.value || 0,
260
+ currency: checkoutData.currency || 'BRL'
261
+ });
262
+ }
200
263
  };
201
264
 
202
- })(window, document, 'script', 'location');
265
+ // Expor no window
266
+ w.cdpTrack = cdpTrack;
267
+
268
+ // Auto page_view no load
269
+ if (document.readyState === 'loading') {
270
+ document.addEventListener('DOMContentLoaded', () => cdpTrack.trackPageView());
271
+ } else {
272
+ cdpTrack.trackPageView();
273
+ }
274
+
275
+ })(window);
203
276
  ```
204
277
 
278
+ ### Uso típico no HTML do cliente
279
+
280
+ ```html
281
+ <!-- 1. Carregar o SDK -->
282
+ <script src="/tracking/cdpTrack.js"></script>
283
+
284
+ <!-- 2. Rastrear lead ao submeter formulário -->
285
+ <script>
286
+ document.getElementById('lead-form').addEventListener('submit', function(e) {
287
+ cdpTrack.trackLead({
288
+ email: document.getElementById('email').value,
289
+ phone: document.getElementById('phone').value,
290
+ first_name: document.getElementById('name').value
291
+ });
292
+ });
293
+ </script>
294
+
295
+ <!-- 3. Rastrear checkout (botão de compra) -->
296
+ <script>
297
+ document.getElementById('buy-btn').addEventListener('click', function() {
298
+ cdpTrack.trackInitiateCheckout({ value: 97.00, currency: 'BRL' });
299
+ });
300
+ </script>
301
+ ```
302
+
303
+ > **Nota:** O Worker recebe os dados crus, aplica SHA-256, e despacha para Meta CAPI v22.0, GA4 MP, TikTok Events API v1.3 e demais plataformas configuradas — tudo em paralelo via `Promise.allSettled`.
304
+
205
305
  ---
206
306
 
207
307
  ## 🔧 INTEGRAÇÃO COM OUTROS AGENTES
@@ -43,6 +43,124 @@ if (isVersionConflict) {
43
43
 
44
44
  ---
45
45
 
46
+ ## 🛡️ GOOGLE CONSENT MODE V2 — IMPLEMENTAÇÃO OBRIGATÓRIA
47
+
48
+ > **CRÍTICO**: Sem Consent Mode v2, campanhas Google Ads em audiências europeias são rejeitadas.
49
+ > Obrigatório para conformidade com GDPR (UE), LGPD (BR) e CCPA (EUA).
50
+
51
+ ### PASSO 1 — Inicialização (ANTES do gtag.js)
52
+
53
+ Inserir **antes** do snippet do gtag.js no `<head>`:
54
+
55
+ ```html
56
+ <!-- Google Consent Mode v2 — Inicializar NEGADO por padrão -->
57
+ <script>
58
+ window.dataLayer = window.dataLayer || [];
59
+ function gtag() { dataLayer.push(arguments); }
60
+
61
+ // OBRIGATÓRIO: definir consent ANTES de qualquer gtag() de medição
62
+ gtag('consent', 'default', {
63
+ 'ad_storage': 'denied', // cookies de anúncio bloqueados até opt-in
64
+ 'analytics_storage': 'denied', // cookies de analytics bloqueados até opt-in
65
+ 'ad_user_data': 'denied', // envio de dados de usuário para Google Ads
66
+ 'ad_personalization': 'denied', // personalização de anúncios
67
+ 'wait_for_update': 500 // aguardar CMP atualizar consentimento (ms)
68
+ });
69
+
70
+ // url_passthrough: preserva gclid/gbraid/wbraid na URL sem cookie
71
+ // Permite atribuição de cliques mesmo sem consent de analytics_storage
72
+ gtag('set', 'url_passthrough', true);
73
+
74
+ // ads_data_redaction: quando ad_storage=denied, reduz dados de clique enviados
75
+ gtag('set', 'ads_data_redaction', true);
76
+ </script>
77
+
78
+ <!-- Carregar gtag.js normalmente após o bloco acima -->
79
+ <script async src="https://www.googletagmanager.com/gtag/js?id=GA4_MEASUREMENT_ID"></script>
80
+ <script>
81
+ window.dataLayer = window.dataLayer || [];
82
+ function gtag() { dataLayer.push(arguments); }
83
+ gtag('js', new Date());
84
+ gtag('config', 'GA4_MEASUREMENT_ID', {
85
+ 'send_page_view': false // cdpTrack controla page_view manualmente
86
+ });
87
+ </script>
88
+ ```
89
+
90
+ ### PASSO 2 — Atualizar Consent após Opt-in do usuário
91
+
92
+ Integrar com o banner de cookies do site (LGPD/GDPR):
93
+
94
+ ```javascript
95
+ // Chamar quando usuário ACEITAR todos os cookies
96
+ function onConsentAccepted() {
97
+ gtag('consent', 'update', {
98
+ 'ad_storage': 'granted',
99
+ 'analytics_storage': 'granted',
100
+ 'ad_user_data': 'granted',
101
+ 'ad_personalization': 'granted'
102
+ });
103
+
104
+ // Opcional: disparar page_view após consent (se necessário)
105
+ gtag('event', 'page_view');
106
+ }
107
+
108
+ // Chamar quando usuário RECUSAR cookies não essenciais
109
+ function onConsentDeclined() {
110
+ gtag('consent', 'update', {
111
+ 'ad_storage': 'denied',
112
+ 'analytics_storage': 'denied',
113
+ 'ad_user_data': 'denied',
114
+ 'ad_personalization': 'denied'
115
+ });
116
+ }
117
+
118
+ // Aceitar apenas cookies analíticos (sem ads)
119
+ function onConsentAnalyticsOnly() {
120
+ gtag('consent', 'update', {
121
+ 'ad_storage': 'denied',
122
+ 'analytics_storage': 'granted',
123
+ 'ad_user_data': 'denied',
124
+ 'ad_personalization': 'denied'
125
+ });
126
+ }
127
+ ```
128
+
129
+ ### PASSO 3 — Verificação (via Intelligence Agent)
130
+
131
+ O Intelligence Agent verifica mensalmente se o Consent Mode está implementado:
132
+
133
+ ```javascript
134
+ // Checklist mínimo no código gerado (browser tracking):
135
+ // ✅ gtag('consent', 'default', {...}) ANTES do gtag.js
136
+ // ✅ ad_storage: 'denied' no default
137
+ // ✅ analytics_storage: 'denied' no default
138
+ // ✅ ad_user_data: 'denied' no default
139
+ // ✅ ad_personalization: 'denied' no default
140
+ // ✅ url_passthrough: true ativo
141
+ // ✅ gtag('consent', 'update', {...}) no callback do CMP/banner
142
+ ```
143
+
144
+ ### PASSO 4 — Integração com cdpTrack (Preservação de gclid)
145
+
146
+ ```javascript
147
+ // O cdpTrack.js deve capturar gclid/gbraid/wbraid da URL mesmo sem consent
148
+ // url_passthrough: true garante que os parâmetros são passados como parâmetros de URL,
149
+ // não como cookies — respeitando consent de analytics_storage
150
+
151
+ function captureGoogleClickId() {
152
+ const params = new URLSearchParams(window.location.search);
153
+ return {
154
+ gclid: params.get('gclid') || null,
155
+ gbraid: params.get('gbraid') || null,
156
+ wbraid: params.get('wbraid') || null
157
+ };
158
+ }
159
+ // Esses IDs são enviados para o Worker e salvos no D1 para Enhanced Conversions offline
160
+ ```
161
+
162
+ ---
163
+
46
164
  ## 🛠️ O QUE VOCÊ GERA
47
165
 
48
166
  ### 1. Browser (Direct SDK)
@@ -363,3 +363,89 @@ INTELLIGENCE_SCHEDULE_MONTHLY = "0 3 1 * *"
363
363
  3. **Alerta Pré-ativo**: Antes de uma API ser descontinuada, alertar com 30 dias de antecedência
364
364
  4. **False-Positive Safe**: Se houver dúvida sobre versão de API, marcar como "verificação manual necessária" em vez de alerta
365
365
  5. **Backoff de Check**: Se o check falhar (API indisponível), tentar novamente em 1 hora (não disparar alerta imediato)
366
+ 6. **Anti-Spam**: Não disparar alerta se o mesmo problema já foi reportado nas últimas 24h
367
+ 7. **Prioridade Correta**: CRITICAL (agora), HIGH (até 1h), MEDIUM (no relatório)
368
+ 8. **Log de Falhas de Alerta**: Se WhatsApp falhar 3× consecutivas, registrar no D1 e tentar via CallMeBot fallback
369
+
370
+ ---
371
+
372
+ ## 🗄️ SCHEMA D1 — intelligence_logs
373
+
374
+ Adicionar ao `server-edge-tracker/schema.sql`:
375
+
376
+ ```sql
377
+ -- TABELA DE LOGS DO INTELLIGENCE AGENT
378
+ CREATE TABLE IF NOT EXISTS intelligence_logs (
379
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
380
+ run_type TEXT NOT NULL, -- 'weekly' | 'monthly' | 'on-demand'
381
+ platforms_checked TEXT, -- JSON array de plataformas verificadas
382
+ issues_found TEXT, -- JSON array de issues encontradas
383
+ issues_count INTEGER DEFAULT 0,
384
+ created_at TEXT DEFAULT (datetime('now'))
385
+ );
386
+
387
+ CREATE INDEX IF NOT EXISTS idx_intel_logs_type ON intelligence_logs(run_type);
388
+ CREATE INDEX IF NOT EXISTS idx_intel_logs_created ON intelligence_logs(created_at);
389
+ ```
390
+
391
+ ---
392
+
393
+ ## ✅ CHECKLIST DE IMPLEMENTAÇÃO
394
+
395
+ Antes de considerar o scheduling implementado, verificar:
396
+
397
+ - [ ] Cron triggers adicionados ao `wrangler.toml` (`0 2 * * 7` e `0 3 1 * *`)
398
+ - [ ] Handlers `scheduled()` adicionados ao `worker.js` (Cloudflare usa `scheduled`, não `fetch`)
399
+ - [ ] Schema D1 atualizado com tabela `intelligence_logs`
400
+ - [ ] Funções de check de versão implementadas com endpoints reais
401
+ - [ ] Funções de auditoria de privacidade implementadas
402
+ - [ ] Sistema de alerta (WhatsApp/CallMeBot) integrado com anti-spam 24h
403
+ - [ ] Logs de execução sendo salvos no D1
404
+ - [ ] Memory Agent atualizado após cada check
405
+ - [ ] Backoff implementado para evitar spam de alertas
406
+ - [ ] Teste manual executado (`GET /api/intelligence/check`)
407
+
408
+ ---
409
+
410
+ ## INPUTS RECEBIDOS
411
+
412
+ - `wrangler.toml` do Worker (para injetar Cron Triggers)
413
+ - `worker.js` (para injetar handlers de `scheduled` events)
414
+ - `schema.sql` (para adicionar tabela `intelligence_logs`)
415
+ - Secrets: `WA_PHONE_ID`, `WA_ACCESS_TOKEN`, `ADMIN_PHONE_NUMBER` (para alertas)
416
+ - `contracts/api-versions.json` (fonte de verdade das versões atuais)
417
+
418
+ ## RESPONSABILIDADE
419
+
420
+ - Configurar Cron Triggers no `wrangler.toml`: semanal (domingo 02:00 UTC) e mensal (1º do mês 03:00 UTC)
421
+ - Implementar handler `scheduled(event, env, ctx)` no Worker
422
+ - Chamar `runIntelligenceWeekly()` quando `event.cron === "0 2 * * 7"`
423
+ - Chamar `runIntelligenceMonthly()` quando `event.cron === "0 3 1 * *"`
424
+ - Adicionar tabela `intelligence_logs` ao schema D1
425
+ - Disparar alertas WhatsApp/CallMeBot ao admin apenas quando houver issues críticos
426
+ - Evitar spam: não repetir alerta do mesmo issue em menos de 24h
427
+ - Registrar resultado de cada execução no D1 (`intelligence_logs`)
428
+
429
+ ## SAÍDA
430
+
431
+ ```json
432
+ {
433
+ "arquivos_modificados": [
434
+ "wrangler.toml (cron triggers adicionados)",
435
+ "worker.js (handler scheduled() adicionado)",
436
+ "schema.sql (tabela intelligence_logs adicionada)"
437
+ ],
438
+ "crons_configurados": {
439
+ "semanal": "0 2 * * 7 (domingo 02:00 UTC — check de versões)",
440
+ "mensal": "0 3 1 * * (dia 1 03:00 UTC — auditoria privacidade)"
441
+ },
442
+ "endpoint_manual": "GET /api/intelligence/check",
443
+ "alertas": {
444
+ "canal_primario": "WhatsApp Meta Cloud API v22.0",
445
+ "canal_fallback": "CallMeBot",
446
+ "anti_spam": "24h cooldown por issue"
447
+ },
448
+ "d1_tabela": "intelligence_logs",
449
+ "secrets_necessarios": ["WA_PHONE_ID", "WA_ACCESS_TOKEN", "ADMIN_PHONE_NUMBER"]
450
+ }
451
+ ```