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.
- package/README.md +82 -21
- package/bin/cdp-edge.js +10 -1
- package/contracts/agent-versions.json +42 -41
- package/contracts/types.ts +81 -0
- package/dist/commands/install.js +6 -1
- package/dist/commands/server.js +4 -4
- package/docs/whatsapp-ctwa.md +3 -2
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +5 -4
- package/extracted-skill/tracking-events-generator/agents/fraud-detection-agent.md +0 -1
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +4 -4
- package/extracted-skill/tracking-events-generator/agents/ml-clustering-agent.md +81 -70
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +6 -2
- package/extracted-skill/tracking-events-generator/cdpTrack.js +7 -0
- package/extracted-skill/tracking-events-generator/models/lancamento-imobiliario.md +344 -0
- package/extracted-skill/tracking-events-generator/route-intent-capture.js +222 -0
- package/package.json +9 -5
- package/server-edge-tracker/INSTALAR.md +5 -5
- package/server-edge-tracker/{index.js → index.ts} +186 -72
- package/server-edge-tracker/modules/{db.js → db.ts} +180 -69
- package/server-edge-tracker/modules/dispatch/{ga4.js → ga4.ts} +12 -10
- package/server-edge-tracker/modules/dispatch/meta.ts +138 -0
- package/server-edge-tracker/modules/dispatch/{platforms.js → platforms.ts} +58 -56
- package/server-edge-tracker/modules/dispatch/{tiktok.js → tiktok.ts} +22 -20
- package/server-edge-tracker/modules/dispatch/{whatsapp.js → whatsapp.ts} +59 -25
- package/server-edge-tracker/modules/{intelligence.js → intelligence.ts} +175 -60
- package/server-edge-tracker/modules/ml/{bidding.js → bidding.ts} +37 -35
- package/server-edge-tracker/modules/ml/{fraud.js → fraud.ts} +49 -56
- package/server-edge-tracker/modules/ml/{logistic.js → logistic.ts} +44 -19
- package/server-edge-tracker/modules/ml/{ltv.js → ltv.ts} +179 -83
- package/server-edge-tracker/modules/ml/{matchquality.js → matchquality.ts} +70 -26
- package/server-edge-tracker/modules/ml/segmentation.ts +407 -0
- package/server-edge-tracker/modules/utils.ts +186 -0
- package/server-edge-tracker/schema-ltv-feedback.sql +11 -0
- package/server-edge-tracker/types.ts +251 -0
- package/server-edge-tracker/wrangler.toml +24 -6
- package/templates/lancamento-imobiliario.md +344 -0
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/server-edge-tracker/modules/dispatch/meta.js +0 -119
- package/server-edge-tracker/modules/ml/segmentation.js +0 -316
- package/server-edge-tracker/modules/utils.js +0 -89
- 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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
182
|
-
return new Response(JSON.stringify({ error: err
|
|
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
|
|
201
|
-
return new Response(JSON.stringify({ error: err
|
|
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
|
|
242
|
-
return new Response(JSON.stringify({ error: err
|
|
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
|
|
270
|
-
return new Response(JSON.stringify({ error: err
|
|
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
|
|
299
|
-
return new Response(JSON.stringify({ error: err
|
|
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
|
|
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
|
|
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
|
|